From bb4e940afd6d5ce342eaf752faf912c95a95d329 Mon Sep 17 00:00:00 2001 From: "JSD\\13999" <1399945104@qq.com> Date: Mon, 15 Jun 2026 16:18:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(vendor):=20=E6=96=B0=E5=A2=9E=20AstarPro?= =?UTF-8?q?=20=E6=8F=92=E4=BB=B6=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FishROV/Assets/ThirdParty/AstarPro.meta | 8 + .../ThirdParty/AstarPro/AssemblyInfo.cs | 4 + .../ThirdParty/AstarPro/AssemblyInfo.cs.meta | 11 + .../AstarPro/AstarPathfindingProject.asmdef | 99 + .../AstarPathfindingProject.asmdef.meta | 7 + .../Assets/ThirdParty/AstarPro/Behaviors.meta | 9 + .../AstarPro/Behaviors/AIDestinationSetter.cs | 130 + .../Behaviors/AIDestinationSetter.cs.meta | 11 + .../Behaviors/AIPathAlignedToSurface.cs | 141 + .../Behaviors/AIPathAlignedToSurface.cs.meta | 11 + .../AstarPro/Behaviors/MoveInCircle.cs | 84 + .../AstarPro/Behaviors/MoveInCircle.cs.meta | 11 + .../ThirdParty/AstarPro/Behaviors/Patrol.cs | 68 + .../AstarPro/Behaviors/Patrol.cs.meta | 11 + .../Assets/ThirdParty/AstarPro/CHANGELOG.md | 3063 ++++++++++++ .../ThirdParty/AstarPro/CHANGELOG.md.meta | 7 + FishROV/Assets/ThirdParty/AstarPro/Core.meta | 2 + .../Assets/ThirdParty/AstarPro/Core/AI.meta | 2 + .../ThirdParty/AstarPro/Core/AI/AIBase.cs | 874 ++++ .../AstarPro/Core/AI/AIBase.cs.meta | 12 + .../ThirdParty/AstarPro/Core/AI/AILerp.cs | 726 +++ .../AstarPro/Core/AI/AILerp.cs.meta | 11 + .../ThirdParty/AstarPro/Core/AI/AIPath.cs | 543 +++ .../AstarPro/Core/AI/AIPath.cs.meta | 11 + .../AstarPro/Core/AI/FollowerEntity.cs | 1825 +++++++ .../AstarPro/Core/AI/FollowerEntity.cs.meta | 11 + .../AstarPro/Core/AI/FollowerEntityProxy.cs | 1066 ++++ .../Core/AI/FollowerEntityProxy.cs.meta | 11 + .../ThirdParty/AstarPro/Core/AI/IAstarAI.cs | 531 ++ .../AstarPro/Core/AI/IAstarAI.cs.meta | 12 + .../AstarPro/Core/AI/LocalSpaceRichAI.cs | 73 + .../AstarPro/Core/AI/LocalSpaceRichAI.cs.meta | 11 + .../ThirdParty/AstarPro/Core/AI/RichAI.cs | 646 +++ .../AstarPro/Core/AI/RichAI.cs.meta | 11 + .../ThirdParty/AstarPro/Core/AI/RichPath.cs | 803 +++ .../AstarPro/Core/AI/RichPath.cs.meta | 8 + .../ThirdParty/AstarPro/Core/AI/Seeker.cs | 831 ++++ .../AstarPro/Core/AI/Seeker.cs.meta | 11 + .../AstarPro/Core/AI/TurnBasedAI.cs | 25 + .../AstarPro/Core/AI/TurnBasedAI.cs.meta | 12 + .../ThirdParty/AstarPro/Core/AstarData.cs | 809 +++ .../AstarPro/Core/AstarData.cs.meta | 7 + .../ThirdParty/AstarPro/Core/AstarMath.cs | 2131 ++++++++ .../AstarPro/Core/AstarMath.cs.meta | 7 + .../ThirdParty/AstarPro/Core/AstarPath.cs | 2626 ++++++++++ .../AstarPro/Core/AstarPath.cs.meta | 16 + .../ThirdParty/AstarPro/Core/Collections.meta | 8 + .../AstarPro/Core/Collections/AstarMemory.cs | 109 + .../Core/Collections/AstarMemory.cs.meta | 7 + .../AstarPro/Core/Collections/BinaryHeap.cs | 387 ++ .../Core/Collections/BinaryHeap.cs.meta | 7 + .../Core/Collections/CircularBuffer.cs | 268 + .../Core/Collections/CircularBuffer.cs.meta | 11 + .../Core/Collections/HierarchicalBitset.cs | 288 ++ .../Collections/HierarchicalBitset.cs.meta | 11 + .../Core/Collections/NativeCircularBuffer.cs | 336 ++ .../Collections/NativeCircularBuffer.cs.meta | 11 + .../Core/Collections/SlabAllocator.cs | 323 ++ .../Core/Collections/SlabAllocator.cs.meta | 11 + .../AstarPro/Core/Collections/Span.cs | 511 ++ .../AstarPro/Core/Collections/Span.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Control.meta | 8 + .../Core/Control/MovementUtilities.cs | 208 + .../Core/Control/MovementUtilities.cs.meta | 12 + .../AstarPro/Core/Control/PIDMovement.cs | 1138 +++++ .../AstarPro/Core/Control/PIDMovement.cs.meta | 11 + .../AstarPro/Core/Control/PIDUtilities.cs | 29 + .../Core/Control/PIDUtilities.cs.meta | 11 + .../Assets/ThirdParty/AstarPro/Core/ECS.meta | 8 + .../AstarPro/Core/ECS/Components.meta | 8 + .../Core/ECS/Components/AgentCylinderShape.cs | 18 + .../ECS/Components/AgentCylinderShape.cs.meta | 11 + .../Core/ECS/Components/AgentMovementPlane.cs | 34 + .../ECS/Components/AgentMovementPlane.cs.meta | 11 + .../Components/AgentMovementPlaneSource.cs | 15 + .../AgentMovementPlaneSource.cs.meta | 11 + .../AgentOffMeshLinkLocalAvoidanceDisabled.cs | 16 + ...tOffMeshLinkLocalAvoidanceDisabled.cs.meta | 11 + .../AgentOffMeshLinkMovementDisabled.cs | 16 + .../AgentOffMeshLinkMovementDisabled.cs.meta | 11 + .../Components/AgentOffMeshLinkTraversal.cs | 413 ++ .../AgentOffMeshLinkTraversal.cs.meta | 11 + .../Components/AgentShouldRecalculatePath.cs | 12 + .../AgentShouldRecalculatePath.cs.meta | 11 + .../Core/ECS/Components/AutoRepathPolicy.cs | 121 + .../ECS/Components/AutoRepathPolicy.cs.meta | 11 + .../Core/ECS/Components/DestinationEntity.cs | 38 + .../ECS/Components/DestinationEntity.cs.meta | 11 + .../Core/ECS/Components/DestinationPoint.cs | 26 + .../ECS/Components/DestinationPoint.cs.meta | 11 + .../Core/ECS/Components/GravityState.cs | 16 + .../Core/ECS/Components/GravityState.cs.meta | 11 + .../ECS/Components/ManagedMovementOverride.cs | 157 + .../ManagedMovementOverride.cs.meta | 11 + .../Core/ECS/Components/ManagedSettings.cs | 99 + .../ECS/Components/ManagedSettings.cs.meta | 11 + .../Core/ECS/Components/ManagedState.cs | 241 + .../Core/ECS/Components/ManagedState.cs.meta | 11 + .../Core/ECS/Components/MovementControl.cs | 94 + .../ECS/Components/MovementControl.cs.meta | 11 + .../Core/ECS/Components/MovementSettings.cs | 127 + .../ECS/Components/MovementSettings.cs.meta | 11 + .../Core/ECS/Components/MovementState.cs | 224 + .../Core/ECS/Components/MovementState.cs.meta | 11 + .../Core/ECS/Components/MovementStatistics.cs | 17 + .../ECS/Components/MovementStatistics.cs.meta | 11 + .../Core/ECS/Components/PhysicsSceneRef.cs | 26 + .../ECS/Components/PhysicsSceneRef.cs.meta | 11 + .../AstarPro/Core/ECS/Components/RVO.meta | 8 + .../Core/ECS/Components/RVO/AgentIndex.cs | 91 + .../ECS/Components/RVO/AgentIndex.cs.meta | 11 + .../Core/ECS/Components/RVO/RVOAgent.cs | 94 + .../Core/ECS/Components/RVO/RVOAgent.cs.meta | 11 + .../Components/ReadyToTraverseOffMeshLink.cs | 10 + .../ReadyToTraverseOffMeshLink.cs.meta | 11 + .../Core/ECS/Components/ResolvedMovement.cs | 32 + .../ECS/Components/ResolvedMovement.cs.meta | 11 + .../Core/ECS/Components/SearchState.cs | 11 + .../Core/ECS/Components/SearchState.cs.meta | 11 + .../Core/ECS/Components/SimulateMovement.cs | 98 + .../ECS/Components/SimulateMovement.cs.meta | 11 + .../Core/ECS/Components/SyncWithTransform.cs | 35 + .../ECS/Components/SyncWithTransform.cs.meta | 11 + .../AstarPro/Core/ECS/EntityAccess.cs | 249 + .../AstarPro/Core/ECS/EntityAccess.cs.meta | 11 + .../AstarPro/Core/ECS/IRuntimeBaker.cs | 9 + .../AstarPro/Core/ECS/IRuntimeBaker.cs.meta | 11 + .../ThirdParty/AstarPro/Core/ECS/Jobs.meta | 8 + .../Core/ECS/Jobs/DisposeGCHandleJob.cs | 3 + .../Core/ECS/Jobs/DisposeGCHandleJob.cs.meta | 11 + .../Core/ECS/Jobs/DrawFollowerGizmosJob.cs | 3 + .../ECS/Jobs/DrawFollowerGizmosJob.cs.meta | 11 + .../JobAlignAgentWithMovementDirection.cs | 51 + ...JobAlignAgentWithMovementDirection.cs.meta | 11 + .../AstarPro/Core/ECS/Jobs/JobApplyGravity.cs | 63 + .../Core/ECS/Jobs/JobApplyGravity.cs.meta | 11 + .../Core/ECS/Jobs/JobClearTemporaryData.cs | 16 + .../ECS/Jobs/JobClearTemporaryData.cs.meta | 11 + .../AstarPro/Core/ECS/Jobs/JobControl.cs | 163 + .../AstarPro/Core/ECS/Jobs/JobControl.cs.meta | 11 + .../Core/ECS/Jobs/JobDrawFollowerGizmos.cs | 112 + .../ECS/Jobs/JobDrawFollowerGizmos.cs.meta | 11 + .../ECS/Jobs/JobDrawFollowerGizmosBase.cs | 65 + .../Jobs/JobDrawFollowerGizmosBase.cs.meta | 11 + .../ECS/Jobs/JobManagedMovementOverride.cs | 45 + .../Jobs/JobManagedMovementOverride.cs.meta | 11 + .../Jobs/JobManagedOffMeshLinkTransition.cs | 101 + .../JobManagedOffMeshLinkTransition.cs.meta | 11 + .../AstarPro/Core/ECS/Jobs/JobMoveAgent.cs | 95 + .../Core/ECS/Jobs/JobMoveAgent.cs.meta | 11 + .../Core/ECS/Jobs/JobPrepareAgentRaycasts.cs | 46 + .../ECS/Jobs/JobPrepareAgentRaycasts.cs.meta | 11 + .../AstarPro/Core/ECS/Jobs/JobRepairPath.cs | 285 ++ .../Core/ECS/Jobs/JobRepairPath.cs.meta | 11 + .../ECS/Jobs/JobStartOffMeshLinkTransition.cs | 33 + .../JobStartOffMeshLinkTransition.cs.meta | 11 + .../ECS/Jobs/JobSyncEntitiesToTransforms.cs | 47 + .../Jobs/JobSyncEntitiesToTransforms.cs.meta | 11 + .../ECS/Jobs/JobUpdateMovementStatistics.cs | 25 + .../Jobs/JobUpdateMovementStatistics.cs.meta | 11 + .../Jobs/ManagedOffMeshLinkTransitionJob.cs | 3 + .../ManagedOffMeshLinkTransitionJob.cs.meta | 11 + .../AstarPro/Core/ECS/Jobs/RepairPathJob.cs | 3 + .../Core/ECS/Jobs/RepairPathJob.cs.meta | 11 + .../ECS/Jobs/StartOffMeshLinkTransitionJob.cs | 3 + .../StartOffMeshLinkTransitionJob.cs.meta | 11 + .../ThirdParty/AstarPro/Core/ECS/Systems.meta | 8 + .../Core/ECS/Systems/AIGizmosSystem.cs | 92 + .../Core/ECS/Systems/AIGizmosSystem.cs.meta | 11 + .../AstarPro/Core/ECS/Systems/AIMoveSystem.cs | 137 + .../Core/ECS/Systems/AIMoveSystem.cs.meta | 11 + .../Core/ECS/Systems/AIMovementSystemGroup.cs | 244 + .../ECS/Systems/AIMovementSystemGroup.cs.meta | 11 + .../ECS/Systems/DestinationEntitySystem.cs | 44 + .../Systems/DestinationEntitySystem.cs.meta | 11 + .../Systems/DestinationMoveInCircleSystem.cs | 43 + .../DestinationMoveInCircleSystem.cs.meta | 11 + .../Systems/FallbackResolveMovementSystem.cs | 46 + .../FallbackResolveMovementSystem.cs.meta | 11 + .../Core/ECS/Systems/FollowerControlSystem.cs | 119 + .../ECS/Systems/FollowerControlSystem.cs.meta | 11 + .../ECS/Systems/InitManagedStateSystem.cs | 39 + .../Systems/InitManagedStateSystem.cs.meta | 11 + .../Core/ECS/Systems/LateSyncSystem.cs | 73 + .../Core/ECS/Systems/LateSyncSystem.cs.meta | 11 + .../Systems/MovementPlaneFromGraphSystem.cs | 247 + .../MovementPlaneFromGraphSystem.cs.meta | 11 + .../ECS/Systems/MovementStatisticsSystem.cs | 33 + .../Systems/MovementStatisticsSystem.cs.meta | 11 + .../ECS/Systems/PollPendingPathsSystem.cs | 84 + .../Systems/PollPendingPathsSystem.cs.meta | 11 + .../AstarPro/Core/ECS/Systems/RVOSystem.cs | 269 + .../Core/ECS/Systems/RVOSystem.cs.meta | 11 + .../Core/ECS/Systems/RepairPathSystem.cs | 49 + .../Core/ECS/Systems/RepairPathSystem.cs.meta | 11 + .../ECS/Systems/SchedulePathSearchSystem.cs | 137 + .../Systems/SchedulePathSearchSystem.cs.meta | 11 + .../Systems/SyncDestinationTransformSystem.cs | 30 + .../SyncDestinationTransformSystem.cs.meta | 11 + .../Systems/SyncTransformsToEntitiesSystem.cs | 88 + .../SyncTransformsToEntitiesSystem.cs.meta | 11 + .../ECS/Systems/TraverseOffMeshLinkSystem.cs | 107 + .../Systems/TraverseOffMeshLinkSystem.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Geometry.meta | 8 + .../ThirdParty/AstarPro/Core/Geometry/Int3.cs | 284 ++ .../AstarPro/Core/Geometry/Int3.cs.meta | 7 + .../AstarPro/Core/Geometry/IntBounds.cs | 68 + .../AstarPro/Core/Geometry/IntBounds.cs.meta | 11 + .../AstarPro/Core/Geometry/Triangulation.meta | 8 + .../Core/Geometry/Triangulation/LICENSE.md | 21 + .../Geometry/Triangulation/LICENSE.md.meta | 7 + .../Triangulation/TriangulationError.cs | 121 + .../Triangulation/TriangulationError.cs.meta | 11 + .../Geometry/Triangulation/Triangulator.cs | 3510 +++++++++++++ .../Triangulation/Triangulator.cs.meta | 11 + .../Core/Geometry/Triangulation/U128.cs | 52 + .../Core/Geometry/Triangulation/U128.cs.meta | 11 + .../andywiecko.BurstTriangulator.asmdef | 18 + .../andywiecko.BurstTriangulator.asmdef.meta | 7 + .../Core/Geometry/Vector2IntExtensions.cs | 11 + .../Geometry/Vector2IntExtensions.cs.meta | 11 + .../AstarPro/Core/GraphUpdateScene.cs | 390 ++ .../AstarPro/Core/GraphUpdateScene.cs.meta | 12 + .../AstarPro/Core/GraphUpdateShape.cs | 198 + .../AstarPro/Core/GraphUpdateShape.cs.meta | 7 + .../ThirdParty/AstarPro/Core/Legacy.meta | 8 + .../AstarPro/Core/Legacy/NNConstraint.cs | 275 ++ .../AstarPro/Core/Legacy/NNConstraint.cs.meta | 11 + .../Assets/ThirdParty/AstarPro/Core/Misc.meta | 2 + .../AstarPro/Core/Misc/AnimationLink.cs | 124 + .../AstarPro/Core/Misc/AnimationLink.cs.meta | 8 + .../AstarPro/Core/Misc/AssemblySearcher.cs | 46 + .../Core/Misc/AssemblySearcher.cs.meta | 11 + .../AstarPro/Core/Misc/AstarDebugger.cs | 345 ++ .../AstarPro/Core/Misc/AstarDebugger.cs.meta | 7 + .../AstarPro/Core/Misc/AutoRepathPolicy.cs | 133 + .../Core/Misc/AutoRepathPolicy.cs.meta | 11 + .../AstarPro/Core/Misc/GraphEditorBase.cs | 13 + .../Core/Misc/GraphEditorBase.cs.meta | 7 + .../AstarPro/Core/Misc/GraphModifier.cs | 252 + .../AstarPro/Core/Misc/GraphModifier.cs.meta | 12 + .../AstarPro/Core/Misc/GraphSnapshot.cs | 44 + .../AstarPro/Core/Misc/GraphSnapshot.cs.meta | 11 + .../AstarPro/Core/Misc/GraphUtilities.cs | 274 ++ .../AstarPro/Core/Misc/GraphUtilities.cs.meta | 12 + .../AstarPro/Core/Misc/LocalSpaceGraph.cs | 26 + .../Core/Misc/LocalSpaceGraph.cs.meta | 8 + .../AstarPro/Core/Misc/NavmeshEdges.cs | 454 ++ .../AstarPro/Core/Misc/NavmeshEdges.cs.meta | 11 + .../AstarPro/Core/Misc/NavmeshPrefab.cs | 492 ++ .../AstarPro/Core/Misc/NavmeshPrefab.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Misc/NodeLink.cs | 151 + .../AstarPro/Core/Misc/NodeLink.cs.meta | 7 + .../AstarPro/Core/Misc/NodeLink2.cs | 406 ++ .../AstarPro/Core/Misc/NodeLink2.cs.meta | 11 + .../AstarPro/Core/Misc/NodeLink3.cs | 294 ++ .../AstarPro/Core/Misc/NodeLink3.cs.meta | 8 + .../AstarPro/Core/Misc/OffMeshLinks.cs | 635 +++ .../AstarPro/Core/Misc/OffMeshLinks.cs.meta | 11 + .../AstarPro/Core/Misc/PathInterpolator.cs | 619 +++ .../Core/Misc/PathInterpolator.cs.meta | 12 + .../Core/Misc/PathPartWithLinkInfo.cs | 32 + .../Core/Misc/PathPartWithLinkInfo.cs.meta | 11 + .../AstarPro/Core/Misc/PathRequestSettings.cs | 157 + .../Core/Misc/PathRequestSettings.cs.meta | 11 + .../AstarPro/Core/Misc/PathfindingTag.cs | 56 + .../AstarPro/Core/Misc/PathfindingTag.cs.meta | 11 + .../Misc/RVODestinationCrowdedBehavior.cs | 313 ++ .../RVODestinationCrowdedBehavior.cs.meta | 12 + .../AstarPro/Core/Misc/UnityCompatibility.cs | 45 + .../Core/Misc/UnityCompatibility.cs.meta | 11 + .../Core/Misc/WindowsStoreCompatibility.cs | 140 + .../Misc/WindowsStoreCompatibility.cs.meta | 8 + .../AstarPro/Core/Misc/WorkItemProcessor.cs | 514 ++ .../Core/Misc/WorkItemProcessor.cs.meta | 12 + .../ThirdParty/AstarPro/Core/Nodes.meta | 2 + .../AstarPro/Core/Nodes/GraphNode.cs | 946 ++++ .../AstarPro/Core/Nodes/GraphNode.cs.meta | 12 + .../ThirdParty/AstarPro/Core/Pathfinding.meta | 8 + .../Core/Pathfinding/BlockableChannel.cs | 3 + .../Core/Pathfinding/BlockableChannel.cs.meta | 8 + .../Core/Pathfinding/GlobalNodeStorage.cs | 320 ++ .../Pathfinding/GlobalNodeStorage.cs.meta | 11 + .../Core/Pathfinding/GraphUpdateProcessor.cs | 292 ++ .../Pathfinding/GraphUpdateProcessor.cs.meta | 12 + .../Core/Pathfinding/HeuristicObjective.cs | 102 + .../Pathfinding/HeuristicObjective.cs.meta | 11 + .../Core/Pathfinding/HierarchicalGraph.cs | 626 +++ .../Pathfinding/HierarchicalGraph.cs.meta | 12 + .../Core/Pathfinding/ITraversalProvider.cs | 261 + .../Pathfinding/ITraversalProvider.cs.meta | 11 + .../Core/Pathfinding/NearestNodeConstraint.cs | 183 + .../Pathfinding/NearestNodeConstraint.cs.meta | 11 + .../AstarPro/Core/Pathfinding/Path.cs | 1216 +++++ .../AstarPro/Core/Pathfinding/Path.cs.meta | 7 + .../AstarPro/Core/Pathfinding/PathHandler.cs | 272 ++ .../Core/Pathfinding/PathHandler.cs.meta | 8 + .../Core/Pathfinding/PathProcessor.cs | 547 +++ .../Core/Pathfinding/PathProcessor.cs.meta | 12 + .../Core/Pathfinding/PathReturnQueue.cs | 93 + .../Core/Pathfinding/PathReturnQueue.cs.meta | 12 + .../Core/Pathfinding/TraversalConstraint.cs | 295 ++ .../Pathfinding/TraversalConstraint.cs.meta | 11 + .../Core/Pathfinding/TraversalCosts.cs | 239 + .../Core/Pathfinding/TraversalCosts.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Pooling.meta | 8 + .../AstarPro/Core/Pooling/ArrayPool.cs | 226 + .../AstarPro/Core/Pooling/ArrayPool.cs.meta | 12 + .../AstarPro/Core/Pooling/ListPool.cs | 211 + .../AstarPro/Core/Pooling/ListPool.cs.meta | 7 + .../AstarPro/Core/Pooling/ObjectPool.cs | 131 + .../AstarPro/Core/Pooling/ObjectPool.cs.meta | 7 + .../AstarPro/Core/Pooling/PathPool.cs | 87 + .../AstarPro/Core/Pooling/PathPool.cs.meta | 7 + .../AstarPro/Core/Pooling/StackPool.cs | 98 + .../AstarPro/Core/Pooling/StackPool.cs.meta | 7 + .../Assets/ThirdParty/AstarPro/Core/RVO.meta | 2 + .../ThirdParty/AstarPro/Core/RVO/RVOAgent.cs | 3 + .../AstarPro/Core/RVO/RVOAgent.cs.meta | 8 + .../AstarPro/Core/RVO/RVOAgentBurst.cs | 2039 ++++++++ .../AstarPro/Core/RVO/RVOAgentBurst.cs.meta | 11 + .../AstarPro/Core/RVO/RVOCoreObstacle.cs | 3 + .../AstarPro/Core/RVO/RVOCoreObstacle.cs.meta | 7 + .../AstarPro/Core/RVO/RVOCoreSimulator.cs | 3 + .../Core/RVO/RVOCoreSimulator.cs.meta | 7 + .../Core/RVO/RVOCoreSimulatorBurst.cs | 1271 +++++ .../Core/RVO/RVOCoreSimulatorBurst.cs.meta | 11 + .../AstarPro/Core/RVO/RVOObstacleCache.cs | 344 ++ .../Core/RVO/RVOObstacleCache.cs.meta | 11 + .../AstarPro/Core/RVO/RVOQuadtree.cs | 3 + .../AstarPro/Core/RVO/RVOQuadtree.cs.meta | 8 + .../AstarPro/Core/RVO/RVOQuadtreeBurst.cs | 711 +++ .../Core/RVO/RVOQuadtreeBurst.cs.meta | 11 + .../AstarPro/Core/Serialization.meta | 2 + .../Core/Serialization/JsonSerializer.cs | 985 ++++ .../Core/Serialization/JsonSerializer.cs.meta | 7 + .../Serialization/SimpleZipReplacement.cs | 82 + .../SimpleZipReplacement.cs.meta | 8 + .../AstarPro/Core/Serialization/TinyJson.cs | 527 ++ .../Core/Serialization/TinyJson.cs.meta | 12 + .../Assets/ThirdParty/AstarPro/Core/Sync.meta | 8 + .../AstarPro/Core/Sync/BlockableChannel.cs | 212 + .../Core/Sync/BlockableChannel.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Sync/Promise.cs | 48 + .../AstarPro/Core/Sync/Promise.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Sync/RWLock.cs | 226 + .../AstarPro/Core/Sync/RWLock.cs.meta | 11 + .../ThirdParty/AstarPro/Core/Sync/SpinLock.cs | 29 + .../AstarPro/Core/Sync/SpinLock.cs.meta | 11 + .../ThirdParty/AstarPro/Core/astarclasses.cs | 1661 +++++++ .../AstarPro/Core/astarclasses.cs.meta | 7 + .../ThirdParty/AstarPro/Documentation.meta | 8 + .../AstarPro/Documentation/documentation.html | 1720 +++++++ .../Documentation/documentation.html.meta | 7 + .../AstarPro/Documentation/documentation.pdf | Bin 0 -> 1030018 bytes .../Documentation/documentation.pdf.meta | 7 + .../AstarPro/Documentation/index.md | 7 + .../AstarPro/Documentation/index.md.meta | 7 + .../Assets/ThirdParty/AstarPro/Drawing.meta | 8 + .../ThirdParty/AstarPro/Drawing/ALINE.asmdef | 93 + .../AstarPro/Drawing/ALINE.asmdef.meta | 7 + .../AstarPro/Drawing/AlineHDRPCustomPass.cs | 49 + .../Drawing/AlineHDRPCustomPass.cs.meta | 11 + .../Drawing/AlineURPRenderPassFeature.cs | 96 + .../Drawing/AlineURPRenderPassFeature.cs.meta | 11 + .../AstarPro/Drawing/CommandBuilder.cs | 3331 +++++++++++++ .../AstarPro/Drawing/CommandBuilder.cs.meta | 11 + .../AstarPro/Drawing/CommandBuilder2D.cs | 497 ++ .../AstarPro/Drawing/CommandBuilder2D.cs.meta | 11 + .../Drawing/CommandBuilder2DExtensions.cs | 597 +++ .../CommandBuilder2DExtensions.cs.meta | 11 + .../Drawing/CommandBuilderExtensions.cs | 490 ++ .../Drawing/CommandBuilderExtensions.cs.meta | 11 + .../AstarPro/Drawing/Compatibility.cs | 3 + .../AstarPro/Drawing/Compatibility.cs.meta | 11 + .../AstarPro/Drawing/Documentation.meta | 8 + .../AstarPro/Drawing/Documentation/index.md | 7 + .../Drawing/Documentation/index.md.meta | 7 + .../ThirdParty/AstarPro/Drawing/Draw.cs | 1373 ++++++ .../ThirdParty/AstarPro/Drawing/Draw.cs.meta | 12 + .../AstarPro/Drawing/DrawingData.cs | 1727 +++++++ .../AstarPro/Drawing/DrawingData.cs.meta | 12 + .../AstarPro/Drawing/DrawingManager.cs | 994 ++++ .../AstarPro/Drawing/DrawingManager.cs.meta | 11 + .../AstarPro/Drawing/DrawingSettings.cs | 74 + .../AstarPro/Drawing/DrawingSettings.cs.meta | 11 + .../AstarPro/Drawing/DrawingUtilities.cs | 122 + .../AstarPro/Drawing/DrawingUtilities.cs.meta | 11 + .../ThirdParty/AstarPro/Drawing/Editor.meta | 8 + .../Drawing/Editor/DrawingEditor.asmdef | 40 + .../Drawing/Editor/DrawingEditor.asmdef.meta | 7 + .../Drawing/Editor/DrawingManagerEditor.cs | 19 + .../Editor/DrawingManagerEditor.cs.meta | 11 + .../Drawing/Editor/DrawingSettingsEditor.cs | 60 + .../Editor/DrawingSettingsEditor.cs.meta | 11 + .../AstarPro/Drawing/GeometryBuilder.cs | 1232 +++++ .../AstarPro/Drawing/GeometryBuilder.cs.meta | 11 + .../AstarPro/Drawing/MonoBehaviourGizmos.cs | 40 + .../Drawing/MonoBehaviourGizmos.cs.meta | 11 + .../AstarPro/Drawing/PackageTools.meta | 8 + .../AstarPro/Drawing/PackageTools/Editor.meta | 8 + .../PackageTools/Editor/DependencyCheck.cs | 47 + .../Editor/DependencyCheck.cs.meta | 11 + .../Editor/PackageToolsEditor.asmdef | 34 + .../Editor/PackageToolsEditor.asmdef.meta | 7 + .../ThirdParty/AstarPro/Drawing/Palette.cs | 124 + .../AstarPro/Drawing/Palette.cs.meta | 11 + .../AstarPro/Drawing/PersistentFilter.cs | 123 + .../AstarPro/Drawing/PersistentFilter.cs.meta | 11 + .../AstarPro/Drawing/Resources.meta | 8 + .../Drawing/Resources/aline_common.cginc | 76 + .../Drawing/Resources/aline_common.cginc.meta | 9 + .../Drawing/Resources/aline_common_line.cginc | 107 + .../Resources/aline_common_line.cginc.meta | 10 + .../Resources/aline_common_surface.cginc | 21 + .../Resources/aline_common_surface.cginc.meta | 10 + .../Drawing/Resources/aline_common_text.cginc | 107 + .../Resources/aline_common_text.cginc.meta | 7 + .../Resources/aline_droid_sans_mono.mat | 71 + .../Resources/aline_droid_sans_mono.mat.meta | 8 + .../Drawing/Resources/aline_fallback_font.png | Bin 0 -> 30818 bytes .../Resources/aline_fallback_font.png.meta | 134 + .../AstarPro/Drawing/Resources/aline_font.png | Bin 0 -> 36393 bytes .../Drawing/Resources/aline_font.png.meta | 106 + .../AstarPro/Drawing/Resources/aline_font.ttf | Bin 0 -> 117072 bytes .../Drawing/Resources/aline_font.ttf.meta | 23 + .../Drawing/Resources/aline_outline.mat | 58 + .../Drawing/Resources/aline_outline.mat.meta | 8 + .../Drawing/Resources/aline_outline.shader | 165 + .../Resources/aline_outline.shader.meta | 9 + .../Drawing/Resources/aline_surface.mat | 52 + .../Drawing/Resources/aline_surface.mat.meta | 2 + .../Drawing/Resources/aline_surface.shader | 174 + .../Resources/aline_surface.shader.meta | 2 + .../Drawing/Resources/aline_text.shader | 88 + .../Drawing/Resources/aline_text.shader.meta | 9 + .../ThirdParty/AstarPro/Drawing/SDFFont.cs | 230 + .../AstarPro/Drawing/SDFFont.cs.meta | 11 + .../AstarPro/Drawing/StreamSplitter.cs | 205 + .../AstarPro/Drawing/StreamSplitter.cs.meta | 11 + .../ThirdParty/AstarPro/Drawing/changelog.cs | 326 ++ .../AstarPro/Drawing/changelog.cs.meta | 11 + .../ThirdParty/AstarPro/Drawing/package.json | 42 + .../AstarPro/Drawing/package.json.meta | 7 + .../Assets/ThirdParty/AstarPro/Editor.meta | 2 + .../AstarPro/Editor/AIBaseEditor.cs | 192 + .../AstarPro/Editor/AIBaseEditor.cs.meta | 12 + .../AstarPro/Editor/AILerpEditor.cs | 31 + .../AstarPro/Editor/AILerpEditor.cs.meta | 12 + .../AstarPro/Editor/AnimationLinkEditor.cs | 23 + .../Editor/AnimationLinkEditor.cs.meta | 8 + .../AstarPro/Editor/AstarPathEditor.cs | 1333 +++++ .../AstarPro/Editor/AstarPathEditor.cs.meta | 7 + .../AstarPathfindingProjectEditor.asmdef | 81 + .../AstarPathfindingProjectEditor.asmdef.meta | 7 + .../Editor/AstarPathfindingProjectEditor.uss | 112 + .../AstarPathfindingProjectEditor.uss.meta | 11 + .../AstarPro/Editor/AstarUpdateChecker.cs | 291 ++ .../Editor/AstarUpdateChecker.cs.meta | 8 + .../AstarPro/Editor/AstarUpdateWindow.cs | 101 + .../AstarPro/Editor/AstarUpdateWindow.cs.meta | 8 + .../Editor/CustomGraphEditorAttribute.cs | 19 + .../Editor/CustomGraphEditorAttribute.cs.meta | 8 + .../AstarPro/Editor/EditorAssets.meta | 2 + .../EditorAssets/AstarEditorSkinDark.guiskin | 3198 ++++++++++++ .../AstarEditorSkinDark.guiskin.meta | 2 + .../EditorAssets/AstarEditorSkinLight.guiskin | 3186 ++++++++++++ .../AstarEditorSkinLight.guiskin.meta | 2 + .../Editor/EditorAssets/ComponentIcons.meta | 8 + .../AIDestinationSetter@256.png | Bin 0 -> 5307 bytes .../AIDestinationSetter@256.png.meta | 127 + .../ComponentIcons/AstarPath@256.png | Bin 0 -> 9036 bytes .../ComponentIcons/AstarPath@256.png.meta | 127 + .../DynamicGridObstacle@256.png | Bin 0 -> 4518 bytes .../DynamicGridObstacle@256.png.meta | 127 + .../ComponentIcons/MoveInCircle@256.png | Bin 0 -> 7628 bytes .../ComponentIcons/MoveInCircle@256.png.meta | 127 + .../ComponentIcons/MovementScripts@256.png | Bin 0 -> 6063 bytes .../MovementScripts@256.png.meta | 127 + .../ComponentIcons/NavmeshAdd@256.png | Bin 0 -> 4536 bytes .../ComponentIcons/NavmeshAdd@256.png.meta | 127 + .../ComponentIcons/NavmeshCut@256.png | Bin 0 -> 5258 bytes .../ComponentIcons/NavmeshCut@256.png.meta | 127 + .../ComponentIcons/NodeLink2@256.png | Bin 0 -> 7161 bytes .../ComponentIcons/NodeLink2@256.png.meta | 127 + .../ComponentIcons/Patrol@256.png | Bin 0 -> 5686 bytes .../ComponentIcons/Patrol@256.png.meta | 127 + .../ProceduralGraphMover@256.png | Bin 0 -> 5483 bytes .../ProceduralGraphMover@256.png.meta | 127 + .../ComponentIcons/RVOController@256.png | Bin 0 -> 4535 bytes .../ComponentIcons/RVOController@256.png.meta | 127 + .../ComponentIcons/Seeker@256.png | Bin 0 -> 8563 bytes .../ComponentIcons/Seeker@256.png.meta | 127 + .../d_AIDestinationSetter@256.png | Bin 0 -> 5307 bytes .../d_AIDestinationSetter@256.png.meta | 127 + .../ComponentIcons/d_AstarPath@256.png | Bin 0 -> 8914 bytes .../ComponentIcons/d_AstarPath@256.png.meta | 127 + .../d_DynamicGridObstacle@256.png | Bin 0 -> 4507 bytes .../d_DynamicGridObstacle@256.png.meta | 127 + .../ComponentIcons/d_MoveInCircle@256.png | Bin 0 -> 7431 bytes .../d_MoveInCircle@256.png.meta | 127 + .../ComponentIcons/d_MovementScripts@256.png | Bin 0 -> 5995 bytes .../d_MovementScripts@256.png.meta | 127 + .../ComponentIcons/d_NavmeshAdd@256.png | Bin 0 -> 4521 bytes .../ComponentIcons/d_NavmeshAdd@256.png.meta | 127 + .../ComponentIcons/d_NavmeshCut@256.png | Bin 0 -> 5246 bytes .../ComponentIcons/d_NavmeshCut@256.png.meta | 127 + .../ComponentIcons/d_Patrol@256.png | Bin 0 -> 5667 bytes .../ComponentIcons/d_Patrol@256.png.meta | 127 + .../d_ProceduralGraphMover@256.png | Bin 0 -> 5410 bytes .../d_ProceduralGraphMover@256.png.meta | 127 + .../ComponentIcons/d_RVOController@256.png | Bin 0 -> 4523 bytes .../d_RVOController@256.png.meta | 127 + .../ComponentIcons/d_Seeker@256.png | Bin 0 -> 8340 bytes .../ComponentIcons/d_Seeker@256.png.meta | 127 + .../Editor/EditorAssets/DarkSkin.meta | 2 + .../EditorAssets/DarkSkin/banner@1x.png | Bin 0 -> 294 bytes .../EditorAssets/DarkSkin/banner@1x.png.meta | 117 + .../EditorAssets/DarkSkin/banner@2x.png | Bin 0 -> 446 bytes .../EditorAssets/DarkSkin/banner@2x.png.meta | 117 + .../DarkSkin/colorInterpolationBox.png | Bin 0 -> 3361 bytes .../DarkSkin/colorInterpolationBox.png.meta | 47 + .../EditorAssets/DarkSkin/deleteButton.png | Bin 0 -> 371 bytes .../DarkSkin/deleteButton.png.meta | 117 + .../EditorAssets/DarkSkin/deleteButton@1x.png | Bin 0 -> 230 bytes .../DarkSkin/deleteButton@1x.png.meta | 56 + .../EditorAssets/DarkSkin/deleteButton@2x.png | Bin 0 -> 307 bytes .../DarkSkin/deleteButton@2x.png.meta | 56 + .../DarkSkin/deleteButton_active.png | Bin 0 -> 367 bytes .../DarkSkin/deleteButton_active.png.meta | 117 + .../DarkSkin/deleteButton_active@1x.png | Bin 0 -> 270 bytes .../DarkSkin/deleteButton_active@1x.png.meta | 56 + .../DarkSkin/deleteButton_active@2x.png | Bin 0 -> 375 bytes .../DarkSkin/deleteButton_active@2x.png.meta | 56 + .../DarkSkin/deleteButton_hover@1x.png | Bin 0 -> 263 bytes .../DarkSkin/deleteButton_hover@1x.png.meta | 117 + .../DarkSkin/deleteButton_hover@2x.png | Bin 0 -> 376 bytes .../DarkSkin/deleteButton_hover@2x.png.meta | 117 + .../DarkSkin/duplicateButton@1x.png | Bin 0 -> 217 bytes .../DarkSkin/duplicateButton@1x.png.meta | 117 + .../DarkSkin/duplicateButton@2x.png | Bin 0 -> 280 bytes .../DarkSkin/duplicateButton@2x.png.meta | 117 + .../DarkSkin/duplicateButton_active@1x.png | Bin 0 -> 251 bytes .../duplicateButton_active@1x.png.meta | 117 + .../DarkSkin/duplicateButton_active@2x.png | Bin 0 -> 350 bytes .../duplicateButton_active@2x.png.meta | 117 + .../DarkSkin/duplicateButton_hover@1x.png | Bin 0 -> 246 bytes .../duplicateButton_hover@1x.png.meta | 117 + .../DarkSkin/duplicateButton_hover@2x.png | Bin 0 -> 356 bytes .../duplicateButton_hover@2x.png.meta | 117 + .../EditorAssets/DarkSkin/editButton@1x.png | Bin 0 -> 331 bytes .../DarkSkin/editButton@1x.png.meta | 56 + .../EditorAssets/DarkSkin/editButton@2x.png | Bin 0 -> 489 bytes .../DarkSkin/editButton@2x.png.meta | 56 + .../DarkSkin/editButton_active@1x.png | Bin 0 -> 326 bytes .../DarkSkin/editButton_active@1x.png.meta | 56 + .../DarkSkin/editButton_active@2x.png | Bin 0 -> 553 bytes .../DarkSkin/editButton_active@2x.png.meta | 56 + .../DarkSkin/editButton_hover@1x.png | Bin 0 -> 324 bytes .../DarkSkin/editButton_hover@1x.png.meta | 117 + .../DarkSkin/editButton_hover@2x.png | Bin 0 -> 566 bytes .../DarkSkin/editButton_hover@2x.png.meta | 117 + .../DarkSkin/gizmoButton_off@1x.png | Bin 0 -> 502 bytes .../DarkSkin/gizmoButton_off@1x.png.meta | 56 + .../DarkSkin/gizmoButton_off@2x.png | Bin 0 -> 829 bytes .../DarkSkin/gizmoButton_off@2x.png.meta | 56 + .../DarkSkin/gizmoButton_off_active@1x.png | Bin 0 -> 499 bytes .../gizmoButton_off_active@1x.png.meta | 117 + .../DarkSkin/gizmoButton_off_active@2x.png | Bin 0 -> 850 bytes .../gizmoButton_off_active@2x.png.meta | 117 + .../DarkSkin/gizmoButton_off_hover@1x.png | Bin 0 -> 483 bytes .../gizmoButton_off_hover@1x.png.meta | 117 + .../DarkSkin/gizmoButton_off_hover@2x.png | Bin 0 -> 821 bytes .../gizmoButton_off_hover@2x.png.meta | 117 + .../DarkSkin/gizmoButton_on@1x.png | Bin 0 -> 444 bytes .../DarkSkin/gizmoButton_on@1x.png.meta | 56 + .../DarkSkin/gizmoButton_on@2x.png | Bin 0 -> 770 bytes .../DarkSkin/gizmoButton_on@2x.png.meta | 56 + .../DarkSkin/gizmoButton_on_active@1x.png | Bin 0 -> 510 bytes .../gizmoButton_on_active@1x.png.meta | 117 + .../DarkSkin/gizmoButton_on_active@2x.png | Bin 0 -> 883 bytes .../gizmoButton_on_active@2x.png.meta | 117 + .../DarkSkin/gizmoButton_on_hover@1x.png | Bin 0 -> 425 bytes .../DarkSkin/gizmoButton_on_hover@1x.png.meta | 117 + .../DarkSkin/gizmoButton_on_hover@2x.png | Bin 0 -> 786 bytes .../DarkSkin/gizmoButton_on_hover@2x.png.meta | 117 + .../EditorAssets/DarkSkin/gridPivotSelect.png | Bin 0 -> 222 bytes .../DarkSkin/gridPivotSelect.png.meta | 36 + .../DarkSkin/headerBackground.png | Bin 0 -> 1069 bytes .../DarkSkin/headerBackground.png.meta | 36 + .../EditorAssets/DarkSkin/hex_diameter@1x.png | Bin 0 -> 1422 bytes .../DarkSkin/hex_diameter@1x.png.meta | 103 + .../EditorAssets/DarkSkin/hex_diameter@2x.png | Bin 0 -> 2094 bytes .../DarkSkin/hex_diameter@2x.png.meta | 103 + .../EditorAssets/DarkSkin/hex_width@1x.png | Bin 0 -> 1349 bytes .../DarkSkin/hex_width@1x.png.meta | 103 + .../EditorAssets/DarkSkin/hex_width@2x.png | Bin 0 -> 1943 bytes .../DarkSkin/hex_width@2x.png.meta | 103 + .../Editor/EditorAssets/DarkSkin/images.meta | 2 + .../DarkSkin/images/gridPivotSelect-03.png | Bin 0 -> 117 bytes .../images/gridPivotSelect-03.png.meta | 45 + .../DarkSkin/images/gridPivotSelect.png | Bin 0 -> 124 bytes .../DarkSkin/images/gridPivotSelect.png.meta | 45 + .../images/gridPivotSelect_Normal.png | Bin 0 -> 150 bytes .../images/gridPivotSelect_Normal.png.meta | 45 + .../DarkSkin/images/gridPivotSelect_On.png | Bin 0 -> 133 bytes .../images/gridPivotSelect_On.png.meta | 45 + .../EditorAssets/DarkSkin/infoButton@1x.png | Bin 0 -> 356 bytes .../DarkSkin/infoButton@1x.png.meta | 56 + .../EditorAssets/DarkSkin/infoButton@2x.png | Bin 0 -> 554 bytes .../DarkSkin/infoButton@2x.png.meta | 56 + .../DarkSkin/infoButton_active@1x.png | Bin 0 -> 394 bytes .../DarkSkin/infoButton_active@1x.png.meta | 56 + .../DarkSkin/infoButton_active@2x.png | Bin 0 -> 633 bytes .../DarkSkin/infoButton_active@2x.png.meta | 56 + .../DarkSkin/infoButton_hover@1x.png | Bin 0 -> 379 bytes .../DarkSkin/infoButton_hover@1x.png.meta | 117 + .../DarkSkin/infoButton_hover@2x.png | Bin 0 -> 576 bytes .../DarkSkin/infoButton_hover@2x.png.meta | 117 + .../EditorAssets/DarkSkin/lock_closed@1x.png | Bin 0 -> 409 bytes .../DarkSkin/lock_closed@1x.png.meta | 76 + .../EditorAssets/DarkSkin/lock_closed@2x.png | Bin 0 -> 1642 bytes .../DarkSkin/lock_closed@2x.png.meta | 76 + .../EditorAssets/DarkSkin/lock_open@1x.png | Bin 0 -> 406 bytes .../DarkSkin/lock_open@1x.png.meta | 76 + .../EditorAssets/DarkSkin/lock_open@2x.png | Bin 0 -> 1618 bytes .../DarkSkin/lock_open@2x.png.meta | 76 + .../EditorAssets/DarkSkin/pixelBox2.png | Bin 0 -> 153 bytes .../EditorAssets/DarkSkin/pixelBox2.png.meta | 54 + .../EditorAssets/DarkSkin/pixelBox3@1x.png | Bin 0 -> 1108 bytes .../DarkSkin/pixelBox3@1x.png.meta | 56 + .../EditorAssets/DarkSkin/pixelBox3@2x.png | Bin 0 -> 1331 bytes .../DarkSkin/pixelBox3@2x.png.meta | 56 + .../DarkSkin/pixelBox3_separator.png | Bin 0 -> 142 bytes .../DarkSkin/pixelBox3_separator.png.meta | 36 + .../DarkSkin/simpleDeleteButton@1x.png | Bin 0 -> 1156 bytes .../DarkSkin/simpleDeleteButton@1x.png.meta | 103 + .../DarkSkin/simpleDeleteButton@2x.png | Bin 0 -> 1269 bytes .../DarkSkin/simpleDeleteButton@2x.png.meta | 103 + .../DarkSkin/simpleDeleteButton_on@1x.png | Bin 0 -> 1085 bytes .../simpleDeleteButton_on@1x.png.meta | 103 + .../DarkSkin/simpleDeleteButton_on@2x.png | Bin 0 -> 1168 bytes .../simpleDeleteButton_on@2x.png.meta | 103 + .../Editor/EditorAssets/LightSkin.meta | 2 + .../EditorAssets/LightSkin/banner@1x.png | Bin 0 -> 294 bytes .../EditorAssets/LightSkin/banner@1x.png.meta | 117 + .../EditorAssets/LightSkin/banner@2x.png | Bin 0 -> 446 bytes .../EditorAssets/LightSkin/banner@2x.png.meta | 117 + .../LightSkin/colorInterpolationBox.png | Bin 0 -> 3362 bytes .../LightSkin/colorInterpolationBox.png.meta | 117 + .../LightSkin/deleteButton@1x.png | Bin 0 -> 230 bytes .../LightSkin/deleteButton@1x.png.meta | 117 + .../LightSkin/deleteButton@2x.png | Bin 0 -> 307 bytes .../LightSkin/deleteButton@2x.png.meta | 117 + .../LightSkin/deleteButton_active@1x.png | Bin 0 -> 263 bytes .../LightSkin/deleteButton_active@1x.png.meta | 117 + .../LightSkin/deleteButton_active@2x.png | Bin 0 -> 380 bytes .../LightSkin/deleteButton_active@2x.png.meta | 117 + .../LightSkin/deleteButton_hover@1x.png | Bin 0 -> 266 bytes .../LightSkin/deleteButton_hover@1x.png.meta | 117 + .../LightSkin/deleteButton_hover@2x.png | Bin 0 -> 390 bytes .../LightSkin/deleteButton_hover@2x.png.meta | 117 + .../LightSkin/duplicateButton@1x.png | Bin 0 -> 217 bytes .../LightSkin/duplicateButton@1x.png.meta | 117 + .../LightSkin/duplicateButton@2x.png | Bin 0 -> 280 bytes .../LightSkin/duplicateButton@2x.png.meta | 117 + .../LightSkin/duplicateButton_active@1x.png | Bin 0 -> 246 bytes .../duplicateButton_active@1x.png.meta | 117 + .../LightSkin/duplicateButton_active@2x.png | Bin 0 -> 359 bytes .../duplicateButton_active@2x.png.meta | 117 + .../LightSkin/duplicateButton_hover@1x.png | Bin 0 -> 249 bytes .../duplicateButton_hover@1x.png.meta | 117 + .../LightSkin/duplicateButton_hover@2x.png | Bin 0 -> 369 bytes .../duplicateButton_hover@2x.png.meta | 117 + .../EditorAssets/LightSkin/editButton@1x.png | Bin 0 -> 303 bytes .../LightSkin/editButton@1x.png.meta | 117 + .../EditorAssets/LightSkin/editButton@2x.png | Bin 0 -> 482 bytes .../LightSkin/editButton@2x.png.meta | 117 + .../LightSkin/editButton_active@1x.png | Bin 0 -> 322 bytes .../LightSkin/editButton_active@1x.png.meta | 117 + .../LightSkin/editButton_active@2x.png | Bin 0 -> 560 bytes .../LightSkin/editButton_active@2x.png.meta | 117 + .../LightSkin/editButton_hover@1x.png | Bin 0 -> 336 bytes .../LightSkin/editButton_hover@1x.png.meta | 117 + .../LightSkin/editButton_hover@2x.png | Bin 0 -> 550 bytes .../LightSkin/editButton_hover@2x.png.meta | 117 + .../LightSkin/gizmoButton_off@1x.png | Bin 0 -> 469 bytes .../LightSkin/gizmoButton_off@1x.png.meta | 117 + .../LightSkin/gizmoButton_off@2x.png | Bin 0 -> 814 bytes .../LightSkin/gizmoButton_off@2x.png.meta | 117 + .../LightSkin/gizmoButton_off_active@1x.png | Bin 0 -> 499 bytes .../gizmoButton_off_active@1x.png.meta | 117 + .../LightSkin/gizmoButton_off_active@2x.png | Bin 0 -> 850 bytes .../gizmoButton_off_active@2x.png.meta | 117 + .../LightSkin/gizmoButton_off_hover@1x.png | Bin 0 -> 494 bytes .../gizmoButton_off_hover@1x.png.meta | 117 + .../LightSkin/gizmoButton_off_hover@2x.png | Bin 0 -> 872 bytes .../gizmoButton_off_hover@2x.png.meta | 117 + .../LightSkin/gizmoButton_on@1x.png | Bin 0 -> 422 bytes .../LightSkin/gizmoButton_on@1x.png.meta | 117 + .../LightSkin/gizmoButton_on@2x.png | Bin 0 -> 712 bytes .../LightSkin/gizmoButton_on@2x.png.meta | 117 + .../LightSkin/gizmoButton_on_active@1x.png | Bin 0 -> 510 bytes .../gizmoButton_on_active@1x.png.meta | 117 + .../LightSkin/gizmoButton_on_active@2x.png | Bin 0 -> 883 bytes .../gizmoButton_on_active@2x.png.meta | 117 + .../LightSkin/gizmoButton_on_hover@1x.png | Bin 0 -> 424 bytes .../gizmoButton_on_hover@1x.png.meta | 117 + .../LightSkin/gizmoButton_on_hover@2x.png | Bin 0 -> 750 bytes .../gizmoButton_on_hover@2x.png.meta | 117 + .../LightSkin/headerBackground.png | Bin 0 -> 1078 bytes .../LightSkin/headerBackground.png.meta | 117 + .../LightSkin/hex_diameter@1x.png | Bin 0 -> 1223 bytes .../LightSkin/hex_diameter@1x.png.meta | 117 + .../LightSkin/hex_diameter@2x.png | Bin 0 -> 1756 bytes .../LightSkin/hex_diameter@2x.png.meta | 117 + .../EditorAssets/LightSkin/hex_width@1x.png | Bin 0 -> 1205 bytes .../LightSkin/hex_width@1x.png.meta | 117 + .../EditorAssets/LightSkin/hex_width@2x.png | Bin 0 -> 1577 bytes .../LightSkin/hex_width@2x.png.meta | 117 + .../EditorAssets/LightSkin/infoButton@1x.png | Bin 0 -> 328 bytes .../LightSkin/infoButton@1x.png.meta | 117 + .../EditorAssets/LightSkin/infoButton@2x.png | Bin 0 -> 517 bytes .../LightSkin/infoButton@2x.png.meta | 117 + .../LightSkin/infoButton_active@1x.png | Bin 0 -> 384 bytes .../LightSkin/infoButton_active@1x.png.meta | 117 + .../LightSkin/infoButton_active@2x.png | Bin 0 -> 628 bytes .../LightSkin/infoButton_active@2x.png.meta | 117 + .../LightSkin/infoButton_hover@1x.png | Bin 0 -> 355 bytes .../LightSkin/infoButton_hover@1x.png.meta | 117 + .../LightSkin/infoButton_hover@2x.png | Bin 0 -> 588 bytes .../LightSkin/infoButton_hover@2x.png.meta | 117 + .../EditorAssets/LightSkin/lock_closed@1x.png | Bin 0 -> 410 bytes .../LightSkin/lock_closed@1x.png.meta | 117 + .../EditorAssets/LightSkin/lock_closed@2x.png | Bin 0 -> 1640 bytes .../LightSkin/lock_closed@2x.png.meta | 117 + .../EditorAssets/LightSkin/lock_open@1x.png | Bin 0 -> 406 bytes .../LightSkin/lock_open@1x.png.meta | 117 + .../EditorAssets/LightSkin/lock_open@2x.png | Bin 0 -> 1617 bytes .../LightSkin/lock_open@2x.png.meta | 117 + .../EditorAssets/LightSkin/pixelBox3@1x.png | Bin 0 -> 1245 bytes .../LightSkin/pixelBox3@1x.png.meta | 117 + .../EditorAssets/LightSkin/pixelBox3@2x.png | Bin 0 -> 1606 bytes .../LightSkin/pixelBox3@2x.png.meta | 117 + .../EditorAssets/LightSkin/reset_normal.png | Bin 0 -> 412 bytes .../LightSkin/reset_normal.png.meta | 117 + .../EditorAssets/LightSkin/reset_on.png | Bin 0 -> 412 bytes .../EditorAssets/LightSkin/reset_on.png.meta | 117 + .../LightSkin/simpleDeleteButton@1x.png | Bin 0 -> 1160 bytes .../LightSkin/simpleDeleteButton@1x.png.meta | 117 + .../LightSkin/simpleDeleteButton@2x.png | Bin 0 -> 1266 bytes .../LightSkin/simpleDeleteButton@2x.png.meta | 117 + .../LightSkin/simpleDeleteButton_on@1x.png | Bin 0 -> 1096 bytes .../simpleDeleteButton_on@1x.png.meta | 117 + .../LightSkin/simpleDeleteButton_on@2x.png | Bin 0 -> 1162 bytes .../simpleDeleteButton_on@2x.png.meta | 117 + .../AstarPro/Editor/EditorAssets/grid.psd | Bin 0 -> 36566 bytes .../Editor/EditorAssets/grid.psd.meta | 33 + .../AstarPro/Editor/EditorAssets/images.meta | 2 + .../Editor/EditorAssets/images/ArrowDown.png | Bin 0 -> 216 bytes .../EditorAssets/images/ArrowDown.png.meta | 36 + .../EditorAssets/images/ArrowDown_on.png | Bin 0 -> 229 bytes .../EditorAssets/images/ArrowDown_on.png.meta | 36 + .../Editor/EditorAssets/images/ArrowUp.png | Bin 0 -> 201 bytes .../EditorAssets/images/ArrowUp.png.meta | 36 + .../Editor/EditorAssets/images/ArrowUp_on.png | Bin 0 -> 217 bytes .../EditorAssets/images/ArrowUp_on.png.meta | 36 + .../Editor/EditorAssets/images/astar_logo.png | Bin 0 -> 8246 bytes .../EditorAssets/images/astar_logo.png.meta | 127 + .../EditorAssets/images/changelog_icon.png | Bin 0 -> 833 bytes .../images/changelog_icon.png.meta | 127 + .../Editor/EditorAssets/images/check_icon.png | Bin 0 -> 708 bytes .../EditorAssets/images/check_icon.png.meta | 127 + .../images/documentation_icon.png | Bin 0 -> 1018 bytes .../images/documentation_icon.png.meta | 127 + .../EditorAssets/images/educate_icon.png | Bin 0 -> 786 bytes .../EditorAssets/images/educate_icon.png.meta | 127 + .../EditorAssets/images/gridPivotSelect.png | Bin 0 -> 247 bytes .../images/gridPivotSelect.png.meta | 36 + .../images/gridPivotSelect_Normal.png | Bin 0 -> 145 bytes .../images/gridPivotSelect_Normal.png.meta | 36 + .../images/gridPivotSelect_On.png | Bin 0 -> 133 bytes .../images/gridPivotSelect_On.png.meta | 36 + .../EditorAssets/images/samples_icon.png | Bin 0 -> 827 bytes .../EditorAssets/images/samples_icon.png.meta | 127 + .../EditorAssets/images/upDownArrow_01.png | Bin 0 -> 249 bytes .../images/upDownArrow_01.png.meta | 36 + .../EditorAssets/images/upDownArrow_02.png | Bin 0 -> 255 bytes .../images/upDownArrow_02.png.meta | 36 + .../EditorAssets/images/upDownButtons_01.png | Bin 0 -> 386 bytes .../images/upDownButtons_01.png.meta | 36 + .../EditorAssets/images/upDownButtons_02.png | Bin 0 -> 425 bytes .../images/upDownButtons_02.png.meta | 36 + .../Editor/EditorAssets/linkButton_active.png | Bin 0 -> 1564 bytes .../EditorAssets/linkButton_active.png.meta | 36 + .../Editor/EditorAssets/linkButton_normal.png | Bin 0 -> 1607 bytes .../EditorAssets/linkButton_normal.png.meta | 36 + .../AstarPro/Editor/EditorAssets/pixelBox.png | Bin 0 -> 233 bytes .../Editor/EditorAssets/pixelBox.png.meta | 36 + .../Editor/EditorAssets/pixelBox2.png | Bin 0 -> 153 bytes .../Editor/EditorAssets/pixelBox2.png.meta | 36 + .../EditorAssets/pixelBox3_separator.png | Bin 0 -> 2851 bytes .../EditorAssets/pixelBox3_separator.png.meta | 36 + .../Editor/EditorAssets/scene_box@1x.png | Bin 0 -> 1298 bytes .../Editor/EditorAssets/scene_box@1x.png.meta | 103 + .../Editor/EditorAssets/scene_box@2x.png | Bin 0 -> 1690 bytes .../Editor/EditorAssets/scene_box@2x.png.meta | 103 + .../AstarPro/Editor/EditorAssets/tooltips.tsv | 4323 +++++++++++++++++ .../Editor/EditorAssets/tooltips.tsv.meta | 7 + .../AstarPro/Editor/EditorHelpers.cs | 104 + .../AstarPro/Editor/EditorHelpers.cs.meta | 11 + .../AstarPro/Editor/GraphEditors.meta | 2 + .../Editor/GraphEditors/GraphEditor.cs | 148 + .../Editor/GraphEditors/GraphEditor.cs.meta | 7 + .../GraphEditors/GridGeneratorEditor.cs | 875 ++++ .../GraphEditors/GridGeneratorEditor.cs.meta | 7 + .../GraphEditors/LayerGridGraphEditor.cs | 36 + .../GraphEditors/LayerGridGraphEditor.cs.meta | 7 + .../GraphEditors/NavMeshGeneratorEditor.cs | 65 + .../NavMeshGeneratorEditor.cs.meta | 7 + .../GraphEditors/PointGeneratorEditor.cs | 56 + .../GraphEditors/PointGeneratorEditor.cs.meta | 7 + .../Editor/GraphEditors/RecastGraphEditor.cs | 590 +++ .../GraphEditors/RecastGraphEditor.cs.meta | 7 + .../AstarPro/Editor/GraphMaskDrawer.cs | 51 + .../AstarPro/Editor/GraphMaskDrawer.cs.meta | 11 + .../AstarPro/Editor/GraphUpdateSceneEditor.cs | 343 ++ .../Editor/GraphUpdateSceneEditor.cs.meta | 7 + .../AstarPro/Editor/Inspectors.meta | 8 + .../Inspectors/AIDestinationSetterEditor.cs | 17 + .../AIDestinationSetterEditor.cs.meta | 11 + .../Editor/Inspectors/FollowerEntityEditor.cs | 379 ++ .../Inspectors/FollowerEntityEditor.cs.meta | 11 + .../Editor/Inspectors/NodeLink2Editor.cs | 29 + .../Editor/Inspectors/NodeLink2Editor.cs.meta | 11 + .../AstarPro/Editor/LocalSpaceRichAIEditor.cs | 15 + .../Editor/LocalSpaceRichAIEditor.cs.meta | 11 + .../AstarPro/Editor/ModifierEditors.meta | 2 + .../ModifierEditors/FunnelModifierEditor.cs | 16 + .../FunnelModifierEditor.cs.meta | 11 + .../ModifierEditors/RaycastModifierEditor.cs | 38 + .../RaycastModifierEditor.cs.meta | 7 + .../ModifierEditors/SmoothModifierEditor.cs | 45 + .../SmoothModifierEditor.cs.meta | 7 + .../AstarPro/Editor/NavmeshAddEditor.cs | 57 + .../AstarPro/Editor/NavmeshAddEditor.cs.meta | 11 + .../AstarPro/Editor/NavmeshCutEditor.cs | 106 + .../AstarPro/Editor/NavmeshCutEditor.cs.meta | 12 + .../AstarPro/Editor/NavmeshPrefabEditor.cs | 168 + .../Editor/NavmeshPrefabEditor.cs.meta | 11 + .../Editor/PathfindingEditorSettings.cs | 13 + .../Editor/PathfindingEditorSettings.cs.meta | 11 + .../AstarPro/Editor/PathfindingTagDrawer.cs | 11 + .../Editor/PathfindingTagDrawer.cs.meta | 11 + .../Editor/ProceduralGridMoverEditor.cs | 47 + .../Editor/ProceduralGridMoverEditor.cs.meta | 11 + .../AstarPro/Editor/RVOControllerEditor.cs | 67 + .../Editor/RVOControllerEditor.cs.meta | 12 + .../AstarPro/Editor/RVONavmeshEditor.cs | 11 + .../AstarPro/Editor/RVONavmeshEditor.cs.meta | 8 + .../AstarPro/Editor/RVOSimulatorEditor.cs | 21 + .../Editor/RVOSimulatorEditor.cs.meta | 8 + .../Editor/RVOSquareObstacleEditor.cs | 12 + .../Editor/RVOSquareObstacleEditor.cs.meta | 8 + .../AstarPro/Editor/RecastMeshObjEditor.cs | 3 + .../Editor/RecastMeshObjEditor.cs.meta | 8 + .../Editor/RecastNavmeshModifierEditor.cs | 129 + .../RecastNavmeshModifierEditor.cs.meta | 11 + .../AstarPro/Editor/RuleEditors.meta | 8 + .../Editor/RuleEditors/GridGraphRuleEditor.cs | 7 + .../RuleEditors/GridGraphRuleEditor.cs.meta | 11 + .../RuleEditors/RuleAnglePenaltyEditor.cs | 22 + .../RuleAnglePenaltyEditor.cs.meta | 11 + .../RuleEditors/RuleElevationPenaltyEditor.cs | 61 + .../RuleElevationPenaltyEditor.cs.meta | 11 + .../RulePerLayerModificationsEditor.cs | 49 + .../RulePerLayerModificationsEditor.cs.meta | 11 + .../Editor/RuleEditors/RuleTextureEditor.cs | 143 + .../RuleEditors/RuleTextureEditor.cs.meta | 11 + .../AstarPro/Editor/SeekerEditor.cs | 298 ++ .../AstarPro/Editor/SeekerEditor.cs.meta | 7 + .../Assets/ThirdParty/AstarPro/Editor/UI.meta | 8 + .../AstarPro/Editor/UI/WelcomeScreen.cs | 208 + .../AstarPro/Editor/UI/WelcomeScreen.cs.meta | 14 + .../AstarPro/Editor/UI/WelcomeScreen.uxml | 29 + .../Editor/UI/WelcomeScreen.uxml.meta | 10 + .../Assets/ThirdParty/AstarPro/Graphs.meta | 2 + .../ThirdParty/AstarPro/Graphs/Grid.meta | 8 + .../AstarPro/Graphs/Grid/GraphCollision.cs | 381 ++ .../Graphs/Grid/GraphCollision.cs.meta | 11 + .../Graphs/Grid/GridAdjacencyMapper.cs | 32 + .../Graphs/Grid/GridAdjacencyMapper.cs.meta | 11 + .../AstarPro/Graphs/Grid/GridGraphScanData.cs | 735 +++ .../Graphs/Grid/GridGraphScanData.cs.meta | 11 + .../Graphs/Grid/GridIterationUtilities.cs | 261 + .../Grid/GridIterationUtilities.cs.meta | 11 + .../ThirdParty/AstarPro/Graphs/Grid/Jobs.meta | 8 + .../Graphs/Grid/Jobs/JobAllocateNodes.cs | 57 + .../Graphs/Grid/Jobs/JobAllocateNodes.cs.meta | 11 + .../Grid/Jobs/JobCalculateGridConnections.cs | 233 + .../Jobs/JobCalculateGridConnections.cs.meta | 11 + .../Graphs/Grid/Jobs/JobCheckCollisions.cs | 33 + .../Grid/Jobs/JobCheckCollisions.cs.meta | 11 + .../Grid/Jobs/JobColliderHitsToBooleans.cs | 32 + .../Jobs/JobColliderHitsToBooleans.cs.meta | 11 + .../Graphs/Grid/Jobs/JobCopyBuffers.cs | 50 + .../Graphs/Grid/Jobs/JobCopyBuffers.cs.meta | 11 + .../Graphs/Grid/Jobs/JobDirtyNodes.cs | 3 + .../Graphs/Grid/Jobs/JobDirtyNodes.cs.meta | 11 + .../AstarPro/Graphs/Grid/Jobs/JobErosion.cs | 190 + .../Graphs/Grid/Jobs/JobErosion.cs.meta | 11 + .../Grid/Jobs/JobFilterDiagonalConnections.cs | 129 + .../Jobs/JobFilterDiagonalConnections.cs.meta | 11 + .../Grid/Jobs/JobMergeRaycastCollisionHits.cs | 31 + .../Jobs/JobMergeRaycastCollisionHits.cs.meta | 11 + .../Graphs/Grid/Jobs/JobNodeGridLayout.cs | 36 + .../Grid/Jobs/JobNodeGridLayout.cs.meta | 11 + .../Graphs/Grid/Jobs/JobNodeWalkability.cs | 73 + .../Grid/Jobs/JobNodeWalkability.cs.meta | 11 + .../Grid/Jobs/JobPrepareCapsuleCommands.cs | 44 + .../Jobs/JobPrepareCapsuleCommands.cs.meta | 11 + .../Graphs/Grid/Jobs/JobPrepareGridRaycast.cs | 111 + .../Grid/Jobs/JobPrepareGridRaycast.cs.meta | 11 + .../Graphs/Grid/Jobs/JobPrepareRaycasts.cs | 50 + .../Grid/Jobs/JobPrepareRaycasts.cs.meta | 11 + .../Grid/Jobs/JobPrepareSphereCommands.cs | 41 + .../Jobs/JobPrepareSphereCommands.cs.meta | 11 + .../Graphs/Grid/Jobs/JobRaycastAll.cs | 121 + .../Graphs/Grid/Jobs/JobRaycastAll.cs.meta | 11 + .../Graphs/Grid/Jobs/JobReadNodeData.cs | 93 + .../Graphs/Grid/Jobs/JobReadNodeData.cs.meta | 11 + .../Graphs/Grid/Jobs/JobRelocateNodes.cs | 24 + .../Graphs/Grid/Jobs/JobRelocateNodes.cs.meta | 11 + .../Graphs/Grid/Jobs/JobWriteNodeData.cs | 91 + .../Graphs/Grid/Jobs/JobWriteNodeData.cs.meta | 11 + .../AstarPro/Graphs/Grid/Rules.meta | 8 + .../Graphs/Grid/Rules/GridGraphRules.cs | 295 ++ .../Graphs/Grid/Rules/GridGraphRules.cs.meta | 11 + .../Graphs/Grid/Rules/RuleAnglePenalty.cs | 81 + .../Grid/Rules/RuleAnglePenalty.cs.meta | 11 + .../Graphs/Grid/Rules/RuleElevationPenalty.cs | 76 + .../Grid/Rules/RuleElevationPenalty.cs.meta | 11 + .../Grid/Rules/RulePerLayerModifications.cs | 82 + .../Rules/RulePerLayerModifications.cs.meta | 11 + .../AstarPro/Graphs/Grid/Rules/RuleTexture.cs | 193 + .../Graphs/Grid/Rules/RuleTexture.cs.meta | 11 + .../ThirdParty/AstarPro/Graphs/GridGraph.cs | 3921 +++++++++++++++ .../AstarPro/Graphs/GridGraph.cs.meta | 7 + .../AstarPro/Graphs/LayerGridGraph.cs | 286 ++ .../AstarPro/Graphs/LayerGridGraph.cs.meta | 7 + .../ThirdParty/AstarPro/Graphs/LinkGraph.cs | 177 + .../AstarPro/Graphs/LinkGraph.cs.meta | 11 + .../ThirdParty/AstarPro/Graphs/NavGraph.cs | 629 +++ .../AstarPro/Graphs/NavGraph.cs.meta | 7 + .../AstarPro/Graphs/NavMeshGraph.cs | 398 ++ .../AstarPro/Graphs/NavMeshGraph.cs.meta | 7 + .../ThirdParty/AstarPro/Graphs/Navmesh.meta | 8 + .../AstarPro/Graphs/Navmesh/AABBTree.cs | 381 ++ .../AstarPro/Graphs/Navmesh/AABBTree.cs.meta | 7 + .../AstarPro/Graphs/Navmesh/BBTree.cs | 583 +++ .../AstarPro/Graphs/Navmesh/BBTree.cs.meta | 7 + .../Graphs/Navmesh/ColliderMeshBuilder2D.cs | 351 ++ .../Navmesh/ColliderMeshBuilder2D.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/Jobs.meta | 8 + .../Graphs/Navmesh/Jobs/JobBuildNodes.cs | 102 + .../Graphs/Navmesh/Jobs/JobBuildNodes.cs.meta | 11 + .../Jobs/JobBuildTileMeshFromVertices.cs | 103 + .../Jobs/JobBuildTileMeshFromVertices.cs.meta | 11 + .../Jobs/JobBuildTileMeshFromVoxels.cs | 293 ++ .../Jobs/JobBuildTileMeshFromVoxels.cs.meta | 11 + .../Jobs/JobCalculateTriangleConnections.cs | 74 + .../JobCalculateTriangleConnections.cs.meta | 11 + .../Graphs/Navmesh/Jobs/JobConnectTiles.cs | 159 + .../Navmesh/Jobs/JobConnectTiles.cs.meta | 11 + .../Navmesh/Jobs/JobConvertAreasToTags.cs | 23 + .../Jobs/JobConvertAreasToTags.cs.meta | 11 + .../Graphs/Navmesh/Jobs/JobCreateTiles.cs | 148 + .../Navmesh/Jobs/JobCreateTiles.cs.meta | 11 + .../Jobs/JobTransformTileCoordinates.cs | 30 + .../Jobs/JobTransformTileCoordinates.cs.meta | 11 + .../Navmesh/Jobs/JobWriteNodeConnections.cs | 60 + .../Jobs/JobWriteNodeConnections.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/NavmeshTile.cs | 132 + .../Graphs/Navmesh/NavmeshTile.cs.meta | 12 + .../AstarPro/Graphs/Navmesh/RecastBuilder.cs | 54 + .../Graphs/Navmesh/RecastBuilder.cs.meta | 11 + .../Graphs/Navmesh/RecastMeshGatherer.cs | 1478 ++++++ .../Graphs/Navmesh/RecastMeshGatherer.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/TileBuilder.cs | 373 ++ .../Graphs/Navmesh/TileBuilder.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/TileHandler.cs | 1417 ++++++ .../Graphs/Navmesh/TileHandler.cs.meta | 8 + .../AstarPro/Graphs/Navmesh/TileLayout.cs | 125 + .../Graphs/Navmesh/TileLayout.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/TileMesh.cs | 55 + .../AstarPro/Graphs/Navmesh/TileMesh.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/TileMeshes.cs | 199 + .../Graphs/Navmesh/TileMeshes.cs.meta | 11 + .../AstarPro/Graphs/Navmesh/Voxels.meta | 2 + .../Navmesh/Voxels/CompactVoxelField.cs | 132 + .../Navmesh/Voxels/CompactVoxelField.cs.meta | 11 + .../Graphs/Navmesh/Voxels/LinkedVoxelField.cs | 295 ++ .../Navmesh/Voxels/LinkedVoxelField.cs.meta | 11 + .../Graphs/Navmesh/Voxels/VoxelContour.cs | 711 +++ .../Navmesh/Voxels/VoxelContour.cs.meta | 11 + .../Graphs/Navmesh/Voxels/VoxelMesh.cs | 551 +++ .../Graphs/Navmesh/Voxels/VoxelMesh.cs.meta | 11 + .../Navmesh/Voxels/VoxelPolygonClipper.cs | 194 + .../Voxels/VoxelPolygonClipper.cs.meta | 12 + .../Navmesh/Voxels/VoxelRasterization.cs | 493 ++ .../Navmesh/Voxels/VoxelRasterization.cs.meta | 11 + .../Graphs/Navmesh/Voxels/VoxelRegion.cs | 803 +++ .../Graphs/Navmesh/Voxels/VoxelRegion.cs.meta | 11 + .../ThirdParty/AstarPro/Graphs/NavmeshBase.cs | 2339 +++++++++ .../AstarPro/Graphs/NavmeshBase.cs.meta | 12 + .../ThirdParty/AstarPro/Graphs/Nodes.meta | 2 + .../AstarPro/Graphs/Nodes/GridNode.cs | 452 ++ .../AstarPro/Graphs/Nodes/GridNode.cs.meta | 7 + .../AstarPro/Graphs/Nodes/GridNodeBase.cs | 543 +++ .../Graphs/Nodes/GridNodeBase.cs.meta | 12 + .../AstarPro/Graphs/Nodes/LevelGridNode.cs | 410 ++ .../Graphs/Nodes/LevelGridNode.cs.meta | 11 + .../AstarPro/Graphs/Nodes/PointNode.cs | 228 + .../AstarPro/Graphs/Nodes/PointNode.cs.meta | 8 + .../AstarPro/Graphs/Nodes/TriangleMeshNode.cs | 688 +++ .../Graphs/Nodes/TriangleMeshNode.cs.meta | 8 + .../ThirdParty/AstarPro/Graphs/Point.meta | 8 + .../AstarPro/Graphs/Point/PointKDTree.cs | 351 ++ .../AstarPro/Graphs/Point/PointKDTree.cs.meta | 12 + .../ThirdParty/AstarPro/Graphs/PointGraph.cs | 926 ++++ .../AstarPro/Graphs/PointGraph.cs.meta | 7 + .../ThirdParty/AstarPro/Graphs/RecastGraph.cs | 1487 ++++++ .../AstarPro/Graphs/RecastGraph.cs.meta | 7 + .../ThirdParty/AstarPro/Graphs/Utilities.meta | 2 + .../Graphs/Utilities/EuclideanEmbedding.cs | 453 ++ .../Utilities/EuclideanEmbedding.cs.meta | 8 + .../Graphs/Utilities/GraphTransform.cs | 604 +++ .../Graphs/Utilities/GraphTransform.cs.meta | 12 + .../AstarPro/Graphs/Utilities/GridLookup.cs | 221 + .../Graphs/Utilities/GridLookup.cs.meta | 12 + .../Graphs/Utilities/NavMeshRenderer.cs | 3 + .../Graphs/Utilities/NavMeshRenderer.cs.meta | 7 + .../Graphs/Utilities/RecastMeshObj.cs | 3 + .../Graphs/Utilities/RecastMeshObj.cs.meta | 11 + .../Graphs/Utilities/RecastNavmeshModifier.cs | 343 ++ .../Utilities/RecastNavmeshModifier.cs.meta | 7 + .../AstarPro/Graphs/Utilities/UtilityJobs.cs | 361 ++ .../Graphs/Utilities/UtilityJobs.cs.meta | 11 + .../Assets/ThirdParty/AstarPro/Modifiers.meta | 2 + .../AstarPro/Modifiers/AdvancedSmooth.cs | 561 +++ .../AstarPro/Modifiers/AdvancedSmooth.cs.meta | 7 + .../AstarPro/Modifiers/AlternativePath.cs | 94 + .../Modifiers/AlternativePath.cs.meta | 7 + .../AstarPro/Modifiers/FunnelModifier.cs | 139 + .../AstarPro/Modifiers/FunnelModifier.cs.meta | 7 + .../AstarPro/Modifiers/Modifiers.cs | 89 + .../AstarPro/Modifiers/Modifiers.cs.meta | 7 + .../AstarPro/Modifiers/RadiusModifier.cs | 283 ++ .../AstarPro/Modifiers/RadiusModifier.cs.meta | 7 + .../AstarPro/Modifiers/RaycastModifier.cs | 314 ++ .../Modifiers/RaycastModifier.cs.meta | 7 + .../Modifiers/SimpleSmoothModifier.cs | 364 ++ .../Modifiers/SimpleSmoothModifier.cs.meta | 7 + .../AstarPro/Modifiers/StartEndModifier.cs | 283 ++ .../Modifiers/StartEndModifier.cs.meta | 7 + .../Assets/ThirdParty/AstarPro/Navmesh.meta | 5 + .../ThirdParty/AstarPro/Navmesh/NavmeshAdd.cs | 265 + .../AstarPro/Navmesh/NavmeshAdd.cs.meta | 11 + .../AstarPro/Navmesh/NavmeshClipper.cs | 111 + .../AstarPro/Navmesh/NavmeshClipper.cs.meta | 11 + .../ThirdParty/AstarPro/Navmesh/NavmeshCut.cs | 1007 ++++ .../AstarPro/Navmesh/NavmeshCut.cs.meta | 11 + .../AstarPro/Navmesh/NavmeshUpdates.cs | 479 ++ .../AstarPro/Navmesh/NavmeshUpdates.cs.meta | 12 + .../AstarPro/Navmesh/RelevantGraphSurface.cs | 94 + .../Navmesh/RelevantGraphSurface.cs.meta | 8 + .../ThirdParty/AstarPro/PackageTools.meta | 9 + .../AstarPro/PackageTools/Editor.meta | 9 + .../PackageTools/Editor/DependencyCheck.cs | 66 + .../Editor/DependencyCheck.cs.meta | 11 + .../PackageTools/Editor/EditorBase.cs | 323 ++ .../PackageTools/Editor/EditorBase.cs.meta | 12 + .../PackageTools/Editor/EditorGUIx.cs | 204 + .../PackageTools/Editor/EditorGUIx.cs.meta | 7 + .../PackageTools/Editor/EnumFlagDrawer.cs | 35 + .../Editor/EnumFlagDrawer.cs.meta | 12 + .../PackageTools/Editor/LegacyEditorHelper.cs | 29 + .../Editor/LegacyEditorHelper.cs.meta | 12 + .../Editor/OptimizationHandler.cs | 164 + .../Editor/OptimizationHandler.cs.meta | 7 + .../Editor/PackageToolsEditor.asmdef | 44 + .../Editor/PackageToolsEditor.asmdef.meta | 7 + .../PackageTools/EditorResourceHelper.cs | 101 + .../PackageTools/EditorResourceHelper.cs.meta | 12 + .../PackageTools/EnumFlagAttribute.cs | 7 + .../PackageTools/EnumFlagAttribute.cs.meta | 12 + .../AstarPro/PackageTools/Migrations.cs | 56 + .../AstarPro/PackageTools/Migrations.cs.meta | 11 + .../AstarPro/PackageTools/PackageTools.asmdef | 43 + .../PackageTools/PackageTools.asmdef.meta | 7 + .../PackageTools/UniqueComponentAttribute.cs | 6 + .../UniqueComponentAttribute.cs.meta | 12 + .../PackageTools/VersionedMonoBehaviour.cs | 109 + .../VersionedMonoBehaviour.cs.meta | 12 + .../ThirdParty/AstarPro/Pathfinders.meta | 2 + .../ThirdParty/AstarPro/Pathfinders/ABPath.cs | 572 +++ .../AstarPro/Pathfinders/ABPath.cs.meta | 7 + .../AstarPro/Pathfinders/ConstantPath.cs | 167 + .../AstarPro/Pathfinders/ConstantPath.cs.meta | 7 + .../AstarPro/Pathfinders/FleePath.cs | 60 + .../AstarPro/Pathfinders/FleePath.cs.meta | 7 + .../AstarPro/Pathfinders/FloodPath.cs | 193 + .../AstarPro/Pathfinders/FloodPath.cs.meta | 7 + .../AstarPro/Pathfinders/FloodPathTracer.cs | 161 + .../Pathfinders/FloodPathTracer.cs.meta | 7 + .../AstarPro/Pathfinders/MultiTargetPath.cs | 472 ++ .../Pathfinders/MultiTargetPath.cs.meta | 7 + .../AstarPro/Pathfinders/RandomPath.cs | 256 + .../AstarPro/Pathfinders/RandomPath.cs.meta | 7 + .../ThirdParty/AstarPro/Pathfinders/XPath.cs | 131 + .../AstarPro/Pathfinders/XPath.cs.meta | 7 + .../Assets/ThirdParty/AstarPro/Plugins.meta | 2 + .../ThirdParty/AstarPro/Plugins/Clipper.meta | 2 + .../AstarPro/Plugins/Clipper/Clipper2Lib.dll | Bin 0 -> 87040 bytes .../Plugins/Clipper/Clipper2Lib.dll.meta | 75 + .../Plugins/Clipper/ClipperLicense.txt | 23 + .../Plugins/Clipper/ClipperLicense.txt.meta | 4 + .../AstarPro/Plugins/DotNetZip.meta | 2 + .../Plugins/DotNetZip/DotNetZipLicense.txt | 33 + .../DotNetZip/DotNetZipLicense.txt.meta | 2 + .../Pathfinding.Ionic.Zip.Reduced.dll | Bin 0 -> 240640 bytes .../Pathfinding.Ionic.Zip.Reduced.dll.meta | 80 + .../AstarPro/Plugins/dependencies.txt | 10 + .../AstarPro/Plugins/dependencies.txt.meta | 2 + FishROV/Assets/ThirdParty/AstarPro/RVO.meta | 2 + .../ThirdParty/AstarPro/RVO/RVOController.cs | 592 +++ .../AstarPro/RVO/RVOController.cs.meta | 11 + .../ThirdParty/AstarPro/RVO/RVONavmesh.cs | 35 + .../AstarPro/RVO/RVONavmesh.cs.meta | 7 + .../ThirdParty/AstarPro/RVO/RVOObstacle.cs | 58 + .../AstarPro/RVO/RVOObstacle.cs.meta | 7 + .../ThirdParty/AstarPro/RVO/RVOSimulator.cs | 158 + .../AstarPro/RVO/RVOSimulator.cs.meta | 8 + .../AstarPro/RVO/RVOSquareObstacle.cs | 26 + .../AstarPro/RVO/RVOSquareObstacle.cs.meta | 7 + FishROV/Assets/ThirdParty/AstarPro/Readme.txt | 24 + .../ThirdParty/AstarPro/Readme.txt.meta | 2 + .../ThirdParty/AstarPro/Readme_upgrading.txt | 5 + .../AstarPro/Readme_upgrading.txt.meta | 2 + .../Assets/ThirdParty/AstarPro/Resources.meta | 8 + .../AstarPro/Resources/handles_aaline.png | Bin 0 -> 570 bytes .../Resources/handles_aaline.png.meta | 105 + .../AstarPro/Resources/line_falloff_1px.png | Bin 0 -> 144 bytes .../Resources/line_falloff_1px.png.meta | 56 + .../Assets/ThirdParty/AstarPro/TurnBased.meta | 9 + .../AstarPro/TurnBased/BlockManager.cs | 162 + .../AstarPro/TurnBased/BlockManager.cs.meta | 12 + .../AstarPro/TurnBased/SingleNodeBlocker.cs | 71 + .../TurnBased/SingleNodeBlocker.cs.meta | 12 + .../Assets/ThirdParty/AstarPro/Utilities.meta | 2 + .../Utilities/AnimationLinkTraverser.cs | 74 + .../Utilities/AnimationLinkTraverser.cs.meta | 12 + .../AstarPro/Utilities/AstarChecksum.cs | 20 + .../AstarPro/Utilities/AstarChecksum.cs.meta | 7 + .../AstarPro/Utilities/BatchedEvents.cs | 247 + .../AstarPro/Utilities/BatchedEvents.cs.meta | 11 + .../AstarPro/Utilities/DotNetReplacements.cs | 150 + .../Utilities/DotNetReplacements.cs.meta | 7 + .../AstarPro/Utilities/DynamicGridObstacle.cs | 3 + .../Utilities/DynamicGridObstacle.cs.meta | 11 + .../AstarPro/Utilities/DynamicObstacle.cs | 258 + .../Utilities/DynamicObstacle.cs.meta | 11 + .../ThirdParty/AstarPro/Utilities/Funnel.cs | 906 ++++ .../AstarPro/Utilities/Funnel.cs.meta | 12 + .../AstarPro/Utilities/GraphGizmoHelper.cs | 265 + .../Utilities/GraphGizmoHelper.cs.meta | 12 + .../Utilities/GraphUpdateUtilities.cs | 104 + .../Utilities/GraphUpdateUtilities.cs.meta | 7 + .../AstarPro/Utilities/GridStringPulling.cs | 588 +++ .../Utilities/GridStringPulling.cs.meta | 11 + .../Utilities/IJobParallelForBatched.cs | 65 + .../Utilities/IJobParallelForBatched.cs.meta | 11 + .../Utilities/JobDependencyTracker.cs | 859 ++++ .../Utilities/JobDependencyTracker.cs.meta | 11 + .../AstarPro/Utilities/MeshUtility.cs | 183 + .../AstarPro/Utilities/MeshUtility.cs.meta | 11 + .../AstarPro/Utilities/PathTracer.cs | 2130 ++++++++ .../AstarPro/Utilities/PathTracer.cs.meta | 11 + .../AstarPro/Utilities/PathUtilities.cs | 739 +++ .../AstarPro/Utilities/PathUtilities.cs.meta | 7 + .../Utilities/ProceduralGraphMover.cs | 261 + .../Utilities/ProceduralGraphMover.cs.meta | 11 + .../ThirdParty/AstarPro/Utilities/Promise.cs | 3 + .../AstarPro/Utilities/Promise.cs.meta | 11 + .../ThirdParty/AstarPro/Utilities/RWLock.cs | 3 + .../AstarPro/Utilities/RWLock.cs.meta | 11 + .../ThirdParty/AstarPro/Utilities/SpinLock.cs | 3 + .../AstarPro/Utilities/SpinLock.cs.meta | 11 + .../Utilities/UnityReferenceHelper.cs | 39 + .../Utilities/UnityReferenceHelper.cs.meta | 7 + .../Assets/ThirdParty/AstarPro/defines.csv | 11 + .../ThirdParty/AstarPro/defines.csv.meta | 4 + FishROV/Assets/ThirdParty/AstarPro/link.xml | 22 + .../Assets/ThirdParty/AstarPro/link.xml.meta | 2 + .../Assets/ThirdParty/AstarPro/package.json | 50 + .../ThirdParty/AstarPro/package.json.meta | 7 + 1206 files changed, 147067 insertions(+) create mode 100644 FishROV/Assets/ThirdParty/AstarPro.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md create mode 100644 FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Legacy.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Nodes.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.pdf create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.pdf.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/index.md create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Documentation/index.md.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/ALINE.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/ALINE.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/AlineHDRPCustomPass.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/AlineHDRPCustomPass.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/AlineURPRenderPassFeature.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/AlineURPRenderPassFeature.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder2D.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder2D.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder2DExtensions.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilder2DExtensions.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilderExtensions.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/CommandBuilderExtensions.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Compatibility.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Compatibility.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Documentation.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Documentation/index.md create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Documentation/index.md.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Draw.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Draw.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingManager.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingManager.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingSettings.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingSettings.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/DrawingUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingEditor.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingEditor.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingManagerEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingManagerEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingSettingsEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Editor/DrawingSettingsEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/GeometryBuilder.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/GeometryBuilder.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/MonoBehaviourGizmos.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/MonoBehaviourGizmos.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools/Editor.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools/Editor/DependencyCheck.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools/Editor/DependencyCheck.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools/Editor/PackageToolsEditor.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PackageTools/Editor/PackageToolsEditor.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Palette.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Palette.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PersistentFilter.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/PersistentFilter.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common.cginc create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common.cginc.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_line.cginc create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_line.cginc.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_surface.cginc create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_surface.cginc.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_text.cginc create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_common_text.cginc.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_droid_sans_mono.mat create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_droid_sans_mono.mat.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_fallback_font.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_fallback_font.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_font.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_font.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_font.ttf create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_font.ttf.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_outline.mat create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_outline.mat.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_outline.shader create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_outline.shader.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_surface.mat create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_surface.mat.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_surface.shader create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_surface.shader.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_text.shader create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/Resources/aline_text.shader.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/SDFFont.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/SDFFont.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/StreamSplitter.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/StreamSplitter.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/changelog.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/changelog.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/package.json create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Drawing/package.json.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AIBaseEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AIBaseEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AILerpEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AILerpEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AnimationLinkEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AnimationLinkEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AstarPath@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AstarPath@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/DynamicGridObstacle@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/DynamicGridObstacle@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MovementScripts@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MovementScripts@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NavmeshAdd@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NavmeshAdd@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NavmeshCut@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NavmeshCut@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NodeLink2@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/NodeLink2@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Patrol@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Patrol@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/ProceduralGraphMover@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/ProceduralGraphMover@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/RVOController@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/RVOController@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Seeker@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Seeker@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AIDestinationSetter@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AIDestinationSetter@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AstarPath@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AstarPath@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_DynamicGridObstacle@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_DynamicGridObstacle@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MoveInCircle@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MoveInCircle@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshCut@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshCut@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Patrol@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Patrol@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Seeker@256.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Seeker@256.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gridPivotSelect.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gridPivotSelect.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/headerBackground.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/headerBackground.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect-03.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect-03.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect_Normal.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect_Normal.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect_On.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/images/gridPivotSelect_On.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_closed@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_closed@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_closed@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_closed@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox2.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox2.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3_separator.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3_separator.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/colorInterpolationBox.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/colorInterpolationBox.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/headerBackground.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/headerBackground.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/reset_normal.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/reset_normal.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/reset_on.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/reset_on.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/grid.psd create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/grid.psd.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown_on.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown_on.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp_on.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp_on.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/astar_logo.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/astar_logo.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/changelog_icon.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/changelog_icon.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/check_icon.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/check_icon.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/documentation_icon.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/documentation_icon.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_01.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_01.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_02.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_02.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownButtons_01.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownButtons_01.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownButtons_02.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownButtons_02.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_active.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_active.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_normal.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_normal.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox3_separator.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox3_separator.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@1x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@1x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@2x.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@2x.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/UI.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.uxml create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.uxml.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GraphCollision.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GraphCollision.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridAdjacencyMapper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridAdjacencyMapper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridGraphScanData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridGraphScanData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridIterationUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/GridIterationUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobAllocateNodes.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobAllocateNodes.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCalculateGridConnections.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCalculateGridConnections.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCheckCollisions.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCheckCollisions.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobColliderHitsToBooleans.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCopyBuffers.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobCopyBuffers.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobDirtyNodes.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobDirtyNodes.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobErosion.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobErosion.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobFilterDiagonalConnections.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobMergeRaycastCollisionHits.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobNodeGridLayout.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobNodeGridLayout.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobNodeWalkability.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobNodeWalkability.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareCapsuleCommands.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareGridRaycast.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareRaycasts.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareRaycasts.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobPrepareSphereCommands.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobRaycastAll.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobRaycastAll.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobReadNodeData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobReadNodeData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobRelocateNodes.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobRelocateNodes.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobWriteNodeData.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Jobs/JobWriteNodeData.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/GridGraphRules.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/GridGraphRules.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleAnglePenalty.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleElevationPenalty.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RulePerLayerModifications.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleTexture.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Grid/Rules/RuleTexture.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/GridGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/GridGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/LayerGridGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/LayerGridGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/LinkGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/LinkGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavMeshGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavMeshGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/AABBTree.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/AABBTree.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/BBTree.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/BBTree.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/ColliderMeshBuilder2D.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/ColliderMeshBuilder2D.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildNodes.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildNodes.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVertices.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVertices.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVoxels.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobBuildTileMeshFromVoxels.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobCalculateTriangleConnections.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobCalculateTriangleConnections.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobConnectTiles.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobConnectTiles.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobConvertAreasToTags.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobConvertAreasToTags.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobCreateTiles.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobCreateTiles.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobTransformTileCoordinates.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobTransformTileCoordinates.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobWriteNodeConnections.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Jobs/JobWriteNodeConnections.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/NavmeshTile.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/NavmeshTile.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/RecastBuilder.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/RecastBuilder.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/RecastMeshGatherer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/RecastMeshGatherer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileBuilder.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileBuilder.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileHandler.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileHandler.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileLayout.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileLayout.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileMesh.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileMesh.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileMeshes.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/TileMeshes.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/CompactVoxelField.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/CompactVoxelField.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/LinkedVoxelField.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/LinkedVoxelField.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelContour.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelContour.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelMesh.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelMesh.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelPolygonClipper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelPolygonClipper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelRasterization.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelRasterization.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelRegion.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Navmesh/Voxels/VoxelRegion.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavmeshBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/NavmeshBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/GridNode.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/GridNode.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/GridNodeBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/GridNodeBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/LevelGridNode.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/LevelGridNode.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/PointNode.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/PointNode.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/TriangleMeshNode.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Nodes/TriangleMeshNode.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Point.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Point/PointKDTree.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Point/PointKDTree.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/PointGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/PointGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/RecastGraph.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/RecastGraph.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/EuclideanEmbedding.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/EuclideanEmbedding.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/GraphTransform.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/GraphTransform.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/GridLookup.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/GridLookup.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/NavMeshRenderer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/NavMeshRenderer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/RecastMeshObj.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/RecastMeshObj.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/RecastNavmeshModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/RecastNavmeshModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/UtilityJobs.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Graphs/Utilities/UtilityJobs.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/AdvancedSmooth.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/AdvancedSmooth.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/AlternativePath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/AlternativePath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/FunnelModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/FunnelModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/Modifiers.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/Modifiers.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/RadiusModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/RadiusModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/RaycastModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/RaycastModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/SimpleSmoothModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/SimpleSmoothModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/StartEndModifier.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Modifiers/StartEndModifier.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshAdd.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshAdd.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshClipper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshClipper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshCut.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshCut.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshUpdates.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/NavmeshUpdates.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/RelevantGraphSurface.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Navmesh/RelevantGraphSurface.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/DependencyCheck.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/DependencyCheck.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EditorBase.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EditorBase.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EditorGUIx.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EditorGUIx.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EnumFlagDrawer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/EnumFlagDrawer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/LegacyEditorHelper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/LegacyEditorHelper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/OptimizationHandler.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/OptimizationHandler.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/PackageToolsEditor.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Editor/PackageToolsEditor.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/EditorResourceHelper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/EditorResourceHelper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/EnumFlagAttribute.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/EnumFlagAttribute.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Migrations.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/Migrations.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/PackageTools.asmdef create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/PackageTools.asmdef.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/UniqueComponentAttribute.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/UniqueComponentAttribute.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/VersionedMonoBehaviour.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/PackageTools/VersionedMonoBehaviour.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/ABPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/ABPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/ConstantPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/ConstantPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FleePath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FleePath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FloodPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FloodPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FloodPathTracer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/FloodPathTracer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/MultiTargetPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/MultiTargetPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/RandomPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/RandomPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/XPath.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Pathfinders/XPath.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/Clipper.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/Clipper/Clipper2Lib.dll create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/Clipper/Clipper2Lib.dll.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/Clipper/ClipperLicense.txt create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/Clipper/ClipperLicense.txt.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/DotNetZip.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/DotNetZip/DotNetZipLicense.txt create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/DotNetZip/DotNetZipLicense.txt.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/DotNetZip/Pathfinding.Ionic.Zip.Reduced.dll create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/DotNetZip/Pathfinding.Ionic.Zip.Reduced.dll.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/dependencies.txt create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Plugins/dependencies.txt.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOController.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOController.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVONavmesh.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVONavmesh.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOObstacle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOObstacle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOSimulator.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOSimulator.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOSquareObstacle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/RVO/RVOSquareObstacle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Readme.txt create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Readme.txt.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Readme_upgrading.txt create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Readme_upgrading.txt.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Resources.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Resources/handles_aaline.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Resources/handles_aaline.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Resources/line_falloff_1px.png create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Resources/line_falloff_1px.png.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/TurnBased.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/TurnBased/BlockManager.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/TurnBased/BlockManager.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/TurnBased/SingleNodeBlocker.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/TurnBased/SingleNodeBlocker.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/AnimationLinkTraverser.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/AnimationLinkTraverser.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/AstarChecksum.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/AstarChecksum.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/BatchedEvents.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/BatchedEvents.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DotNetReplacements.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DotNetReplacements.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DynamicGridObstacle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DynamicGridObstacle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DynamicObstacle.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/DynamicObstacle.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/Funnel.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/Funnel.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GraphGizmoHelper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GraphGizmoHelper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GraphUpdateUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GraphUpdateUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GridStringPulling.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/GridStringPulling.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/IJobParallelForBatched.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/IJobParallelForBatched.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/JobDependencyTracker.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/JobDependencyTracker.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/MeshUtility.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/MeshUtility.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/PathTracer.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/PathTracer.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/PathUtilities.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/PathUtilities.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/ProceduralGraphMover.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/ProceduralGraphMover.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/Promise.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/Promise.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/RWLock.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/RWLock.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/SpinLock.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/SpinLock.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/UnityReferenceHelper.cs create mode 100644 FishROV/Assets/ThirdParty/AstarPro/Utilities/UnityReferenceHelper.cs.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/defines.csv create mode 100644 FishROV/Assets/ThirdParty/AstarPro/defines.csv.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/link.xml create mode 100644 FishROV/Assets/ThirdParty/AstarPro/link.xml.meta create mode 100644 FishROV/Assets/ThirdParty/AstarPro/package.json create mode 100644 FishROV/Assets/ThirdParty/AstarPro/package.json.meta diff --git a/FishROV/Assets/ThirdParty/AstarPro.meta b/FishROV/Assets/ThirdParty/AstarPro.meta new file mode 100644 index 0000000..5db2fbb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26f16e26de7bdd143a48c5e0863bb184 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs b/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs new file mode 100644 index 0000000..b968318 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs @@ -0,0 +1,4 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AstarPathfindingProjectEditor")] +#if UNITY_2023_1_OR_NEWER +[assembly: Unity.Properties.GeneratePropertyBagsForAssembly] +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs.meta new file mode 100644 index 0000000..bfaaf59 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de324f1b7181d202dbbc6b1421023e3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef b/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef new file mode 100644 index 0000000..d0e8ba4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef @@ -0,0 +1,99 @@ +{ + "name": "AstarPathfindingProject", + "rootNamespace": "", + "references": [ + "GUID:2bafac87e7f4b9b418d9448d219b01ab", + "GUID:f4059aaf6c60a4a58a177a2609feb769", + "GUID:2665a8d13d1b3f18800f46e256720795", + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:8a2eafa29b15f444eb6d74f94a930e1d", + "GUID:de4e6084e6d474788bb8c799d6b461eb", + "GUID:734d92eba21c94caba915361bd5ac177", + "GUID:a5baed0c9693541a5bd947d336ec7659", + "GUID:db11b4b5d7520bc479416b48c98206cb", + "GUID:8819f35a0fc84499b990e90a4ca1911f" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "Pathfinding.Ionic.Zip.Reduced.dll", + "Clipper2Lib.dll" + ], + "autoReferenced": true, + "defineConstraints": [ + "MODULE_BURST", + "MODULE_MATHEMATICS", + "MODULE_COLLECTIONS" + ], + "versionDefines": [ + { + "name": "com.unity.burst", + "expression": "1.8.7", + "define": "MODULE_BURST" + }, + { + "name": "com.unity.mathematics", + "expression": "1.2.6", + "define": "MODULE_MATHEMATICS" + }, + { + "name": "com.unity.collections", + "expression": "1.5.1", + "define": "MODULE_COLLECTIONS" + }, + { + "name": "com.unity.collections", + "expression": "0.11-preview", + "define": "MODULE_COLLECTIONS_0_11_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.0.0", + "define": "MODULE_COLLECTIONS_2_0_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.1.0", + "define": "MODULE_COLLECTIONS_2_1_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.2.0", + "define": "MODULE_COLLECTIONS_2_2_0_OR_NEWER" + }, + { + "name": "com.unity.entities", + "expression": "1.1.0-pre.3", + "define": "MODULE_ENTITIES" + }, + { + "name": "com.unity.entities", + "expression": "1.0.8", + "define": "MODULE_ENTITIES_1_0_8_OR_NEWER" + }, + { + "name": "com.unity.entities", + "expression": "1.3.0", + "define": "MODULE_ENTITIES_1_3_0_OR_NEWER" + }, + { + "name": "com.unity.test-framework", + "expression": "1.5.1", + "define": "MODULE_TEST_FRAMEWORK" + }, + { + "name": "com.unity.entities.graphics", + "expression": "0.0.0", + "define": "MODULE_ENTITIES_GRAPHICS" + }, + { + "name": "com.unity.inputsystem", + "expression": "0.0.0", + "define": "MODULE_INPUT_SYSTEM" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef.meta b/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef.meta new file mode 100644 index 0000000..841e06d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/AstarPathfindingProject.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: efa45043feb7e4147a305b73b5cea642 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors.meta b/FishROV/Assets/ThirdParty/AstarPro/Behaviors.meta new file mode 100644 index 0000000..d28eb1b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5eeb3c5df5ca840d3ae6b93e6b207d74 +folderAsset: yes +timeCreated: 1497206641 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs new file mode 100644 index 0000000..df673c0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs @@ -0,0 +1,130 @@ +using UnityEngine; +using Pathfinding.Util; +#if MODULE_ENTITIES +using Unity.Entities; +#endif + +namespace Pathfinding { + /// + /// Sets the destination of an AI to the position of a specified object. + /// This component should be attached to a GameObject together with a movement script such as AIPath, RichAI or AILerp. + /// This component will then make the AI move towards the set on this component. + /// + /// Essentially the only thing this component does is to set the property to the position of the target every frame. + /// But there is some additional complexity to make sure that the destination is updated immediately before the AI searches for a path as well, in case the + /// target moved since the last Update. There is also some complexity to reduce the performance impact, by using the system to + /// process all AIDestinationSetter components in a single batch. + /// + /// When using ECS, outside a subscene, this component is instead added as a managed component to the entity. + /// The destination syncing is then handled by the for better performance. + /// + /// See: + /// + /// \section aidestinationsetter-subscenes Usage in ECS subscenes + /// This component can be used in subscenes and it will automatically be baked into a component. + /// + /// Every frame, the will copy the position of the target entity to the field (and optionally the facing direction as well). + /// + /// See: Read more in the subscenes section on : followerentity-subscenes (view in online documentation for working links) + /// + [UniqueComponent(tag = "ai.destination")] + [AddComponentMenu("Pathfinding/AI/Behaviors/AIDestinationSetter")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/aidestinationsetter.html")] + public class AIDestinationSetter : VersionedMonoBehaviour +#if MODULE_ENTITIES + , IRuntimeBaker +#endif + { + /// The object that the AI should move to + public Transform target; + + /// + /// If true, the agent will try to align itself with the rotation of the . + /// + /// This can only be used together with the movement script. + /// Other movement scripts will ignore it. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// + public bool useRotation; + + IAstarAI ai; +#if MODULE_ENTITIES + Entity entity; + World world; +#endif + + void OnEnable () { + ai = GetComponent(); +#if MODULE_ENTITIES + if (ai is FollowerEntity follower) { + // This will call OnCreatedEntity on this component, if the entity has already been created. + follower.RegisterRuntimeBaker(this); + } else +#endif + { + // Update the destination right before searching for a path as well. + // This is enough in theory, but this script will also update the destination every + // frame as the destination is used for debugging and may be used for other things by other + // scripts as well. So it makes sense that it is up to date every frame. + if (ai != null) ai.onSearchPath += UpdateDestination; + + // Will make OnUpdate be called once every frame with all components. + // This is significantly faster than letting Unity call the Update method + // on each component. + // See https://blog.unity.com/technology/1k-update-calls + BatchedEvents.Add(this, BatchedEvents.Event.Update, OnUpdate, 0); + } + } + + void OnDisable () { +#if MODULE_ENTITIES + if (world != null && world.IsCreated && world.EntityManager.Exists(entity)) { + world.EntityManager.RemoveComponent(entity); + } + if (ai != null && !(ai is FollowerEntity)) ai.onSearchPath -= UpdateDestination; +#else + if (ai != null) ai.onSearchPath -= UpdateDestination; +#endif + BatchedEvents.Remove(this); + } + +#if MODULE_ENTITIES + void IRuntimeBaker.OnCreatedEntity (World world, Entity entity) { + // Do nothing except add the component. Actual syncing is handled by the SyncDestinationTransformSystem. + this.entity = entity; + this.world = world; + world.EntityManager.AddComponentObject(entity, this); + } + +#if UNITY_EDITOR + public class AIDestinationSetterBaker : Baker { + public override void Bake (AIDestinationSetter authoring) { + var entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.WorldSpace); + if (authoring.target != null) { + var targetEntity = GetEntity(authoring.target.gameObject, TransformUsageFlags.Dynamic); + AddComponent(entity, new Pathfinding.ECS.DestinationEntity { + destination = targetEntity, + useRotation = authoring.useRotation + }); + } + } + } +#endif +#endif + + /// Updates the AI's destination every frame + static void OnUpdate (AIDestinationSetter[] components, int count) { + for (int i = 0; i < count; i++) { + components[i].UpdateDestination(); + } + } + + /// Updates the AI's destination immediately + void UpdateDestination () { + if (target != null && ai != null) ai.destination = target.position; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs.meta new file mode 100644 index 0000000..b0a55d0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIDestinationSetter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c9679e68a0f1144e79c664d9a11ca121 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 8dca5cfad5de0f444847aaaaa7cf73b8, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs new file mode 100644 index 0000000..f3a1f40 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs @@ -0,0 +1,141 @@ +using UnityEngine; +using System.Collections.Generic; +using UnityEngine.Profiling; + +namespace Pathfinding { + using Pathfinding.Pooling; + using Unity.Collections.LowLevel.Unsafe; + + /// + /// Movement script for curved worlds. + /// This script inherits from AIPath, but adjusts its movement plane every frame using the ground normal. + /// + [HelpURL("https://arongranberg.com/astar/documentation/stable/aipathalignedtosurface.html")] + public class AIPathAlignedToSurface : AIPath { + /// Scratch dictionary used to avoid allocations every frame + static readonly Dictionary scratchDictionary = new Dictionary(); + + protected override void OnEnable () { + base.OnEnable(); + movementPlane = new Util.SimpleMovementPlane(rotation); + } + + protected override void ApplyGravity (float deltaTime) { + // Apply gravity + if (usingGravity) { + // Gravity is relative to the current surface. + // Only the normal direction is well defined however so x and z are ignored. + verticalVelocity += deltaTime * (float.IsNaN(gravity.x) ? Physics.gravity.y : gravity.y); + } else { + verticalVelocity = 0; + } + } + + /// + /// Calculates smoothly interpolated normals for all raycast hits and uses that to set the movement planes of the agents. + /// + /// To support meshes that change at any time, we use Mesh.AcquireReadOnlyMeshData to get a read-only view of the mesh data. + /// This is only efficient if we batch all updates and make a single call to Mesh.AcquireReadOnlyMeshData. + /// + /// This method is quite convoluted due to having to read the raw vertex data streams from unity meshes to avoid allocations. + /// + public static void UpdateMovementPlanes (AIPathAlignedToSurface[] components, int count) { + Profiler.BeginSample("UpdateMovementPlanes"); + var meshes = ListPool.Claim(); + var componentsByMesh = new List >(); + var meshToIndex = scratchDictionary; + for (int i = 0; i < count; i++) { + var c = components[i].lastRaycastHit.collider; + // triangleIndex can be -1 if the mesh collider is convex, and the raycast started inside it. + // This is not a documented behavior, but it seems to happen in practice. + if (c is MeshCollider mc && components[i].lastRaycastHit.triangleIndex != -1) { + var sharedMesh = mc.sharedMesh; + if (meshToIndex.TryGetValue(sharedMesh, out var meshIndex)) { + componentsByMesh[meshIndex].Add(components[i]); + } else if (sharedMesh != null && sharedMesh.isReadable) { + meshToIndex[sharedMesh] = meshes.Count; + meshes.Add(sharedMesh); + componentsByMesh.Add(ListPool.Claim()); + componentsByMesh[meshes.Count-1].Add(components[i]); + } else { + // Unreadable mesh + components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal); + } + } else { + // Not a mesh collider, or the triangle index was -1 + components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal); + } + } + var meshDatas = Mesh.AcquireReadOnlyMeshData(meshes); + for (int i = 0; i < meshes.Count; i++) { + var m = meshes[i]; + var meshIndex = meshToIndex[m]; + var meshData = meshDatas[meshIndex]; + var componentsForMesh = componentsByMesh[meshIndex]; + + var stream = meshData.GetVertexAttributeStream(UnityEngine.Rendering.VertexAttribute.Normal); + + if (stream == -1) { + // Mesh does not have normals + for (int j = 0; j < componentsForMesh.Count; j++) componentsForMesh[j].SetInterpolatedNormal(componentsForMesh[j].lastRaycastHit.normal); + continue; + } + var vertexData = meshData.GetVertexData(stream); + var stride = meshData.GetVertexBufferStride(stream); + var normalOffset = meshData.GetVertexAttributeOffset(UnityEngine.Rendering.VertexAttribute.Normal); + unsafe { + var normals = (byte*)vertexData.GetUnsafeReadOnlyPtr() + normalOffset; + + for (int j = 0; j < componentsForMesh.Count; j++) { + var comp = componentsForMesh[j]; + var hit = comp.lastRaycastHit; + int t0, t1, t2; + + // Get the vertex indices corresponding to the triangle that was hit + if (meshData.indexFormat == UnityEngine.Rendering.IndexFormat.UInt16) { + var indices = meshData.GetIndexData(); + t0 = indices[hit.triangleIndex * 3 + 0]; + t1 = indices[hit.triangleIndex * 3 + 1]; + t2 = indices[hit.triangleIndex * 3 + 2]; + } else { + var indices = meshData.GetIndexData(); + t0 = indices[hit.triangleIndex * 3 + 0]; + t1 = indices[hit.triangleIndex * 3 + 1]; + t2 = indices[hit.triangleIndex * 3 + 2]; + } + + // Get the normals corresponding to the 3 vertices + var n0 = *((Vector3*)(normals + t0 * stride)); + var n1 = *((Vector3*)(normals + t1 * stride)); + var n2 = *((Vector3*)(normals + t2 * stride)); + + // Interpolate the normal using the barycentric coordinates + Vector3 baryCenter = hit.barycentricCoordinate; + Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z; + interpolatedNormal = interpolatedNormal.normalized; + Transform hitTransform = hit.collider.transform; + interpolatedNormal = hitTransform.TransformDirection(interpolatedNormal); + comp.SetInterpolatedNormal(interpolatedNormal); + } + } + } + meshDatas.Dispose(); + for (int i = 0; i < componentsByMesh.Count; i++) ListPool.Release(componentsByMesh[i]); + ListPool.Release(ref meshes); + scratchDictionary.Clear(); + Profiler.EndSample(); + } + + void SetInterpolatedNormal (Vector3 normal) { + if (normal != Vector3.zero) { + var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, normal); + movementPlane = new Util.SimpleMovementPlane(Quaternion.LookRotation(fwd, normal)); + } + if (rvoController != null) rvoController.movementPlane = movementPlane; + } + + protected override void UpdateMovementPlane () { + // The UpdateMovementPlanes method will take care of this + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs.meta new file mode 100644 index 0000000..91969e5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/AIPathAlignedToSurface.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4cf55afa704da4bada4b28145f5f4685 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs new file mode 100644 index 0000000..9b9a296 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs @@ -0,0 +1,84 @@ +using UnityEngine; +using Pathfinding.Drawing; + +namespace Pathfinding { + /// + /// Moves an agent in a circle around a point. + /// + /// This script is intended as an example of how you can make an agent move in a circle. + /// In a real game, you may want to replace this script with your own custom script that is tailored to your game. + /// The code in this script is simple enough to copy and paste wherever you need it. + /// + /// When used in an ECS subscene, it will automatically be baked into a component. + /// + /// [Open online documentation to see videos] + /// + /// See: move_in_circle (view in online documentation for working links) + /// See: + /// See: + /// See: + /// See: + /// See: + /// + [UniqueComponent(tag = "ai.destination")] + [AddComponentMenu("Pathfinding/AI/Behaviors/MoveInCircle")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/moveincircle.html")] + public class MoveInCircle : VersionedMonoBehaviour { + /// [MoveInCircle] + /// Target point to rotate around + public Transform target; + /// Radius of the circle + public float radius = 5; + /// Distance between the agent's current position, and the destination it will get. Use a negative value to make the agent move in the opposite direction around the circle. + public float offset = 2; + + IAstarAI ai; + + void OnEnable () { + ai = GetComponent(); + } + + public static Vector3 CalculateDestination (Vector3 position, Vector3 target, Vector3 targetUp, float radius, float offset) { + var normal = (position - target).normalized; + var tangent = Vector3.Cross(normal, targetUp); + return target + normal * radius + tangent * offset; + } + + void Update () { + ai.destination = CalculateDestination(ai.position, target.position, target.up, radius, offset); + } + + /// [MoveInCircle] + + public override void DrawGizmos () { + if (target) Draw.Circle(target.position, target.up, radius, Color.white); + } + +#if MODULE_ENTITIES + // The code below is only used when this component is used in an ECS subscene + + /// + /// ECS component corresponding to . + /// See: + /// + public struct DestinationMoveInCircle : Unity.Entities.IComponentData, Unity.Entities.IEnableableComponent { + public Unity.Entities.Entity target; + public float radius; + public float offset; + } + +#if UNITY_EDITOR + public class MoveInCircleBaker : Unity.Entities.Baker { + public override void Bake (MoveInCircle authoring) { + var entity = GetEntity(Unity.Entities.TransformUsageFlags.Dynamic); + AddComponent(entity, new DestinationMoveInCircle { + target = GetEntity(authoring.target, Unity.Entities.TransformUsageFlags.Dynamic), + radius = authoring.radius, + offset = authoring.offset + }); + } + } +#endif +#endif + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs.meta new file mode 100644 index 0000000..0572f6b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/MoveInCircle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3124c7cfbe5ac4b1cbe42bd6b1e4279d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 8cf8c12a4f0221b438c0d7ffa0eab6f4, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs new file mode 100644 index 0000000..47f4c00 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs @@ -0,0 +1,68 @@ +using UnityEngine; +using System.Collections; + +namespace Pathfinding { + /// + /// Simple patrol behavior. + /// This will set the destination on the agent so that it moves through the sequence of objects in the array. + /// Upon reaching a target it will wait for seconds. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// See: + /// See: + /// See: + /// + [UniqueComponent(tag = "ai.destination")] + [AddComponentMenu("Pathfinding/AI/Behaviors/Patrol")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/patrol.html")] + public class Patrol : VersionedMonoBehaviour { + /// Target points to move to in order + public Transform[] targets; + + /// Time in seconds to wait at each target + public float delay = 0; + + /// + /// If true, the agent's destination will be updated every frame instead of only when switching targets. + /// + /// This is good if you have moving targets, but is otherwise unnecessary and slightly slower. + /// + public bool updateDestinationEveryFrame = false; + + /// Current target index + int index = -1; + + IAstarAI agent; + float switchTime = float.NegativeInfinity; + + protected override void Awake () { + base.Awake(); + agent = GetComponent(); + } + + /// Update is called once per frame + void Update () { + if (targets.Length == 0) return; + + // Note: using reachedEndOfPath and pathPending instead of reachedDestination here because + // if the destination cannot be reached by the agent, we don't want it to get stuck, we just want it to get as close as possible and then move on. + if (agent.reachedEndOfPath && !agent.pathPending && float.IsPositiveInfinity(switchTime)) { + switchTime = Time.time + delay; + } + + if (Time.time >= switchTime) { + index++; + switchTime = float.PositiveInfinity; + + index = index % targets.Length; + agent.destination = targets[index].position; + agent.SearchPath(); + } else if (updateDestinationEveryFrame) { + index = index % targets.Length; + agent.destination = targets[index].position; + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs.meta new file mode 100644 index 0000000..7d5bfd6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Behaviors/Patrol.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22e6c29e32504465faa943c537d8029b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: af22be3b7ab2b3b44afe7297460363ff, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md b/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md new file mode 100644 index 0000000..b002181 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md @@ -0,0 +1,3063 @@ +## 5.4.6 (2026-01-22) +- Added \reflink{FollowerEntity.nextOffMeshLink}, to get information about the next off-mesh link in the agent's path. +- Fixed some compilation warnings in Unity 6.4. +- Added support for the Input System Package to the example scenes. They now work both with the legacy input system and the new Input System package. +- Fixed an exception that could happen if an off-mesh link was disabled on the frame before an agent was about to traverse it. +- Fixed paths on grid graphs could in rare cases be calculated incorrectly if the end point was right in the middle between two nodes (regression in 5.4). +- Fixed GetNearest on grid graphs could fail to return a node if it was almost, but not quite, outside the search radius. +- Fixed gizmos would sometimes not be drawn for objects loaded in using async or additive scene loading (regression in 5.4.5). +- Fixed ProceduralGraphMover throwing an exception if target is destroyed in the middle of an update. +- Exposed \reflink{RandomPath.rnd} so that you can set a custom random number generator for the path. + This allows for reproducible random paths by using a fixed seed. +- Fixed A* Inspector -> Settings -> Colors -> Opacity settings did not work. +- Fixed occasional "Diagonal Connections Not Supported" error when using \reflink{FollowerEntity} on a grid graph. +- Avoid "The RecastNavmeshModifier has been moved or resized since it was enabled" error in some cases when the object's collider was destroyed before the \reflink{RecastNavmeshModifier}. + +## 5.4.5 (2025-11-21) +- Breaking changes + - Some tree colliders that were previously included in recast graph scans may now be excluded, in accordance with the layer and tag filtering settings on the recast graph. See below for more details. + This is a bug fix, but you may have been relying on the previous (incorrect) behavior. +- Fixed having many FollowerEntity agents with local avoidance enabled in the exact same position, could cause significant performance issues in some rare cases. +- Fixed recast graphs would not take the layers, tags nor collider isTrigger state of tree prefabs into account when scanning. This could result in tree collider being included in the scan even if they should be excluded. +- Fixed \reflink{NavmeshBase.ReplaceTiles} not applying tags to the new tiles. +- Fixed a small memory leak caused by prefabs sometimes being kept in memory longer than they should, in the Unity editor. +- The navmesh graph will now show a "Fix" button if the input mesh is not readable. + Clicking the button will try to enable read/write on the mesh import settings automatically. +- Fixed an exception that could be thrown when scanning a navmesh graph twice in a row with a null source mesh. +- Fixed example code not being updated for 5.4 changes in the \ref multiple-agent-types tutorial. +- Fixed a regression in 5.4 that \reflink{FollowerEntity.Move} would not move the Transform immediately, but only the agent's internal position. +- Improved performance and reduced allocations when destroying navmesh/recast graphs. +- Reduced allocations when loading graphs. +- Add \reflink{NavmeshBase.ToTileMeshes} method to export tiles of the graph to the portable \reflink{TileMeshes} representation. +- Improve performance of serializing and deserializing \reflink{TileMeshes}. +- Fixed calling \reflink{NavmeshBase.ClearTiles} and then \reflink{NavmeshBase.ReplaceTiles} could skip applying navmesh cuts. +- Reduce memory usage of recast/navmesh graphs. +- Fixed \reflink{FollowerEntity} sometimes instantly stopping after a nearby graph update caused the navmesh underneath it to be destroyed. +- The FollowerEntity will now refresh the endpoints of any pending path every frame, to ensure it is as up-to-date as possible when the path is calculated. This is important primarily when you have lots of agents calculating paths at the same time, or the game is running at a low framerate. + + +## 5.4.4 (2025-10-06) +- Note + - This stable release contains the features of all beta releases 5.4.0 through 5.4.3. +- Breaking changes + - This release contains several breaking changes, check \ref v5_4 for more info. + - There's compatibility code in place to keep the old APIs working for almost all use cases, but for best performance and accuracy, you should update your code to use the new APIs. +- New features and improvements + - Added an \reflink{example_ecs;ECS example scene}. + \video{generated/scenes/ECS/overviewvideo.webm} + - Added how-to page about ECS: \ref ecs. + - The \reflink{FollowerEntity} movement script can now be used in %ECS subscenes, in which it will be baked into a pure entity. + - The \reflink{AIDestinationSetter} component now also works in %ECS subscenes, being baked into a \reflink{DestinationEntity} component. + - The \reflink{MoveInCircle} component now also works in %ECS subscenes, being baked into a \reflink{DestinationMoveInCircle} component. + - Added \reflink{FollowerEntityProxy} which allows you to use a high-level API to interact with a baked \reflink{FollowerEntity}. + - Added \reflink{AstarData.SerializeGraph}, to serialize a single graph, instead of all of them. + - It is now possible to visualize graphs in standalone games. + Check the \reflink{AstarPath.showGraphsInStandalonePlayer} checkbox in A* Inspector → Settings → Debug. + - Added an option for visualizing the bounding boxes of graph updates in the scene view. A* Inspector → Settings → Debug → \reflink{AstarPath.graphUpdateDebugMode;Graph Update Debug Mode}. + \shadowimage{graph_update_debug_mode_inspector.png} + \shadowimage{graph_update_debug_mode.png} + - Added \reflink{RecastGraph.perTerrainLayerModifications; per terrain layer modifications} to recast graphs. You can use this to paint different tags on recast graphs. + For example to make an agent prefer to walk along a road, instead of on the grass next to it. + \shadowimage{recast/per_terrain_layer_modifications.png} + \shadowimage{generated/scenes/RecastTerrain/terraintagswithgraph.png} + - Making regions harder or easier to traverse has been made more intuitive and accurate. Instead of only allowing a cost for entering a node (which is not very accurate on navmesh/recast graphs as their nodes can vary so much in size), you can now set a cost multiplier which is scaled by the distance traversed inside the node. + \shadowimage{changelog/tagpopup.png} + \see \ref upgrade-traversalcosts for more details. + - Added a new \reflink{NearestNodeConstraint} struct, which replaces \reflink{NNConstraint}. + It works the same as the old NNConstraint, but is more efficient and has a few new features. + \see \ref upgrade-nnconstraint for more details. + - Added new \reflink{TraversalConstraint} and \reflink{TraversalCosts} structs to more easily set what nodes are traversable and what costs they have. + \see \ref upgrade-traversalconstraint for more details. + - The \reflink{ITraversalProvider} interface has received an overhaul, and can now block individual connections, and provide costs that scale with the distance traversed in a node. + \see \ref upgrade-itraversalprovider for more details. + - Due to the above updates, using tags to create areas with different traversal costs is now much better supported on recast and navmesh graphs. + - Added \reflink{AIMovementSystemGroup.CustomTimeScale}, to control the simulation speed of the \reflink{FollowerEntity} movement script separately from Unity's Time.timeScale. + - Added \reflink{FollowerEntity.localAvoidanceTemporarilyDisabled} to indicate if local avoidance has been temporarily disabled while traversing an off-mesh link. + - Improved performance of the \reflink{FollowerEntity} movement script. + - Added \reflink{AstarPath.GetNearestBorder} which finds the nearest navmesh border to a given position. + - Added \reflink{FollowerEntity.nearestNavmeshBorder} as a convenience property for getting the nearest navmesh border to the agent. + \video{generated/scenes/Linecasting/getnearestborder.webm} + - Added a nicer error message for the extremely unlikely case that the graphs are fragmented enough to run out of hierarchical nodes. + - Improved performance when scanning or updating recast graphs. In particular on the main thread and especially so when using terrains. + - Improved performance of linecasts on recast graphs, for many cases quite significantly. + - Added an overload of \reflink{NavGraph.GetNodes} that takes a ref parameter with custom data to be passed to the callback. This is useful for avoiding GC allocations when iterating over all nodes in a graph. + - The graph index is now displayed in the graph info box in the A* inspector. + - Added \reflink{PointGraph.Clear}, to remove all nodes from a point graph. + - Improved \reflink{GraphMask} so that it can now hold a limited number of large graph indices. Previously it was limited to only graph indices smaller than 31. + - Added \reflink{GraphMask.FromGraph}. + - Added \reflink{GraphMask.Contains(NavGraph)}. + - Added \reflink{GraphMask.containsAllGraphs}. + - Rewrote the \ref tags documentation page to be more clear and up-to-date. +- Changes + - Made the new version popup a bit less obnoxious. + - Changed \reflink{FollowerEntity} to look for navmesh borders at the closest point on the navmesh to it, instead of close to its own pivot. This fixes the agent ignoring navmesh walls when being raised high up in the air, and having gravity disabled. + - Changed voxel size on recast graphs to be rounded to the nearest multiple of 0.001 (internal integer precision) to fix some issues with rounding errors. + - Renamed \reflink{IAstarAI.canMove} to \reflink{IAstarAI.simulateMovement}, to avoid confusion with the \reflink{IAstarAI.isStopped} property. + - Reduced pathfinding thread count on machines with more than 8 cores, when using the AutomaticHighLoad thread count mode. + These machines are likely using hyperthreading anyway, and the performance boost from using more threads is not large. However, reducing the thread count reduces the memory overhead quite a bit. + - Exposed \reflink{FollowerEntity.destinationFacingDirection}. + - Linecasts on grid graphs that go directly through a diagonal connection will now actually use that diagonal connection, instead of only using axis-aligned connections. + Axis-aligned connections are still possible to use as an automatic fallback, however. + - Deprecated GraphUpdateObject.nnConstraint and replaced it with \reflink{GraphUpdateObject.graphMask}. + - Changed linecasts on graphs to take a \reflink{TraversalConstraint} instead of a filter function, for more flexibility. + - Made \reflink{GraphMask.value} private. + - Removed implicit conversion from integers to \reflink{GraphMask}s, use e.g. \reflink{GraphMask.everything} or \reflink{GraphMask.FromGraphIndex} instead. Check \ref v5_4 for more info. +- Fixes + - Fixed local avoidance agents could sometimes get stuck on ghost obstacles, due to hash collisions that were much more likely than they should have been. + - Fixed exception when scanning recast graphs on Unity 6.2+ when the new Mesh LOD feature was used. + - Fixed compilation warning in Unity 6.2+. + - Fixed compatibility with Unity 6.2+, which would otherwise cause baked FollowerEntities to fall through the ground. + - Improved error checking when scanning and updating recast graphs. Previously, if the tile contained too many vertices, it could lead to an invalid navmesh and potentially exceptions. Now it will log an error, with instructions for how to remedy the situation, and leave the tile empty. + - Fixed some edge cases where the graph visualizations would not show/hide when they should. + - Fixed RVO agents that are in the exact same position and have the same destination will now manage to untangle themselves, instead of getting stuck together. + - Fixed creating a \reflink{FollowerEntity} and immediately setting \reflink{FollowerEntity.simulateMovement} to false on the same frame, could cause an exception to be thrown. + - Fixed a \reflink{FollowerEntity} whose path calculations were failing, would try to recalculate its path basically every frame, instead of backing off after a little while. + - Fixed setting the rotation speed of a \reflink{FollowerEntity} to infinity would break movement. Now it will be allowed to rotate any amount instantly. + - Fixed a \reflink{FollowerEntity} whose path calculation failed would continue following its previous path indefinitely. Now the agent will slow down similarly to if \reflink{FollowerEntity.isStopped} is set. + - Fixed \reflink{FollowerEntity} could in some situations start sliding a tiny amount (around one millimeter per second) when it should be standing still, due to a floating point error. + - Fixed removing the \reflink{SimulateMovementFinalize} ECS component from a \reflink{FollowerEntity} would disable too many jobs, and not easily allow you to override how movement was handled. + - Fixed the \reflink{FollowerEntity} could, in some rare cases, end up endlessly spinning in place when a desired facing direction had been set for it. + - Fixed selecting a \reflink{FollowerEntity} in the unity editor could end up clearing \reflink{FollowerEntity.destinationFacingDirection} unintentionally. + - Fixed linecasts on grid graphs could produce a longer list of passed through nodes than expected, when the target point was exactly in the corner of a node. + - Fixed various issues with using the \reflink{FollowerEntity} on a grid graph, that could result in choppy movement. + - Fixed editing the points of a \reflink{GraphUpdateScene} could fail to save some changes, due to limitations in Unity's Undo system. + - Fixed scanning a recast graph in a world with a terrain that had a missing tree prefab could result in an exception. + - Fixed scanning a recast graph in a scene with multiple terrains that shared tree prefabs could throw an exception. + - Fixed a memory leak that could cause nodes to linger in memory long after they were removed from the graph. + - Fixed \reflink{FunnelModifier} could skip the first and last portal when \reflink{FunnelModifier.splitAtEveryPortal} was enabled. + - Fixed an incompatibility with an older version of the unity collections package, which could cause an exception to be thrown when exiting the scene (introduced in 5.3.7). + - Fixed scanning very large layered grid graphs could throw an exception. + - Got rid of some small but persistent GC allocations related to debug drawing. + - Got rid of some small GC allocations in the example scenes relating to OnGUI calls. + - Fixed the update checker throwing exceptions in some rare cases. This was a regression in 5.3.5. + - Fixed \reflink{ConstantPath.allNodes} could contain duplicate nodes when used on recast graphs. This was a regression in 5.0. + + +## 5.3.7 (2025-05-06) +- Significantly improved performance when scanning grid graphs when using Unity 6000.1+. + This optimization has been tried multiple times before, but due to Unity bugs it has had to be rolled back. + I think Unity has fixed the final physx bug relating to this now, so this optimization is back. + Please report in the forum if you notice any hard crashes of the unity editor, when scanning grid graphs, after this update. +- Fixed using local avoidance on rotated graphs and isometric/hexagonal grid graphs could result in agents taking curved instead of straight paths. +- Fixed enabling thick raycasts on grid graphs did not do anything. + +## 5.3.6 (2025-04-25) +- Fixed a tiny native memory leak accidentally introduced in 5.3.5. +- Fixed using the 'Duplicate Graph' button in the A* inspector would log a warning about duplicate guids. +- Reduced overhead of the RVO system when there are no agents using local avoidance in the scene. + +## 5.3.5 (2025-04-22) +- Added \reflink{RecastGraph.collectionSettings.physicsScene} and \reflink{RecastGraph.collectionSettings.physicsScene2D} to allow specifying which physics scene to use when scanning a recast graph. +- Added \reflink{FollowerEntity.reachedCrowdedEndOfPath} which is like \reflink{FollowerEntity.reachedEndOfPath}, but will also return true if the end of the path is crowded, and this agent has stopped because it cannot get closer. + \video{generated/scenes/Recast3D/crowdeddestination.webm} +- Fixed an edge case which could cause navmesh cutting to throw an exception (regression in 5.3.4). +- Fixed \reflink{FollowerEntity} would not take tags or penalties into account when simplifying its path on recast/navmesh graphs. This could cause it to move over high penalty areas that it should have avoided. +- Fixed \reflink{FollowerEntity} would behave strangely on isometric grid graphs. +- \reflink{FollowerEntity} now behaves better (though not perfectly) on hexagonal graphs. +- Fixed \reflink{FunnelModifier} would not take \reflink{ITraversalProvider}s into account when simplifying its path on recast/navmesh graphs. +- Fixed a minor GC allocation happening every frame when using URP. +- Fixed debug drawing would cause a minor overhead even in standalone builds, where it wasn't used. +- Fixed a significant memory leak when starting unity in batch mode. +- Fixed modifiers attached to a Seeker would not run when pathfinding is done outside of play-mode. +- When using \reflink{AstarData.DeserializeGraphsAdditive}, the new graphs will fill any empty slots in the array of graphs (if any graphs have been removed), instead of always being appended to the end of the array. + This fixes the graphs array growing indefinitely when repeatedly removing and adding graphs in some cases. + +## 5.3.4 (2025-03-18) +- Navmesh cuts now work much better in slopes. + Previously, whenever the navmesh cut bounds touched a triangle bounds, the triangle would be cut by the full navmesh cut. + Now, the full 3d extruded cut shape is used to calculate the cut. Resulting is more predictable cuts, and fixes + a ton of edge cases where a navmesh cut could cause an agent to not be able to navigate across a seemingly navigable area. + The video below shows the old behavior on the left, and the new one on the right. + \video{changelog/navmeshcutfallingslowly_5_3_4_stack.webm} +- Navmesh cut gizmos are now oriented relative to the closest graph, instead of the first graph. + This has no effect on the actual navmesh cut, but it makes it easier to see where the cut will be applied, in case there are multiple graphs in the scene. +- Fixed updating a recast graph on a unity terrain could in rare cases create reactangular gaps along a border of tiles. +- Reflection is now used to find graph types even in WebGL builds. This allows you to use custom graph types in WebGL builds too. + Make sure your custom graph types are annotated with the [Pathfinding.Util.Preserve] attribute, otherwise they may be stripped out by the Unity WebGL build process. +- Fixed a bug causing navmesh cuts to add a tiny gap in the navmesh. + +## 5.3.3 (2025-01-31) +- Fixed sometimes not being able to delete graphs from AstarPath components on prefabs. +- Fixed a tiny memory leak happening sometimes when editing a prefab with an AstarPath component. +- Fixed scanning grid graphs in Unity 6000.0.36f1+ would throw an exception, due to changes to unity's job system. +- Fixed setting Recast graph -> 'Filter Objects By' to Tags would incorrectly include everything in the scene. + +## 5.3.2 (2025-01-27) +- Fixed compatibility with com.unity.entities version 1.3.9 (latest version at the time of this update). +- Fixed an out of range exception that could happen when using local avoidance and the latest burst package was installed (it was optimizing away my error checking!). +- Fixed a rare edge case that could cause the \reflink{FunnelModifier} on the High setting to produce a weird looking path that included some backtracking. +- Fixed an exception could be thrown when cameras were rendered without a color target. +- Fixed a race condition that could in rare cases cause an exception to be thrown from \reflink{FollowerEntity.SearchPath}. +- Fixed scanning grid graphs with invalid settings could throw an exception. +- \reflink{GridGraph.SetDimensions} will now throw an exception if the given width, depth or node size is less than or equal to zero. +- \reflink{MovementState.hierarchicalNodeIndex} and \reflink{MovementControl.hierarchicalNodeIndex} are now set to -1 at the end of the \reflink{AIMovementSystemGroup} to prevent accidental use of stale data. +- Fixed the \reflink{FollowerEntity} would sometimes still use gravity during traversal of off-mesh links, even if the built-in movement was disabled. +- Fixed a memory leak that could sometimes happen when calling \reflink{FollowerEntity.GetRemainingPath}. + +## 5.3.1 (2025-01-13) +- Fixed a regression in 5.3.0 causing navmesh cutting to be much slower than it should be. +- Fixed triangle nodes on recast/navmesh graphs with no adjacent triangles would not be able to be traversed via off-mesh links. +- Fixed triangle nodes on recast/navmesh graphs with no adjacent triangles would not be detected by the \reflink{ConstantPath} path type. +- Fixed some missing videos in the \ref get-started-recast tutorial. + +## 5.3.0 (2025-01-09) +- Added a new get started video: + \youtube{PZXX4xGzCCA} +- Rewrote the \ref getstarted to make it easier to follow, and more up-to-date. +- Added a new tutorial: \ref get-started-grid. +- Added a new tutorial: \ref get-started-point. +- Added a new tutorial: \ref get-started-recast. +- Added a lightbox to all images in the documentation. +- Various other documentation improvements. +- Fixed enabling \reflink{FollowerEntity.isStopped} would make the agent resist being rotated by other means. +- Fixed \reflink{FollowerEntity} could vibrate a lot when being dragged outside the navmesh. +- The \reflink{FollowerEntity} control loop now always runs at least once per frame, instead of skipping some frames if the fps was very high. +- Fixed a bug causing paths calculating using the Manhattan or None heuristics on grid graphs to look much worse than they should (regression in 5.0). + The paths were still optimal, but they were not as straight as they should have been. +- Fixed compilation errors when the "com.danielmansson.mathematics.fixedpoint" package was installed in the same project. +- Fixed some edge cases where navmesh cuts on recast graphs could break connections between adjacent tiles. +- The \reflink{FollowerEntity} component now defaults to drawing its path in the scene view. This can be disabled in the inspector, or by changing \reflink{FollowerEntity.debugFlags}. +- Fixed the \reflink{RuleTexture} on grid graphs would not necessarily realize if its texture reference had changed. +- The \reflink{RecastGraph} now defaults to a voxel size of 0.25, instead of 0.5. + The default character radius was reduced a few versions ago, but the voxel size was not updated to match (it's recommended to keep the voxel size to at most half the character radius). +- Fixed the layer mask for the recast graph would be used even if the graph was set to filter by tags, not by layers. +- Fixed navmesh cuts could, in very degenerate cases, throw an exception due to an incorrect comparator. +- Fixed some edge cases where linecasts on recast/navmesh graphs could return that an obstacle existed if the end point of the linecast was exactly on the border between two nodes. +- Fixed an edge case when adding nodes to a point graph that could result in an exception being thrown. +- Local avoidance agents in layers that an agent does not try to avoid, no longer count towards the "Max Neighbours" limit. +- The FollowerEntity is will now use exponential back off if its path calculations continue to fail. Previously it would try to recalculate its path as quickly as possible in this case, + but now it will recalculate its path more slowly to improve performance. + +## 5.2.5 (2024-11-20) +- Breaking changes + - If you have built your own ECS baker for the FollowerEntity. You must now also add the \reflink{PhysicsSceneRef} component to the entity. +- When the \reflink{FollowerEntity} traverses off-mesh links and custom off mesh link handling code is used, it will no longer enable the agent's built-in movement by default. + Instead it will only be enabled if \reflink{AgentOffMeshLinkTraversalContext.enableBuiltInMovement} is enabled or \reflink{AgentOffMeshLinkTraversalContext.MoveTowards} is called. + This resolves issues many users have had where the agent's built-in movement was interefering with animation-driven movement during off-mesh links. +- Fixed navmesh cutting could throw an exception due to a multithreading race condition, if navmesh cutting was used and multiple regular graph updates were happening at the same time (regression in 5.2.0). +- Improved performance of \reflink{FollowerEntity} slightly. +- Fixed the inspector for \reflink{RulePerLayerModifications} would not properly show the tag dropdown (possibly only an issue in Unity 6). +- \reflink{RecastGraph.SnapBoundsToScene} will now make the bounding box taller to ensure the agent can stand on every valid surface. +- The \reflink{FollowerEntity} will now use the physics scene from its GameObject when performing raycasts. Previously it always used the default physics scene. + This is useful when, for example, you use Unity's multiplayer testing mode, which creates multiple players in the same unity editor instance, each with their own physics scene. +- \reflink{RecastGraph}s now default to rasterizing colliders, instead of meshes. This has slightly better performance, and is a better choice for most games. +- Improved performance when deep profiling is enabled. +- Increased the minimum supported version of the (optional) entities package to 1.1.0 (up from 1.0.0). + +## 5.2.4 (2024-10-14) +- Fixed trying to use navmesh cutting with an older version of the Unity collections package could throw an exception, instead of logging an error message saying that this is unsupported. +- Fixed an exception that could happen when setting FollowerEntity.movementPlaneSource to NavmeshNormal and the agent lost the path it was following. +- Fixed using the \reflink{NavmeshAdd} component could cause Unity to crash (regression in 5.2.0). +- Fixed incompatibility with the high definition render pipeline accidentally introduced in 5.2.3. +- Fixed the \reflink{FollowerEntity} component throwing an exception if it was opened in prefab isolation mode while the game was running. +- Fixed \reflink{NavmeshAdd.ForceUpdate} throwing an exception if there was no \reflink{AstarPath} component in the scene. +- Fixed gizmos were not rendered when opening prefab assets in isolation mode and the high definition render pipeline was used. +- Fixed a memory leak when the AstarPath component was put in a prefab. +- Fixed an exception that could happen if one tried to load a graph from a zip file that contained nodes (not just settings) into an AstarPath component on a prefab. + +## 5.2.3 (2024-10-07) +- Fixed point graphs could in rare cases get stuck in an infinite loop when scanning. + +## 5.2.2 (2024-10-05) +- \a Please \a delete \a your \a previous \a installation \a before \a upgrading. +- Fixed standalone builds would fail for some platforms and configuration options (regression in 5.2.0). +- Fixed changing the gravity setting on the \reflink{FollowerEntity} component during runtime would not affect anything. +- Changed \reflink{FollowerEntity.enableGravity} to have no effect if the agent's orientation is set to YAxisForward (2D mode). + Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode. + +## 5.2.1 (2024-10-03) +- Fixed upgrading to 5.2.0 from an older version could result in compilation errors. UnityPackages are truly terrible. +- Fixed a few edge cases with navmesh cutting (regression in 5.2.0). +- Improved navmesh simplification when using navmesh cutting. + +## 5.2.0 (2024-10-01) +- Breaking changes + - Renamed RecastMeshObj to \reflink{RecastNavmeshModifier}. + An interface has been provided to handle backwards compatibility for most cases, but there are some cases where manual script changes are required (a search and replace will do it). + - Renamed DynamicGridObstacle to \reflink{DynamicObstacle}, because it supports recast graphs too, since a while back. + An interface has been provided to handle backwards compatibility for most cases, but there are some cases where manual script changes are required (a search and replace will do it). + - The \reflink{NavmeshCut} component now requires Unity 2022.3 or newer to use, due to Unity bugs in earlier versions. + - The \reflink{NavmeshCut} component now requires com.unity.collections version 2.2.0 or newer to use. + - Moved \reflink{ListPool}, \reflink{ArrayPool} and \reflink{PathPool} to a new namespace Pathfinding.Pooling. + - Removed all usages of the custom Int2 struct, and replaced them with Unity's equivalent Vector2Int struct. +- Improvements and new features + - Significantly improved robustness of navmesh cutting. + Previously, if navmesh cuts were placed in just the right way, the cutting could fail and the navmesh would not be updated correctly. + This was rare, but if the game allowed user-placed navmesh cuts, it was bound to happen eventually. + - Reduced GC allocations during navmesh cutting by up to 85%. + - Improved performance of navmesh cutting by about 25%. + - Navmesh cutting now works outside play mode too. + - Added \reflink{AstarPath.Linecast}, which is a convenience method for checking if there is line-of-sight between two points. + This will delegate the check to the most reasonable graph. + - Added a tutorial on linecasting: \ref linecasting. + \video{generated/scenes/Linecasting/linecast.webm} + - Added a tutorial on how to add and remove nodes from a point graph: \ref creating-point-nodes. + - Added \reflink{FollowerEntity.CreateEntity}. A static function to create an entity without using the FollowerEntity MonoBehaviour itself. + - Added \reflink{NavGraph.RandomPointOnSurface} to get a random point on the surface of a graph, optionally filtered by a \reflink{NNConstraint}. + - Nodes in point graphs can now be removed using \reflink{PointGraph.RemoveNode}. Previously it was only possible to add nodes. + - Point graphs will now draw a box around each node in the scene view, even for nodes that have been added using a script. + - It's now possible to duplicate graphs in the inspector. + - Improved styling of graph edit icons in the inspector: + \shadowimage{changelog/edit_icon2.png} + - Added \reflink{AstarData.DuplicateGraph}. + - Updated various screenshots in the documentation to be more up to date, and in HiDPI, so they look great on retina displays too. + - \reflink{FollowerEntity.velocity} can now be assigned to. + - Added \reflink{IAstarAI.updatePosition} and \reflink{IAstarAI.updateRotation}. These properties have existed on all movement scripts, but they were not added to the interface before. + - Reduced GC allocations and improved performance slightly, when loading a sequence of scenes that contain the \reflink{AstarPath} component. +- Fixes + - Fixed \reflink{ManagedState.Clone} would throw an exception if its PathTracer was not initialized. + - Fixed \reflink{SingleNodeBlocker.Block} would not unblock the previously blocked node (in contrast to what the documentation said, and different from what the BlockAt method did). + - Fixed layered grid graphs could in rare cases generate one-way connections between nodes, when \reflink{GridGraph.maxStepUsesSlope} was enabled. + - Fixed the graph display window in the scene view would be rendered off-screen on HiDPI displays. + - Fixed the \reflink{GraphUpdateScene} shortcuts help window in the scene view would be rendered off-screen on HiDPI displays. + - Fixed \reflink{FollowerEntity.SearchPath} would not take into account any custom pathfinding settings set on the \reflink{FollowerEntity} component. + - Fixed undo events could in some situations reset pathfinding settings on the \reflink{FollowerEntity} component. + - Fixed calling \reflink{FollowerEntity.GetRemainingPath} could throw an exception if the path contained off-mesh links (recent regression). + - Fixed having \reflink{FollowerEntity.isStopped} enabled when a FollowerEntity component was enabled, could cause its rotation to be reset. + - Fixed the \reflink{FollowerEntity} could attach itself to a graph that did not match its graph mask, for a few frames, until its first path was calculated. + - Fixed updating a layered grid graph and forcing nodes to be walkable, could, in some cases, create connections to nodes that didn't exist, causing errors later on. + - Reduced "shyness" around locked local avoidance agents close to the end of an agent's path. + - Fixed the heuristic optimization subsystem could throw an exception in some cases if all graphs were empty. + - Fixed scanning a recast graph with very tiny \reflink{RecastNavmeshModifier} components could in exceedingly rare cases freeze Unity. + - Fixed the \ref writing-graph-generators tutorial created node connections incorrectly (regression in 5.0). + - Fixed one overload of NavmeshBase.Linecast would ignore the \a hint parameter. + - Worked around a Unity bug which could cause the welcome screen to throw an exception. +- Changes + - Removed obsolete script MineBotAI. It has been deprecated for around 7 years and has been replaced by AIPath together with MineBotAnimation. + - Removed the experimental warning for the \reflink{FollowerEntity} component. + - \reflink{RecastGraph.characterRadius} now defaults to 0.5, instead of 1.5. This is a more reasonable default for most humanoid characters. + - Split out the \reflink{RepairPathSystem} from the \reflink{FollowerControlSystem}. + - Added the DisallowMultipleComponent attribute to the AstarPath component, and all built-in movement scripts. + - Renamed AstarData.navmesh to \reflink{AstarData.navmeshGraph} for consistency. + - \reflink{NavGraph.RelocateNodes} will now verify that it is safe to update nodes. It's safe to update nodes in a work item, or during a graph update. + - Moved various internal classes to a new namespace Pathfinding.Collections. + - Moved various internal classes to a new namespace Pathfinding.Sync. + - Moved \reflink{ListPool}, \reflink{ArrayPool} and \reflink{PathPool} to a new namespace Pathfinding.Pooling. + - Removed AIBase.centerOffset, as it's been deprecated for 6 years. + - Removed AIBase.rotationIn2D, as it's been deprecated for 6 years. Use \reflink{AIBase.orientation} instead. + - Removed AILerp.rotationIn2D, as it's been deprecated for 6 years. Use \reflink{AILerp.orientation} instead. + - Removed AIBase.target, as it's been deprecated for 7 years. Use the \reflink{AIDestinationSetter} component instead. + - Removed AILerp.target, as it's been deprecated for 7 years. Use the \reflink{AIDestinationSetter} component instead. + - Removed AILerp.ForceSearchPath, as it's been deprecated for 7 years. Use \reflink{AILerp.SearchPath} instead. + - Removed GridNode.GetConnectionInternal, as it's been deprecated for 8 years. Use \reflink{GridNode.HasConnectionInDirection} instead. + - Removed LevelGridNode.HasConnection, as it's been deprecated for 8 years. Use \reflink{LevelGridNode.HasConnectionInDirection} instead. + - Removed RVOController.mask, as it's been deprecated for 7 years. Use settings on your movement script instead. + - Removed RVOController.enableRotation, as it's been deprecated for 7 years. Use settings on your movement script instead. + - Removed RVOController.rotationSpeed, as it's been deprecated for 7 years. Use settings on your movement script instead. + - Removed RVOController.maxSpeed, as it's been deprecated for 7 years. Use settings on your movement script instead. + - Removed RVOController.ForceSetVelocity, as it's been deprecated for 7 years. Use \reflink{RVOController.velocity} instead. + - Removed RVOController.Teleport, as it's been deprecated for 8 years. It is no longer necessary. + +## 5.1.6 (2024-08-06) +- Fixed compatibility with com.unity.mathematics 1.3.0 and lower. + +## 5.1.5 (2024-08-06) +- If an async scan is running when the AstarPath component is disabled, or another scan is started, then the code will block until the in-progress async scan has finished. + Previously, unloading a scene while an async scan was running could in some cases lead to memory leaks and exceptions. +- Fixed \reflink{FollowerEntity} could get teleported a small distance in 2D games, right when the game started. +- Fixed \reflink{PointGraph.GetNearest} could throw an exception in some situations if there was no acceptable node nearby. +- Fixed \reflink{GridGraph.RelocateNodes} could leave the grid graph with a corrupted state. +- Fixed updating point graphs could throw an exception if some nodes had been manually added to the graph. +- Fixed some compilation warnings in Unity 6. +- Fixed \reflink{FollowerEntity.updateRotation} and \reflink{FollowerEntity.updatePosition} would not always take effect when set from the unity inspector. +- Fixed \reflink{AstarPath.showSearchTree} could show incorrect information on recast graphs. +- Fixed \reflink{FloodPathTracer} not working with recast graphs (regression in 5.0). +- Fixed using graph coloring modes G, H or F on recast graphs could display incorrect information. +- Fixed toggling \reflink{AstarPath.showSearchTree} would not always re-render the graph visualization. +- Fixed an \reflink{ITraversalProvider} on a \reflink{FollowerEntity} could be called with a null node in some cases. +- Fixed tons of grammar and spelling mistakes in the documentation. +- Fixed Seeker.StartMultiTargetPath would never use the Seeker's graphMask. +- Fixed calling \reflink{AstarPath.FlushWorkItems} would freeze unity if a work item threw an exception. +- \reflink{AstarData.DeserializeGraphsAdditive} now returns an array with the deserialized graphs. +- Trying to deserialize graphs from an invalid zip file will now throw an exception instead of just logging an error message. +- Renamed RecastGraph.SnapForceBoundsToScene to \reflink{RecastGraph.SnapBoundsToScene}. +- RecastGraph.SnapBoundsToScene will now ensure the resulting bounds are non-zero along all axes. +- Improved smoothness of the \reflink{FollowerEntity} when using the \reflink{MovementPlaneSource}.NavmeshNormal mode. +- The \ref spherical example scene now uses the \reflink{FollowerEntity} component instead of the \reflink{AIPathAlignedToSurface} component. +- \reflink{AstarPath.showSearchTree} now renders the search tree with thicker lines, to make it easier to see. +- Deprecated \reflink{PointNode.SetPosition}. Use \reflink{PointNode.position} instead. +- Fixed \reflink{GridGraphRule.Pass}.AfterApplied not being called in some cases. + +## 5.1.4 (2024-06-12) +- Fixed an exception that could be thrown after updating a graph (regression in 5.1.3). +- Reduced memory usage slightly. +- Improved performance when updating a recast graph in a scene with a terrain. + +## 5.1.3 (2024-06-11) +- \reflink{FollowerEntity.updatePosition} and \reflink{FollowerEntity.updateRotation} can now be set in the inspector, and the values are no longer reset if the component is disabled. +- Fixed Unity's Random state would get reset by the FollowerEntity movement script on scene initialization. +- Paths now take their assigned \reflink{ITraversalProvider} into account when finding the start and end nodes. + This fixes several issues where an agent could get stuck and not be able to calculate a path, because the node it was standing on (or the node closest to the destination) + was walkable according to the path's \reflink{NNConstraint}, but not according to its \reflink{ITraversalProvider}. +- Fixed \reflink{FollowerEntity} sometimes getting stuck when traversing non-flat grid graphs. +- Fixed \reflink{FollowerEntity} could throw an exception if it was disabled while it was traversing an off-mesh link. +- Exposed a few more properties in the \reflink{FollowerEntity} class, and improved its documentation. +- The number of temporary nodes for path calculations can now grow automatically. Therefore the ASTAR_MORE_MULTI_TARGET_PATH_TARGETS define is no longer needed. + This was only of importance if you used multi target paths with a lot of targets, or if you had a very large number of off-mesh links in your scene. +- Fixed the 'Round Collider Detail' setting was not visible in the recast graph inspector when in 2D mode in some cases, even when it should. +- Fixed \reflink{AstarData.DeserializeGraphsAdditive} would not remove null graphs from the end of the list of graphs before appending the new ones. +- Fixed disabling a \reflink{NavmeshPrefab} component could later cause a null reference exception when the graph was destroyed. +- Fixed \reflink{NavmeshPrefab}s could sometimes leave a 1 voxel wide gap at their borders (recent regression). +- Various documentation improvements. + +## 5.1.2 (2024-05-29) +- Added \reflink{AstarPath.IsPointOnNavmesh} to more easily check if a point is on the navmesh surface of any graph. +- Added \reflink{NavGraph.IsPointOnNavmesh}, which works the same, but only checks a single graph. +- Fixed \reflink{ProceduralGraphMover} would not scan new tiles correctly when moving in the +x or +z directions (recent regression). +- Fixed the recast graph inspector would not show the Character Radius field in 2D mode (regression in 5.1.0). + +## 5.1.1 (2024-05-08) +- Added a welcome screen when importing the package into a new project. + It makes it easy to import the example scenes into the project, something new users are often confused about how to do. + It also has convenient links to the documentation and changelog. + \shadowimage{changelog/welcome_screen.png} +- Added \reflink{RecastGraph.collectionSettings.onCollectMeshes} to allow adding custom meshes to be rasterized by the recast graph. +- Fixed \reflink{FollowerEntity} could throw an exception when it was scaled by a negative value. +- Improved compatibility with other packages. Previously, \reflink{FollowerEntity} could throw an exception in some projects if, for example, DoTween Pro was installed. +- Improved styling of the documentation. + +## 5.1.0 (2024-05-02) +- Breaking changes + - It is no longer possible to use both the layer mask and tag mask at the same time when scanning recast graphs. + The tag mask was very seldom used and it was a bit confusing to have both options available at the same time in the inspector. +- New Features and improvements + - Tags applied when recast graphs are scanned will now be preserved even when using navmesh cutting. + Previously, navmesh cutting would not be able to preserve these tags. + Note that this only applies to tags that are set either on a \reflink{RecastNavmeshModifier} component, or using a graph update that happens during the initial scan. + - Navmesh cutting is now better at collapsing adjacent triangles into fewer triangles, if possible. + This can improve pathfinding quality, and improve performance ever so slightly. + - Restructured the recast graph inspector, with clearer headings and a more consistent layout. + \shadowimage{changelog/recastgraph_inspector.png} + - Recast graphs now have a new option for per layer modifications. + This means you can now set tags or make nodes unwalkable based on the layer of the surface under the node, without having to attach a \reflink{RecastNavmeshModifier} component to every object. + \shadowimage{recast/per_layer_modifications.png} + - \reflink{MecanimBridge} now supports the \reflink{FollowerEntity} movement script. + - There's now a button in all relevant example scenes that link them to the corresponding page in the documentation. + - When the scene view camera is in 2D mode, one can now add points to the \reflink{GraphUpdateScene} by shift-clicking even in empty areas of the screen. Previously, one had to click on a GameObject. + This should improve the workflow when working on 2D games. + - Implemented \reflink{FollowerEntity.Move}. + - Changing \reflink{FollowerEntity.enableGravity} doesn't cause a structural entity change anymore, which means it can be used from an off-mesh link handler. +- Fixes and changes + - Fixed scanning recast graphs asynchronously could block due to it creating too many jobs, so that Unity forced some to execute on the main thread. + - Fixed a few edge cases when scanning recast graphs. + - Fixed asynchronously scanning grid graphs would not split up some main thread work into smaller chunks, which could cause frame drops. + - Fixed \reflink{FollowerEntity} could throw exceptions when traversing a grid graph that was being updated at the same time. + - Fixed when adding the first point to a \reflink{GraphUpdateScene} component by shift-clicking in the scene view, the GameObject could end up being deselected. + - Recast graphs now handle degenerate triangles a bit better. + - Fixed tiled recast graphs could generate a navmesh outside the bounding box of the graph, if the tile size didn't divide the graph size evenly. + - Fixed \reflink{FollowerEntity} could get stuck when one of its world coordinates were around 2100, due to coordinate overflows in some calculations. + - Added a warning in the grid graph inspector when trying to use the \reflink{RulePerLayerModifications;per layer modifications rule} without height testing enabled. + - Minor other improvements to editing points for the \reflink{GraphUpdateScene} component. + - Renamed RecastGraph.rasterizeColliders to \reflink{RecastGraph.collectionSettings.rasterizeColliders}. + - Renamed RecastGraph.rasterizeMeshes to \reflink{RecastGraph.collectionSettings.rasterizeMeshes}. + - Renamed RecastGraph.rasterizeTerrain to \reflink{RecastGraph.collectionSettings.rasterizeTerrain}. + - Renamed RecastGraph.rasterizeTrees to \reflink{RecastGraph.collectionSettings.rasterizeTrees}. + - Renamed RecastGraph.colliderRasterizeDetail to \reflink{RecastGraph.collectionSettings.colliderRasterizeDetail}. + - Renamed RecastGraph.mask to \reflink{RecastGraph.collectionSettings.layerMask}. + - Renamed RecastGraph.tagMask to \reflink{RecastGraph.collectionSettings.tagMask}. + - Renamed RecastGraph.terrainSampleSize to \reflink{RecastGraph.collectionSettings.terrainHeightmapDownsamplingFactor}. + +## 5.0.9 (2024-04-15) +- Minor internal changes. +- Various minor documentation improvements. +- Reduced allocations during pathfinding slightly. + +## 5.0.8 (2024-04-12) +- Made \reflink{AstarPath.PausePathfindingSoon} public. +- Made \reflink{RepairPathSystem.ResolveOffMeshLinkHandler} public. +- Made \reflink{RepairPathSystem.NextLinkToTraverse} public. +- Added a public constructor for \reflink{MovementTarget}. + +## 5.0.7 (2024-04-11) +- New Features and improvements + - Improved the \ref calling-pathfinding documentation page. + - Improved support for \reflink{FollowerEntity} on spherical (or other weirdly shaped) worlds, using the new \reflink{MovementPlaneSource}.NavmeshNormal mode. + \video{generated/scenes/WonkyNavmesh/overviewvideo.webm} + - Explicitly reference .dll files in the project to improve compatibility with some IDEs. + - Added \reflink{AstarPath.GetNavmeshBorderData}. + - Added \reflink{AgentOffMeshLinkTraversalContext.Abort}, which can be used to abort traversal of an off-mesh link, from the coroutine that handles it. + - Improved performance of the \reflink{FollowerEntity} component. +- Fixes and changes + - The \reflink{AstarPath} component now shows up as "AstarPath" in the component menu, instead of "Pathfinder". This should make it easier to find for new users, as it is referred to as AstarPath in the documentation. + - Fixed the agent would get stuck on obstacles placed using the "P" key in the "InfiniteWorld" example scene, due to incorrect layer settings. + - Fixed various edge cases when the AstarPath component is put in a prefab. + - Fixed \reflink{FollowerEntity.GetRemainingPath} throwing an exception in some cases (regression in 5.0.6). + - Fixed \reflink{AstarPath} drawing graphs even when an unrelated prefab was opened in isolation mode. + - Fixed division by zero in \reflink{FollowerEntity} when rotation speed was zero. + - Fixed a race condition when scanning or updating recast graphs, which could cause graph updates to throw exception in very rare cases. + - Fixed \reflink{RecastNavmeshModifier} with the WalkableSurfaceWithTag mode could fail to apply the tag if it was also marked as solid, and the surface was close to another surface. + - Fixed a small GC allocation happening every frame when using the \reflink{FollowerEntity} component. + - Fixed the gravity applied to the \reflink{AIPathAlignedToSurface} movement script was much higher than it should be. + - Fixed the \reflink{AIPathAlignedToSurface} movement script could throw an exception when completely inside a convex mesh collider. + - Fixed \reflink{NodeLink2} components could throw an exception if they were loaded in as enabled, but then immediately destroyed before Unity had a chance to call the OnEnable method. + - Reduced max graph count from 256 to 255. + - Added \reflink{GraphNode.InvalidGraphIndex}. + - Renamed AgentOffMeshLinkTraversalContext.linkInfo to \reflink{AgentOffMeshLinkTraversalContext.link}. + +## 5.0.6 (2024-03-29) +- Added \reflink{IAstarAI.GetRemainingPath(List,List,bool)} to get information about all the parts (including off-mesh links) of the path that the agent is currently following. + \shadowimage{generated/scenes/RecastOffMeshLinks/remainingpath.png} +- Fixed \reflink{FollowerEntity.GetRemainingPath(List,bool)} would only output the path up to the next off-mesh link. +- Fixed \reflink{FollowerEntity} could return incorrect values for a few properties (e.g. \reflink{FollowerEntity.hasPath}) after it had been disabled. +- Fixed \reflink{RandomPath} could in very rare situations cause an exception that crashed the pathfinding threads, due to a race condition. +- Fixed \reflink{MultiTargetPath} could calculate incorrect paths on grid graphs in some situations. +- Fixed various edge cases when the AstarPath component is put in a prefab. +- Fixed \reflink{RecastNavmeshModifier} components could log an error message about being moved, even if they had not moved. +- Fixed enabling \reflink{ABPath.calculatePartial} could cause an exception or an incorrect path to be calculated, in some situations. +- Fixed several broken documentation links. +- If an \reflink{RVOSimulator} is enabled in a scene that already has an active RVOSimulator, the new one will be disabled and a warning will be logged. + This now works like the \reflink{AstarPath} component, which also uses a singleton pattern. + +## 5.0.5 (2024-03-21) +- Added \reflink{FollowerEntity.movementOverrides} to allow more easily overriding custom movement logic for the \reflink{FollowerEntity} component. + \video{generated/scenes/RecastTerrain/movementoverride.webm} +- Made \reflink{GraphUpdateScene.GetGraphUpdate} virtual. +- Fixed agents with the \reflink{FollowerEntity} component trying to traverse an off-mesh link could get stuck waiting for a long time at the beginning of the link, if either local avoidance or rotation smoothing was enabled. +- Fixed help links for components used an incorrect extension, and would therefore often not work. + +## 5.0.4 (2024-03-15) +- Added \reflink{RVOController.SetObstacleQuery}. +- Added \reflink{GridGraph.GetBoundsFromRect}. +- Exposed \reflink{AstarPath.Snapshot}. +- Improve the \ref upgrading. +- Clarified the \ref installation. +- Fixed \reflink{NavmeshAdd} components not affecting completely empty recast graph tiles. +- Fixed \reflink{RichAI.steeringTarget} being incorrect when very close to the destination. +- Fixed \reflink{NavmeshBase.PointOnNavmesh} could return null right on the edge beteween two triangles, in some cases. +- Fixed an exception that could happen when a degenerate triangle was generated when scanning a recast graph or a navmesh prefab. +- Fixed exception that could happen sometimes when the \reflink{FollowerEntity} was simplifying its path on a grid graph. +- Fixed \reflink{GridGraph.SetWalkability} not refreshing off-mesh links. + +## 5.0.3 (2024-03-09) +- Added \reflink{NavmeshPrefab.removeTilesWhenDisabled}. +- The \reflink{NavmeshPrefab} will now add its stored tiles to the graph every time the component is enabled, instead of only the first time. +- Clarified the \ref installation. +- Fixed teleport link in the off-mesh link example scene only worked in the start->end direction, but not in the end->start direction. +- Fixed \reflink{FollowerEntity} not taking penalties into account when simplifying its path on grid graphs. +- Fixed some data migrations for components would not run. +- Fixed compilation errors in Unity 2022.1. +- Fixed compilation warnings when using older versions of the collections package (before 2.1.0). +- Fixed \reflink{RVOSimulator.useNavmeshAsObstacle} not working with the \reflink{AIPath} and \reflink{RichAI} movement scripts. +- Renamed AgentOffMeshLinkTraversal.firstPosition to \reflink{AgentOffMeshLinkTraversal.relativeStart}. +- Renamed AgentOffMeshLinkTraversal.secondPosition to \reflink{AgentOffMeshLinkTraversal.relativeEnd}. + +## 5.0.2 (2024-03-06) +- Fixed some example scenes contained objects only intended for development, with missing scripts. + +## 5.0.1 (2024-03-05) +- The Asset Store version of the package is now distributed as an NPM package, instead of a UnityPackage. + This should make it easier to install and update the package. + +## 5.0 (2024-03-05) +- Read more about the new features in the blog post. +- These release notes combine all changes that were evaluated in the 4.3.x beta versions which lead up to the 5.0 release. +- Major New Features + - Burst powered scanning of recast graphs. + Recast graphs now use the Burst Compiler and the Unity Job System. + Together with a lot of work on improving the underlying algorithms, this has resulted in much faster scans and updates of recast graphs. + It’s now up to 3.5x faster. Graph updates and async scans also run almost entirely in separate threads now, to make the fps impact as minimal as possible. + - Burst powered scanning of grid graphs and layered grid graphs. + Grid graphs have received a major rewrite in how they scan the graph. + It is now powered by burst and the unity job system, which makes scanning graphs around 3x faster. + It also supports new features like the \reflink{GridGraph.maxStepUsesSlope;Max Step Uses Slope} option, + which makes the graph much better connected in most scenarios involving slopes. + When upgrading this field will be disabled for compatibility reasons, but it will be enabled for all new graphs. + \shadowimage{gridgraph/max_step_uses_slope.png} + - There's also a whole new system for creating rules for grid graphs. + These rules allow you to tweak the grid graph scanning process in a very flexible way, which also works with graph updates + transparently. See \ref grid-rules. + - For 2D enthusiasts, the grid graph can now align itself to a Tilemap directly from the inspector. + Something which otherwise might require complex trigonometry, especially for isometric games, or those using hexagonal tiles. + \shadowimage{gridgraph/tilemap_result.png} + - More accurate and faster linecasts on grid graphs + Linecasts on grid graphs have received a major rewrite, with a big focus on robustness and correctness. + Previously, edge cases like "the linecast goes precisely along the border to an unwalkable obstacle", or "the linecast ends exactly at a corner of an obstacle", + were not well defined. Now, the between the walkable part of the navmesh and the obstacles is defined as walkable. + Edge cases are handled a lot better now. + - New local avoidance algorithm + - The new local avoidance algorithm based on ORCA (Optimal Reciprocal Collision Avoidance), leads to nicer movement in most cases. + - It's also faster! Not only due to the new algorithm, but also because now everything is powered by burst and the unity job system. + Local avoidance is up to 10x faster than in 4.2.x. + - Takes the navmesh into account. + The local avoidance system now takes the navmesh into account automatically, and with good performance. + Previously, the RVONavmesh component has been used for a similar result, but it had much worse performance, + and it was not as robust. + \shadowimage{rvo/rvo_stress_30000_white.png} + - New ECS-powered movement script: \reflink{FollowerEntity}. + The focus for this new movement script is that it should “just work”, and that it should be very robust and handle all those pesky edge cases. + In terms of performance, it’s also slightly better than both the AIPath and RichAI movement scripts. + Out of the box, it supports local avoidance, smooth and very responsive movement, off-mesh links, staying away from walls, facing a given direction when it reaches its destination, and a lot more. + You do not need to know anything about ECS to use it, everything is abstracted away, and you can use it much like the other movement scripts. + - The \reflink{ProceduralGraphMover} (previously ProceduralGridMover) now supports recast graphs too. + Now your infinite worlds can have recast graphs too! + \video{recast/graph_mover.webm} + - Improved off-mesh links + Off-mesh links have been almost completely rewritten to be more robust and easier to use. + You can now specify a tag for them, and choose which graphs they are allowed to link. + They are also much more robust when combined with graph updates, whereas previously they could sometimes lose their connection to the navmesh. + And during those graph updates, they require much less cpu power to refresh themselves, so now you can have a lot more of them in your scene without any performance issues. + Take a look at the new tutorial on off-mesh links: \ref example_recast_offmeshlinks. + \video{generated/scenes/RecastOffMeshLinks/interact.webm} + - Added support for all 'Enter Play Mode Options' options. + This means you can cut down on your iteration times significantly. See https://docs.unity3d.com/Manual/ConfigurableEnterPlayMode.html + - Improved Documentation + - Added new tutorials and documentation pages: + - \ref offmeshlinks. + \video{generated/scenes/RecastOffMeshLinks/interact.webm} + - \ref tilemaps. + \shadowimage{gridgraph/tilemap_result.png} + - \ref move_in_circle. + \video{move_in_circle/move_in_circle.mp4} + - \ref moving_graph. + - \ref traversal_provider. + - \ref grid-rules. + - \ref grid-rules-write. + - \ref playermovement. + - Added 6 new example scenes, each with their own dedicated page in the documentation: + - Added a new example scene: \ref example_recast3d + \shadowimage{generated/scenes/Recast3D/overview.png} + - Added a new example scene: \ref example_recast_doors + \shadowimage{generated/scenes/RecastDoors/overview.png} + - Added a new example scene: \ref example_recast2d + \shadowimage{generated/scenes/Recast2D/overview.png} + - Added a new example scene: \ref example_recast_offmeshlinks + \video{generated/scenes/RecastOffMeshLinks/interact.webm} + - Added a new example scene: \ref example_recast_tags + \shadowimage{generated/scenes/RecastTags/overview.png} + - Added a new example scene: \ref example_recast_terrain + \shadowimage{generated/scenes/RecastTerrain/overview.png} + - Added a documentation page for the turn based example scene: \ref example_turn_based + \shadowimage{generated/scenes/HexagonalTurnBased/overview.png} + - Improved the infinite world example scene with nicer meshes: \ref example_infinite_world + \shadowimage{generated/scenes/InfiniteWorld/overview.png} + - Improved documentation and look of the path types example scene: \ref example_path_types + \shadowimage{generated/scenes/PathTypes/multitargetpath.png} + - Improved documentation and look of the moving example scene: \ref example_recast_moving + \shadowimage{generated/scenes/RecastMoving/overview.png} + - Added a flowchart to help new users find the right graph type for their game. See \ref graph-tldr. + - Improved accuracy of path searches on recast/navmesh graphs significantly. + - Previously, paths on recast/navmesh graphs have been searched by going from triangle center to triangle center. This is not very accurate + and can lead to agents choosing suboptimal paths. + Now, paths use a more accurate method which moves between different points on the sides of the triangles instead of the centers. + This is much more accurate and should lead to agents choosing better paths in almost all cases. + - It also accounts for the exact start and end point of the path more accurately now. + - However, this improved accuracy comes with a cost, and pathfinding performance on navmesh/recast graphs is a bit lower than before. + Luckily, it is still very fast, and most games are not bottlenecked by this. + - Recast graphs now treat convex colliders (box, sphere, capsule and convex mesh colliders) are as solid, and will no longer generate a navmesh inside of them. + - Recast graphs now have better support for trees + - Colliders on child objects will also be taken into account. + - You can use the \reflink{RecastNavmeshModifier} component on trees to cutomize how they are rasterized. + - Significantly improved performance when you have many trees. + - Tree rotation is now taken into account. + - You can now mark surfaces with specific tags, for recast graphs. + Just add the \reflink{RecastNavmeshModifier} component to an object and set the mode to \reflink{RecastNavmeshModifier.Mode.WalkableSurfaceWithTag}. + - \reflink{NavmeshCut}s now support some 3D shapes (box/sphere/capsule) in addition to the previous 2D shapes (rectangle/circle) which makes them a lot more pleasant to use. + - \video{generated/scenes/MiscVideos/navmeshcutshapes.webm} + - \reflink{NavmeshCut}s now support expanding the cut by the agent radius. Making them more useful if you have multiple graphs for different agent sizes. + - The recast graph now supports terrain holes. + - Added support for pathfinding, local avoidance and movement on spherical worlds, and other strange world shapes. See \ref spherical. + - Added 2D support for the recast graph. + The recast graph now supports 2D colliders and the inspector has a new enum that allows you to change between 2D and 3D mode. + There's a new field \reflink{RecastGraph.backgroundTraversability} that controls if a navmesh should be generated for an unobstructed background in 2D mode. + - Improved consistency guarantees when doing an async graph scan. + Previously, the graphs could be in partially scanned states during the scan, which could cause e.g. GetNearest calls to return incorrect results, or throw exceptions. + Now, the graphs will be in a consistent state at all times during the scan (from the main thread's perspective). + - Added icons to many components. This should make it easier to find the right component in the inspector. + \shadowimage{changelog/component_icons.png} + - Added \reflink{NavmeshPrefab} to allow easily storing recast graph tiles in prefabs and load them at runtime (great for big procedurally generated levels). + - The LayeredGridGraph now supports 8 neighbours per node instead of 4 as was the case previously. + - You can now right-click on almost any property in a component, and get a link directly to the online-documentation for it. + This existed in some old versions too, but it had to be removed due to Unity limitations. But now it’s back again! + - When hovering over properties in the Unity inspector, you now get much better tooltips on most components. +- New Features and Improvements + - Added \reflink{NodeLink2.graphMask}, which allows you to set which graphs a link is allowed to connect. + - Added \reflink{GridNodeBase.OffsetToConnectionDirection}. + - Added \reflink{GridNodeBase.CoordinatesInGrid}, which is the vector (\reflink{GridNodeBase.XCoordinateInGrid}, \reflink{GridNodeBase.ZCoordinateInGrid}), but calculated in a more optimized way. + - Added support for triggering an \reflink{Interactable} component using an off-mesh link. + - Added \reflink{NodeLink2.onTraverseOffMeshLink} to allow custom traversal logic for specific off-mesh links. + - \reflink{NodeLink2} now automatically updates the graph connections when the links is moved around, even when not in play mode. + - \reflink{NodeLink2}'s gizmos are now aligned to the graph it is connected to. + - Improved performance when doing small graph updates on a recast graph, when there's a large terrain in the scene. + - Optimized graph updates on recast graph so that if multiple graph updates are scheduled that try to update the same tiles, it will try to avoid doing duplicate work. + This can significantly improve performance when using e.g. the \reflink{DynamicObstacle} component. + - Reworked how one-way links are handled internally. + This fixes a number of issues with one-way links, that could lead to exceptions or pathfinding just not working. + Even for a one-way link, the target node now also knows that it has a connection to the source node. + - Added \reflink{GraphNode.AddPartialConnection} and \reflink{GraphNode.RemovePartialConnection} for the cases where you need low-level connection modifications. + - Added a filter parameter to \reflink{GraphNode.GetConnections} to allow excluding incoming or outgoing connections. By default it only includes outgoing connections. + - Added a link to the online documentation when right-clicking on fields in the Unity inspector (supported for most, but not all, fields). + This was removed a while ago due to Unity limitations, but it is now back! + - Added better tooltips for most components. + - Added \reflink{NavGraph.bounds}. + - Added a warning if the input mesh to a navmesh graph contains degenerate triangles. These triangles are now also removed automatically, instead of causing errors down the line. + - Added \reflink{GridGraph.SetWalkability} as a helper function to set walkability for many nodes at once. + - Added \reflink{IAstarAI.movementPlane}. + - Off-mesh links are now never serialized with the graphs. Instead, the NodeLink2 component will always recreate them when the scene is loaded. + This significantly reduces ways things can get out of sync. + - Off-mesh links are now smarter about when they recalculate their connections due to graph updates. This resolves several previous issues in which off-mesh links could be broken by graph updates. + - Significantly refactored how graphs are updated and scanned. + All graph updates now guarantee that any updates will be done atomically from the main-thread's perspective. + Previously, in-progress graph updates could in some cases be visible, leading to weird results from e.g. the GetNearest method. + It could also lead to exceptions when using the FollowerEntity component. + - Graphs are now scanned and updated concurrently. + Previously, only one graph could be scanned or updated at a time (even if they might have used parallelism internally). + - Added a new example movement behavior: \reflink{MoveInCircle}. + - Improve graph update performance when there are lots of dynamic \reflink{RecastNavmeshModifier} components in the scene. + - The documentation now includes screenshots of the inspector for all components in the package. + - Added \reflink{AstarPath.OnPathsCalculated}. + - Added \reflink{GraphDebugMode.NavmeshBorderObstacles}. + - Clarified documentation on \reflink{GraphHitInfo}'s fields, about what their values are when no obstacle was hit. + - Added ASTAR_MORE_MULTI_TARGET_PATH_TARGETS to the A* Inspector -> Optimizations tab. It allows you to increase the maximum number of targets for a multi target path from around 256 to around 4096. + - Reduced likelyhood of gaps being generated between tiles in recast graphs. This will reduce the precision of the navmesh a bit on the y-axis along tile edges, but agents should be able to navigate better (and it will look better). + - Combined several methods in the layered grid graph and regular grid graph to use the same implementation, reducing code duplication and improving compile times. + - Added \reflink{GridGraph.RecalculateConnectionsInRegion}. + - Added \reflink{GridGraph.RecalculateAllConnections}. + - The grid graph inspector will now list which tags will be used for erosion, when the tags option is enabled. + - You can now choose which tags erosion is allowed to overwrite, when the tags option is enabled, using the new \reflink{GridGraph.erosionTagsPrecedenceMask} option. + - Changed the behavior of the recast graph's collider detail field. Previously it would not scale with the cell size, which made it hard to transfer between scenes of different scales. + Now it scales with the cell size, so leaving it at the default value of 1 should work well for almost all games. + - The recast graph's center, size and rotation fields will now be rounded to a multiple of 0.5 if it is very close to one. + - Added \reflink{AIDestinationSetter.useRotation}, which is usable with the \reflink{FollowerEntity} movement script. + \video{generated/scenes/Recast3D/facingdirection.webm} + - Navmesh graphs now have an option to expand all navmesh cuts by a constant amount. + - Simplified the code for path types significantly. There's now a lot more code sharing. + - Improved the \reflink{MultiTargetPath}. + - It now properly properly works even with multiple targets within a single node. This is particularly important for accuracy on navmesh graphs. + Previously all endpoints would be snapped to the closest node center. + - Improved performance when only finding the closest target (\reflink{MultiTargetPath.pathsForAll}=false), sometimes significantly so. + - Removed the MultiTargetPath.heuristicMode field, it now automatically does the best thing. + - Added an overload of \reflink{GraphNode.GetConnections} which takes a ref value that will be passed to the callback. This can be used to avoid allocations when getting connections due to not having to allocate a closure. + - \reflink{ABPath} now accepts an ending condition (\reflink{ABPath.endingCondition}), making the XPath path type obsolete. + - Added \reflink{NodeLink2.pathfindingTag} to be able to control which agents can traverse an off-mesh link. + - Added the \reflink{PathfindingTag} struct. You can add this to your scripts to automatically get a nice dropdown in the inspector for selecting pathfinding tags. + - Added \reflink{GraphNode.ContainsPoint(Vector3)} and \reflink{GraphNode.ContainsPointInGraphSpace}. + - Added \reflink{GridNodeBase.ContainsPoint} and \reflink{GridNodeBase.ContainsPointInGraphSpace}. + - \reflink{RecastNavmeshModifier} components now have a "Geometry Source" field, so that you can choose manually if you want to use a mesh or a collider when voxelizing it. + - \reflink{RecastNavmeshModifier} components now have a "Include In Scan" field, which allows you to choose if the object should be filtered using layer/tag masks, or if it should always/never be included in the scan. + Previously RecastNavmeshModifier components were always included in the scan. When upgrading from, all existing RecastNavmeshModifier components will be set to "AlwaysInclude" for compatibility. + - Added support for scheduling paths (\reflink{AstarPath.StartPath}) from within the Unity Job System. + - Allow running nearest node queries and other node lookup functions from within the unity job system. + This requires you to call the \reflink{AstarPath.LockGraphDataForReading} function to ensure safety. + - Reduced the overhead of having many \reflink{AIDestinationSetter} components. + - Added a new distance metric when searching for the closest node: \reflink{DistanceMetric.ClosestAsSeenFromAboveSoft(Vector3)}. + This is much nicer for character movement in most cases, and also usually faster than the default euclidean metric. + \shadowimage{distance_metric/distance_metric_manhattan_soft.png} + - Improved performance when searching for nearest nodes on navmesh/recast graphs (sometimes up to 5x, but typically only by a small percentage). + - Improved rebuild performance of the recast/navmesh internal bounding box tree (particularly important when using navmesh cutting). + - Improved accuracy of GetNearest calls on grid graphs. Previously the returned node was not necessarily the closest one if \reflink{AstarPath.fullGetNearestSearch} was not enabled. + - Added \reflink{RecastGraph.Resize} to allow changing the number of tiles of a graph during runtime. + - Added \reflink{RecastGraph.ReplaceTiles} to allow replacing multiple tiles at once. + - Added \reflink{ITraversalProvider.filterDiagonalGridConnections} for better control over how diagonal grid connections are handled. + - Added a warning to the inspector when scanning a recast graph that includes unreadable meshes. + Previously a warning would only be logged to the console. + \shadowimage{changelog/recast_unreadable_warning.png} + - Implemented a new and better algorithm for path simplification on grid graphs based on the paper "Toward a String-Pulling Approach to Path Smoothing on Grid Graphs". + This essentially makes the raycast modifier obsolete for grid graphs (unless you use the physics raycasting option) as the new algorithm is both faster and more accurate. + It is used automatically by the \reflink{FunnelModifier} on grid graphs. + It also takes penalties/tags into account, which has been a long requested feature. + - Improved handling of tags and \reflink{ITraversalProvider}s for grid graphs. + Previously when a path was calculated it would only take tag walkability and ITraversalProviders into account in a very basic way. + For example normally an agent is not allowed to move diagonally between two unwalkable nodes, even if both the start and end nodes are themselves walkable. + However this check did not apply to tags and ITraversalProviders because that info was precalculated and tags/ITraversalProviders can be set per path. + Now this is properly taken into account. + This also means things like the \reflink{GridGraph.cutCorners} option will apply to tags and ITraversalProviders. + See \reflink{GridNode.FilterDiagonalConnections} for more details. + - Changed when the AIPath/RichAI movement scripts stop when \reflink{AIBase.whenCloseToDestination} is set to Stop. + Previously it would stop up to \reflink{AIBase.endReachedDistance} units from the end of the path. + end up within \reflink{AIBase.endReachedDistance} units from the destination. + Essentially \reflink{AIBase.reachedDestination} would remain false even though the agent could actually reach the destination by just moving a bit further. + So this behavior has been changed so that it now only stops when it is within \reflink{AIBase.endReachedDistance} units from the destination, not the end of the path. + - The \reflink{RecastNavmeshModifier} now has an option for making a mesh solid. This is useful to prevent a navmesh from being generated inside objects. + - Added \reflink{Pathfinding.ITraversalProvider;ITraversalProvider.CanTraverse(path,from,to)}. + This allows you to control not only which nodes are traversable, but also which connections can be used. + - The \reflink{ProceduralGraphMover} script now has an option for specifying which graph to update. + - Added a mode for the RecastNavmeshModifier to make objects be completely ignored when scanning recast graphs. + This is useful if you are running out of layers in your project. + - Added \reflink{GridNodeBase.HasAnyGridConnections}. + - Removed LayerGridGraph.mergeSpanRange. This was a rarely used and hard to understand setting. Now it is automatically set to half the LayerGridGraph.characterHeight. + - Improved performance when scanning graphs in the editor outside of play mode by not searching through assemblies for graph types every time. + - Made \reflink{AstarData.ClearGraphs} public. + - Added a helper window with shortcuts when the GraphUpdateScene object is selected and the Move tool is active. + - When GraphModifier events and some callbacks like AstarPath.OnGraphsUpdated are actually fired has been inconsistent and not very well (or accurately) documented for a long time. + This release makes it more consistent and better documented. Note that this is a breaking change so if you have used any of those callbacks in your code + you may want to have a look at the #Pathfinding.GraphModifier documentation which describes when all events are fired. + - Added an \reflink{FunnelModifier.quality} option to try harder to simplify the path when using the FunnelModifier. + - To be more descriptive all modifiers have had their visible names changed to include "Modifier" (unless it was already included). + This is the same type of simplification that the \reflink{RichAI.funnelSimplification} option is using. + - Added the property \reflink{IAstarAI.desiredVelocityWithoutLocalAvoidance} to the AIPath and RichAI components. + This allows you to both read and write the internal velocity of the agent. + - The recast graph's position/rotation and size can now be edited using handles in the scene view. + - Improved rotation filtering for AIPath/RichAI components. + This reduces jitter in the agent's rotation when they are close to standing still. This is particularly important when using local avoidance. + - Greatly improved the performance of scanning a grid graph with a high erosion setting. + - The RVOSimulator inspector has been improved visually. + - Local avoidance now supports more aggressive collision prevention. + This is controlled by the \link Pathfinding.RVO.RVOSimulator.hardCollisions RVOSimulator.hardCollisions\endlink field. + Enabling this will cause the system to try very hard to avoid any kind of agent overlap, similar to how a physics engine would do it. + This does not influence agent avoidance when the agents are not overlapping. + - RichAI now has a \reflink{RichAI.whenCloseToDestination} field which behaves like the one that the AIPath script has had for some time. + - AIPath/RichAI now have options for more intelligently stopping earlier if there are already a lot of agents around the destination (see #Pathfinding.AIBase.rvoDensityBehavior). + This prevents the agents from walking around endlessly trying to reach the destination when many agents have the same destination. + This can only be used together with the local avoidance system, i.e. when there's also an RVOController attached to the character. + - The \reflink{RandomPath} will now select a random point on the end node, instead of always using the center. + If you want to use the center, set Seeker -> Start End Modifier -> End Point Snapping to Node Center. + - \reflink{GraphNode.RandomPointOnSurface} is now thread-safe. +- Breaking changes + - Take a look at \ref upgrading, for help on how to upgrade from 4.2.x to 5.0. + - The example script \reflink{MineBotAnimation} has been changed to normalize the NormalizedSpeed parameter it sends to the animator by a new parameter called \reflink{MineBotAnimation.naturalSpeed}. + - Renamed the ProceduralGridMover component to \reflink{ProceduralGraphMover}, since it now supports more than just grids. + - Changed navmesh/recast graph linecasts to return a hit if the linecast started outside the graph. + Previously, if doing a linecast from inside the graph to the outside, it would return a hit, but a linecast from the outside to the inside would return no hit. + This was a bug, but it's been present for so long that at this point it's basically a breaking change. + - The built-in movement scripts no longer listen for \a all paths that their attached Seeker component calculates, instead they only listen for paths that they request. + If you have been relying on this by calling seeker.StartPath from another script, and expecting the movement scripts to start to follow the path, you will need to change this to call \reflink{IAstarAI.SetPath;ai.SetPath} instead. + This has been the recommended way to do it for a long time, but now it is required. + The Seeker component will log a warning if a path is calculated but no script is listening for its completion. + - Namespace structure has been significantly improved. I have tried to keep most changes to internal classes that are not used often by users, but you may have to adjust your \a using statements slightly. + Here's a summary of the changes: + - \reflink{Pathfinding.Graphs.Grid} has been added, and a lot of classes related to the grid graph have been moved there. + - \reflink{Pathfinding.Graphs.Grid.Jobs} is new and contains a lot of internal grid scanning code. + - \reflink{Pathfinding.Graphs.Grid.Rules} is new, and contains grid graph rules. + - \reflink{Pathfinding.Graphs.Navmesh} replaces Pathfinding.Recast. + - \reflink{Pathfinding.Graphs.Navmesh.Voxelization} replaces Pathfinding.Voxels. + Sadly C# doesn't support public namespace aliases, so these changes cannot be made backwards compatible. + - If you have written your own graph type (one of a rare bunch), you may need to update your scanning code slightly. + The \reflink{NavGraph.ScanInternal} method should now return a \reflink{IGraphUpdatePromise}. + Take a look at \ref writing-graph-generators for more details. + - Scanning when the game starts now happens during early \a OnEnable instead of during early \a Awake. + This should typically not cause any issues, since it's good practice in Unity to not depend on other components being initialized in \a Awake. + But this may cause issues if you have been depending on this very specific initialization order. + - The type of some tag fields have changed from int to \reflink{PathfindingTag}. If you have assigned a tag to those fields before, you will now first need to convert it to a PathfindingTag: \code new PathfindingTag((uint)tag)\endcode. + - Removed support for deserializing graphs from version 3.8.x and below. 3.8.x was released over 8 years ago, so this should hopefully have given people enough time to upgrade. + - \reflink{NavGraph.GetNearest} will now always take the constraint into account. Previously this depended on the graph type and it was generally hard to use to get consistent results across graph types. + - \reflink{NavGraph.GetNearest} now returns an \reflink{NNInfo} instead of a NNInfoInternal. + - The \reflink{PathInterpolator} class has been refactored significantly and now represents points using a Cursor struct, instead of storing it on the interpolator itself. + Normally the \reflink{PathInterpolator} is not used by users, but a few may have used it in their own movement scripts. + In that's the case for you, then you will need to make small modifications to your code to create a cursor representing the current point. + - Removed support for local avoidance obstacles (\reflink{RVOSquareObstacle}). + Local avoidance obstacles never worked particularly well and the performance was poor. + Use regular graph updates to update the pathfinding graphs instead. + - The \reflink{RVONavmesh} component has been deprecated. This is now handled automatically by enabling the \reflink{RVOSimulator.useNavmeshAsObstacle} option. + - GridGraph.nodes now has the type GridNodeBase[] instead of GridNode[]. The same is true for the layered grid graph. + In most cases everything should work as normal, however if you have been accessing GridNode specific methods or properties on a node you may need to cast the node to a GridNode (or LevelGridNode) to avoid compile errors. + - The LayeredGridGraph now only supports 15 layers by default. This is to reduce memory usage for the common case and to reduce code complexity. If this causes issues for your workflow, please let me know in the forum. + - Due to local avoidance now supporting non-planar worlds many internal coordinates have been changed from float2 to float3. + If you have been using the RVOController script then you do not need to do anything, however if you have used the internal IAgent interface + then you may have to change your code a bit to pass 3D coordiantes instead of 2D ones. + - GraphModifier events and some callbacks on the AstarPath object (e.g. OnGraphsUpdated) have changed semantics. Read below for more info. + - OnPostUpdate no longer runs during scan. + - Made GridGraph.neighbourGridOffsetsX and neighbourGridOffsetsZ to static readonly members since they data is constant. + - Graph updates for grid graphs no longer call the GraphUpdateObject.Apply method, instead the GraphUpdateObject.ApplyJob method is called. + If you have been inheriting from the GraphUpdateObject to make custom graph updates you may need to convert your code so that it works with Burst. + - Fixed hexagon connection costs were sqrt(3/2)≈1.22 times too large. + The connection costs are now approximately 1000*the distance between the nodes, as it should be. + For example if you have set the hexagon width to 1 then the cost to move between two adjacent hexagons is now 1000 instead of 1224 like it was before. + For almost all users this will not affect anything, however it may improve pathfinding performance a bit. + If you have been using penalties on your graph then you may have to divide them by 1.22 to get the same behavior. + Similarly if you have been using the ConstantPath to get a set of nodes you may have to divide the maxGScore parameter by 1.22. + - The AIPath and RichAI scripts no longer use the Update and FixedUpdate methods. + Instead a separate script (\reflink{BatchedEvents}) is used which allows all components of a specific type to be processed at once. + This is slightly faster and is also required for the \link Pathfinding.AIBase.rvoDensityBehavior AIBase.rvoDensityBehavior\endlink implementation. + If you have been overriding the Update or FixedUpdate methods you will need to change your code to instead override the OnUpdate method. + - Local avoidance has hard collisions enabled by default. If this is not what you want you can disable this in the RVOSimulator component settings. See below for more details. + - Changed \reflink{IAstarAI.endOfPath} to return the agent's current position if the agent has no path, and no destination is set (instead of (+inf,+inf,+inf)). +- Changes + - The AstarPath component will now initialize itself during early \a OnEnable instead of during early \a Awake. + This makes it possible to set custom settings on the AstarPath component, such as the thread count, from a script when the game starts. + Previously this was kinda tricky, since no user scripts would have been run before the AstarPath component initialization. + - Removed \reflink{AstarPath.prioritizeGraphs}. It was always a bit of a hack. Use \reflink{NNConstraint.graphMask} if you want to choose which graphs are searched. + - Removed \reflink{AstarPath.fullGetNearestSearch}. It is now always treated as true. + - Removed double buffering support from the local avoidance system. + Double buffering was causing issues with newer features, and it didn't give that much benefit anyway (and caused agents to be less responsive). + The system can already simulate up to 30000 agents at 60 fps, so the use cases were limited. + - Renamed NNConstraint.Default to \reflink{NNConstraint.Walkable} for clarity. + - Deprecated a lot of the older example scenes. They have been moved into the OldExamples folder. + - Renamed GraphNode.ContainsConnection to \reflink{GraphNode.ContainsOutgoingConnection} to distinguish incoming from outgoing connections. + - Deprecated GraphNode.AddConnection and GraphNode.RemoveConnection. Use the static methods \reflink{GraphNode.Connect} and \reflink{GraphNode.Disconnect} instead. + - Deprecated \reflink{GraphUpdateObject.trackChangedNodes}. \reflink{AstarPath.Snapshot} will be the replacement for this. + - Deprecated \reflink{GraphUpdateObject.RevertFromBackup}. \reflink{AstarPath.Snapshot} will be the replacement for this. + - Deprecated GridGraph.CalculateConnections(int,int). Use \reflink{GridGraph.RecalculateConnectionsInRegion} instead to batch multiple calls. + This method still works, but the overhead of calling it will be significantly higher than in earlier versions. + This new overhead comes from properly handling the new \reflink{GridGraph.maxStepUsesSlope} option, and job scheduling overhead. + If you are recalculating connections for a large number of nodes, you should use \reflink{GridGraph.RecalculateConnectionsInRegion} instead. + The RecalculateConnectionsInRegion method scales better to larger node counts than CalculateConnections ever did. + - Deprecated \reflink{XPath}. Use \reflink{ABPath} with an ending condition instead. + - Removed Jump Point Search support for grid graphs. It was quite limited, and very few people seemed to use it. But it contributed a significant amount of code. + - Removed GraphCollision.rayDirection. This option has never been exposed in the inspector and it's not that useful. + - Deprecated \reflink{NavmeshBase.nearestSearchOnlyXZ}. Use \reflink{NNConstraint.distanceMetric} instead. + - Deprecated \reflink{NNConstraint.distanceXZ}. Use \reflink{NNConstraint.distanceMetric} instead. + - Deprecated \reflink{NavGraph.GetNearestForce}. Use \reflink{NavGraph.GetNearest} instead, as it does the same thing now. + - Deprecated the AIPath/RichAI.repathRate field. It has been replaced by the \reflink{AIBase.autoRepath.period} field. + - Removed a lot of deprecated code. All of these have been deprecated for at least a few years. + - Removed previously deprecated members of the NavGraph class: matrix, inverseMatrix, SetMatrix, RelocateNodes(Matrix4x4,Matrix4x4). + - Removed previously deprecated method NavGraph.ScanGraph, use \reflink{NavGraph.Scan} instead. + - Removed previously deprecated property RecastGraph.forcedBounds. + - Removed previously deprecated method RecastGraph.ClosestPointOnNode (use node.ClosestPointOnNode instead). + - Removed previously deprecated method RecastGraph.ContainsPoint (use node.ContainsPoint instead). + - Removed previously deprecated method GridGraph.CheckConnection (use node.HasConnectionInDirection instead). + - Removed previously deprecated method GridGraph.GetNodesInArea (use \reflink{GridGraph.GetNodesInRegion} instead). + - Removed previously deprecated method overload GridGraph.CalculateConnections(int,int,GridNode). + - Removed previously deprecated method GridGraph.UpdateNodePositionCollision. + - Removed previously deprecated method GridGraph.UpdateSizeFromWidthDepth (use \reflink{GridGraph.SetDimensions} instead). + - Removed previously deprecated method GridGraph.GenerateMatrix (use \reflink{GridGraph.UpdateTransform} instead). + - Removed previously deprecated method GridGraph.GetNodeConnection (use \reflink{GridNodeBase.GetNeighbourAlongDirection} instead). + - Removed previously deprecated method GridGraph.HasNodeConnection (use \reflink{GridNodeBase.HasConnectionInDirection} instead). + - Removed previously deprecated method GridGraph.SetNodeConnection (use \reflink{GridNode.SetConnectionInternal} instead). + - Removed previously deprecated class LegacyAIPath, as it's been deprecated for about 6 years now. + - Removed previously deprecated class LegacyRichAI, as it's been deprecated for about 6 years now. + - Removed previously deprecated class LegacyRVOController, as it's been deprecated for about 6 years now. + - Removed previously deprecated method Seeker.GetNewPath (use \reflink{ABPath.Construct} instead). + - Removed previously deprecated overload of Seeker.StartMultiTargetPath (use \reflink{Seeker.StartPath} instead). + - Removed previously deprecated field AstarPath.astarData (use \reflink{AstarPath.data} instead). + - Removed previously deprecated field AstarPath.limitGraphUpdates (use \reflink{AstarPath.batchGraphUpdates} instead). + - Removed previously deprecated field AstarPath.maxGraphUpdateFreq (use \reflink{AstarPath.graphUpdateBatchingInterval} instead). + - Removed previously deprecated property AstarPath.IsAnyGraphUpdatesQueued (use \reflink{AstarPath.IsAnyGraphUpdateQueued} instead). + - Removed previously deprecated callback AstarPath.OnGraphsWillBeUpdated. + - Removed previously deprecated callback AstarPath.OnGraphsWillBeUpdated2. + - Removed previously deprecated method AstarPath.QueueWorkItemFloodFill. + - Removed previously deprecated method AstarPath.EnsureValidFloodFill. + - Removed previously deprecated method AstarPath.FloodFill. + - Removed previously deprecated method AstarPath.FlushWorkItems(bool,bool) (use \reflink{AstarPath.FlushWorkItems()} instead). + - Removed previously deprecated method AstarPath.FlushThreadSafeCallbacks (use \reflink{AstarPath.FlushWorkItems()} instead). + - Removed previously deprecated method AstarPath.BlockUntilPathQueueBlocked (use \reflink{AstarPath.PausePathfinding} instead). + - Removed previously deprecated method AstarPath.WaitForPath (use \reflink{AstarPath.BlockUntilCalculated} instead). + - Removed previously deprecated method AstarPath.RegisterSafeUpdate (use \reflink{AstarPath.AddWorkItem} instead). + - Removed previously deprecated method GraphNode.RecalculateConnectionCosts. + - Removed previously deprecated component TileHandlerHelper, as this is built-in to navmesh/recast graphs now. + - \reflink{PointGraph.optimizeForSparseGraph} is now included in the free version too. This option makes scanning a large point graph much faster. + - Changed some private fields in \reflink{AIPath} to be protected instead, to make it easier to subclass it. + - Changed the signature of GraphNode.GetPortal to use out-parameters instead of output lists. The old signature will continue to work, but it is marked as deprecated. + - Moved ProceduralGraphMover and DynamicObstacle out from the ExampleScenes folder to the Utilities folder. + - Change: \reflink{LevelGridNode.HasAnyGridConnections} is now a property instead of a method. + - Renamed GridGraph.maxClimb to the more descriptive name \reflink{Pathfinding.GridGraph.maxStepHeight}. +- Fixes + - Fixed the AIPath.velocity and RichAI.velocity properties being very jittery (and often completely incorrect) when a rigidbody was attached to the character. + - Fixed linecasts on grid graphs would not add the start node to the \a trace parameter if the end node was the same as the start node. + - Fixed enabling \reflink{AIPath.constrainInsideGraph} could cause the agent to be teleported to the world origin if it was too far away from any walkable nodes. + - Fixed the AILerp component would recalculate the path every frame if the agent's position, the destination, and the destination of the last calculated path were all identical. + - Fixed the \reflink{NavMeshGraph} would not work for some pretty bad meshes. + - Fixed some deprecation warnings in newer versions of Unity. + - Added \reflink{OffMeshLinks.GetNearest}. + - The navmesh graph will now warn if the input mesh contains duplicate triangle edges, instead of crashing. + - Fixed \reflink{AstarPath.FlushGraphUpdates} and \reflink{FlushWorkItems} would not flush items if some were in progress, but none were queued. + - Improved error handling for \reflink{RecastNavmeshModifier} when it is moved during runtime. + - Fixed paths contained some internal fields which made it impossible to write custom path types. + - Fixed some linecast edge cases on grid graphs in which the \reflink{GraphHitInfo}'s fields were not set correctly. + - Fixed the recast graph's 'Snap bounds to scene' button could misalign the graph if the graph was rotated. + - Fixed rotated \reflink{RecastGraph}s could in some cases end up with gaps between tiles because some meshes that should have been included in the tile were missed. + - The \reflink{RVOController} will now disable itself if the global \reflink{RVOSimulator} is disabled. + - Fixed GridGraph and LayerGridGraph not always using \reflink{GridGraph.newGridNodeDelegate} when creating nodes. + - Fixed editing \reflink{NavmeshCut} components in a scene without an AstarPath component would throw an exception. + - Fixed "Some meshes were statically batched..." error message being logged multiple times when scanning a recast graph. Now it is limited to at most once per scan. + - Fixed RVOExampleAgent could throw an exception if it tried to follow a path with length 1. + - Fixed exception that could be thrown when using local avoidance and the navmesh had degenerate edges (could happen when using the \reflink{NavmeshAdd} component). + - Fixed graph updates on grid graphs sometimes updated a slightly larger number of nodes than strictly necessary. + - Fixed the \reflink{FunnelModifier} could simplify the path incorrectly on rotated recast graphs. + - Fixed setting \reflink{RichAI.rotation} while the agent was stationary would rotate the agent, but it would immediately rotate back to the previous heading. + - Fixed an exception could be thrown when using the \reflink{NavmeshClamp} script with multiple graphs. + - Fixed the \reflink{RecastNavmeshModifier} component would sometimes still affect objects even if it was disabled. + - Fixed \reflink{ProceduralGraphMover} not working with layered grid graphs. + - Fixed the graphs would be rendered even when the scene view was in prefab isolation mode and you were viewing an unrelated prefab. + - Improved error messages when trying to include a non-readable mesh in a recast graph scan. + - Cleaned up some large audio files in the example scenes to reduce the package install size quite significantly. + - Removed some `.blend1` (blender backup files) that had been accidentally included in the package. + - Fixed some visual scaling bugs in the collision visualization code for the grid graph. + - Worked around a null reference exception in Unity's ClearPreviewCache that could appear randomly from time to time. + - Fixed RichAI always teleporting to the closest point on the navmesh when the script is enabled even if \reflink{AIBase.canMove} is false. + - Fixed a crash when running on iOS. + - Fixed AIPath/RichAI throwing exceptions when an RVOController on the same object was disabled. + - Fixed paths could sometimes be cancelled without a reason if 'draw gizmos' was disabled on the Seeker component. + - Fixed implementations of Object.Equals for Int3, Int2, IntRect, Connection and SimpleMovementPlane would throw an exception if compared to an object of a different type. + - Fixed an exception could be thrown when resizing a grid graph to make it smaller. + - Fixed RVOController.layer showing up as a layer mask instead of a single layer field. + - Fixed a null reference exception which could happen in rare cases when using one-way links on point graphs. + - Fixed graph updates not always picking up physics changes done during the same frame as the update if "Auto Sync Transforms" has been disabled in the Unity physics settings. + - Fixed changing the scene and scanning a graph in the same frame may cause the graph to not pick up the latest scene changes if Physics.autoSyncTransforms is disabled. + - Fixed null reference exception when a navmesh/recast graph is marked as not being affected by navmesh cuts. + - Fixed collider faces with the exact same y coordinate as the base of a grid graph would sometimes not get detected. + - Fixed AstarPath.OnGraphsUpdated not being called after NavmeshCut updates. + - Fixed GraphModifier.OnGraphsPreUpdate could be called multiple times per update when using navmesh cutting. + - Fixed GraphModifier.OnGraphsPreUpdate could be called in the middle of a graph scan. Now graph scans will only send PreScan, PostScan and LatePostScan events. + - Fixed weird undo/redo behaviour when editing graph settings. Previously some undo events would not be saved and sometimes you would have to press ctrl+z twice in order to undo a change. + +## 4.2.19 (2023-11-14) +- The documentation now has a dropdown to allow you to choose between different versions of the package, and a notification if you are viewing a previous version. + \shadowimage{changelog/documentation_version_dropdown.png} +- Added a new tutorial: \ref traversal_provider. +- You can now set a custom traversal provider on the Seeker component, and it will be applied to all path requests. See \reflink{Seeker.traversalProvider}. +- Changed \link Pathfinding.Seeker.IsDone Seeker.IsDone\endlink to return true from within the OnPathComplete callback. + Previously it would always return false, which was confusing since the callback explicitly indicates that the path has been calculated. +- Made the grid graph's collision visualization in the inspector green instead of blue. + This is done to make the connection to colliders more clear. The color is also easier to see against the grey background. +- The \reflink{RaycastModifier} now defaults to graph raycasting in the pro version of the package. +- The transform tool for the GameObject with the AstarPath component is now hidden to reduce clutter, as each graph has its own transform tool instead. +- \reflink{AutoRepathPolicy} will now automatically randomly spread out path recalculations a bit to avoid many agents recalculating their paths at the same time. +- Added \reflink{GraphMask.FromGraphIndex}. +- Added an option for a custom edge filter in \reflink{GraphUtilities.GetContours}. +- Added \reflink{GridGraph.GetNeighbourDirections}. +- Added \reflink{NavGraph.isScanned}. +- Added \reflink{NavGraph.IsInsideBounds}. +- Added \reflink{GraphNode.ContainsPoint} and \reflink{GraphNode.ContainsPointInGraphSpace}. +- Added \reflink{GridGraph.AlignToTilemap}. +- Added \reflink{IAstarAI.endOfPath}. +- Added \reflink{GridNodeBase.HasConnectionsToAllAxisAlignedNeighbours}. +- Added \reflink{GraphUpdateScene.GetGraphUpdate}. +- Added \reflink{ABPath.cost} for accessing the total cost of a calculated path. +- Added a way to align a grid graph to a tilemap. Previously this has been quite tricky, especially for isometric and hexagonal tilemap layouts. + \shadowimage{gridgraph/align_to_tilemap.png} +- Added ASTAR_LARGER_GRIDS which can be set under the Optimizations tab to enable grid graphs larger than 1024x1024. +- Changed how \reflink{GridGraph.aspectRatio} works for isometric and hexagonal grid graphs. Now it works more intuitively to just make the nodes N times wider. + Before it only behaved properly for rectangular grid graphs. +- Deprecated NNConstraint.Default. Use the equivalent \reflink{NNConstraint.Walkable} property instead, as this name is more descriptive. +- Deprecated \reflink{AIPath.OnTargetReached}, \reflink{RichAI.OnTargetReached}, \reflink{AILerp.OnTargetReached}. + You can use \reflink{IAstarAI.reachedDestination} or \reflink{IAstarAI.reachedEndOfPath} instead. + The OnTargetReached method will continue to work as before, but using it is discouraged. +- Deprecated the AdvancedSmooth modifier. It never worked particularly well, and given that no one has asked a single support question about it the last 5 years or so, I don't think a lot of people use it. +- Fixed \link Pathfinding.IAstarAI.SetPath ai.SetPath\endlink did not accept paths if you called it from the OnPathComplete callback for the path. +- Fixed the first tag color in the inspector would sometimes be initialized to completely transparent. +- Fixed \reflink{NavmeshCut}s with custom meshes could calculate an incorrect bounding box, which could lead to it not updating some tiles, even if it was touching them. +- Fixed \reflink{GraphUpdateScene.GetBounds} sometimes incorrectly returning an empty bounding box if the \a convex option was enabled. +- Fixed some cases where a recast graph would miss some connections between nodes in different tiles. Especially if the shared edge was long. +- Fixed Navmesh Cutting Update Interval would not be serialized, and therefore always reset to 0 when the game started. +- Fixed the grid graph's collision preview showing the side view when using 2D physics even though that's irrelevant for 2D. +- Fixed an exception could in rare circumstances be thrown when using \reflink{ABPath.calculatePartial}. +- Fixed editor-only data would sometimes not be loaded from graphs, leading to some settings in the graph inspectors to be lost (e.g. if the grid graph's collision preview was open or closed). +- Fixed \reflink{GraphNode.ContainsOutgoingConnection} could throw an exception if the node had no connections at all. +- Fixed calling \reflink{GraphUtilities.GetContours} with a grid graph that had one-way connections could cause an infinite loop. + Now an exception will be thrown if any one-way connections are found. This is reasonable because the contours of a grid graph are not really well-defined if any one-way connections exist. +- Removed previously deprecated methods on the Path class: GetState, Log, LogError and ReleaseSilent. They have all been deprecated for over 5 years. +- Removed previously deprecated methods on the AstarData class: GetGraphType(string), CreateGraph(string), AddGraph(string, and GetRaycastableGraphs. They have all been deprecated for over 5 years. + +## 4.2.18 (2022-11-08) +- Added a documentation page on the architecture of the package: \ref architecture. +- Added a tutorial on migrating from Unity's pathfinding to this package: \ref migrating-from-unity. +- Added a tutorial on how to deal with pathfinding in large worlds: \ref large-worlds. +- Nearest node queries on layered grid graphs are now significantly faster. +- Improved performance of linecasts on grid graphs by approximately a factor of 2. +- Improved accuracy of linecasts on grid graphs. In particular, many edge cases were previously undefined and were not consistently handled. + Now linecasts which only touch corners of obstacles are always allowed, and also linecasts which go right on the border between walkable and unwalkable nodes. +- Add \reflink{GridNodeBase.NormalizePoint} and \reflink{GridNodeBase.UnNormalizePoint}. +- Various documentation improvements across the whole package. +- The game view will now automatically repaint after a graph is scanned in the editor. Previously only the scene view was repainted. +- Grid graphs and layered grid graphs now have support for \reflink{NNConstraint.distanceXZ}. + This makes \reflink{AIPath.constrainInsideGraph} work a lot better for grid graphs which have slopes. +- Changed the color of gizmos drawn for the \reflink{NavmeshAdd} component from green to purple to avoid confusion with the \reflink{Seeker}s gizmos. +- If you have a persistent (DontDestroyOnLoad) AstarPath component while loading a new scene with an existing AstarPath component, the newly loaded AstarPath component + will disable itself instead of logging an error and leaving both components in inconsistent states. This will make it easier to keep a single AstarPath component + around for the whole duration of the game. +- A garbage collection will no longer be forced immediately after scanning graphs. Unity has decent support for incremental GC now, and it's best to rely on that instead. + This may improve performance when scanning graphs. +- Renamed AutoRepathPolicy.interval/maximumInterval to \reflink{AutoRepathPolicy.period}/maximumPeriod since that's a more descriptive name. +- Changed \reflink{AutoRepathPolicy} to make it usable for custom movement scripts without having to implement the \reflink{IAstarAI} interface. +- Increased artificial limit for the maximum length of paths from 2048 to 16384. +- \reflink{GridNodeBase.RemoveConnection} now also works for grid connection, not just custom connections. +- \reflink{GridNodeBase.AddConnection} will now remove an existing grid connection (if it exists) to the same node, before adding the new custom connection. + Before, you could get two connections between the nodes instead of one, like you'd expect. +- Using \reflink{IAstarAI.SetPath} will now set the destination of the movement script to the end point of the path by default. + If you pass a \reflink{RandomPath}, \reflink{FleePath} or \reflink{MultiTargetPath}, the destination will be set once the path has been calculated since that's when it is first known. + This makes properties like \reflink{IAstarAI.reachedDestination} and \reflink{IAstarAI.remainingDistance} work out of the box for those path types. +- Created a property drawer for the \reflink{GraphMask} struct. So now you can include GraphMask fields in your own scripts and they will show up nicely in the inspector. +- Improved error checking in \reflink{FloodPath} for if the start node was destroyed before the path calculation started. +- Fixed saving or loading graphs could deadlock on WebGL since the zip library tired to use threads, which doesn't work on WebGL. +- Fixed linecasts on grid graphs would not always return a hit if the start point or end point was outside the graph. +- Fixed ExitGUIException could show up in the console sometimes when editing component properties. +- Fixed floating point errors causing the \reflink{FunnelModifier} to simplify the path incorrectly in rare cases. +- Fixed not being able to check for updates in Unity 2022.1+ because https must now be used for all web requests. +- Fixed inheriting from scripts in this package (e.g. AIPath) and adding new fields, would not make those fields show up in the inspector unless you had a custom editor script too. + Now all fields the editor scripts cannot handle will show up automatically. +- Deprecated \reflink{AstarPath.prioritizeGraphs}. +- Deprecated some older linecast overloads for grid graphs. +- Deprecated LevelGridNode.GetConnection in favor of \reflink{LevelGridNode.HasConnectionInDirection}. + +## 4.2.17 (2021-11-06) +- Fixed RVO example scenes not working properly (regression introduced in 4.2.16). + +## 4.2.16 (2021-10-24) +- Breaking changes + - Changed when the AIPath movement script stops when \reflink{AIPath.whenCloseToDestination} is set to Stop. + See below for more details. The new behavior is usually what you want. + If you really want the old behavior then set \code ai.whenCloseToDestination = CloseToDestinationMode.ContinueToExactDestination\endcode + and in a separate script run \code ai.isStopped = ai.reachedEndOfPath\endcode every frame. + - The AIPath script will now clear the path it is following if a path calculation fails. + See below for more details. + - Changed the order of execution for the AstarPath script to -10000. + Previously the order of execution was left at the Unity default value which is very unpredictable. + This may cause issues for you if you relied on Awake to be executed before the graphs were loaded and scanned. + If you need full control over when the graphs are scanned it is recommended that you disable \reflink{scanOnStartup} and instead call \reflink{AstarPath.Scan} manually when you want to scan the graphs. +- Improvements + - Added some helper visualizations for the collision settings for the grid graph. + \shadowimage{changelog/grid_graph_collision_visualization.png} + - Improved the way pages are grouped in the documentation to hopefully make it more intuitive. + - Added \link Pathfinding.GridGraph.SetGridShape GridGraph.SetGridShape\endlink. + - Added a new dynamic path recalculation mode to the movement scripts. + This mode is smarter about when it recalculates paths. It will recalculate the path often when the destination changes a lot + and much less frequently if the destination doesn't change much or if it is very far away. + This can help improve both the responsiveness of your agents and the performance if you have many agents that do frequent path recalculations. + Any existing agents will still use the old method of recalculating the path every N seconds, but you can change this in the inspector. + New agents will by default use the new Dynamic mode. + - Improved performance of the AIPath and RichAI inspectors a bit. + - Improved the accuracy and performance of the RaycastModifier when thick raycasting is enabled. + - The \link Pathfinding.ProceduralGridMover ProceduralGridMover\endlink script now has an option for specifying which graph to update. + - Added a filter parameter to graph linecast methods. This can be used to mark additional nodes as not being traversable by the linecast. + See GridGraph.Linecast. + - The \link Pathfinding.RaycastModifier RaycastModifier\endlink now respects which tags are traversable and any ITraversalProvider set on the path. + - Added a setter for the rotation property of all movement scripts (\reflink{IAstarAI.rotation}). + - Exposed \reflink{AILerp.velocity}. + - Added a get/set property \reflink{GridGraph.is2D} which is equivalent to the inspector's "2D" toggle. + - Added \reflink{GraphUpdateObject.stage} which contains info about if a graph update has been applied or not. + - Improved the DynamicObstacle to handle cases when graph updates take a very long time in a better way. + - Improved the \reflink{NavmeshAdd} component inspector. + - Added a \reflink{NavmeshClipper.graphMask} field to the \reflink{NavmeshCut} and \reflink{NavmeshAdd} components. +- Changes + - The AIPath/RichAI.canSearch field has been replaced by the \reflink{AIBase.autoRepath.mode} field. + For backwards compatibility setting canSearch to false will set the mode to Never and setting it to true will set the mode to EveryNSeconds. + - The AIPath/RichAI.repathRate field has been replaced by the \reflink{AIBase.autoRepath.period} field. + For backwards compatibility you can both read and write to the old name and it will work as before. + Note that this field is not used for the new Dynamic path recalculation mode. + - The AIPath script will now clear the path it is following if a path calculation fails. + Previously it would continue following its previous path. This could lead to problems if the world had changed so that there was no longer a valid path to the target. + The agent could then in some situations just continue trying to walk through obstacles instead of stopping. + In pretty much all cases this change in behavior is what you want and will not cause any problems when upgrading. + - Changed when the AIPath movement script stops when \reflink{AIPath.whenCloseToDestination} is set to Stop. + Consider the case when the destination was slightly outside the navmesh and the path instead goes to the closest point it can reach. + Previously it would stop up to \reflink{AIPath.endReachedDistance} units from the end of the path. + This could lead to unexpected behavior since it may be the case that by just moving a bit closer to the end of the path it would + end up within \reflink{AIPath.endReachedDistance} units from the destination. + Essentially \reflink{AIPath.reachedDestination} would remain false even though the agent could actually reach the destination by just moving a bit further. + So this behavior has been changed so that it now only stops when it is within \reflink{AIPath.endReachedDistance} units from the destination, not the end of the path. + - Made GridGraph.GetRectFromBounds public. + - Right clicking fields in the inspector no longer shows the "Show in online documentation" context menu, instead the normal unity context menu is shown. + The previous menu prevented things like resetting individual fields to their prefab values and similar things. + - Changed some 'internal' access modifiers to 'public' or 'protected' on the Path class. This makes it easier to create custom path types. +- Fixes + - Fixed a bug which could very rarely cause some node connections between tiles in a recast graph to be missed, causing various subtle movement issues. + - Fixed \reflink{AIPath.reachedEndOfPath} not always being reset to false when the component was disabled and then enabled again. + - \reflink{GraphNode.ClosestPointOnNode} is now available for all node types instead of only being implemented for \reflink{GridNode} and \reflink{MeshNode}. + - Fixed a case where the Seeker -> Start End Modifier -> Snapping to NodeConnection mode would not respect tags or \reflink{ITraversalProvider}s. + This could result in the path partially entering a node which should not be traversable by it. + - Fixed a case where using the \reflink{ABPath.calculatePartial} option together with Seeker->Start End Modifier->Snapping=ClosestOnNode would make the path seemingly ignore which tags were traversable in some cases. + - Fixed clicking Apply in the Optimizations tab could log error messages about some build platforms not existing. + - Fixed exception when using \reflink{MecanimBridge} with \reflink{RichAI}. + - Fixed \reflink{AIPath.reachedDestination} would throw an exception if you tried to access it when the agent had no path. + - Fixed calling `AstarData.ClearGraphs` and then immediately calling `AstarData.AddGraph` would result in an exception. + - Fixed \reflink{UpdateGraphsNoBlock(GraphUpdateObject,GraphNode,GraphNode,bool)} could return incorrect results if there were already some pending graph updates. + - Fixed grid graph would see 2D triggers as obstacles. Now all 2D colliders marked as triggers will be ignored by the grid graph. + - Fixed a memory leak that could happen after switching scenes. Thanks nGenius for finding it. + - Fixed RichAI always teleporting to the closest point on the navmesh when the script is enabled even if \reflink{RichAI.canMove} is false. + - Fixed GraphUpdateScene not using the outline from an attached PolygonCollider2D properly. + - Fixed some cases where graph updates with 2D colliders would not use the most up to date physics engine data. + All graph updates are now preceeded by a call to Physics2D.SyncTransforms (in addition to Physics.SyncTransform which was already being called). + - The RaycastModifier now respects graphMasks set on paths. Thanks brettkercher for reporting the bug. + - Fixed linecasts on navmesh/recast graphs could fail when the start of the line was close to a steep slope. + - Fixed linecasts on navmesh/recast graphs would incorrectly return no obstructions if for example used between two floors of a building that were not connected. + - Fixed RichAI trying to traverse an off mesh link twice if the path's endpoint was also the endpoint of an off-mesh link. + - Fixed a missing script in the turnbased example scene causing warnings when opening that scene. + - Fixed warnings would be logged when first importing the package on recent version of Unity due to a change in how Unity imports fbx files. + - Make DynamicObstacle call Physics.SyncTransforms to ensure it has the most up to date data for the collider. + - Removed 'Upgrading serialized data ...' message as it was mostly just annoying. + - Fixed GameObject references (like the PointGraph's Root field) would not get serialized properly if the AstarPath component was stored in a prefab. + - Fixed some smaller memory memory leaks in the unity editor. + - Fixed a bug which could cause a SynchronizationLockException to be thrown in some cases when the game was quit in a non-graceful way (e.g. when Unity exits play mode after recompiling scripts). + +## 4.2.15 (2020-03-30) +- Added \link Pathfinding.IAstarAI.GetRemainingPath ai.GetRemainingPath\endlink. +- Fixed importing the package using the unity package manager would cause the A* inspector to not be able to load since it couldn't find the editor resources folder. +- Fixed the package would report the wrong version number. This could cause the "New Update Available" window to show up unnecessarily. + +## 4.2.14 (2020-03-23) +- Fixed DynamicObstacle throwing an exception when scanning sometimes. This bug was introduced in 4.2.13. + +## 4.2.13 (2020-03-21) +- Fixed paths could sometimes be cancelled without a reason if 'draw gizmos' was disabled on the Seeker component. Thanks Eran for reporting the bug. +- Fixed DynamicObstacle logging an error about not having a collider attached even outside of play mode. + +## 4.2.12 (2020-02-20) +- Fixed "Not allowed to access vertices on mesh" error which some users are seeing after upgrading to Unity 2019.3. + +## 4.2.11 (2019-11-28) +- Fixed animations for the agent character were missing in the example scenes in some newer versions of Unity. This could cause exceptions to be thrown in some example scenes. +- Removed some stray uses of the old and deprecated GUIText component. This could cause descriptions for some example scenes not to show up in newer versions of Unity. + +## 4.2.10 (2019-11-19) +- Upgrade notes + - This release is supported in Unity 2017.4 LTS and later. Support for earlier versions has been dropped (however it probably still works in all 2017.x releases). + - The Unity WebPlayer target is no longer supported. This target was deprecated in Unity 5.4. +- Known bugs + - In some versions of Unity the spider bot in the example scenes may be missing its animations. This is due to a bug in how Unity upgrades scenes and is unfortunately tricky to fix for all Unity versions simultaneously. This does not affect any other part of the package. +- Fixed a crash when scanning a graph on the WebGL platform and exception support is disabled. + +## 4.2.9 (2019-11-15) +- Upgrade notes + - This release is supported in Unity 2017.4 LTS and later. Support for earlier versions has been dropped (however it probably still works in all 2017.x releases). + - The Unity WebPlayer target is no longer supported. This target was deprecated in Unity 5.4. + - Added a visualization for which dimension of the hexagons that is being edited when using a hexagonal grid graph. + \shadowimage{gridgraph/hexagon_dimension.png} +- RichAI's Funnel Simplification now does a straight line check as the first thing it does. + This can help improve both performance and the quality of the path. +- Added a small helper window to the scene view when the A* Inspector is open. + I think this should work well with the dark Unity theme as well, but please start a thread in the support forum if something looks off. + \shadowimage{changelog/scene_view_helper.png} +- Using managed code stripping even up to the High level is now supported out of the box. +- If an RVOController was attached to a GameObject with an AIPath/RichAI component during runtime after the movement script had been initialized then the movement script would previously possibly not find it. + This is fixed now and the RVOController notifies the AIPath/RichAI script that it has been attached. +- The \link Pathfinding.IAstarAI.SetPath SetPath\endlink method on all built-in movement scripts can now be passed a null parameter. This will clear the current path of the agent. + In earlier versions this caused an exception to be thrown. +- Fixed NavmeshBase.Linecast (used by RecastGraph and Navmesh linecast methods) would not fill the \a trace out parameter with the starting node in case the start point of the linecast was identical to the end point. +- AIPath and RichAI now clear their paths when they are disabled. Not clearing the path has caused some issues when using object pooling and also some other unexpected behavior. + If you want to just temporarily disable the movement then use the \link Pathfinding.IAstarAI.canMove canMove\endlink or \link Pathfinding.IAstarAI.isStopped isStopped\endlink properties. +- Fixed RichAI.remainingDistance could refer to the previous path for one frame after a new path had been calculated. +- Fixed AIPath and RichAI scripts sometimes starting with a height of 0.01 when creating a new component in the unity inspector. + Now they start with a more reasonable height of 2.0. +- The AIBase.usingGravity setter is now protected instead of private which helps when overriding some methods. +- The "Show Surface" mode on recast and navmesh graphs is now enabled by default when creating a new graph. +- The system no longer destroys itself in OnApplicationQuit and is instead always destroyed during OnDestroy. Using OnApplicationQuit could cause trouble when the quitting process was cancelled (e.g. using Application.wantsToQuit). +- Fixed exceptions could be thrown in the editor if the project contains some assemblies that can for some reason not be read. Thanks joshcamas for reporting this. +- Changed the automatic graph coloring limits code to ignore nodes that are unwalkable. This improves the contrast when some unwalkable nodes, that are not visible anyway, have very high penalties (or whatever other value you are visualizing in the scene view). +- Fixed missing null checks in TriangleMeshNode.GetPortal and TriangleMeshNode.SharedEdge. +- The RichAI inspector will now show a helpful warning if one tries to use it in a scene that does not contain a navmesh or recast graph. +- #Pathfinding.GraphUtilities.GetContours for grid graphs now simplifies the contour more consistently. Previously there could be one or two additional points where the algorithm started to traverse the contour. + +## 4.2.8 (2019-04-29) +- Made it possible for nearest node queries on point graphs to find the closest connection instead of just the closest node. + This will make it easier to use graphs when you have many long connections. + See #Pathfinding.PointGraph.nearestNodeDistanceMode. +- Improved the Seeker->StartEndModifier's Connection snapping mode. Now it will behave better if the path only moves along a single connection in the graph. +- Fixed a crash when deploying for Nintendo Switch due to a Unity bug when setting thread names. Thanks ToastyStoemp for reporting this. +- Fixed some compiler warnings in the ObjImporter class that would show up on some platforms. +- Fixed GridGraph.CalculateConnectionsForCellAndNeighbours would throw an exception when called with the coordinates for a node on the border of the grid. Thanks davidpare for reporting this. + +## 4.2.7 (2019-04-05) +- Significantly improved graph rendering performance for recast graphs when using a very large number of small tiles. +- Fixed GridGraph.CountNodes throwing an exception when the graph is not scanned. Now it will return 0. + +## 4.2.6 (2019-03-23) +- Fixed AIPath.reachedDestination and RichAI.reachedDestination only worked when the y coordinate of the agent was close to zero... which it of course was in all my tests. + Sorry about this silly bug and the headache it may have caused. +- Fixed loading a serialized navmesh graph when the source mesh no longer existed would cause the graph to be offset if a navmesh cut was later used to cut it. + +## 4.2.5 (2019-02-14) +- Added a new documentation page for how to create and configure graphs during runtime. \ref runtime-graphs. +- Added a new documentation page about editing point graph connections manually. \ref editing-graphs. +- Fixed exceptions could be thrown if the project contains some assemblies that can for some reason not be read. +- Fixed the visualization for unwalkable nodes (red cubes) sometimes disappearing in newer versions of Unity (2018.2+ I think) due to a change in how Time.renderedFrameCount works. + Thanks Kevin Jenkins for reporting this. +- Fixed applying optimizations (under the Optimizations tab) could cause several error messages to be logged about unsupported platforms in Unity 2018.3 or newer. Thanks NoxMortem for reporting the issue. +- Fixed AIPath throwing an exception if it was given a valid path that contained no nodes at all. +- Made Path.GetTagPenalty public instead of internal. +- Added #Pathfinding.ABPath.FakePath. +- Worked around a null reference exception bug when using IL2CPP and deploying for iPhone. + This is caused by a bug in the IL2CPP compiler. +- Fixed custom graph types could not be used if they were in another assembly. Thanks juskelis for reporting this and founderio for finding a fix. + +## 4.2.4 (2018-12-03) +- Added an option for which dimension of the hexagon to adjust in the grid graph editor when using the hexagonal mode. + This significantly helps with making a hexagonal graph line up with your other game elements as previously you might have had to manually calculate some complicated conversion factors in order to do this. +- Fixed loading navmesh graphs from a file could be extremely slow if the graph had been saved with the source mesh field set to a mesh with an empty name and your project had a lot of things in its Resources folder. +- Fixed a massive performance regression when using RVO together with IL2CPP and .net 4.6 due to changes in how the .net framework handles locking internally. +- Made GraphNode.Destroy public again (it was made internal in 4.2) because without that, it is not possible to make custom graph types. +- Made Path.PipelineState, Path.duration and Path.pathID public again (they were made internal in 4.2) because those properties are actually useful even for non-internal use. + This also fixes some incompatibility issues with the Node Canvas integration package. Thanks jsaracev and Grofit for reporting this. + +## 4.2.3 (2018-11-07) +- Fixed some compiler warnings in the free version on newer versions of Unity. +- Fixed a bug which caused point graphs to interpret the nearest node distance limit as being 1/1000th the actual value in the free version of the package and in the pro version when not using the 'optimize for sparse graph' option. + This bug caused the point graph example scene to not work in the free version of the package. + +## 4.2.2 (2018-10-25) +- Fixed upgrading from an earlier 4.x version to 4.2 could cause compiler errors in some newer versions of Unity because the UnityPackage doesn't import the new directory structure correctly. + +## 4.2.1 (2018-10-23) +- Fixed a bug which caused scanning navmesh graphs to throw an exception when using the free version of the package. Thanks Hunted for reporting the bug. + +## 4.2 (2018-10-17) +- 4.1.17 through 4.1.26 were beta versions, all their changelogs have been merged into this one. +- Upgrade notes + - This release contains some breaking changes (primarily the pivot point change for characters that you can read about below). + This will affect very few users, but I still recommend that you take a backup of your project before upgrading. + - Since 4.1.23 the base of the character when using AIPath/RichAI is always at the pivot point of the Transform. + This reduces code complexity and improves performance. Most users would already have had configured their characters that way, but in + rare cases you may have to adjust your characters a bit (mostly just move them up or down a bit). + - When changing some low level fields that can affect the connectivity of the graph such as #Pathfinding.MeshNode.connections you must now call #Pathfinding.GraphNode.SetConnectivityDirty(). + The documentation for all the relevant fields mention this. Currently it only applies to the connection fields on the MeshNode, PointNode and GridNodeBase classes. + All high level methods that modify the connectivity do this automatically. Most likely you do not have to change your code. + - When updating the graph using a work item, it is no longer necessary to call the QueueFloodFill or FloodFill methods. Due to the hierarchical graph update mentioned below, this is all handled transparently behind the scenes. + You should remove any calls to those deprecated methods as they will (for backwards compatibility reasons) force a full recalculation of the connected components which is much slower than what is likely necessary. +- Improvements + - Converted all comments to XML comments. + This will drastically improve intellisense for most users as Doxygen style comments are not supported by that many editors. + As the whole codebase has been converted using a script, there is a potential for errors in the translation. + Please let me know if you find anything which looks odd or where the intellisense info could be improved. + - Improved recast graph scanning speed by up to 60% in the unity editor on Mono2x. + Surisingly this optimization doesn't seem to matter much for other compiler backends. + This may cause minor changes to your navmesh. If this change causes bad navmeshes to be generated or if it reduces performance, please start a thread in the forum. + - The RVOController and AIPath/RichAI scripts now take the scale of the object into account when using their respective height and radius fields. + If you have an existing character with a non-unit scale the height and radius fields will automatically be updated to compensate for this scale. + However if you are scaling characters during runtime, this may change their behaviour. + - Added a new \link Pathfinding.GraphMask GraphMask\endlink struct to represent a set of graphs. + Previously graph masks have been represented using pure integers (e.g. in #Pathfinding.Seeker.graphMask and #Pathfinding.NNConstraint.graphMask). + The new struct contains some nice helper methods like #Pathfinding.GraphMask.FromGraphName which has been requested by many users. + - Added a new documentation page describing the inspector: \ref inspector. + - Added a new documentation page with an overview of the different included movement scripts: \ref movementscripts. + - Added a new property on all movement scripts called \link Pathfinding.IAstarAI.reachedDestination reachedDestination\endlink. + The existing \link Pathfinding.IAstarAI.reachedEndOfPath reachedEndOfPath\endlink property has some quirks, which are very reasonable when considering how everything works, + but are not very intuitive for new users and it can easily lead to quite brittle code. This new property will work as expected for most use cases. + - Added a height and radius for the AIPath and RichAI movement scripts. + This in turn deprecates the old Pathfinding.AIBase.centerOffset field as it is now implicitly height/2. + If an RVOController is attached to the same GameObject, its height and radius will be driven by the movement script's values. + The script will try to autodetect reasonable height and radius values from other attached components upon upgrading. + - Improved look of all movement script inspectors by grouping the fields into different sections. Also added some validation to prevent invalid field values. + - Added #Pathfinding.AstarData.GetNodes. + - Optimized graph rendering a bit. In particular if multiple scene views/in game cameras are used. + - The default graph rendering mode is now 'Solid Color' instead of 'Areas'. This new mode will render the graphs with a single color as the name implies. + The Area coloring mode has turned out to sometimes be confusing for new users and it is not useful that often anyway. + - Improved performance of small graph updates on large graphs by a very large amount. + Previously when making a small update to a large graph, updating the connected components of the graph using a flood fill has been the thing which took the longest time by far. + Using a new internal hierarchical graph it is now possible to update large graphs much faster. For example making a small update to a 1024*1024 grid graph is on the order of 30 times faster and is now perfectly reasonable to do in real time (slightly depending on how the graph looks and its settings). + The cost of the flood fill was previously offloaded to a separate thread, so it would not always be noticed in the Unity profiler, but it was there and could affect the performance of the game in strange ways. + The performance of scanning a graph or updating the whole graph remains roughly the same. + For more information about how this works, see #Pathfinding.HierarchicalGraph. + - Removed small allocation that was previously done for each calculated path on grid graphs. + - Pathfinding threads now show up in the Unity Profiler in the Timeline view when using a recent Unity version (2017.3 or higher). + - Scanning layered grid graphs is now approximately 2.2x as fast. + - Updating layered grid graphs is now approximately 1.5x as fast. + - Scanning and updating layered grid graphs now allocates a lot less memory. + - Added assembly definition (.asmdef) files to the package and restructured things a bit. + This will help cut down on the compilation times in your project. See https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html for more info. + - Improved performance of scanning and updating recast graphs, in particular with very large tiles. + - Improved performance of get nearest queries on point graphs when not using \link Pathfinding.PointGraph.optimizeForSparseGraph optimizeForSparseGraph\endlink. + - Reduced memory usage of local avoidance slightly. + - All navmesh/recast graphs now support navmesh cutting out of the box and the TileHandlerHelper component is no longer necessary. + If you have a TileHandlerHelper component in your scene you can safely delete it after upgrading. + - AIPath/RichAI now has an option for disabling rotation completely. This was possible before as well, but the options you had to set were not entirely intuitive. + The inspector has been restructured a bit to improve the ease of use. +- Changes + - Removed non-generic version of PathPool which has been deprecated since early 2016. + - Removed various methods in the AstarMath and Polygon classes which have been deprecated since early 2016. + - Removed a few other things that have been deprecated since early 2016. + - Removed AstarPath.ScanLoop which has been deprecated since 2015. + - Removed IntRect.Rotate and IntRect.Offset because they were not used by any part of the package. + - Removed the mostly internal method GraphCollision.Raycast because it was not used by any code in the package. + - Changed signature and behavior slightly of the mostly internal method \reflink{GraphCollision.CheckHeightAll}. + - Replaced LayerGridGraph.SampleCell with the new method LayerGridGraph.SampleHeights which does essentially the same thing, but in a more efficient way. + - The rotationIn2D option for the built-in movement scripts has been renamed to \reflink{AIBase.orientation} and is now an enum. +- Fixes + - Replaced usage of the WWW class with the newer UnityWebRequest in the update checker to avoid a deprecation compiler warning in newer versions of Unity. + - Fixed a bug in the A* inspector that could cause the inspector to log errors and look weird in Unity 2018.3b5 and up. Thanks Aisit for reporting the bug. + - Fixed a rare race condition that could cause exceptions in various parts of the code due to the StackPool class not being thread safe. Thanks nindim for reporting the issue. + - Fixed applying optimizations (under the Optimizations tab) could cause several error messages to be logged about unsupported platforms in Unity 2018.2 or newer and in 2017.4. + - Fixed trying to set a tag on a node to a value outside the valid range (0...32) could silently cause very weird behavior as other fields were affected by that. + - Fixed minor allocation when calling #AstarPath.GetNearest without an NNConstraint. + - Fixed a bug introduced in 4.1.22 which could cause a null reference exception to sometimes be thrown when visualizing graphs in the scene view. + - Fixed #Pathfinding.Path.IsDone could due to a race condition return true a tiny amount of time before the path was actually calculated. + - Fixed a bug introduced in 4.1.18 which caused various rotation settings to be hidden in the inspector for the RichAI component. + - Fixed removing a movement script from the same component as an RVOController during runtime would not cause the RVOController to stop using the (now destroyed) movement script's position for local avoidance calculations. + - Fixed the ObjectPlacer example script which is used in some example scenes would not always update the graph properly after destroying objects. + In Unity object destruction is delayed until after the Update loop, but the script did not wait to update the graph until after the object had actually been destroyed. Thanks djzombie for reporting this. + - Fixed recalculating a part of a recast graph using a graph update object would not expand the bounding box with the character radius to make sure that all tiles that could be affected by something inside the box were updated. + - Worked around occational crashes when using RVO with the IL2CPP backend due to a bug in IL2CPP. + - Fixed scanning a recast graph could throw an exception if there was a RecastNavmeshModifier component attached to a GameObject with a MeshFilter that had a null/missing mesh. Thanks ccm for finding the bug. + - Fixed FloodPath throwing an exception when starting on a node that isn't connected to any other node. Thanks BulwarkStudios for finding the bug. + - Fixed applying optimizations (under the Optimizations tab) could cause several error messages to be logged about unsupported platforms in Unity 2018.1 or newer. Thanks NFMonster for reporting the issue. + - Rotation speed and acceleration are now decoupled for AIPath and RichAI. Previously the acceleration limited how quickly the agents could rotate due to how the math for centripetal acceleration works out. + This was originally added in 4.1.11, but due to a typo the change was disabled. Now it is properly working. + - Fixed GraphModifier.OnGraphsPostUpdate being called multiple times during a single update if multiple tiles were updated due to navmesh cutting. + This can significantly improve performance if you are using many off-mesh links or you are using the RVONavmesh component. + - Fixed an edge case bug which could cause get nearest node queries on a newly scanned point graph to return older destroyed nodes. + - Fixed work items throwing exceptions could cause some internal data to get into an invalid state. + - Fixed tree colliders would not be rasterized correctly when using the recast graph and the tree prefabs had been scaled. + - Fixed not finding graph types and graph editors from other assemblies than the one which contains the pathfinding system (important when using asmdef files). + - Fixed #AstarPath.FlushWorkItems always pausing the pathfinding threads even if no work items were queued. + - Fixed loading navmesh graphs (and potentially in rare cases: recast graphs) from the cache or from a file could result in bad graph data which confused the funnel modifier which would create very zig-zaggy paths. + If you have been experiencing this then just re-save your graphs/regenerate the cache after you have updated and then it should work as it should. Thanks Igor Aherne for reporting this. + +## 4.1.16 (2018-04-26) +- Fixed PointNode.ContainsConnection could throw an exception if the node didn't have any connections. +- Fixed AILerp's started out with a destination set to (0,0,0) instead of not having a destination set. + So if you did not set a destination for it, it would try to move to the world origin. + +## 4.1.15 (2018-04-06) +- Fixed RichAI.desiredVelocity always being zero. Thanks sukrit1234 for finding the bug. +- Added some video examples to \link Pathfinding.AIPath.pickNextWaypointDist AIPath.pickNextWaypointDist\endlink. +- Fixed a bug introduced in 4.1.14 which caused scanning recast graphs in the Unity editor to fail with an error sometimes. +- Fixed the position returned from querying the closest point on the graph to a point (AstarPath.GetNearest) on layered grid graphs would always be the node center, not the closest point on the node's surface. Thanks Kevin_Jenkins for reporting this. + This caused among other things the ClosestOnNode option for the Seeker's StartEndModifier to be identical to the SnapToNode option. +- Fixed RVOController.velocity being zero when the game was paused (Time.timeScale = 0). + +## 4.1.14 (2018-03-06) +- Fixed Pathfinding.GridNode.ClosestPointOnNode being completely broken. Thanks Ivan for reporting this. + This was used internally in some cases when pathfinding on grid graphs. So this fixes a few cases of strange pathfinding results too. +- It is now possible to use pathfinding from editor scripts. See \ref editor-mode. + +## 4.1.13 (2018-03-06) +- Fixed LayerGridGraph.GetNode not performing out of bounds checks. +- Exposed a public method \link Pathfinding.PointGraph.ConnectNodes PointGraph.ConnectNodes\endlink which can be useful if you are creating a graph from scratch using e.g PointGraph.AddNode. +- Improved the \ref multiple-agent-types tutorial. +- Improved the \ref custom_movement_script tutorial, among other things it can now also be followed if you are creating a 2D game. + The movement script that you write has also been improved. +- Improved how the RichAI movement script keeps track of the node it is on. It should now be more stable in some cases, especially when the ground's y-coordinate lines up badly with the y-coordinate of the navmesh. +- Added an \link Pathfinding.AIPath.constrainInsideGraph option\endlink to AIPath for constraining the agent to be inside the traversable surface of the graph at all times. + I think it should work everywhere without any issues, but please post in the forum if anything seems to break. +- Fixed the proper fonts were not imported in the documentation html, so for many browsers it fell back to some other less pretty font. + +## 4.1.12 (2018-02-27) +- Fixed right clicking on array elements in the Unity inspector would bring up the 'Show in online documentation' context menu instead of the Unity built-in context menu (which is very useful). +- Navmesh assets used in the navmesh graph no longer have to be at the root of the Resources folder, they can be in any subfolder to the Resources folder. + +## 4.1.11 (2018-02-22) +- You can now set which graphs an agent should use directly on the Seeker component instead of having to do it through code. + \shadowimage{multiple_agents/seeker.png} +- Added tutorial for how to deal with agents of different sizes: \ref multiple-agent-types. +- Fixed scanning recast graphs could in rare cases throw an exception due to a multithreading race condition. Thanks emrys90 for reporting the bug. +- Fixed a regression in 4.0.6 which caused position based penalty to stop working for layered grid graphs. Thanks DougW for reporting the bug. +- Rotation speed and acceleration are now decoupled for AIPath and RichAI. Previously the acceleration limited how quickly the agents could rotate due to how the math for centripetal acceleration works out. +- Acceleration can now be set to a custom value on the AIPath class. It defaults to a 'Default' mode which calculates an acceleration such that the agent reaches its top speed in about 0.4 seconds. This is the same behaviour that was hardcoded in earlier versions. +- Fixed a bug in \link Pathfinding.GraphUtilities.GetContours GraphUtilities.GetContours\endlink for grid graphs when the nodes parameter was explicitly passed as non null that could cause some contours not to be generated. Thanks andrewBeers for reporting the bug. +- Improved documentation for \link Pathfinding.StartEndModifier.Exactness StartEndModifier.Exactness\endlink. + +## 4.1.10 (2018-01-21) +- 4.1.0 through 4.1.9 were beta versions, all their changelogs have been merged into this one. +- Upgrade notes + - Fixed the AIPath script with rotationIn2D would rotate so that the Z axis pointed in the -Z direction instead of as is common for Unity 2D objects: to point in the +Z direction. + - ALL classes are now inside the Pathfinding namespace to reduce potential naming collisions with other packages. + Make sure you have "using Pathfinding;" at the top of your scripts. + Previously most scripts have been inside the Pathfinding namespace, but not all of them. + The exception is the AstarPath script to avoid breaking too much existing code (and it has a very distinctive name so name collisions are not likely). + - Since the API for several movement scripts have been unified (see below), many members of the movement scripts have been deprecated. + Your code should continue to work exactly as before (except bugs of course, but if some other behaviour is broken, please start a thread in the forum) but you may get deprecation warnings. + In most cases the changes should be very easy to make as the visible changes mostly consist of renames. + - A method called \link Pathfinding.IAstarAI.SetPath SetPath\endlink has been added to all movement scripts. This replaces some hacks you could achieve by calling the OnPathComplete method on the movement scripts + from other scripts. If you have been doing that you should now call SetPath instead. + - Paths calculated with a heuristic scale greater than 1 (the default is 1) might be slightly less optimal compared to before. + See below for more information. + - The StartEndModifier's raycasting options are now only used if the 'Original' snapping option is used as that's the only one it makes sense for. + - The RaycastModifier has changed a bit, so your paths might look slightly different, however in all but very rare cases it should be at least as good as in previous versions. + - Linecast methods will now assign the #Pathfinding.GraphHitInfo.node field with the last node that was traversed in case no obstacle was hit, previously it was always null. + - Multithreading is now enabled by default (1 thread). This may affect you if you have been adding the AstarPath component during runtime using a script, though the change is most likely positive. + - The DynamicObstacle component will now properly update the graph when the object is deactivated since the object just disappeared and shouldn't block the graph anymore. + Previously it only did this if the object was destroyed, not if it was deactivated. + - If you have written a custom graph type you may have to change the access modifier on some methods. + For example the ScanInternal method has been changed from being public to being protected. + - Some internal methods on graphs have been hidden. They should never have been used by user code + but in case you have done that anyway you will have to access them using the IGraphInternals or IUpdatableGraph interface now. + - Removed some compatibility code for Seekers for when upgrading from version 3.6.7 and earlier (released about 2 years ago). + If you are upgrading from a version that old then the 'Valid Tags' field on the Seeker component may get reset to the default value. + If you did not use that field then you will not have to do anything. + - AIPath now rotates towards actual movement direction when RVO is used. +- Improvements + - Improved pathfinding performance by around 8% for grid graphs, possibly more for other graph types. + This involved removing a special case for when the pathfinding heuristic is not admissable (in short, when A* Inspector -> Settings -> Heuristic Scale was greater than 1). + Now paths calculated with the heuristic scale greater than 1 might be slightly less optimal compared to before. + If this is important I suggest you reduce the heuristic scale to compensate. + Note that as before: a heuristic scale of 1 is the default and if it is greater than 1 then the calculated paths may no longer be the shortest possible ones. + - Improved overall pathfinding performance by an additional 10-12% by heavily optimizing some core algorithms. + - Improved performance of querying for the closest node to a point when using the PointGraph and \link Pathfinding.PointGraph.optimizeForSparseGraph optimizeForSparseGraph\endlink. + The improvements are around 7%. + - Unified the API for the included movement scripts (AIPath, RichAI, AILerp) and added a large number of nice properties and functionality. + - The \link Pathfinding.IAstarAI IAstarAI\endlink interface can now be used with all movement scripts. + - To make it easier to migrate from Unity's navmesh system, this interface has been designed to be similar to Unity's NavmeshAgent API. + - The interface has several nice properties like: + \link Pathfinding.IAstarAI.remainingDistance remainingDistance\endlink, + \link Pathfinding.IAstarAI.reachedEndOfPath reachedEndOfPath\endlink, + \link Pathfinding.IAstarAI.pathPending pathPending\endlink, + \link Pathfinding.IAstarAI.steeringTarget steeringTarget\endlink, + \link Pathfinding.IAstarAI.isStopped isStopped\endlink, + \link Pathfinding.IAstarAI.destination destination\endlink, and many more. + - You no longer need to set the destination of an agent using a Transform object, instead you can simply set the \link Pathfinding.IAstarAI.destination destination\endlink property. + Note that when you upgrade, a new AIDestinationSetter component will be automatically created which has a 'target' field. So your existing code will continue to work. + - Improved behavior when AIPath/RichAI characters move down slopes. + Previously the way gravity was handled could sometimes lead to a 'bouncing' behavior unless the gravity was very high. Old behavior on the left, new behavior on the right. + \htmlonly \endhtmlonly + - Improved the grid graph inspector by adding preconfigured modes for different node shapes: square grids, isometric grids and hexagons. + This also reduces clutter in the inspector since irrelevant options can be hidden. + \shadowimage{changelog/grid_shape.png} + - For 2D grid graphs the inspector will now show a single rotation value instead of a full 3D rotation which makes it a lot easier to configure. + - Improved the performance of the \link Pathfinding.RaycastModifier RaycastModifier\endlink significantly. Common speedups on grid graphs range from 2x to 10x. + - The RaycastModifier now has a \link Pathfinding.RaycastModifier.Quality quality enum\endlink. The higher quality options use a new algorithm that is about the same performance (or slightly slower) compared to the RaycastModifier in previous versions + however it often manages to simplify the path a lot more. + The quality of the previous RaycastModifier with default settings corresponds to somewhere between the Low and Medium qualities. + - Improved support for HiDPI (retina) screens as well as improved visual coherency for some icons. + \shadowimage{changelog/retina_icons.png} + - Improved the 'eye' icon for when a graph's gizmos are disabled to make it easier to spot. + - Added \link Pathfinding.GridGraph.CalculateConnectionsForCellAndNeighbours GridGraph.CalculateConnectionsForCellAndNeighbours\endlink. + - AIPath now works with point graphs in 2D as well (assuming the 'rotate in 2D' checkbox is enabled). + - Improved the performance of the RVONavmesh component when used together with navmesh cutting, especially when many navmesh cuts are moving at the same time. + - A warning is now displayed in the editor if one tries to use both the AIDestinationSetter and Patrol components on an agent at the same time. + - Improved linecasts on recast/navmesh graphs. They are now more accurate (there were some edge cases that previously could cause it to fail) and faster. + Performance has been improved by by around 3x for longer linecasts and 1.4x for shorter ones. + - Linecast methods will now assign the #Pathfinding.GraphHitInfo.node field with the last node that was traversed in case no obstacle was hit. + - Linecast on graphs now set the hit point to the endpoint of the line if no obstacle was hit. Previously the endpoint would be set to Vector3.zero. Thanks borluse for suggesting this. + - Multithreading is now enabled by default (1 thread). + - The DynamicObstacle component now works with 2D colliders. + - Clicking on the graph name in the inspector will no longer focus the name text field. + To edit the graph name you will now have to click the Edit/Pen button to the right of the graph name. + Previously it was easy to focus the text field by mistake when you actually wanted to show the graph settings. + \shadowimage{changelog/edit_icon.png} + - Reduced memory usage of the PointGraph when using \link Pathfinding.PointGraph.optimizeForSparseGraph optimizeForSparseGraph\endlink. + - Improved the StartEndModifier inspector slightly. + - The Seeker inspector now has support for multi-editing. + - The AIPath and RichAI scripts now rotate to face the direction they are actually moving with when using local avoidance (RVO) + instead of always facing the direction they want to move with. At very low speeds they fall back to looking the direction they want to move with to avoid jitter. + - Improved the Seeker inspector. Unified the UI for setting tag penalties and determining if a tag should be traversable. + \shadowimage{changelog/seeker_tags.png} + - Reduced string allocations for error messages when paths fail. + - Added support for 2D physics to the #Pathfinding.RaycastModifier component. + - Improved performance of GraphUpdateObjects with updatePhysics=false on rotated navmesh/recast graphs. + - Improved the inspector for AILerp. + - RVO obstacles can now be visualized by enabling the 'Draw Obstacles' checkbox on the RVOSimulator component. + \shadowimage{changelog/rvo_navmesh_obstacle.png} + - Reduced allocations in the funnel modifier. + - Added a 'filter' parameter to \link Pathfinding.PathUtilities.BFS PathUtilities.BFS\endlink and \link Pathfinding.PathUtilities.GetReachableNodes PathUtilities.GetReachableNodes\endlink. + - Added a method called \link Pathfinding.IAstarAI.SetPath SetPath\endlink to all movement scripts. + - Added \link Pathfinding.GraphNode.Graph GraphNode.Graph\endlink. + - Added #Pathfinding.MeshNode.ContainsPoint(Vector3) in addition to the already existing MeshNode.ContainsPoint(Int3). + - Added #Pathfinding.MeshNode.ContainsPointInGraphSpace. + - Added #Pathfinding.TriangleMeshNode.GetVerticesInGraphSpace. + - Added Pathfinding.AstarData.FindGraph(predicate). + - Added Pathfinding.AstarData.FindGraphWhichInheritsFrom(type). + - Added a new class \link Pathfinding.GraphUtilities GraphUtilities\endlink which has some utilities for extracting contours of graphs. + - Added a new method Linecast(GridNodeBase,GridNodeBase) to the GridGraph class which is much faster than the normal Linecast methods. + - Added \link Pathfinding.GridGraph.GetNode(int,int) GridGraph.GetNode(int,int)\endlink. + - Added MeshNode.AddConnection(node,cost,edge) in addition to the already existing AddConnection(node,cost) method. + - Added a \reflink{Pathfinding.NavMeshGraph.recalculateNormals} setting to the navmesh graph for using the original mesh normals. This is useful for spherical/curved worlds. +- Documentation + - Added a documentation page on error messages: \ref error-messages. + - Added a tutorial on how to create a wandering AI: \ref wander. + - Added tutorial on bitmasks: \ref bitmasks. + - You can now right-click on most fields in the Unity Inspector to bring up a link to the online documentation. + \shadowimage{inspector_doc_links.png} + - Various other documentation improvements and fixes. +- Changes + - Height or collision testing for grid graphs now never hits triggers, regardless of the Unity Physics setting 'Queries Hit Triggers' + which has previously controlled this. + - Seeker.StartPath will no longer overwrite the path's graphMask unless it was explicitly passed as a parameter to the StartPath method. + - The built in movement scripts no longer uses a coroutine for scheduling path recalculations. + This shouldn't have any impact for you unless you have been modifying those scripts. + - Replaced the MineBotAI script that has been used in the tutorials with MineBotAnimation. + The new script does not inherit from AIPath so in the example scenes there is now one AIPath component and one MineBotAnimation script on each unit. + - Removed prompt to make the package support UnityScript which would show up the first time you used the package in a new project. + Few people use UnityScript nowadays so that prompt was mostly annoying. UnityScript support can still be enabled.. + - If deserialization fails, the graph data will no longer be stored in a backup byte array to be able to be recovered later. + This was not very useful, but more importantly if the graph data was very large (several megabytes) then Unity's Undo system would choke on it + and essentially freeze the Unity editor. + - The StartEndModifier's raycasting options are now only used if the 'Original' snapping option is used as that's the only one it makes sense for. + - The RaycastModifier.subdivideEveryIter field has been removed, this is now always enabled except for the lowest quality setting. + - The RaycastModifier.iterations field has been removed. The number of iterations is now controlled by the quality field. + Unfortunately this setting cannot be directly mapped to a quality value, so if you are upgrading all RaycastModifier components will use the quality Medium after the upgrade. + - The default value for \reflink{RVOController.lockWhenNotMoving} is now false. + - Tiles are now enabled by default on recast graphs. + - Modifiers now register/unregister themselves with the Seeker component during OnEnable/OnDisable instead of Awake/OnDestroy. + If you have written any custom modifiers which defines those methods you may have to add the 'override' modifier to those methods and call base.OnEnable/OnDisable. + - When paths fail this is now always logged as a warning in the Unity console instead of a normal log message. + - Node connections now store which edge of the node shape that is used for that node. This is used for navmesh/recast graphs. + - The \reflink{RVOController.velocity} property can now be assigned to and that has the same effect as calling ForceSetVelocity. + - Deprecated the RVOController.ForceSetVelocity method. You should use the velocity property instead. + - All graphs now explicitly implement the IUpdatableGraph interface. + This is done to hide those methods (which should not be used directly) and thereby reduce confusion about which methods should be used to update graphs. + - Hid several internal methods behind the IGraphInternals interface to reduce clutter in the documentation and IntelliSense suggestions. + - Removed NavGraph.UnloadGizmoMeshes because it was not used for anything. + - Since 4.0 individual graphs can be scanned using AstarPath.Scan. The older NavGraph.Scan method now redirects to that method + which is more robust. This may cause slight changes in behavior, however the recommendation in the documentation has always been to use AstarPath.Scan anyway + so I do not expect many to have used the NavGraph.Scan method. + - Deprecated the NavGraph.ScanGraph method since it just does the same thing as NavGraph.Scan. + - Deprecated the internal methods Path.LogError and Path.Log. + - Added the new internal method Path.FailWithError which replaces LogError and Log. + - Made the AIPath.TrySearchPath method private, it should never have been public to begin with. +- Fixes + - Fixed AIPath/RichAI throwing exceptions in the Unity Editor when drawing gizmos if the game starts while they are enabled in a disabled gameObject. + - Fixed some typos in the documentation for PathUtilities.BFS and PathUtilities.GetReachableNodes. + - For some point graph settings, path requests to points that could not be reached would fail completely instead of going to the closest node that it could reach. Thanks BYELIK for reporting this bug. + If you for some reason have been relying on the old buggy behavior you can emulate it by setting A* Inspector -> Settings -> Max Nearest Node Distance to a very low value. + - Fixed connection costs were assumed to be equal in both directions for bidirectional connections. + - Fixed a compiler error when building for UWP/HoloLens. + - Fixed some cases where circles used for debugging could have a much lower resolution than intended (Pathfinding.Util.Draw.CircleXZ). + - Fixed RVO agents which were locked but some script sent it movement commands would cause the RVO system to think it was moving + even though it was actually stationary, causing some odd behavior. Now locked agents are always treated as stationary. + - Fixed RVO obstacles generated from graph borders (using the RVONavmesh component) could be incorrect if a tiled recast graph and navmesh cutting was used. + The bug resulted in an RVO obstacle around the tile that was most recently updated by a navmesh cut even where there should be no obstacle. + - Fixed the RVONavmesh component could throw an exception in some cases when using tiled recast graphs. + - Fixed a regression in some 4.0.x version where setting \link Pathfinding.RVOController.velocity RVOController.velocity\endlink to make the agent's movement externally controlled + would not work properly (the system would always think the agent had a velocity of zero). + - Fixed the RichAI movement script could sometimes get stuck on the border between two tiles. + (due to a possibility of division by zero that would cause its velocity to become NaN). + - Fixed AIPath/RichAI movement not working properly with rigidbodies in Unity 2017.3+ when the new physics setting "Auto Sync Transforms" was disabled. Thanks DougW for reporting this and coming up with a fix. + - Fixed a few cases where \link Pathfinding.RichAI RichAI\endlink would automatically recalculate its path even though \link Pathfinding.RichAI.canSearch canSearch\endlink was disabled. + - Fixed some compiler warnings when using Unity 2017.3 or later. + - Fixed graphical artifacts in the graph visualization line drawing code which could show up at very large coordinate values or steep viewing angles. + Differential calculus can be really useful sometimes. + - Fixed the \ref MultiTargetPathExample.cs. + - Fixed the width/depth fields in the recast graph inspector causing warnings to be logged (introduced in 4.1.7). Thanks NoxMortem for reporting this. + - Fixed the Pathfinding.GraphHitInfo.tangentOrigin field was offset by half a node when using linecasting on grid graphs. + - Fixed the AIPath script with rotationIn2D would rotate so that the Z axis pointed in the -Z direction instead of as is common for Unity 2D objects: to point in the +Z direction. + - Fixed the AILerp script with rotationIn2D would rotate incorrectly if it started out with the Z axis pointed in the -Z direction. + - Clamp recast graph bounding box size to be non-zero on all axes. + - The DynamicObstacle component will now properly update the graph when the object is deactivated since the object just disappeared and shouldn't block the graph anymore. + Previously it only did this if the object was destroyed, not if it was deactivated. + - Fixed \link Pathfinding.AILerp AILerp\endlink ceasing to work properly if one of the paths it tries to calculate fails. + - Fixed the \link Pathfinding.FunnelModifier FunnelModifier\endlink could yield a zero length path in some rare circumstances when using custom node links. + This could lead to an exception in some of the movement scripts. Thanks DougW for reporting the bug. + - Fixed calling Seeker.CancelCurrentPathRequest could in some cases cause an exception to be thrown due to multithreading race conditions. + - Fixed a multithreading race condition which could cause a path canceled by Seeker.CancelCurrentPathRequest to not actually be canceled. + - Fixed a rare ArrayOutOfBoundsException when using the FunnelModifier with the 'unwrap' option enabled. + - Fixed Seeker -> Start End Modifier could not be expanded in the Unity inspector. Thanks Dee_Lucky for reporting this. + - Fixed a few compatiblity bugs relating to AIPath/RichAI that were introduced in 4.1.0. + - Fixed funnel modifier could sometimes fail if the agent started exactly on the border between two nodes. + - Fixed another bug which could cause the funnel modifier to produce incorrect results (it was checking for colinearity of points in 2D instead of in 3D). + - Fixed the funnel modifier would sometimes clip a corner near the end of the path. + - Fixed ProceduralGraphMover would not detect user defined graphs that were subclasses of the GridGraph class. Thanks viveleroi for reporting this. + - Fixed enabling and disabling a AIPath or RichAI component a very large number of times could potentially have a negative performance impact. + - Fixed AIPath/RichAI would continue searching for paths even when the component had been disabled. + - MeshNode.ContainsPoint now supports rotated graphs properly. MeshNode is used in navmesh and recast graphs. + - Fixed Linecast for navmesh and recast graphs not working for rotated graphs. + - Fixed RVONavmesh component not working properly with grid graphs that had height differences. + - Fixed 2D RVO agents sometimes ignoring obstacles. + - Fixed RVONavmesh not removing the obstacles it had created when the component was disabled. + - Fixed RaycastModifier could miss obstacles when thick raycasting was used due to Unity's Physics.SphereCast method not + reporting hits very close to the start of the raycast. + - In the free version the inspector for RaycastModifier now displays a warning if graph raycasting is enabled since + for all built-in graphs raycasts are only supported in the pro version. + - Fixed some cases where the funnel modifier would produce incorrect results. + - Fixed typo in a private method in the AstarPath class. Renamed the UpdateGraphsInteral method to UpdateGraphsInternal. + - Fixed AIPath.remainingDistance and AIPath.targetReached could be incorrect for 1 frame when a new path had just been calculated (introduced in a previous beta release). + +## 4.0.11 (2017-09-09) +- Fixed paths would ignore the ITraversalProvider (used for the turn based utilities) on the first node of the path, resulting in successful paths where they should have failed. +- Fixed BlockManager.BlockMode.AllExceptSelector could often produce incorrect results. Thanks Cquels for spotting the bug. +- Fixed various bugs related to destroying/adding graphs that could cause exceptions. Thanks DougW for reporting this. +- Fixed destroying a grid graph would not correctly clear all custom connections. Thanks DougW for reporting this. +- Fixed the MultiTargetPath did not reset all fields to their default values when using path pooling. +- Added some additional error validation in the MultiTargetPath class. +- Fixed scanning a recast graph that was not using tiles using Unity 2017.1 or later on Windows could block indefinitely. Thanks David Drummond and ceebeee for reporting this. +- Improved compatibility with Nintendo Switch. Thanks Noogy for the help. +- Fixed GraphUpdateScene would not handle the GameObject's scale properly which could cause it to not update some nodes. +- Fixed a regression in 4.0 which could cause the error to be omitted from log messages when paths failed. +- Fixed several bugs relating to #Pathfinding.NNConstraint.distanceXZ and #Pathfinding.NavmeshBase.nearestSearchOnlyXZ. Thanks koirat for reporting this. +- Fixed scanning a graph that threw an error would prevent any future scans. Thanks Baste for reporting this. +- Added a new get started video tutorial. See \ref getstarted. +- The PointGraph.nodeCount property is now protected instead of private, which fixes some compatibility issues. +- Improved compatibility with Unity 2017.1, esp. when using the experimental .Net 4.6 target. Thanks Scott_Richmond for reporting the issues. +- Fixed DynamicObstacle trying to update the graphs even when outside of play mode. +- Fixed runtime error when targeting the Windows Store. Thanks cedtat for reporting the bug. +- Fixed compilation error when targeting the Windows Store. Introduced in 4.0.3. Thanks cedtat for reporting the bug. + +## 4.0.10 (2017-05-01) +- Fixed compiler errors in the free version because the ManualRVOAgent.cs script being included by mistake. Thanks hummerbummer for reporting the issue. +- Fixed Unity's scene view picking being blocked by graph gizmos. Thanks Scott_Richmond for reporting the bug. + +## 4.0.9 (2017-04-28) +- Significantly improved performance and reduced allocations when recalculating indivudal recast tiles during runtime and there are terrains in the scene. +- Fixed the GraphUpdateScene inspector showing a warning for one frame after the 'convex' field has been changed. +- Fixed a few compiler warnings in Unity 5.6. Thanks TotalXep for reporting the issue. +- Fixed graph drawing could generate large amounts of garbage due to a missing GetHashCode override which causes Mono to have to allocate some dummy objects. +- Fixed graph gizmo lines could be rendered incorrectly on Unity 5.6 on mac and possibly on Windows too. + +## 4.0.8 (2017-04-28) +- Added rotationIn2D to the AIPath script. It makes it possible to use the Y axis as the forward axis of the character which is useful for 2D games. +- Exposed the GridGraph.LayerCount property which works for both grid graphs and layered grid graphs (for grid graphs it always returns 1). +- Made the LayerGridGraph.layerCount field internal to discourage its use outside the LayerGridGraph class. +- Fixed exception when destroying some graph types (introduced in 4.0.6). Thanks unfalco for reporting the bug. +- Fixed exception in GridGraph.GetNodesInRegion when being called with an invalid rectangle or a rectangle or bounds object that was completely outside the graph. Thanks WillG for finding the bug. +- Fixed AIPath/RichAI not rotating to the correct direction if they started in a rotation such that the forward axis was perpendicular to the movement plane. + +## 4.0.7 (2017-04-27) +- Fixed 2D example scenes had their grids rotated by (90,0,0) instead of (-90,0,0). + It doesn't matter for those scenes, but the (-90,0,0) leads to more intuitive axis rotations for most use cases. Thanks GeloMan for noticing this. +- Renamed AISimpleLerp to AILerp in the component menu as the documentation only refers to it by the name 'AILerp'. +- Added a new documentation page and video tutorial (\ref pathfinding-2d) showing how to configure pathfinding in 2D games. + +## 4.0.6 (2017-04-21) +- Fixed creating a RichAI and in the same frame setting the target and calling UpdatePath would always result in that path being canceled. +- Fixed a race condition which meant that if you called RichAI.UpdatePath, AILerp.SearchPath or AIPath.SearchPath during the same frame that the agent was created + then the callback for that path would sometimes be missed and the AI would wait indefinitely for it. This could cause the agents to sometimes never start moving. +- Fixed adding a new graph while graph updates were running at the same time could potentially cause errors. +- Added NavGraph.exists which will become false when a graph has been destroyed. +- Fixed TileHandlerHelper could throw exceptions if the graph it was tracking was destroyed. +- Fixed TileHandlerHelper not detecting new NavmeshCut or NavmeshAdd components that were created before the + TileHandlerHelper component was created or when it was disabled. +- TileHandlerHelper no longer logs an error if it is created before a recast/navmesh graph exists in the scene + and when one is created the TileHandlerHelper will automatically detect it and start to update it. +- Fixed TileHandlerHelper could throw exceptions if the graph it was tracking changed dimensions. +- Fixed recast graphs would always rasterize capsule colliders as if they had their 'direction' setting set to 'Y-axis'. Thanks emrys90 for reporting the bug. +- The package now contains a 'documentation.html' file which contains an offline version of the 'Get Started' tutorial. + +## 4.0.5 (2017-04-18) +- Improved compatibility with Opsive's Behavior Designer - Movement Pack (https://www.assetstore.unity3d.com/en/#!/content/16853). + - The 4.0 update combined with the Movement Pack caused some compiler errors previously. + +## 4.0.4 (2017-04-17) +- Fixed the funnel modifier not working if 'Add Points' on the Seeker's Start End Modifier was enabled. Thanks Blaze_Barclay for reporting it. +- Fixed code typo in the \ref write-modifiers tutorial as well as made a few smaller improvements to it. +- Fixed some cases where the LegacyRVOController would not behave like the RVOController before version 4.0. +- Fixed LegacyAIPath not using the same custom inspector as the AIPath component. + +## 4.0.3 (2017-04-16) +- Improved code style and improved documentation for some classes. +- Reduced memory allocations a bit when using the NavmeshAdd component. +- Fixed graph types not necessarily being initialized when scanning the graph outside of play mode. +- Fixed LayerGridGraph not reporting scanning progress properly. + This caused it to not work well with ScanAsync and when scanning the graph in the editor the progress bar would only update once the whole graph had been scanned. +- Removed the DebugUtility class which was only used for development when debugging the recast graph. + +## 4.0.2 (2017-04-16) +- Fixed a minor bug in the update checker. +- Deduplicated code for drawing circles and other shapes using Debug.Draw* or Gizmos.Draw* and moved this code to a new class Pathfinding.Util.Draw. + +## 4.0.1 (2017-04-15) +- Improved how AIPath and RichAI work with rigidbodies. +- Added option for gravity to AIPath. +- Removed the RichAI.raycastingForGroundPlacement field as it is automatically enabled now if any gravity is used. +- AIPath and RichAI now inherit from the same base class Pathfinding.AIBase. + +## 4.0 (2017-04-10) +- Upgrade Notes + - This release contains some significant changes. It is strongly recommended that you back up your + project before upgrading. + - If you get errors immediately after upgrading, try to delete the AstarPathfindingProject folder + and import the package again. Sometimes UnityPackages will leave old files which can cause issues. + - Moved some things to inside the Pathfinding namespace to avoid naming collisions with other packages. + Make sure you have the line 'using Pathfinding;' at the top of your scripts. + Some example scripts have been moved to the Pathfinding.Examples namespace. + - The RVOController component no longer handles movement as it turned out that was a bad idea. + Having multiple components that handled movement (e.g RichAI and RVOController) didn't turn out well + and it was very hard to configure the settings so that it worked well. + The RVOController now exposes the CalculateMovementDelta method which allows other scripts to + ask it how the local avoidance system thinks the character should move during this frame. + If you use the RichAI or AIPath components for movement, everything should work straight away. + If you use a custom movement script you may need to change your code to use the CalculateMovementDelta + method for movement. Some settings may need to be tweaked, but hopefully it should not be too hard. + - Node connections are now represented using an array of structs (of type \link Pathfinding.Connection Connection\endlink) instead of + one array for target nodes and one array for costs. + - When upgrading an existing project legacy versions of the RVOController, RichAI, AIPath and GraphUpdateScene components + will be used for compatibility reasons. You will have to click a button in the inspector to upgrade them to the latest versions. + I have tried to make sure that the movement scripts behave the same as they did before version 4.0, but it is possible that there are some minor differences. + If you have used a custom movement script which inherits from AIPath or RichAI then the legacy components cannot be used automatically, instead the new versions will be used from the start. +- New Features And Improvements + - Local Avoidance + - The RVO system has been cleaned up a lot. + - Agents will now always avoid walls and obstacles even if that would put them on a collision course with another agent. + This helps with a previous problem of agents being able to be pushed into walls and obstacles (note that RVONavmesh or RVOSquareObstacle still need to be used). + - The RVOSimulator can now be configured for XZ space or XY space (2D). + - The RVOController no longer handles movement itself as this turned out to be a really bad idea (see upgrade notes section). + - The RVOController can now be used to stop at a target much more precisely than before using the SetTarget method. + - Agents are now \link Pathfinding.RVO.RVOSimulator.symmetryBreakingBias biased slightly\endlink towards passing other agents on the right side, this helps resolve some situations + with a lot of symmetry much faster. + - All fuzzy and hard to adjust parameters from the \link Pathfinding.RVO.RVOSimulator RVOSimulator\endlink component have been removed. + It should now be much easier to configure. + - The RichAI movement script now works a lot better with the RVOController. + Previously the movement could be drastically different when the RVOController was used + and local avoidance didn't work well when the agent was at the edge of the navmesh. + - Improved gizmos for the RVOController. + - Added RVOController.ForceSetVelocity to use when you want agents to avoid a player (or otherwise externally controlled) character. + - RVO agents can now have different priorities, lower priority agents will avoid higher priority agents more. + - The neighbour distance field is now automatically calculated. This makes it easier to configure the agents and it will + also improve performance slightly when the agents are moving slowly (for example in very crowded scenarios). + - Added support for grid graphs to \link Pathfinding.RVO.RVONavmesh RVONavmesh\endlink. + - Added a new example scene for RVO in 2D + \htmlonly \endhtmlonly + - General + - Huge increase in the performance of graph gizmos. + This was accomplished by bypassing the Unity Gizmos and creating a custom gizmo rendererer that is able to retain + the gizmo meshes instead of recreating them every frame (as well as using a lot fewer draw calls than Unity Gizmos). + Therefore the graphs usually only need to check if the nodes have changed, and only if they have changed they will + rebuild the gizmo meshes. This may cause graph updates to seem like they introduce more lag than they actually do + since a graph update will also trigger a gizmo rebuild. So make sure to always profile with gizmos disabled. + For a 1000*1000 graph, which previously almost froze the editor, the time per frame went from over 4200 ms to + around 90 ms when no nodes had changed. + \htmlonly \endhtmlonly + - Improved the style of graph gizmos. A solid surface is now rendered instead of only the connections between the nodes. + The previous mode of rendering only connections is of course still available. + \shadowimage{3vs4/gizmos.png} + - Added a new example scene showing how to configure hexagon graphs. + - Added gizmos for hexagon graphs (grid graphs with certain settings). + \shadowimage{3vs4/hexagon_thin.png} + - Implemented async scanning. \link AstarPath.ScanAsync AstarPath.active.ScanAsync \endlink is an IEnumerable that can be iterated over several frames + so that e.g a progress bar can be shown while calculating the graphs. Note that this does not guarantee + a good framerate, but at least you can show a progress bar. + - Improved behaviour of the AIPath movement script. + - AIPath now works in the XY plane as well. In fact it works with any graph rotation. + The Z axis is always the forward axis for the agent, so for 2D games with sprites you may have to attach the sprite + to a child object which is rotated for it to show up correctly. + - Previously the slowdownDistance had to be smaller than the forwardLook field otherwise the character + could slow down even when it had not reached the end of the path. + - The agent should stop much more precisely at the end of the path now. + - The agent now rotates with a fixed angular speed instead of a varying one as this is often more realistic. + - Reduced the likelihood of the agent spinning around when it reaches the end of the path. + - It no longer uses the forwardLook variable. + It was very tricky to set correctly, now the pickNextWaypointDist variable is used for everything instead + and generally this should give you smoother movement. + - Improved behaviour of the \link Pathfinding.RichAI RichAI \endlink movement script. + - The agent should stop much more precisely at the end of the path now. + - Reduced the likelihood of the agent spinning around when it reaches the end of the path. + - Scanning the graph using AstarPath.Scan will now profile the various parts of the graph scanning + process using the Unity profiler (Profiler.BeginSample and Profiler.EndSample). + - \link Pathfinding.DynamicObstacle DynamicObstacle \endlink will now update the graph immediately if an object with that component is created during runtime + instead of waiting until it was moved for the first time. + - \link Pathfinding.GraphUpdateScene GraphUpdateScene \endlink and \link Pathfinding.GraphUpdateShape GraphUpdateShape \endlink can now handle rotated graphs a lot better. + The rotation of the object the GraphUpdateScene component is attached to determines the 'up' direction for the shape + and thus which points will be considered to be inside the shape. + The world space option had to be removed from GraphUpdateScene because it didn't really work with rotated graphs. + The lockToY option for GraphUpdateScene has also been removed because it wasn't very useful and after this change it would only have had an impact + in rare cases. + - Improved \link Pathfinding.GraphUpdateScene GraphUpdateScene \endlink editor. When editing the points in the scene view it now shows helper lines + to indicate where a new point is going to be added and which other points it will connect to + as well as several other minor improvements. + \htmlonly \endhtmlonly + - \link Pathfinding.GraphUpdateScene GraphUpdateScene \endlink now supports using the bounds from 2D colliders and the shape from PolygonCollider2D. + - Added opaqueness slider for the gizmos under Inspector -> Settings -> Colors. + - Added \link Pathfinding.Path.BlockUntilCalculated Path.BlockUntilCalculated \endlink which is identical to AstarPath.BlockUntilCalculated. + - Added Seeker.CancelCurrentPathRequest. + - Added \link Pathfinding.NavGraph.GetNodes NavGraph.GetNodes(System.Action) \endlink which calls a delegate with each node in the graph. + Previously NavGraph.GetNodes(GraphNodeDelegateCancelable) existed which did the same thing but required the delegate + to return true if it wanted the graph to continue calling it with more nodes. It turns out this functionality was very rarely needed. + - Individual graphs can now be scanned using #AstarPath.Scan(NavGraph) and other related overloads. + - Improved \link Pathfinding.BinaryHeap priority queue \endlink performance. On average results in about a 2% overall pathfinding performance increase. + - ObjectPool now requires a ref parameter when calling Release with an object to help prevent silly bugs. + - 'Min Area Size' has been removed. The edge cases are now handled automatically. + - Added ObjectPoolSimple as a generic object pool (ObjectPool also exists, but for that T must implement IAstarPooledObject). + - \link Pathfinding.RaycastModifier RaycastModifier \endlink now supports multi editing. + - Added \link Pathfinding.GraphNode.RandomPointOnSurface GraphNode.RandomPointOnSurface \endlink. + - Added \link Pathfinding.GraphNode.SurfaceArea GraphNode.SurfaceArea \endlink. + - Int2 and \link Pathfinding.Int3 Int3 \endlink now implement IEquatable for slightly better performance and fewer allocations in some places. + - \link Pathfinding.Examples.LocalSpaceRichAI LocalSpaceRichAI \endlink can now be used with any rotation (even things like moving on an object that is upside down). + - The \link Pathfinding.FunnelModifier funnel modifier \endlink can now handle arbitrary graphs (even graphs in the 2D plane) if the new unwrap option is enabled. + - The \link Pathfinding.FunnelModifier funnel modifier \endlink can split the resulting path at each portal if the new \link Pathfinding.FunnelModifier.splitAtEveryPortal splitAtEveryPortal \endlink option is enabled. + - Recast/Navmesh Graphs + - Recast graph scanning is now multithreaded which can improve scan times significantly. + - Recast graph scanning now handles large worlds with lots of objects better. This can improve scan times significantly. + \htmlonly \endhtmlonly + - Improved performance of nearest node queries for Recast/navmesh graphs. + - Editing navmesh cut properties in the inspector now forces updates to happen immediately which makes editing easier. + - Long edges in recast graphs are now split at tile borders as well as at obstacle borders. + This can in particular help on terrain maps where the tile borders do not follow the elevation that well + so the max edge length can be reduced to allow the border to follow the elevation of the terrain better. + - Recast graphs can now be rotated arbitrarily. + - Navmesh cutting still works! + - The RichAI script currently does not support movement on rotated graphs, but the AIPath script does. + - Improved performance of navmesh cutting for large worlds with many tiles and NavmeshAdd components. + - Navmesh graphs and recast graphs now share the same base code which means that navmesh graphs + now support everything that previously only recast graphs could be used for, for example + navmesh cutting. + - The NavmeshCut inspector now shows a warning if no TileHandlerHelper component is present in the scene. + A TileHandlerHelper component is necessary for the NavmeshCuts to update the graphs. + - Recast graphs now use less memory due to the BBTree class now using around 70% less memory per node. + - Recast graphs now allocate slightly less memory when recalculating tiles or scanning the graph. + - Cell height on Recast graphs is now automatically set to a good value. + - Navmesh cutting is now a bit better at using object pooling to avoid allocations. + - TileHandlerHelper now updates the tiles properly when one or multiple tiles on the recast graph are recalculated + due to a graph update or because it was rescanned. + - Navmesh cutting now uses more pooling to reduce allocations slightly. + - Improved performance of loading and updating (using navmesh cutting) recast tiles with a large number of nodes. + - Grid Graphs + - Added LevelGridNode.XCoordinateInGrid, LevelGridNode.ZCoordinateInGrid, LevelGridNode.LayerCoordinateInGrid. + - Added GridGraph.GetNodesInRegion(IntRect). + Also works for layered grid graphs. + - Layered grid graphs now have support for 'Erosion Uses Tags'. + - Added GridGraph.CalculateConnections(GridNodeBase) which can be used for both grid graphs and layered grid graphs. + - Grid graphs can now draw the surface and outline of the graph instead of just the connections between the nodes. + The inspector now contains several toggles that can be used to switch between the different rendering modes. + - The ProceduralGraphMover component now works with LayerGridGraph as well. + - Added GridGraph.RecalculateCell(x,y) which works both for grid graphs and layered grid graphs. + This replaces the UpdateNodePositionCollision method and that method is now deprecated. + - Improved GridGraph.RelocateNodes which is now a lot more resilient against floating point errors. + - Added dimetric (60°) to the list of default values for the isometric angle field on grid graphs. + - Changing the width/depth of a grid graph will now keep the current pivot point at the same position instead of always keeping the bottom left corner fixed. + (the pivot point can be changed to the center/bottom left/top left/top right/bottom right right next to the position field in the grid graph inspector) + - Improved fluidity and stability when resizing a grid graph in the scene view. + It now snaps to full node increments in size. + - Grid graphs now display a faint grid pattern in the scene view even when the graph is not scanned + to make it easier to position and resize the graph. + - Improved styling of some help boxes in the grid graph inspector when using the dark UI skin. + - The size of the unwalkable node gizmo (red cube) on grid graphs is now based on the node size to avoid the gizmos being much larger or much smaller than the nodes. + - Implemented \link Pathfinding.ABPath.EndPointGridGraphSpecialCase special case for paths on grid graphs \endlink so that if you request a path to an unwalkable node with several + walkable nodes around it, it will now not pick the closest walkable node to the requested target point and find a path to that + but it will find the shortest path which goes to any of the walkable nodes around the unwalkable node. + \htmlonly Before, After \endhtmlonly. + This is a special case of the MultiTargetPath, for more complicated configurations of targets the multi target path needs to be used to be able to handle it correctly. +- Changes + - Node connections are now represented using an array of structs (of type Connection) instead of + one array for target nodes and one array for costs. + - When scanning a graph in the editor, the progress bar is not displayed until at least 200 ms has passed. + Since displaying the progress bar is pretty slow, this makes scanning small graphs feel more snappy. + - GridGraph and LayerGridGraph classes now have a 'transform' field instead of a matrix and inverseMatrix fields. + The GraphTransform class also has various other nice utilities. + - Moved mesh collecting code for Recast graphs to a separate class to improve readability. + - Refactored out large parts of the AstarPath class to separate smaller classes to improve readability and increase encapsulation. + - AstarPath.RegisterSafeUpdate is now implemented using WorkItems. This yields a slightly different behavior (previously callbacks added using RegisterSafeUpdate would + always be executed before work items), but that should rarely be something that you would depend on. + - Replaced AstarPath.BlockUntilPathQueueBlocked with the more robust AstarPath.PausePathfinding method. + - The default radius, height and center for RVOControllers is now 0.5, 2 and 1 respectively. + - To reduce confusion. The second area color is now a greenish color instead of a red one. + The red color would often be mistaken as indicating unwalkable nodes instead of simply a different connected component. + Hopefully green will be a more neutral color. + - Renamed AstarPath.astarData to AstarPath.data. + - Renamed NavmeshCut.useRotation and NavmeshAdd.useRotation to useRotationAndScale (since they have always affected scale too). + - Renamed GridGraph.GenerateMatrix to GridGraph.UpdateTransform to be consistent with recast/navmesh graphs. + The GenerateMatrix method is now deprecated. + - Renamed AstarPath.WaitForPath to AstarPath.BlockUntilCalculated. + - Renamed GridNode.GetConnectionInternal to HasConnectionInDirection. + - Renamed NNInfo.clampedPosition to NNInfo.position. + - Renamed GridGraph.GetNodesInArea to GetNodesInRegion to avoid confusing the word 'area' for what is used to indicate different connected components in graphs. + - Renamed AIPath.turningSpeed to \link Pathfinding.AIPath.rotationSpeed rotationSpeed\endlink. + - Deprecated Seeker.GetNewPath. + - Deprecated NavGraph.matrix, NavGraph.inverseMatrix, NavGraph.SetMatrix and NavGraph.RelocateNodes(Matrix4x4,Matrix4x4). + They have been replaced with a single transform field only available on some graph types as well as a few other overloads of teh RelocateNodes method. + - Changed the signature of NavGraph.GetNodes(GraphNodeDelegateCancelable) to the equivalent NavGraph.GetNodes(System.Func). + - Replaced all instances of GraphNodeDelegate with the equivalent type System.Action. + - Made a large number of previously public methods internal to reduce confusion about which methods one should use in a class and make the documentation easier to read. + In particular the Path class has had its set of public methods reduced a lot. + - Made AstarData.AddGraph(NavGraph) private. Scripts should use AstarData.AddGraph(System.Type) instead. + - Moved internal fields of NNInfo into a new NNInfoInternal struct to make the API easier to use. Previously NNInfo contained some internal fields, but now they are only in NNInfoInternal. + - Moved GetNeighbourAlongDirection to GridNodeBase and made it public. + - An overload of the GridGraph.CalculateConnections method has been made non-static. + - LayerGridGraph.LinkedLevelNode and LayerGridGraph.LinkedLevelCell are now private classes since they are only used by the LayerGridGraph. + - MonoModifier.OnDestroy is now a virtual function. + - AstarPath.IsUsingMultithreading and NumbParallelThreads have been made non-static. + - AstarPath.inGameDebugPath is now private. + - AstarPath.lastScanTime is now read only. + - Removed the 'climb axis' field from grid graphs. The axis is now automatically set to the graph's UP direction (which is + the only direction that makes sense and all other directions can be transformed to this one anyway). + - Removed the 'worldSpace' parameter from RecastGraph.ReplaceTile, it is no longer possible to supply world space vertices to + that method since graph space vertices are required for some things. + - Removed BBTree.QueryCircle and BBTree.Query since they were not used anywhere. + - Removed the Path.searchIterations field because it wasn't very useful even as debug information. + - Removed the Path.maxFrameTime field because it was not used. + - Removed the Path.callTime property because it was not used. + - Removed the ABPath.startHint, ABPath.endHint fields because they were not used. + - Removed the ABPath.recalcStartEndCosts field because it was not used. + - Removed the RecursiveBinary and RecursiveTrinary modes for RichAI.funnelSimplification because the Iterative mode + was usually the best and fastest anyway (also the other two modes had a rare bug where they could get cought in infinite loops). + - Removed the Polygon.Subdivide method because it was not used anywhere. + - Removed the NavGraph.Awake method because it was not used for anything. + - Removed ASTAR_OPTIMIZE_POOLING from Optimization tab. It is now always enabled in standalone builds and always disabled in the Unity editor. + - Removed various unused Recast code. + - Removed support for forcing the inspector skin to be dark or light. The value provided by EditorGUIUtility.isProSkin is always used now. + - Removed multiplication operator for Int3 with a Vector3 because it is a nonstandard operation on vectors (and it is not that useful). + - Removed the since long deprecated example script AIFollow. + - Removed the AdaptiveSampling algorithm for local avoidance. Only GradientDescent is used now. + - Removed empty PostProcess method in NavMeshGraph. +- Fixes + - Fixed RichAI and AIPath trying to use CharacterControllers even if the CharacterController component was disabled. + - Fixed rotated recast/navmesh graphs would ensure each node's vertices were laid out clockwise in XZ space instead of in graph space which could cause parts of the graph to become disconnected from the rest. + - Fixed a bug where graphs could fail to be deserialized correctly if the graph list contained a null element + - Fixed a bug where the json serializer could emit True/False instead of true/false which is the proper json formatting. + - Fixed LayerGridGraphs' "max climb" setting not working properly with rotated graphs. + - Fixed LayerGridGraphs' "character height" setting not working properly with rotated graphs. + - Fixed LayerGridGraphs assuming there were no obstacles nearby if no ground was found. + - Fixed DynamicObstacle getting caught in an infinite loop if there was no AstarPath component in the scene when it was created. Thanks MeiChen for finding the bug. + - Fixed NodeLink2 deserialization causing exceptions if the node hadn't linked to anything when it was serialized. Thanks Skalev for finding the bug. + - Fixed the AlternativePath modifier could crash the pathfinding threads if it logged a warning since it used the Debug.Log(message,object) overload which + can only be used from the Unity thread. + - Fixed an issue where layer mask fields in graph editors would show 'Nothing' if they only included layers which had no name set. + - Fixed potential memory leak. + Paths in the path pool would still store the callback which is called when the path has been calculated + which that means it would implicitly hold a reference to the object which had the method that would be called. + Thanks sehee for pointing this out. + - Fixed GridNode.ClosestPointOnNode could sometimes return the wrong y coordinate relative to the graph (in particular when the graph was rotated) and the y coordinate would not snap to the node's surface. + - Fixed AstarData.AddGraph would fill *all* empty slots in the graph array with the graph instead of just the first. Thanks bitwise for finding the bug. + - Improved compatibility with Unity 5.5 which was need due to the newly introduced UnityEngine.Profiling namespace. + - Fixed graph updates on LayeredGridGraphs not respecting GraphUpdateObject.resetPenaltyOnPhysics. + - Fixed potential memory leak when calling RecalculateCell on a layered grid graph. + - LevelGridNode.ContainsConnection now reports correct values (previously it would only check + non-grid connections). + - Fixed not being able to deserialize settings saved with some old versions of the A* Pathfinding Project. + - Tweaked ListPool to avoid returning lists with a very large capacity when a small one was requested + as this could cause performance problems since Clear is O(n) where n is the capacity (not the size of the list). + - Fixed GraphUpdateScene causing 'The Grid Graph is not scanned, cannot update area' to be logged when exiting play mode. + - Fixed scanning a recast graph could in very rare circumstances throw a 'You are trying to pool a list twice' exception due to a multithreading + race condition. + - Fixed recast/navmesh graphs could return the wrong node as the closest one in rare cases, especially near tile borders. + - Fixed another case of recast/navmesh graphs in rare cases returning the wrong node as the closest one. + - Fixed gizmo drawing with 'Show Search Tree' enabled sometimes right after graph updates drawing nodes outside the + search tree as if they were included in it due to leftover data from graph updates. + - Fixed navmesh and recast graphs would unnecessarily be serialized by Unity which would slow down the inspector slightly. + - Fixed AstarEnumFlagDrawer not working with private fields that used the [SerializeField] attribute. + This does not impact anything that the A* Pathfinding Project used, but some users are using the AstarEnumFlagDrawer for + other fields in their projects. Thanks Skalev for the patch. + - Clicking 'Apply' in the Optimizations tab will now always refresh the UI instead of assuming that + a recompilation will happen (it will not happen if only defines for other platforms than the current one were modified). + - Fixed not being able to multi-edit RVOSquareObstacle components. + - Fixed GridNode.ClearConnections(true) not removing all reversed connections and could sometimes remove the wrong ones. + - Fixed TileHandlerHelper regularly checking for if an update needs to be done even if TileHandlerHelper.updateInterval was negative + even though the documentation specifies that it should not do that (it only disabled updates when updateInterval = -1). + - Fixed PathUtilities.GetPointsAroundPointWorld and PathUtilities.GetPointsAroundPoint returning incorrect results sometimes. + - Fixed Path.immediateCallback not being reset to null when using path pooling. + - TileHandlerHelper will now work even if Scan On Awake in A* Inspector -> Settings is false and you are scanning the graph later. + - Fixed AstarWorkItem.init could be called multiple times. + - Fixed some documentation typos. + - Fixed colliders being included twice in the recast rasterization if the GameObject had a RecastNavmeshModifier attached to it which effectively made RecastNavmeshModifier not work well at all with colliders. + - Fixed inspector for RecastNavmeshModifier not updating if changes were done to the fields by a script or when an undo or redo was done. + - Fixed SimpleSmoothModifier custom editor would sometimes set all instances of a field to the same value + when editing multiple objects at the same time. + - Fixed division by zero when the TimeScale was zero in the AstarDebugger class. Thanks Booil Jung for reporting the issue. + - Various other small fixes in the AstarDebugger class. + - Fixed division by zero when generating a recast graph and the cell size was much larger than the bounds of the graph. + - Fixed the recast graph data structures could be invalid while a graph update was running in a separate thread. + This could cause API calls like AstarPath.GetNearest to throw exceptions. Now the affected tiles are recalculated + in a separate thread and then the updates are applied to the existing graph in the Unity thread. + - Fixed some cases where the AlternativePath modifier would apply penalties incorrectly and possibly crash the pathfinding thread. + - Fixed IAgent.NeighbourCount would sometimes not be reset to 0 when the agent was locked and thus takes into account no other agents. + - Fixed RVO threads would sometimes not be terminated which could lead to memory leaks if switching scenes a lot. + - Fixed GridGraph.GetNearest and NavGraph.GetNearest not handling constraint=null. +- Internal changes + - These are changes to the internals of the system and will most likely not have any significant externally visible effects. + - Removed some wrapper methods for the heap in the PathHandler class since they were just unnecessary. Exposed the heap field as readonly instead. + - Renamed BinaryHeapM to BinaryHeap. + - Renamed ExtraMesh to RasterizationMesh. + - Refactored TileHandler.CutPoly to reduce code messiness and also fixed some edge case bugs. +- Documentation + - Among other things: improved the \ref writing-graph-generators guide (among other things it no longer uses hard to understand calculations to get the index of each node). + +## 3.8.8.1 (2017-01-12) +- Fixes + - Fixed the 'Optimization' tab sometimes logging errors when clicking Apply on Unity 5.4 and higher. + - More UWP fixes (pro version only). + +## 3.8.8 (2017-01-11) +- Fixes + - Fixed errors when deploying for the Universal Windows Platform (UWP). + This includes the Hololens platform. + - It is no longer necessary to use the compiler directive ASTAR_NO_ZIP when deploying for UWP. + zipping will be handled by the System.IO.Compression.ZipArchive class on those platforms (ZipArchive is not available on other platforms). + If you have previously enabled ASTAR_NO_ZIP it will stay enabled to ensure compatibility. + - Changed some comments from the ' + +## 3.8.7 (2016-11-26) +- Fixes + - Improved compatibility with Unity 5.5 which was needed due to the newly introduced UnityEngine.Profiling namespace. + +## 3.8.6 (2016-10-31) +- Upgrade Notes + - Note that a few features and some fixes that have been available in the beta releases are not + included in this version because they were either not ready to be released or depended on other + changes that were not ready. + - Dropped support for Unity 5.1. + - Moved some things to inside the Pathfinding namespace to avoid naming collisions with other packages. + Make sure you have the line 'using Pathfinding;' at the top of your scripts. + - Seeker.StartMultiTargetPath will now also set the enabledTags and tagPenalties fields on the path. + Similar to what StartPath has done. This has been the intended behaviour from the start, but bugs happen. + See http://forum.arongranberg.com/t/multitargetpath-doesnt-support-tag-constraints/2561/3 + - The JsonFx library is no longer used, so the Pathfinding.JsonFx.dll file in the plugins folder + may be removed to reduce the build size a bit. UnityPackages cannot delete files, so you have to delete it manually. + - RecastGraph.UpdateArea (along with a few other functions) is now explicitly implemented for the IUpdatableGraph interface + as it is usually a bad idea to try to call those methods directly (use AstarPath.UpdateGraphs instead). + - AstarPath.FlushWorkItems previously had pretty bad default values for the optional parameters. + By default it would not necessarily complete all work items, it would just complete those that + took a single frame. This is pretty much never what you actually want so to avoid + confusion the default value has been changed. +- New Features and Improvements + - The JsonFx library is no longer used. Instead a very tiny json serializer and deserializer has been written. + In addition to reducing code size and being slightly faster, it also means that users using Windows Phone + no longer have to use the ASTAR_NO_JSON compiler directive. I do not have access to a windows phone + however, so I have not tested to build it for that platform. If any issues arise I would appreciate if + you post them in the forum. + - Improved inspector for NavmeshCut. + - NodeLink2 can now be used even when using cached startup or when loading serialized data in other ways just as long as the NodeLink2 components are still in the scene. + - LevelGridNode now has support for custom non-grid connections (just like GridNode has). + - Added GridNode.XCoordinateInGrid and GridNode.ZCoordinateInGrid. + - Improved documentation for GraphUpdateShape a bit. +- Changes + - Removed EditorUtilities.GetMd5Hash since it was not used anywhere. + - Deprecated TileHandler.GetTileType and TileHandler.GetTileTypeCount. + - Seeker.StartPath now properly handles MultiTargetPath objects as well. + - Seeker.StartMultiTargetPath is now deprecated. Note that it will now also set the + enabledTags and tagPenalties fields on the path. Similar to what StartPath has done. + - Removed GridGraph.bounds since it was not used or set anywhere. + - GraphNode.AddConnection will now throw an ArgumentNullException if you try to call it with a null target node. + - Made PointGraph.AddChildren and PointGraph.CountChildren protected since it makes no sense for them to be called by other scripts. + - Changed how the 'Save & Load' tab looks to make it easier to use. + - Renamed 'Path Debug Mode' to 'Graph Coloring' and 'Path Log Mode' to 'Path Logging' in the inspector. + - RecastGraph.UpdateArea (along with a few other functions) is now explicitly implemented for the IUpdatableGraph interface + as it is usually a bad idea to try to call those methods directly (use AstarPath.UpdateGraphs instead). + - Removed ConnectionType enum since it was not used anywhere. + - Removed NodeDelegate and GetNextTargetDelegate since they were not used anywhere. +- Fixes + - Fixed TinyJson not using culture invariant float parsing and printing. + This could cause deserialization errors on systems that formatted floats differently. + - Fixed the EndingCondition example script. + - Fixed speed being multiplied by Time.deltaTime in the AI script in the get started tutorial when it shouldn't have been. + - Fixed FunnelModifier could for some very short paths return a straight line even though a corner should have been inserted. + - Fixed typo. 'Descent' (as in 'Gradient Descent') was spelled as 'Decent' in some cases. Thanks Brad Grimm for finding the typo. + - Fixed some documentation typos. + - Fixed some edge cases in RandomPath and FleePath where a node outside the valid range of G scores could be picked in some cases (when it was not necessary to do so). + - Fixed editor scripts in some cases changing the editor gui styles instead of copying them which could result in headers in unrelated places in the Unity UI had the wrong sizes. Thanks HP for reporting the bug. + - Fixed NavmeshCut causing errors when cutting the navmesh if it was rotated upside down or scaled with a negative scale. + - Fixed TriangleMeshNode.ClosestPointOnNodeXZ could sometimes return the wrong point (still on the node surface however). + This could lead to characters (esp. when using the RichAI component) teleporting in rare cases. Thanks LordCecil for reporting the bug. + - Fixed GridNodes not serializing custom connections. + - Fixed nodes could potentially get incorrect graph indices assigned when additive loading was used. + - Added proper error message when trying to call RecastGraph.ReplaceTile with a vertex count higher than the upper limit. +- Known Bugs + - Calling GetNearest when a recast graph is currently being updated on another thread may in some cases result in a null reference exception + being thrown. This does not impact navmesh cutting. This bug has been present (but not discovered) in previous releases as well. + - Calling GetNearest on point graphs with 'optimizeForSparseGraph' enabled may in some edge cases return the wrong node as being the closest one. + It will not be widely off target though and the issue is pretty rare, so for real world use cases it should be fine. + This bug has been present (but not discovered) in previous releases as well. + +## 3.8.3 through 3.8.5 were beta versions + +## 3.8.2 (2016-02-29) +- Improvements + - DynamicObstacle now handles rotation and scaling better. + - Reduced allocations due to coroutines in DynamicObstacle. +- Fixes + - Fixed AstarPath.limitGraphUpdates not working properly most of the time. + In order to keep the most common behaviour after the upgrade, the value of this field will be reset to false when upgrading. + - Fixed DynamicObstacle not setting the correct bounds at start, so the first move of an object with the DynamicObstacle + component could leave some nodes unwalkable even though they should not be. Thanks Dima for reporting the bug. + - Fixed DynamicObstacle stopping to work after the GameObject it is attached to is deactivated and then activated again. + - Fixed RVOController not working after reloading the scene due to the C# '??' operator not being equivalent to checking + for '== null' (it doesn't use Unity's special comparison check). Thanks Khan-amil for reporting the bug. + - Fixed typo in documentation for ProceduralGraphMover.floodFill. +- Changes + - Renamed 'Max Update Frequency' to 'Max Update Interval' in the editor since it has the unit [second], not [1/second]. + - Renamed AstarPath.limitGraphUpdates to AstarPath.batchGraphUpdates and AstarPath.maxGraphUpdateFreq to AstarPath.graphUpdateBatchingInterval. + Hopefully these new names are more descriptive. The documentation for the fields has also been improved slightly. + +## 3.8.1 (2016-02-17) +- Improvements + - The tag visualization mode for graphs can now use the custom list of colors + that can be configured in the inspector. + Thanks Arakade for the patch. +- Fixes + - Recast graphs now handle meshes and colliders with negative scales correctly. + Thanks bvance and peted for reporting it. + - Fixed GridGraphEditor throwing exceptions when a user had created a custom grid graph class + which inherits from GridGraph. + - Fixed Seeker.postProcessPath not being called properly. + Instead it would throw an exception if the postProcessPath delegate was set to a non-null value. + Thanks CodeSpeaker for finding the bug. + +## 3.8 (2016-02-16) +- The last version released on the Unity Asset Store was 3.7, so if you are upgrading + from that version check out the release notes for 3.7.1 through 3.7.5 as well. +- Breaking Changes + - For the few users that have written their own Path Modifiers. The 'source' parameter to the Apply method has been removed from the IPathModifier interface. + You will need to remove that parameter from your modifiers as well. + - Modifier priorities have been removed and the priorities are now set to sensible hard coded values since at least for the + included modifiers there really is only one ordering that makes sense (hopefully there is no use case I have forgotten). + This may affect your paths if you have used some other modifier order. + Hopefully this change will reduce confusion for new users. +- New Features and Improvements + - Added NodeConnection mode to the StartEndModifier on the Seeker component. + This mode will snap the start/end point to a point on the connections of the start/end node. + Similar to the Interpolate mode, but more often does what you actually want. + - SimpleSmoothModifier now has support for multi editing. + - Added a new movement script called AILerp which uses linear interpolation to follow the path. + This is good for games which want the agent to follow the path exactly and not use any + physics like behaviour. This movement script works in both 2D and 3D. + - Added a new 2D example scene which uses the new AILerp movement script. + - All scripts now have a HelpURLAttribute + so the documentation button at the top left corner of every script inspector now links directly to the documentation. + - Recast graphs can now draw the surface of a navmesh in the scene view instead of only + the node outlines. Enable it by checking the 'Show mesh surface' toggle in the inspector. + Drawing the surface instead of the node outlines is usually faster since it does not use + Unity Gizmos which have to rebuild the mesh every frame. + - Improved GUI for the tag mask field on the Seeker. + - All code is now consistently formatted, utilising the excellent Uncrustify tool. + - Added animated gifs to the \link Pathfinding.RecastGraph.cellSize Recast graph \endlink documentation showing how some parameters change the resulting navmesh. + If users like this, I will probably follow up and add similar gifs for variables in other classes. + \shadowimage{recast/character_radius.gif} +- Fixes + - Fixed objects in recast graphs being rasterized with an 0.5 voxel offset. + Note that this will change how your navmesh is rasterized (but usually for the better), so you may want to make sure it still looks good. + - Fixed graph updates to navmesh and recast graphs not checking against the y coordinate of the bounding box properly (introduced in 3.7.5). + - Fixed potential bug when loading graphs from a file and one or more of the graphs were null. + - Fixed invalid data being saved when calling AstarSerializer.SerializeGraphs with an array that was not equal to the AstarData.graphs array. + The AstarSerializer is mostly used internally (and internally it is always called with the AstarData.graphs array). Thanks munkman for reporting this. + - Fixed incorrect documentation for GridNode.NodeInGridIndex. Thanks mfjk for reporting it! + - Fixed typo in a recast graph log message (where -> were). Thanks bigdaddio for reporting it! + - Fixed not making sure the file is writable before writing graph cache files (Perforce could sometimes make it read-only). Thanks Jørgen Tjernø for the patch. + - Fixed RVOController always using FindObjectOfType during Awake, causing performance issues in large scenes. Thanks Jørgen Tjernø for the patch. + - Removed QuadtreeGraph, AstarParallel, NavMeshRenderer and NavmeshController from the released version. + These were internal dev files but due to typos they had been included in the release. + It will also automatically refresh itself if the graph has been rescanned with a different number of tiles. + - Fixed SimpleSmoothModifier not always including the exact start point of the path. + - Fixed ASTAR_GRID_NO_CUSTOM_CONNECTIONS being stripped out of the final build, so that entry in the Optimizations tab didn't actually do anything. + - Fixed performance issue with path pooling. If many paths were being calculated and pooled, the performance could be + severely reduced unless ASTAR_OPTIMIZE_POOLING was enabled (which it was not by default). + - Fixed 3 compiler warnings about using some deprecated Unity methods. +- Changes + - Recast graphs' 'Snap To Scene' button now snaps to the whole scene instead of the objects that intersect the bounds that are already set. + This has been a widely requested change. Thanks Jørgen Tjernø for the patch. + - Moved various AstarMath functions to the new class VectorMath and renamed some of them to reduce confusion. + - Removed various AstarMath functions because they were either not used or they already exist in e.g Mathf or System.Math. + DistancePointSegment2, ComputeVertexHash, Hermite, MapToRange, FormatBytes, + MagnitudeXZ, Repeat, Abs, Min, Max, Sign, Clamp, Clamp01, Lerp, RoundToInt. + - PathEndingCondition (used with XPath) is now abstract since it doesn't really make any sense to use the default implementation (always returns true). + - A 'Recyle' method is no longer required on path classes (reduced boilerplate). + - Removed old IFunnelGraph interface since it was not used by anything. + - Removed old ConvexMeshNode class since it was not used by anything. + - Removed old script NavmeshController since it has been disabled since a few versions. + - Removed Int3.DivBy2, Int3.unsafeSqrMagnitude and Int3.NormalizeTo since they were not used anywere. + - Removed Int2.sqrMagnitude, Int2.Dot since they were not used anywhere and are prone to overflow (use sqrMagnitudeLong/DotLong instead) + - Deprecated Int2.Rotate since it was not used anywhere. + - Deprecated Int3.worldMagnitude since it was not used anywhere. + +## 3.7.5 (2015-10-05) +- Breaking changes + - Graph updates to navmesh and recast graphs now also check that the nodes are contained in the supplied bounding box on the Y axis. + If the bounds you have been using were very short along the Y axis, you may have to change them so that they cover the nodes they should update. +- Improvements + - Added GridNode.ClosestPointOnNode. + - Optimized GridGraph.CalculateConnections by approximately 20%. + This means slightly faster scans and graph updates. +- Changes + - Graph updates to navmesh and recast graphs now also check that the nodes are contained in the supplied bounding box on the Y axis. + If the bounds you have been using were very short along the Y axis, you may have to change them so that they cover the nodes they should update. +- Fixes + - Fixed stack overflow exception when a pivot root with no children was assigned in the heuristic optimization settings. + - Fixed scanning in the editor could sometimes throw exceptions on new versions of Unity. + Exceptions contained the message "Trying to initialize a node when it is not safe to initialize any node". + This happened because Unity changed the EditorGUIUtility.DisplayProgressBar function to also call + OnSceneGUI and OnDrawGizmos and that interfered with the scanning. + - Fixed paths could be returned with invalid nodes if the path was calculated right + before a call to AstarPath.Scan() was done. This could result in + the funnel modifier becoming really confused and returning a straight line to the + target instead of avoiding obstacles. + - Fixed sometimes not being able to use the Optimizations tab on newer versions of Unity. + +## 3.7.4 (2015-09-13) +- Changes + - AIPath now uses the cached transform field in all cases for slightly better performance. +- Fixes + - Fixed recast/navmesh graphs could in rare cases think that a point on the navmesh was + in fact not on the navmesh which could cause odd paths and agents teleporting short distances. +- Documentation Fixes + - Fixed the Seeker class not appearing in the documentation due to a bug in Doxygen (documentation generator). + +## 3.7.3 (2015-08-18) +- Fixed GridGraph->Unwalkable When No Ground used the negated value (true meant false and false meant true). + This bug was introduced in 3.7 when some code was refactored. Thanks DrowningMonkeys for reporting it. + +## 3.7.2 (2015-08-06) +- Fixed penalties not working on navmesh based graphs (navmesh graphs and recast graphs) due to incorrectly configured compiler directives. +- Removed undocumented compiler directive ASTAR_CONSTANT_PENALTY and replaced with ASTAR_NO_TRAVERSAL_COST which + can strip out code handling penalties to get slightly better pathfinding performance (still not documented though as it is not really a big performance boost). + +## 3.7.1 (2015-08-01) +- Removed a few cases where exceptions where needed to better support WebGL when exception handling is disabled. +- Fixed MultiTargetPath could return the wrong path if the target of the path was the same as the start point. +- Fixed MultiTargetPath could sometimes throw exceptions when using more than one pathfinding thread. +- MultiTargetPath will now set path and vectorPath to the shortest path even if pathsForAll is true. +- The log output for MultiTargetPath now contains the length (in nodes) of the shortest path. +- Fixed RecastGraph throwing exceptions when trying to rasterize trees with missing (null) prefabs. Now they will simply be ignored. +- Removed RecastGraph.bbTree since it was not used for anything (bbTrees are stored inside each tile since a few versions) +- Improved performance of loading and updating large recast graph tiles (improved performance of internal AABB tree). +- Removed support for the compiler directive ASTAR_OLD_BBTREE. + +## 3.7 (2015-07-22) +- The last version that was released on the Unity Asset Store + was version 3.6 so if you are upgrading from that version also check out the release + notes for 3.6.1 through 3.6.7. +- Upgrade notes + - ProceduralGraphMover.updateDistance is now in nodes instead of world units since this value + is a lot less world scale dependant. So the defaults should fit more cases. + You may have to adjust it slightly. + - Some old parts of the API that has been marked as deprecated long ago have been removed (see below). + Some other unused parts of the API that mostly lead to confusion have been removed as well. +- Improvements + - Rewrote several documentation pages to try to explain concepts better and fixed some old code. + - \ref accessing-data + - \ref graph-updates + - \ref writing-graph-generators + - Pathfinding.NavmeshCut + - And some other smaller changes. + - Added an overload of Pathfinding.PathUtilities.IsPathPossible which takes a tag mask. + - \link Pathfinding.XPath XPath \endlink now works again. + - The ProceduralGraphMover component now supports rotated graphs (and all other ways you can transform it, e.g isometric angle and aspect ratio). + - Rewrote GridGraph.Linecast to be more accurate and more performant. + Previously it used a sampling approach which could cut corners of obstacles slightly and was pretty inefficient. + - Linted lots of files to remove trailing whitespace, fix imports, use 'var' when relevant and various other small tweaks. + - Added AstarData.layerGridGraph shortcut. +- Fixes + - Fixed compilation errors for Windows Store. + The errors mentioned ThreadPriority and VolatileRead. + - Fixed LayerGridGraph.GetNearest sometimes returning the wrong node inside a cell (e.g sometimes it would always return the node with the highest y coordinate).\n + This did not happen when the node size was close to 1 and the grid was positioned close to the origin. + Which it of course was in all my tests (tests are improved now). + - Fixed GridGraph.Linecast always returning false (no obstacles) when the start point and end point was the same. + Now it returns true (obstacle) if the start point was inside an obstacle which makes more sense. + - Linecasts on layered grid graphs now use the same implementation as the normal grid graph.\n + This fixed a TON of bugs. If you relied on the old (buggy) behaviour you might have to change your algorithms a bit. + It will now report more accurate hit information as well. + - Fixed documentation on LayerGridGraph.Linecast saying that it would return false if there was an obstacle in the way + when in fact exactly the opposite was true. + - Fixed inspector GUI throwing exceptions when two or more grid graphs or layered grid graphs were visible and thickRaycast was enabled on only one of them. + - Fixed a few options only relevant for grid graphs were visible in the layered grid graph inspector as well. + - Fixed GridGraph.CheckConnection returned the wrong result when neighbours was Four and dir was less than 4. + - All compiler directives in the Optimizations tab are now tested during the package build phase. So hopefully none of them should give compiler errors now. + - Improved accuracy of intellisense. + - Fixed the editor sometimes incorrectly comparing versions which could cause the 'New Update' window to appear even though no new version was available. +- Changes + - Removed code only necessary for compatibility with Unity 4.5 and lower. + - Removed a lot of internal unused old code. + - Renamed GridGraph.GetNodePosition to GridGraph.GraphPointToWorld to avoid confusion. + - Renamed 3rd party plugin license files to prevent the Unity Asset Store + from detecting those as the license for the whole package. + - Changed Seeker.traversableTags to be a simple int instead of a class. + - GridNode and LevelGridNode now inherit from a shared base class called GridNodeBase. + - Removed support for the compiler directive ConfigureTagsAsMultiple since it was not supported by the whole codebase + and it was pretty old. + - Marked a few methods in AstarData as deprecated since they used strings instead of types. + If string to type conversion is needed it should be done elsewhere. + - Removed some methods which have been marked as obsolete for a very long time. + - AstarData.GetNode + - PathModifier and MonoModifier.ApplyOriginal + - Some old variants of PathModifier.Apply + - GridGeneratorEditor.ResourcesField + - Int3.safeMagnitude and safeSqrMagnitude + - GraphUpdateUtilities.IsPathPossible (this has been since long been moved to the PathUtilities class) + - All constructors on path classes. The static Construct method should be used instead since that can handle path pooling. + - GraphNode.Position, walkable, tags, graphIndex. These had small changes made to their names (if they use upper- or lowercase letters) a long time ago. + (for better or for worse, but I want to avoid changing the names now again to avoid breaking peoples' code) + - GridNode.GetIndex. + - Removed the Node class which has been marked as obsolete a very long time. This class has been renamed to GraphNode to avoid name conflicts. + - Removed LocalAvoidanceMover which has been marked as obsolete a very long time. The RVO system has replaced it. + - Removed Seeker.ModifierPass.PostProcessOriginal since it was not used. This also caused Seeker.postProcessOriginalPath to be removed. + - Removed support for ASTAR_MORE_PATH_IDS because it wasn't really useful, it only increased the memory usage. + - Removed Path.height, radius, turnRadius, walkabilityMask and speed since they were dummy variables that have not been used and are + better implemented using inheritance anyway. This is also done to reduce confusion for users. + - Removed the old local avoidance system which has long since been marked as obsolete and replaced by the RVO based system. + +## 3.6.7 (2015-06-08) +- Fixes + - Fixed a race condition when OnPathPreSearch and OnPathPostSearch were called. + When the AlternativePath modifier was used, this could cause the pathfinding threads to crash with a null reference exception. + +## 3.6.6 (2015-05-27) +- Improvements + - Point Graphs are now supported when using ASTAR_NO_JSON. + - The Optimizations tab now modifies the player settings instead of changing the source files. + This is more stable and your settings are now preserved even when you upgrade the system. + - The Optimizations tab now works regardless of the directory you have installed the package in. + Hopefully the whole project is now directory agnostic, but you never know. +- Changes + - Switched out OnVoidDelegate for System.Action. + You might get a compiler error because of this (for the few that use it) + but then just rename your delegate to System.Action. +- Fixes + - Fixed recast graphs not saving all fields when using ASTAR_NO_JSON. + +## 3.6.5 (2015-05-19) +- Fixes + - Fixed recast graphs generating odd navmeshes on non-square terrains. + - Fixed serialization sometimes failing with the error 'Argument cannot be null' when ASTAR_NO_JSON was enabled. + - The 'Walkable Climb' setting on recast graphs is now clamped to be at most equal to 'Walkable Height' because + otherwise the navmesh generation can fail in some rare cases. +- Changes + - Recast graphs now show unwalkable nodes with a red outline instead of their normal colors. + +## 3.6.4 (2015-04-19) +- Fixes + - Improved compatibility with WIIU and other big-endian platforms. + +## 3.6.3 (2015-04-19) +- Fixes + - Fixed RVONavmesh not adding obstacles correctly (they were added added, but all agents ignored them). + +## 3.6.2 (2015-04-14) +- Fixes + - Fixed null reference exception in the PointGraph OnDrawGizmos method. + - Fixed a few example scene errors in Unity 5. + +## 3.6.1 (2015-04-06) +- Upgrade notes: + - The behaviour of NavGraph.RelocateNodes has changed. + The oldMatrix was previously treated as the newMatrix and vice versa so you might + need to switch the order of your parameters if you are calling it. +- Highlights: + - Works in WebGL/IL2CPP (Unity 5.0.0p3). + At least according to my limited tests. + - Implemented RelocateNodes for recast graphs (however it cannot be used on tiled recast graphs). + - Added support for hexagon graphs. + Enable it by changing the 'Connections' field on a grid graph to 'Six'. + - Fixed AstarData.DeserializeGraphsAdditive (thanks tmcsweeney). + - Fixed pathfinding threads sometimes not terminating correctly. + This would show up as a 'Could not terminate pathfinding thread...' error message. + - Added a version of GridGraph.RelocateNodes which takes grid settings instead of a matrix for ease of use. +- Changes: + - Removed NavGraph.SafeOnDestroy + - Removed GridGraph.scans because it is a pretty useless variable. + - Removed NavGraph.CreateNodes (and overriden methods) since they were not used. + - Made GridGraph.RemoveGridGraphFromStatic private. + - Removed NavMeshGraph.DeserializeMeshNodes since it was not used. + - Made Seeker.lastCompletedVectorPath, lastCompletedNodePath, OnPathComplete, OnMultiPathComplete, OnPartialPathComplete + private since they really shouldn't be used by other scripts. + - Removed Seeker.saveGetNearestHints, Seeker.startHint, Seeker.endHint, Seeker.DelayPathStart since they were not used. + - Removed unused methods of little use: AstarData.GuidToIndex and AstarData.GuidToGraph. + - Removed RecastGraph.vertices and RecastGraph.vectorVertices since they were obsolete and not used. + - Removed some old Unity 4.3 and Unity 3 compatibility code. + - Recast graphs' 'Snap to scene' button now takes into account the layer mask and the tag mask when snapping, it now also checks terrains and colliders instead of just meshes (thanks Kieran). +- Fixes: + - Fixed RecastGraph bounds gizmos could sometimes be drawn with the wrong color. + - Fixed a rare data race which would cause an exception with the message + 'Trying to initialize a node when it is not safe to initialize any nodes' to be thrown + - Tweaked Undo behaviour, should be more stable now. + - Fixed grid graph editor changing the center field very little every frame (floating point errors) + causing an excessive amount of undo items to be created. + - Reduced unecessary dirtying of the scene (thanks Ben Hymers). + - Fixed RVOCoreSimulator.WallThickness (thanks tmcsweeney). + - Fixed recast graph not properly checking for the case where an object had a MeshFilter but no Renderer (thanks 3rinJax). + - Fixed disabling ASTAR_RECAST_ARRAY_BASED_LINKED_LIST (now ASTAR_RECAST_CLASS_BASED_LINKED_LIST) would cause compiler errors. + - Fixed recast graphs could sometimes voxelize the world incorrectly and the resulting navmesh would have artifacts. + - Fixed graphMask code having been removed from the free version in some cases + due to old code which treated it as a pro only feature. + - Improved compatibility with Xbox One. + - Fixed RVOController layer field not working when multiple agents were selected. + - Fixed grid nodes not being able to have custom connections in the free version. + - Fixed runtime error on PS4. + +## 3.6 (2015-02-02) +- Upgrade notes: + - Cache data for faster startup is now stored in a separate file.\n + This reduces the huge lag some users have been experiencing since Unity changed their Undo system.\n + You will need to open the AstarPath components which used cached startup, go to the save and load tab + and press a button labeled "Transfer cache data to a separate file". +- Highlights: + - Added support for the Jump Point Search algorithm on grid graphs (pro only).\n + The JPS algorithm can be used to speed up pathfinding on grid graphs *without any penalties or tag weights applied* (it only works on uniformly weighted graphs). + It can be several times faster than normal A*. + It works best on open areas. + - Added support for heuristic optimizations (pro only).\n + This can be applied on any static graph, i.e any graph which does not change. + It requires a rather slow preprocessing step so graph updates will be really slow when using this. + However when the preprocessing is done, it can speed up pathfinding with an order of magnitude. + It works especially well in mazes with lots of options and dead ends.\n + Combined with JPS (mentioned above) I have seen it perform up to 20x better than regular A* with no heuristic optimizations. + - Added PointNode.gameObject which will contain the GameObject each node was created from. + - Added support for RVO obstacles.\n + It is by no means perfect at this point, but at least it works. + - Undo works reasonably well again.\n + It took a lot of time working around weird Unity behaviours. + For example Unity seems to send undo events when dragging items to object fields (why? no idea). + - Dragging meshes to the NavmeshGraph.SourceMesh field works again.\n + See fix about undo above. + - Extended the max number of possible areas (connected components) to 2^17 = 131072 up from 2^10 = 1024.\n + No memory usage increase, just shuffling bits around.\n + Deprecated compiler directive ASTAR_MORE_AREAS + - Extended the max number of graphs in the inspector to 256 up from 4 or 32 depending on settings.\n + No memory usage increase, just shuffling bits around. + I still don't recommend that you actually use this many graphs. + - Added RecastTileUpdate and RecastTileUpdateHandler scripts for easier recast tile updating with good performance. + - When using A* Inspector -> Settings -> Debug -> Path Debug Mode = {G,F,H,Penalties} + you previously had to set the limits for what should be displayed as "red" in the scene view yourself, this is now + optionally automatically calculated. The UI for it has also been improved. +- Improvements: + - Added penaltyAnglePower to Grid Graph -> Extra -> Penalty from Angle.\n + This can be used to increase the penalty even more for large angles than for small angles (more than it already does, that is). + - ASTAR_NO_JSON now works for recast graphs as well. + - Added custom inspector for RecastNavmeshModifier, hopefully it will not be as confusing anymore. +- Changes: + - FleePath now has a default flee strength of 1 to avoid confusion when the FleePath doesn't seem to flee from anything. + - Removed some irrelevant defines from the Optimizations tab. + - IAgent.Position cannot be changed anymore, instead use the Teleport and SetYPosition methods. + - Exposed GraphUpdateObject.changedNodes. + - Deprecated the threadSafe paremeter on RegisterSafeUpdate, it is always treated as true now. + - The default value for AstarPath.minAreaSize is now 0 since the number of areas (connected component) indices has been greatly increased (see highlights). + - Tweaked ProceduralWorld script (used for the "Procedural" example scene) to reduce FPS drops. +- Fixes: + - AstarPath.FlushGraphUpdates will now complete all graph updates instead of just making sure they have started.\n + In addition to avoiding confusion, this fixes a rare null reference exception which could happen when using + the GraphUpdateUtilities.UpdateGraphsNoBlock method. + - Fixed some cases where updating recast graphs could throw exceptions. (message begun with "No Voxelizer object. UpdateAreaInit...") + - Fixed typo in RVOSimulator. desiredSimulatonFPS -> desiredSimulationFPS. + - RVO agents move smoother now (previously their velocity could change widely depending on the fps, the average velocity was correct however) + - Fixed an exception which could, with some graph settings, be thrown when deserializing on iPhone when bytecode stripping was enabled. + - Fixed a NullReferenceException in MultiTargetPath which was thrown if the path debug mode was set to "Heavy". + - Fixed PathUtilies.BFS always returning zero nodes (thanks Ajveach). + - Made reverting GraphUpdateObjects work. The GraphUpdateUtilities.UpdateGraphsNoBlock was also fixed by this change. + - Fixed compile error with monodevelop. + - Fixed a bug which caused scanning to fail if more than one NavmeshGraph existed. + - Fixed the lightweight local avoidance example scene which didn't work previously. + - Fixed SimpleSmoothModifier not exposing Roundness Factor in the editor for the Curved Nonuniform mode. + - Fixed an exception when updating RecastGraphs and using RelevantGraphSurfaces and multithreading. + - Fixed exceptions caused by starting paths from other threads than the Unity thread. + - Fixed an infinite loop/out of memory exception that could occur sometimes when graph updates were being done at the start of the game (I hate multithreading race conditions). + - Fixed the Optimizations tab not working when JS Support was enabled. + - Fixed graph updating not working on navmesh graphs (it was broken before due to a missing line of code). + - Fixed some misspelled words in the documentation. + - Removed some unused and/or redundant variables. + - Fixed a case where graphs added using code might not always be configured correctly (and would throw exceptions when scanning). + - Improved Windows Store compatibility. + - Fixed a typo in the GridGraph which could cause compilation to fail when building for Windows Phone or Windows Store (thanks MariuszP) + - Lots of code cleanups and comments added to various scripts. + - Fixed some cases where MonoDevelop would pick up the wrong documention for fields since it doesn't support all features that Doxygen supports. + - Fixed a bug which caused the points field on GraphUpdateScene to sometimes not be editable. + - Fixed a bug which could cause RVO agents not to move if the fps was low and Interpolation and Double Buffering was used. + - Set the execution order for RVOController and RVOSimulator to make sure that other scripts will + get the latest position in their Update method. + - Fixed a bug which could cause some nearest point on line methods in AstarMath to return NaN. + This could happen when Seeker->Start End Modifier->StartPoint and EndPoint was set to Interpolate. + - Fixed a runtime error on PS Vita. + - Fixed an index out of range exception which could occur when scanning LayeredGridGraphs. + - Fixed an index out of range exception which could occur when drawing gizmos for a LayeredGridGraph. + - Fixed a bug which could cause ProduralGridMover to update the graph every frame regardless + of if the target moved or not (thanks Makak for finding the bug). + - Fixed a number of warnings in Unity 5. + +## 3.5.9.7 (3.6 beta 6, 2015-01-28) +## 3.5.9.6 (3.6 beta 5, 2015-01-28) +## 3.5.9.5 (3.6 beta 4, 2015-01-27) +## 3.5.9.1 (3.6 beta 3, 2014-10-14) +## 3.5.9 (3.6 beta 2, 2014-10-13) +## 3.5.8 (3.6 beta 1) + - See release notes for 3.6 + +## 3.5.2 (2013-09-01) (tiny bugfix and small feature release) +- Added isometric angle option for grid graphs to help with isometric 2D games. +- Fixed a bug with the RVOAgent class which caused the LightweightRVO example scene to not work as intended (no agents were avoiding each other). +- Fixed some documentation typos. +- Fixed some compilations errors some people were having with other compilers than Unity's. + +## 3.5.1 (2014-06-15) +- Added avoidance masks to local avoidance. + Each agent now has a layer and each agent can specify which layers it will avoid. + +## 3.5 (2014-06-12) +- Added back local avoidance!! + The new system uses a sampling based algorithm instead of a geometric one. + The API is almost exactly the same so if you used the previous system this will be a drop in replacement. + As for performance, it is roughly the same, maybe slightly worse in high density situations and slightly better + in less dense situations. It can handle several thousand agents on an i7 processor. + Obstacles are not yet supported, but they will be added in a future update. + +- Binary heap switched out for a 4-ary heap. + This improves pathfinding performances by about 5%. +- Optimized scanning of navmesh graphs (not the recast graphs) + Large meshes should be much faster to scan now. +- Optimized BBTree (nearest node lookup for navmesh/recast graphs, pro version only) + Nearest node queries on navmesh/recast graphs should be slightly faster now. +- Minor updates to the documentation, esp. to the GraphNode class. + +## 3.4.0.7 +- Vuforia test build + +## 3.4.0.6 +- Fixed an issue where serialization could on some machines sometimes cause an exception to get thrown. +- Fixed an issue where the recast graph would not rasterize terrains properly near the edges of it. +- Added PathUtilities.BFS. +- Added PathUtilities.GetPointsAroundPointWorld. + +## 3.4.0.5 +- Added offline documentation (Documentation.zip) +- Misc fixes for namespace conflicts people have been having. This should improve compatibility with other packages. + You might need to delete the AstarPathfindingProject folder and reimport the package for everything to work. + +## 3.4.0.4 +- Removed RVOSimulatorEditor from the free version, it was causing compiler errors. +- Made PointGraph.nodes public. + +## 3.4.0.3 +- Removed Local Avoidance due to licensing issues. + Agents will fall back to not avoiding each other. + I am working to get the local avoidance back as soon as possible. + +## 3.4.0.2 +- Unity Asset Store forced me to increase version number. + +## 3.4.0.1 +- Fixed an ArrayIndexOutOfBounds exception which could be thrown by the ProceduralGraphMover script in the Procedural example scene if the target was moved too quickly. +- The project no longer references assets from the Standard Assets folder (the package on the Unity Asset Store did so by mistake before). + +## 3.4 +- Fixed a null reference exception when scanning recast graphs and rasterizing colliders. +- Removed duplicate clipper_library.dll which was causing compiler errors. +- Support for 2D Physics collision testing when using Grid Graphs. +- Better warnings when using odd settings for Grid Graphs. +- Minor cleanups. +- Queued graph updates are no longer being performed when the AstarPath object is destroyed, this just took time. +- Fixed a bug introduced in 3.3.11 which forced grid graphs to be square in Unity versions earlier than 4.3. +- Fixed a null reference in BBTree ( used by RecastGraph). +- Fixed NavmeshGraph not rebuilding BBTree on cached start (causing performance issues on larger graphs). + +- Includes all changes from the beta releases below + +## Beta 3.3.14 ( available for everyone! ) +- All dlls are now in namespaces (e.g Pathfinding.Ionic.Zip instead of just Ionic.Zip ) to avoid conflicts with other packages. +- Most scripts are now in namespaces to avoid conflicts with other packages. +- GridNodes now support custom connections. +- Cleanups, preparing for release. +- Reverted to using an Int3 for GraphNode.position instead of an abstract Position property, the tiny memory gains were not worth it. + +## Beta 3.3.13 ( 4.3 compatible only ) +- Fixed an issue where deleting a NavmeshCut component would not update the underlaying graph. +- Better update checking. + +## Beta 3.3.12 ( 4.3 compatible only ) +- Fixed an infinite loop which could happen when scanning graphs during runtime ( not the first scan ). +- NodeLink component is now working correctly. +- Added options for optimizations to the PointGraph. +- Improved TileHandler and navmesh cutting. +- Fixed rare bug which could mess up navmeshes when using navmesh cutting. + +## Beta 3.3.11 ( 4.3 compatible only ) +- Fixed update checking. A bug has caused update checking not to run unless you had been running a previous version in which the bug did not exist. + I am not sure how long this bug has been here, but potentially for a very long time. +- Added an update notification window which pops up when there is a new version of the A* Pathfinding Project. +- Lots of UI fixes for Unity 4.3 +- Lots of other UI fixes and imprements. +- Fixed gravity for RichAI. +- Fixed Undo for Unity 4.3 +- Added a new example scene showing a procedural environment. + +## Beta 3.3.10 +- Removed RecastGraph.includeOutOfBounds. +- Fixed a few bugs when updating Layered Grid Graphs causing incorrect connections to be created, and valid ones to be left out. +- Fixed a null reference bug when removing RVO agents. +- Fixed memory leaks when deserializing graphs or reloading scenes. + +## Beta 3.3.9 +- Added new tutorial page about recast graphs. +- Recast Graph: Fixed a bug which could cause vertical surfaces to be ignored. +- Removed support for C++ Recast. +- Fixed rare bug which could mess up navmeshes when using navmesh cutting. +- Improved TileHandler and navmesh cutting. +- GraphModifiers now take O(n) (linear) time to destroy at end of game instead of O(n^2) (quadratic). +- RecastGraph now has a toggle for using tiles or not. +- Added RelevantGraphSurface which can be used with RecastGraphs to prune away non-relevant surfaces. +- Removed RecastGraph.accurateNearestNode since it was not used anymore. +- Added RecastGraph.nearestSearchOnlyXZ. +- RecastGraph now has support for removing small areas. +- Added toggle to show or hide connections between nodes on a recast graph. +- PointNode has some graph searching methods overloaded specially. This increases performance and reduces alloacations when searching + point graphs. +- Reduced allocations when searching on RecastGraph. +- Reduced allocations in RichAI and RichPath. Everything is pooled now, so for most requests no allocations will be done. +- Reduced allocations in general by using "yield return null" instead of "yield return 0" +- Fixed teleport for local avoidance agents. Previously moving an agent from one position to another + could cause it to interpolate between those two positions for a brief amount of time instead of staying at the second position. + +## Beta 3.3.8 +- Nicer RichAI gizmo colors. +- Fixed RichAI not using raycast when no path has been calculated. + +## Beta 3.3.7 +- Fixed stack overflow exception in RichPath +- Fixed RichPath could sometimes generate invalid paths +- Added gizmos to RichAI + +## Beta 3.3.6 +- Fixed node positions being off by half a node size. GetNearest node queries on grid graphs would be slightly inexact. +- Fixed grid graph updating could get messed up when using erosion. +- ... among other things, see below + +## Beta 3.3.5 and 3.3.6 +- Highlights + - Rewritten graph nodes. Nodes can now be created more easily (less overhead when creating nodes). + - Graphs may use their custom optimized memory structure for storing nodes. + - Performance improvements for scanning recast graphs. + - Added a whole new AI script. RichAI (and the class RichPath for some things): + This script is intended for navmesh based graphs and has features such as: + - Guarantees that the character stays on the navmesh + - Minor deviations from the path can be fixed without a path recalculation. + - Very exact stop at endpoint (seriously, precision with something like 7 decimals). + No more circling around the target point as with AIPath. + - Does not use path modifiers at all (for good reasons). It has an internal funnel modifier however. + - Simple wall avoidance to avoid too much wall hugging. + - Basic support for off-mesh links (see example scene). + - Improved randomness for RandomPath and FleePath, all nodes considered now have an equal chance of being selected. + - Recast now has support for tiles. This enabled much larger worlds to be rasterized (without OutOfMemory errors) and allows for dynamic graph updates. Still slow, but much faster than + a complete recalculation of the graph. + - Navmesh Cutting can now be done on recast graphs. This is a kind of (relatively) cheap graph updating which punches a hole in the navmesh to make place for obstacles. + So it only supports removing geometry, not adding it (like bridges). This update is comparitively fast, and it makes real time navmesh updating possible. + See video: http://youtu.be/qXi5qhhGNIw. + - Added RecastNavmeshModifier which can be attached to any GameObject to include that object in recast rasterization. It exposes more options and is also + faster for graph updates with logarithmic lookup complexity instead of linear (good for larger worlds when doing graph updating). + - Reintroducing special connection costs for start and end nodes. + Before multithreading was introduced, pathfinding on navmesh graphs could recalculate + the connection costs for the start and end nodes to take into account that the start point is not actually exactly at the start node's position + (triangles are usually quite a larger than the player/npc/whatever). + This didn't work with multithreading however and could mess up pathfinding, so it was removed. + Now it has been reintroduced, working with multithreading! This means more accurate paths + on navmeshes. + - Added several methods to pick random points (e.g for group movement) to Pathfinding.PathUtlitilies. + - Added RadiusModifier. A new modifier which can offset the path based on the character radius. Intended for navmesh graphs + which are not shrinked by the character radius at start but can be used for other purposes as well. + - Improved GraphUpdateScene gizmos. Convex gizmos are now correctly placed. It also shows a bounding box when selected (not showing this has confused a lot of people). + - AIPath has gotten some cleanups. Among other things it now behaves correctly when disabled and then enabled again + making it easy to pool and reuse (should that need arise). + - Funnel modifier on grid graphs will create wider funnels for diagonals which results in nicer paths. + - If an exception is thrown during pathfinding, the program does no longer hang at quit. + - Split Automatic thread count into Automatic High Load and Automatic Low Load. The former one using a higher number of thread. + - Thread count used is now shown in the editor. + - GridGraph now supports ClosestOnNode (StartEndModifier) properly. SnapToNode gives the previous behaviour on GridGraphs (they were identical before). + - New example scene Door2 which uses the NavmeshCut component. +- Fixes + - Fixed spelling error in GridGraph.uniformWidthDepthGrid. + - Erosion radius (character radius, recast graphs) could become half of what it really should be in many cases. + - RecastGraph will not rasterize triggers. + - Fixed recast not being able to handle multiple terrains. + - Fixed recast generating an incorrect mesh for terrains in some cases (not the whole terrain was included). + - Linecast on many graph types had incorrect descriptions saying that the function returns true when the line does not intersect any obstacles, + it is actually the other way around. Descriptions corrected. + - The list of nodes returned by a ConstantPath is now guaranteed to have no duplicates. + - Many recast constants are now proper constants instead of static variables. + - Fixed bug in GridNode.RemoveGridGraph which caused graphs not being cleaned up correctly. Could cause problems later on. + - Fixed an ArgumentOutOfRange exception in ListPool class. + - RelocateNodes on NavMeshGraph now correctly recalculates connection costs and rebuilds the internal query tree (thanks peted on the forums). + - Much better member documentation for RVOController. + - Exposed MaxNeighbours from IAgent to RVOController. + - Fixed AstarData.UpdateShortcuts not being called when caching was enabled. This caused graph shortcuts such as AstarPath.astarData.gridGraph not being set + when loaded from a cache. + - RVOCoreSimulator/RVOSimulator now cleans up the worker threads correctly. + - Tiled recast graphs can now be serialized. +- Changes + - Renamed Modifier class to PathModifier to avoid naming conflicts with user scripts and other packages. + - Cleaned up recast, put inside namespace and split into multiple files. + - ListPool and friends are now threadsafe. + - Removed Polygon.Dot since the Vector3 class already contains such a method. + - The Scan functions now use callbacks for progress info instead of IEnumerators. Graphs can now output progress info as well. + - Added Pathfinding.NavGraph.CountNodes function. + - Removed GraphHitInfo.success field since it was not used. + - GraphUpdateScene will now fall back to collider.bounds or renderer.bounds (depending on what is available) if no points are + defined for the shape. + - AstarPath.StartPath now has an option to put the path in the front of the queue to prioritize its calculation over other paths. + - Time.fixedDeltaTime by Time.deltaTime in AIPath.RotateTowards() to work with both FixedUpdate and Update. (Thanks Pat_AfterMoon) + You might have to configure the turn speed variable after updating since the actual rotation speed might have changed a bit depending on your settings. + - Fixed maxNeighbourDistance not being used correctly by the RVOController script. It would stay at the default value. If you + have had trouble getting local avoidance working on world with a large scale, this could have been the problem. (Thanks to Edgar Sun for providing a reproducible example case) + - Graphs loaded using DeserializeGraphsAdditive will get their graphIndex variables on the nodes set to the correct values. (thanks peted for noticing the bug). + - Fixed a null reference exception in MultiTargetPath (thanks Dave for informing me about the bug). + - GraphUpdateScene.useWorldSpace is now false per default. + - If no log output is disabled and we are not running in the editor, log output will be discarded as early as possible for performance. + Even though in theory log output could be enabled between writing to internal log strings and deciding if log output should be written. + - NavGraph.inverseMatrix is now a field, not a property (for performance). All writes to matrix should be through the SetMatrix method. + - StartEndModifier now uses ClosestOnNode for both startPoint and endPoint by default. +- Known bugs + - Linecasting on graphs is broken at the moment. (working for recast/navmesh graph atm. Except in very special cases) + - RVONavmesh does not work with tiled recast graphs. + + + +## 3.2.5.1 +- Fixes + - Pooling of paths had been accidentally disabled in AIPath. + +## 3.2.5 +- Changes + - Added support for serializing dictionaries with integer keys via a Json Converter. + - If drawGizmos is disabled on the seeker, paths will be recycled instantly. + This will show up so that if you had a seeker with drawGizmos=false, and then enable + drawGizmos, it will not draw gizmos until the next path request is issued. +- Fixes + - Fixed UNITY_4_0 preprocesor directives which were indented for UNITY 4 and not only 4.0. + Now they will be enabled for all 4.x versions of unity instead of only 4.0. + - Fixed a path pool leak in the Seeker which could cause paths not to be released if a seeker + was destroyed. + - When using a non-positive maxDistance for point graphs less processing power will be used. + - Removed unused 'recyclePaths' variable in the AIPath class. + - NullReferenceException could occur if the Pathfinding.Node.connections array was null. + - Fixed NullReferenceException which could occur sometimes when using a MultiTargetPath (Issue #16) + - Changed Ctrl to Alt when recalcing path continously in the Path Types example scene to avoid + clearing the points for the MultiTargetPath at the same time (it was also using Ctrl). + - Fixed strange looking movement artifacts during the first few frames when using RVO and interpolation was enabled. + - AlternativePath modifier will no longer cause underflows if penalties have been reset during the time it was active. It will now + only log a warning message and zero the penalty. + - Added Pathfinding.GraphUpdateObject.resetPenaltyOnPhysics (and similar in GraphUpdateScene) to force grid graphs not to reset penalties when + updating graphs. + - Fixed a bug which could cause pathfinding to crash if using the preprocessor directive ASTAR_NoTagPenalty. + - Fixed a case where StartEndModifier.exactEndPoint would incorrectly be used instead of exactStartPoint. + - AlternativePath modifier now correctly resets penalties if it is destroyed. + +## 3.2.4.1 +- Unity Asset Store guys complained about the wrong key image. + I had to update the version number to submit again. + +## 3.2.4 +- Highlights + - RecastGraph can now rasterize colliders as well! + - RecastGraph can rasterize colliders added to trees on unity terrains! + - RecastGraph will use Graphics.DrawMeshNow functions in Unity 4 instead of creating a dummy GameObject. + This will remove the annoying "cleaning up leaked mesh object" debug message which unity would log sometimes. + The debug mesh is now also only visible in the Scene View when the A* object is selected as that seemed + most logical to me (don't like this? post something in the forum saying you want a toggle for it and I will implement + one). + - GraphUpdateObject now has a \link Pathfinding.GraphUpdateObject.updateErosion toggle \endlink specifying if erosion (on grid graphs) should be recalculated after applying the guo. + This enables one to add walkable nodes which should have been made unwalkable by erosion. + - Made it a bit easier (and added more correct documentation) to add custom graph types when building for iPhone with Fast But No Exceptions (see iPhone page). +- Changes + - RecastGraph now only rasterizes enabled MeshRenderers. Previously even disabled ones would be included. + - Renamed RecastGraph.includeTerrain to RecastGraph.rasterizeTerrain to better match other variable naming. +- Fixes + - AIPath now resumes path calculation when the component or GameObject has been disabled and then reenabled. + +## 3.2.3 (free version mostly) +- Fixes + - A UNITY_IPHONE directive was not included in the free version. This caused compilation errors when building for iPhone. +- Changes + - Some documentation updates + +## 3.2.2 +- Changes + - Max Slope in grid graphs is now relative to the graph's up direction instead of world up (makes more sense I hope) +- Note + - Update really too small to be an update by itself, but I was updating the build scripts I use for the project and had to upload a new version because of technical reasons. + +## 3.2.1 +- Fixes + - Fixed bug which caused compiler errors on build (player, not in editor). + - Version number was by mistake set to 3.1 instead of 3.2 in the previous version. + +## 3.2 +- Highlights + - A complete Local Avoidance system is now included in the pro version! + - Almost every allocation can now be pooled. Which means a drastically lower allocation rate (GC get's called less often). + - Initial node penalty per graph can now be set. + Custom graph types implementing CreateNodes must update their implementations to properly assign this value. + - GraphUpdateScene has now many more tools and options which can be used. + - Added Pathfinding.PathUtilities which contains some usefull functions for working with paths and nodes. + - Added Pathfinding.Node.GetConnections to enable easy getting of all connections of a node. + The Node.connections array does not include custom connections which for example grid graphs use. + - Seeker.PostProcess function was added for easy postprocessing of paths calculated without a seeker. + - AstarPath.WaitForPath. Wait (block) until a specific path has been calculated. + - Path.WaitForPath. Wait using a coroutine until a specific path has been calculated. + - LayeredGridGraph now has support for up to 65535 layers (theoretically, but don't try it as you would probably run out of memory) + - Recast graph generation is now up to twice as fast! + - Fixed some UI glitches in Unity 4. + - Debugger component has more features and a slightly better layout. +- Fixes + - Fixed a bug which caused the SimpleSmoothModifier with uniformSegmentLength enabled to skip points sometimes. + - Fixed a bug where importing graphs additively which had the same GUID as a graph already loaded could cause bugs in the inspector. + - Fixed a bug where updating a GridGraph loaded from file would throw a NullReferenceException. + - Fixed a bug which could cause error messages for paths not to be logged + - Fixed a number of small bugs related to updating grid graphs (especially when using erosion as well). + - Overflows could occur in some navmesh/polygon math related functions when working with Int3s. This was because the precision of them had recently been increased. + Further down the line this could cause incorrect answers to GetNearest queries. + Fixed by casting to long when necessary. + - Navmesh2.shader defined "Cull Off" twice. + - Pathfinding threads are now background threads. This will prevent them from blocking the process to terminate if they of some reason are still alive (hopefully at least). + - When really high penalties are applied (which could be underflowed negative penalties) a warning message is logged. + Really high penalties (close to max uint value) can otherwise cause overflows and in some cases infinity loops because of that. + - ClosestPointOnTriangle is now spelled correctly. + - MineBotAI now uses Update instead of FixedUpdate. + - Use Dark Skin option is now exposed again since it could be incorrectly set sometimes. Now you can force it to light or dark, or set it to auto. + - Fixed recast graph bug when using multiple terrains. Previously only one terrain would be used. + - Fixed some UI glitches in Unity 4. +- Changes + - Removed Pathfinding.NNInfo.priority. + - Removed Pathfinding.NearestNodePriority. + - Conversions between NNInfo and Node are now explicit to comply with the rule of "if information might be lost: use explicit casts". + - NNInfo is now a struct. + - GraphHitInfo is now a struct. + - Path.vectorPath and Path.path are now List and List respectively. This is done to enable pooling of resources more efficiently. + - Added Pathfinding.Node.RecalculateConnectionCosts. + - Moved IsPathPossible from GraphUpdateUtilities to PathUtilities. + - Pathfinding.Path.processed was replaced with Pathfinding.Path.state. The new variable will have much more information about where + the path is in the pathfinding pipeline. + - Paths should not be created with constructors anymore, instead use the PathPool class and then call some Setup() method + - When the AstarPath object is destroyed, calculated paths in the return queue are not returned with errors anymore, but just returned. + - Removed depracated methods AstarPath.AddToPathPool, RecyclePath, GetFromPathPool. +- Bugs + - C++ Version of Recast does not work on Windows. + - GraphUpdateScene does in some cases not draw correctly positioned gizmos. + - Starting two webplayers and closing down the first might cause the other one's pathfinding threads to crash (unity bug?) (confirmed on osx) + +## 3.1.4 (iOS fixes) +- Fixes + - More fixes for the iOS platform. + - The "JsonFx.Json.dll" file is now correctly named. +- Changes + - Removed unused code from DotNetZip which reduced the size of it with about 20 KB. + +## 3.1.3 (free version only) +- Fixes + - Some of the fixes which were said to have been made in 3.1.2 were actually not included in the free version of the project. Sorry about that. + - Also includes a new JsonFx and Ionic.Zip dll. This should make it possible to build with the .Net 2.0 Subset again see: + http://www.arongranberg.com/forums/topic/ios-problem/page/1/ + +## 3.1.2 (small bugfix release) +- Fixes + - Fixed a bug which caused builds for iPhone to fail. + - Fixed a bug which caused runtime errors on the iPhone platform. + - Fixed a bug which caused huge lag in the editor for some users when using grid graphs. + - ListGraphs are now correctly loaded as PointGraphs when loading data from older versions of the system. +- Changes + - Moved JsonFx into the namespace Pathfinding.Serialization.JsonFx to avoid conflicts with users own JsonFx libraries (if they used JsonFx). + +- Known bugs + - Recast graph does not work when using static batching on any objects included. + +## 3.1.1 (small bugfix release) +- Fixes + - Fixed a bug which would cause Pathfinding.GraphUpdateUtilities.UpdateGraphsNoBlock to throw an exception when using multithreading + - Fixed a bug which caused an error to be logged and no pathfinding working when not using multithreading in the free version of the project + - Fixed some example scene bugs due to downgrading the project from Unity 3.5 to Unity 3.4 + +## 3.1 +- Fixed bug which caused LayerMask fields (GridGraph inspector for example) to behave weirdly for custom layers on Unity 3.5 and up. +- The color setting "Node Connection" now actually sets the colors of the node connections when no other information should be shown using the connection colors or when no data is available. +- Put the Int3 class in a separate file. +- Casting between Int3 and Vector3 is no longer implicit. This follows the rule of "if information might be lost: use explicit casts". +- Renamed ListGraph to PointGraph. "ListGraph" has previously been used for historical reasons. PointGraph is a more suitable name. +- Graph can now have names in the editor (just click the name in the graph list) +- Graph Gizmos can now be selectively shown or hidden per graph (small "eye" icon to the right of the graph's name) +- Added GraphUpdateUtilities with many useful functions for updating graphs. +- Erosion for grid graphs can now use tags instead of walkability +- Fixed a bug where using One Way links could in some cases result in a NullReferenceException being thrown. +- Vector3 fields in the graph editors now look a bit better in Unity 3.5+. EditorGUILayout.Vector3Field didn't show the XYZ labels in a good way (no idea why) +- GridGraph.useRaycastNormal is now enabled only if the Max Slope is less than 90 degrees. Previously it was a manual setting. +- The keyboard shortcut to scan all graphs does now also work even when the graphs are not deserialized yet (which happens a lot in the editor) +- Added NodeLink script, which can be attached to GameObjects to add manual links. This system will eventually replace the links system in the A* editor. +- Added keyboard shortcuts for adding and removing links. See Menubar -> Edit -> Pathfinding + \note Some features are restricted to Unity 3.5 and newer because of technical limitations in earlier versions (especially multi-object editing related features). + + +## 3.1 beta (version number 3.0.9.9 in Unity due to technical limitations of the System.Versions class) +- Multithreading is now enabled in the free version of the A* Pathfinding Project! +- Better support for graph updates called during e.g OnPostScan. +- PathID is now used as a short everywhere in the project +- G,H and penalty is now used as unsigned integers everywhere in the project instead of signed integers. +- There is now only one tag per node (if not the \#define ConfigureTagsAsMultiple is set). +- Fixed a bug which could make connections between graphs invalid when loading from file (would also log annoying error messages). +- Erosion (GridGraph) can now be used even when updating the graph during runtime. +- Fixed a bug where the GridGraph could return null from it's GetNearestForce calls which ended up later throwing a NullReferenceException. +- FunnelModifier no longer warns if any graph in the path does not implement the IFunnelGraph interface (i.e have no support for the funnel algorithm) + and instead falls back to add node positions to the path. +- Added a new graph type : LayerGridGraph which works like a GridGraph, but has support for multiple layers of nodes (e.g multiple floors in a building). +- ScanOnStartup is now exposed in the editor. +- Separated temporary path data and connectivity data. +- Rewritten multithreading. You can now run any number of threads in parallel. +- To avoid possible infinite loops, paths are no longer returned with just an error when requested at times they should not (e.g right when destroying the pathfinding object) +- Cleaned up code in AstarPath.cs, members are now structured and many obsolete members have been removed. +- Rewritten serialization. Now uses Json for settings along with a small part hardcoded binary data (for performance and memory). + This is a lot more stable and will be more forwards and backwards compatible. + Data is now saved as zip files(in memory, but can be saved to file) which means you can actually edit them by hand if you want! +- Added dependency JsonFx (modified for smaller code size and better compatibility). +- Added dependency DotNetZip (reduced version and a bit modified) for zip compression. +- Graph types wanting to serialize members must add the JsonOptIn attribute to the class and JsonMember to any members to serialize (in the JsonFx.Json namespace) +- Graph types wanting to serialize a bit more data (custom), will have to override some new functions from the NavGraph class to do that instead of the old serialization functions. +- Changed from using System.Guid to a custom written Guid implementation placed in Pathfinding.Util.Guid. This was done to improve compabitility with iOS and other platforms. + Previously it could crash when trying to create one because System.Guid was not included in the runtime. +- Renamed callback AstarPath.OnSafeNodeUpdate to AstarPath.OnSafeCallback (also added AstarPath.OnThreadSafeCallback) +- MultiTargetPath would throw NullReferenceException if no valid start node was found, fixed now. +- Binary heaps are now automatically expanded if needed, no annoying warning messages. +- Fixed a bug where grid graphs would not update the correct area (using GraphUpdateObject) if it was rotated. +- Node position precision increased from 100 steps per world unit to 1000 steps per world unit (if 1 world unit = 1m, that is mm precision). + This also means that all costs and penalties in graphs will need to be multiplied by 10 to match the new scale. + It also means the max range of node positions is reduced a bit... but it is still quite large (about 2 150 000 world units in either direction, that should be enough). +- If Unity 3.5 is used, the EditorGUIUtility.isProSkin field is used to toggle between light and dark skin. +- Added LayeredGridGraph which works almost the same as grid graphs, but support multiple layers of nodes. +- \note Dropped Unity 3.3 support. + + Known Bugs: The C++ version of Recast does not work on Windows + +## Documentation Update +- Changed from FixedUpdate to Update in the Get Started Guide. CharacterController.SimpleMove should not be called more than once per frame, + so this might have lowered performance when using many agents, sorry about this typo. +## 3.0.9 +- The List Graph's "raycast" variable is now serialized correctly, so it will be saved. +- List graphs do not generate connections from nodes to themselves anymore (yielding slightly faster searches) +- List graphs previously calculated cost values for connections which were very low (they should have been 100 times larger), + this can have caused searches which were not very accurate on small scales since the values were rounded to the nearest integer. +- Added Pathfinding.Path.recalcStartEndCosts to specify if the start and end nodes connection costs should be recalculated when searching to reflect + small differences between the node's position and the actual used start point. It is on by default but if you change node connection costs you might want to switch it off to get more accurate paths. +- Fixed a compile time warning in the free version from referecing obsolete variables in the project. +- Added AstarPath.threadTimeoutFrames which specifies how long the pathfinding thread will wait for new work to turn up before aborting (due to request). This variable is not exposed in the inspector yet. +- Fixed typo, either there are eight (8) or four (4) max connections per node in a GridGraph, never six (6). +- AlternativePath will no longer cause errors when using multithreading! +- Added Pathfinding.ConstantPath, a path type which finds all nodes in a specific distance (cost) from a start node. +- Added Pathfinding.FloodPath and Pathfinding.FloodPathTracer as an extreamly fast way to generate paths to a single point in for example TD games. +- Fixed a bug in MultiTargetPath which could make it extreamly slow to process. It would not use much CPU power, but it could take half a second for it to complete due to excessive yielding +- Fixed a bug in FleePath, it now returns the correct path. It had previously sometimes returned the last node searched, but which was not necessarily the best end node (though it was often close) +- Using \#defines, the pathfinder can now be better profiled (see Optimizations tab -> Profile Astar) +- Added example scene Path Types (mainly useful for A* Pro users, so I have only included it for them) +- Added many more tooltips in the editor +- Fixed a bug which would double the Y coordinate of nodes in grid graphs when loading from saved data (or caching startup) +- Graph saving to file will now work better for users of the Free version, I had forgot to include a segment of code for Grid Graphs (sorry about that) +- Some other bugfixes +## 3.0.8.2 +- Fixed a critical bug which could render the A* inspector unusable on Windows due to problems with backslashes and forward slashes in paths. +## 3.0.8.1 +- Fixed critical crash bug. When building, a preprocessor-directive had messed up serialization so the game would probably crash from an OutOfMemoryException. +## 3.0.8 +- Graph saving to file is now exposed for users of the Free version +- Fixed a bug where penalties added using a GraphUpdateObject would be overriden if updatePhysics was turned on in the GraphUpdateObject +- Fixed a bug where list graphs could ignore some children nodes, especially common if the hierarchy was deep +- Fixed the case where empty messages would spam the log (instead of spamming somewhat meaningful messages) when path logging was set to Only Errors +- Changed the NNConstraint used as default when calling NavGraph.GetNearest from NNConstraint.Walkable to NNConstraint.None, this is now the same as the default for AstarPath.GetNearest. +- You can now set the size of the red cubes shown in place of unwalkable nodes (Settings-->Show Unwalkable Nodes-->Size) +- Dynamic search of where the EditorAssets folder is, so now you can place it anywhere in the project. +- Minor A* inspector enhancements. +- Fixed a very rare bug which could, when using multithreading cause the pathfinding thread not to start after it has been terminated due to a long delay +- Modifiers can now be enabled or disabled in the editor +- Added custom inspector for the Simple Smooth Modifier. Hopefully it will now be easier to use (or at least get the hang on which fields you should change). +- Added AIFollow.canSearch to disable or enable searching for paths due to popular request. +- Added AIFollow.canMove to disable or enable moving due to popular request. +- Changed behaviour of AIFollow.Stop, it will now set AIFollow.canSearch and AIFollow.canMove to false thus making it completely stop and stop searching for paths. +- Removed Path.customData since it is a much better solution to create a new path class which inherits from Path. +- Seeker.StartPath is now implemented with overloads instead of optional parameters to simplify usage for Javascript users +- Added Curved Nonuniform spline as a smoothing option for the Simple Smooth modifier. +- Added Pathfinding.WillBlockPath as function for checking if a GraphUpdateObject would block pathfinding between two nodes (useful in TD games). +- Unity References (GameObject's, Transforms and similar) are now serialized in another way, hopefully this will make it more stable as people have been having problems with the previous one, especially on the iPhone. +- Added shortcuts to specific types of graphs, AstarData.navmesh, AstarData.gridGraph, AstarData.listGraph +- Known Bugs: The C++ version of Recast does not work on Windows +## 3.0.7 +- Grid Graphs can now be scaled to allow non-square nodes, good for isometric games. +- Added more options for custom links. For example individual nodes or connections can be either enabled or disabled. And penalty can be added to individual nodes +- Placed the Scan keyboard shortcut code in a different place, hopefully it will work more often now +- Disabled GUILayout in the AstarPath script for a possible small speed boost +- Some debug variables (such as AstarPath.PathsCompleted) are now only updated if the ProfileAstar define is enabled +- DynamicObstacle will now update nodes correctly when the object is destroyed +- Unwalkable nodes no longer shows when Show Graphs is not toggled +- Removed Path.multithreaded since it was not used +- Removed Path.preCallback since it was obsolate +- Added Pathfinding.XPath as a more customizable path +- Added example of how to use MultiTargetPaths to the documentation as it was seriously lacking info on that area +- The viewing mesh scaling for recast graphs is now correct also for the C# version +- The StartEndModifier now changes the path length to 2 for correct applying if a path length of 1 was passed. +- The progressbar is now removed even if an exception was thrown during scanning +- Two new example scenes have been added, one for list graphs which includes sample links, and another one for recast graphs +- Reverted back to manually setting the dark skin option, since it didn't work in all cases, however if a dark skin is detected, the user will be asked if he/she wants to enable the dark skin +- Added gizmos for the AIFollow script which shows the current waypoint and a circle around it illustrating the distance required for it to be considered "reached". +- The C# version of Recast does now use Character Radius instead of Erosion Radius (world units instead of voxels) +- Fixed an IndexOutOfRange exception which could occur when saving a graph with no nodes to file +- Known Bugs: The C++ version of Recast does not work on Windows +## 3.0.6 +- Added support for a C++ version of Recast which means faster scanning times and more features (though almost no are available at the moment since I haven't added support for them yet). +- Removed the overload AstarData.AddGraph (string type, NavGraph graph) since it was obsolete. AstarData.AddGraph (Pathfinding.NavGraph) should be used now. +- Fixed a few bugs in the FunnelModifier which could cause it to return invalid paths +- A reference image can now be generated for the Use Texture option for Grid Graphs +- Fixed an editor bug with graphs which had no editors +- Graphs with no editors now show up in the Add New Graph list to show that they have been found, but they cannot be used +- Deleted the \a graphIndex parameter in the Pathfinding.NavGraph.Scan function. If you need to use it in your graph's Scan function, get it using Pathfinding.AstarData.GetGraphIndex +- Javascript support! At last you can use Js code with the A* Pathfinding Project! Go to A* Inspector-->Settings-->Editor-->Enable Js Support to enable it +- The Dark Skin is now automatically used if the rest of Unity uses the dark skin(hopefully) +- Fixed a bug which could cause Unity to crash when using multithreading and creating a new AstarPath object during runtime +## 3.0.5 +- \link Pathfinding.PointGraph List Graphs \endlink now support UpdateGraphs. This means that they for example can be used with the DynamicObstacle script. +- List Graphs can now gather nodes based on GameObject tags instead of all nodes as childs of a specific GameObject. +- List Graphs can now search recursively for childs to the 'root' GameObject instead of just searching through the top-level children. +- Added custom area colors which can be edited in the inspector (A* inspector --> Settings --> Color Settings --> Custom Area Colors) +- Fixed a NullReference bug which could occur when loading a Unity Reference with the AstarSerializer. +- Fixed some bugs with the FleePath and RandomPath which could cause the StartEndModifier to assign the wrong endpoint to the path. +- Documentation is now more clear on what is A* Pathfinding Project Pro only features. +- Pathfinding.NNConstraint now has a variable to constrain which graphs to search (A* Pro only).\n + This is also available for Pathfinding.GraphUpdateObject which now have a field for an NNConstraint where it can constrain which graphs to update. +- StartPath calls on the Seeker can now take a parameter specifying which graphs to search for close nodes on (A* Pro only) +- Added the delegate AstarPath.OnAwakeSettings which is called as the first thing in the Awake function, can be used to set up settings. +- Pathfinding.UserConnection.doOverrideCost is now serialized correctly. This represents the toggle to the right of the "Cost" field when editing a link. +- Fixed some bugs with the RecastGraph when spans were partially out-of-bounds, this could generate seemingly random holes in the mesh +## 3.0.4 (only pro version affected) +- Added a Dark Skin for Unity Pro users (though it is available to Unity Free users too, even though it doesn't look very good). + It can be enabled through A* Inspector --> Settings --> Editor Settings --> Use Dark Skin +- Added option to include or not include out of bounds voxels (Y axis below the graph only) for Recast graphs. +## 3.0.3 (only pro version affected) +- Fixed a NullReferenceException caused by Voxelize.cs which could surface if there were MeshFilters with no Renderers on GameObjects (Only Pro version affected) +## 3.0.2 +- Textures can now be used to add penalty, height or change walkability of a Grid Graph (A* Pro only) +- Slope can now be used to add penalty to nodes +- Height (Y position) can now be usd to add penalty to nodes +- Prioritized graphs can be used to enable prioritizing some graphs before others when they are overlapping +- Several bug fixes +- Included a new DynamicObstacle.cs script which can be attached to any obstacle with a collider and it will update grids around it to account for changed position +## 3.0.1 +- Fixed Unity 3.3 compability +## 3.0 +- Rewrote the system from scratch +- Funnel modifier +- Easier to extend the system + + +## x. releases are major rewrites or updates to the system. +## .x releases are quite big feature updates +## ..x releases are the most common updates, fix bugs, add some features etc. +## ...x releases are quickfixes, most common when there was a really bad bug which needed fixing ASAP. diff --git a/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md.meta b/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md.meta new file mode 100644 index 0000000..015a9ba --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e271255bdfe8941f9ab0acccbb14dd82 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core.meta b/FishROV/Assets/ThirdParty/AstarPro/Core.meta new file mode 100644 index 0000000..e1e64a7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b6b8abb917bca4ce0ad1b26452b3c58d diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI.meta new file mode 100644 index 0000000..38e138b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5ab9be352d07b44e68ad7c1a03eef3a5 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs new file mode 100644 index 0000000..962d5a1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs @@ -0,0 +1,874 @@ +using UnityEngine; +using System.Collections; +using UnityEngine.Serialization; +using Unity.Jobs; + +namespace Pathfinding { + using Pathfinding.RVO; + using Pathfinding.Util; + using Pathfinding.Jobs; + using Pathfinding.Drawing; + using UnityEngine.Jobs; + + /// + /// Base class for AIPath and RichAI. + /// This class holds various methods and fields that are common to both AIPath and RichAI. + /// + /// See: + /// See: + /// See: (all movement scripts implement this interface) + /// + [RequireComponent(typeof(Seeker))] + public abstract class AIBase : VersionedMonoBehaviour { + /// \copydocref{IAstarAI.radius} + public float radius = 0.5f; + + /// \copydocref{IAstarAI.height} + public float height = 2; + + /// + /// Determines how often the agent will search for new paths (in seconds). + /// The agent will plan a new path to the target every N seconds. + /// + /// If you have fast moving targets or AIs, you might want to set it to a lower value. + /// + /// See: + /// See: + /// + /// Deprecated: This has been renamed to . + /// See: + /// + public float repathRate { + get { + return this.autoRepath.period; + } + set { + this.autoRepath.period = value; + } + } + + /// + /// \copydocref{IAstarAI::canSearch} + /// Deprecated: This has been superseded by . + /// + public bool canSearch { + get { + return this.autoRepath.mode != AutoRepathPolicy.Mode.Never; + } + set { + if (value) { + if (this.autoRepath.mode == AutoRepathPolicy.Mode.Never) { + this.autoRepath.mode = AutoRepathPolicy.Mode.EveryNSeconds; + } + } else { + this.autoRepath.mode = AutoRepathPolicy.Mode.Never; + } + } + } + + /// \copydocref{IAstarAI.simulateMovement} + [FormerlySerializedAs("canMove")] + public bool simulateMovement = true; + + /// \copydocref{IAstarAI.canMove} + [System.Obsolete("Renamed to simulateMovement")] + public bool canMove { + get => simulateMovement; + set => simulateMovement = value; + } + + /// Max speed in world units per second + [UnityEngine.Serialization.FormerlySerializedAs("speed")] + public float maxSpeed = 1; + + /// + /// Gravity to use. + /// If set to (NaN,NaN,NaN) then Physics.Gravity (configured in the Unity project settings) will be used. + /// If set to (0,0,0) then no gravity will be used and no raycast to check for ground penetration will be performed. + /// + public Vector3 gravity = new Vector3(float.NaN, float.NaN, float.NaN); + + /// + /// Layer mask to use for ground placement. + /// Make sure this does not include the layer of any colliders attached to this gameobject. + /// + /// See: + /// See: https://docs.unity3d.com/Manual/Layers.html + /// + public LayerMask groundMask = -1; + + /// + /// Distance to the end point to consider the end of path to be reached. + /// + /// When the end of the path is within this distance then will return true. + /// When the is within this distance then will return true. + /// + /// Note that the may not be reached just because the end of the path was reached. The may not be reachable at all. + /// + /// See: + /// See: + /// + public float endReachedDistance = 0.2f; + + /// + /// What to do when within units from the destination. + /// The character can either stop immediately when it comes within that distance, which is useful for e.g archers + /// or other ranged units that want to fire on a target. Or the character can continue to try to reach the exact + /// destination point and come to a full stop there. This is useful if you want the character to reach the exact + /// point that you specified. + /// + /// Note: will become true when the character is within units from the destination + /// regardless of what this field is set to. + /// + public CloseToDestinationMode whenCloseToDestination = CloseToDestinationMode.Stop; + + /// + /// Controls if the agent slows down to a stop if the area around the destination is crowded. + /// + /// Using this module requires that local avoidance is used: i.e. that an RVOController is attached to the GameObject. + /// + /// See: + /// See: local-avoidance (view in online documentation for working links) + /// + public RVODestinationCrowdedBehavior rvoDensityBehavior = new RVODestinationCrowdedBehavior(true, 0.5f, false); + + [SerializeField] + [HideInInspector] + [UnityEngine.Serialization.FormerlySerializedAs("repathRate")] + float repathRateCompatibility = float.NaN; + + [SerializeField] + [HideInInspector] + [UnityEngine.Serialization.FormerlySerializedAs("canSearch")] + [UnityEngine.Serialization.FormerlySerializedAs("repeatedlySearchPaths")] + bool canSearchCompability = false; + + /// + /// Determines which direction the agent moves in. + /// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. + /// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games. + /// + /// Using the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane + /// if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph) + /// will all tell the agent which movement plane it is supposed to use. + /// + /// [Open online documentation to see images] + /// + [UnityEngine.Serialization.FormerlySerializedAs("rotationIn2D")] + public OrientationMode orientation = OrientationMode.ZAxisForward; + + /// + /// If true, the AI will rotate to face the movement direction. + /// See: + /// + public bool enableRotation = true; + + /// + /// Position of the agent. + /// If is true then this value will be synchronized every frame with Transform.position. + /// + protected Vector3 simulatedPosition; + + /// + /// Rotation of the agent. + /// If is true then this value will be synchronized every frame with Transform.rotation. + /// + protected Quaternion simulatedRotation; + + /// + /// Position of the agent. + /// In world space. + /// If is true then this value is idential to transform.position. + /// See: + /// See: + /// + public Vector3 position { get { return updatePosition ? tr.position : simulatedPosition; } } + + /// + /// Rotation of the agent. + /// If is true then this value is identical to transform.rotation. + /// + public virtual Quaternion rotation { + get { return updateRotation ? tr.rotation : simulatedRotation; } + set { + if (updateRotation) { + tr.rotation = value; + } else { + simulatedRotation = value; + } + } + } + + /// Accumulated movement deltas from the method + protected Vector3 accumulatedMovementDelta = Vector3.zero; + + /// + /// Current desired velocity of the agent (does not include local avoidance and physics). + /// Lies in the movement plane. + /// + protected Vector2 velocity2D; + + /// + /// Velocity due to gravity. + /// Perpendicular to the movement plane. + /// + /// When the agent is grounded this may not accurately reflect the velocity of the agent. + /// It may be non-zero even though the agent is not moving. + /// + protected float verticalVelocity; + + /// Cached Seeker component + protected Seeker seeker; + + /// Cached Transform component + protected Transform tr; + + /// Cached Rigidbody component + protected Rigidbody rigid; + + /// Cached Rigidbody component + protected Rigidbody2D rigid2D; + + /// Cached CharacterController component + protected CharacterController controller; + + /// Cached RVOController component + protected RVOController rvoController; + + /// + /// Plane which this agent is moving in. + /// This is used to convert between world space and a movement plane to make it possible to use this script in + /// both 2D games and 3D games. + /// + public SimpleMovementPlane movementPlane = new SimpleMovementPlane(Quaternion.identity); + + /// + /// Determines if the character's position should be coupled to the Transform's position. + /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not move + /// instead only the property will change. + /// + /// This is useful if you want to control the movement of the character using some other means such + /// as for example root motion but still want the AI to move freely. + /// See: Combined with calling from a separate script instead of it being called automatically one can take a similar approach to what is documented here: https://docs.unity3d.com/Manual/nav-CouplingAnimationAndNavigation.html + /// + /// See: which in contrast to this field will disable all movement calculations. + /// See: + /// + public bool updatePosition { get; set; } = true; + + /// + /// Determines if the character's rotation should be coupled to the Transform's rotation. + /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate + /// instead only the property will change. + /// + /// See: + /// + public bool updateRotation { get; set; } = true; + + /// + /// Determines how the agent recalculates its path automatically. + /// This corresponds to the settings under the "Recalculate Paths Automatically" field in the inspector. + /// + public AutoRepathPolicy autoRepath = new AutoRepathPolicy(); + + /// Indicates if gravity is used during this frame + protected bool usingGravity { get; set; } + + /// Delta time used for movement during the last frame + protected float lastDeltaTime; + + /// Position of the character at the end of the last frame + protected Vector3 prevPosition1; + + /// Position of the character at the end of the frame before the last frame + protected Vector3 prevPosition2; + + /// Amount which the character wants or tried to move with during the last frame + protected Vector2 lastDeltaPosition; + + /// Only when the previous path has been calculated should the script consider searching for a new path + protected bool waitingForPathCalculation = false; + + /// Time when the last path request was started + protected float lastRepath = float.NegativeInfinity; + + /// + /// True if the Start method has been executed. + /// Used to test if coroutines should be started in OnEnable to prevent calculating paths + /// in the awake stage (or rather before start on frame 0). + /// + protected bool startHasRun = false; + + /// Backing field for + Vector3 destinationBackingField = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + + /// \copydocref{IAstarAI.destination} + public Vector3 destination { + get { return destinationBackingField; } + set { + // Note: vector3 equality operator will return false if both are (inf,inf,inf). So do the extra check to see if both are infinity. + if (rvoDensityBehavior.enabled && !(value == destinationBackingField || (float.IsPositiveInfinity(value.x) && float.IsPositiveInfinity(destinationBackingField.x)))) { + destinationBackingField = value; + rvoDensityBehavior.OnDestinationChanged(value, reachedDestination); + } else { + destinationBackingField = value; + } + } + } + + /// \copydocref{IAstarAI.velocity} + public Vector3 velocity { + get { + return lastDeltaTime > 0.000001f ? (prevPosition1 - prevPosition2) / lastDeltaTime : Vector3.zero; + } + } + + /// \copydocref{IAstarAI.desiredVelocity} + public Vector3 desiredVelocity { + get { return lastDeltaTime > 0.00001f ? movementPlane.ToWorld(lastDeltaPosition / lastDeltaTime, verticalVelocity) : Vector3.zero; } + } + + /// \copydocref{IAstarAI.desiredVelocityWithoutLocalAvoidance} + public Vector3 desiredVelocityWithoutLocalAvoidance { + get { return movementPlane.ToWorld(velocity2D, verticalVelocity); } + set { velocity2D = movementPlane.ToPlane(value, out verticalVelocity); } + } + + /// \copydocref{IAstarAI.endOfPath} + public abstract Vector3 endOfPath { get; } + + /// \copydocref{IAstarAI.reachedDestination} + public abstract bool reachedDestination { get; } + + /// \copydocref{IAstarAI.isStopped} + public bool isStopped { get; set; } + + /// \copydocref{IAstarAI.onSearchPath} + public System.Action onSearchPath { get; set; } + + /// + /// Cached delegate for the method. + /// + /// Caching this avoids allocating a new one every time a path is calculated, which reduces GC pressure. + /// + protected OnPathDelegate onPathComplete; + + /// True if the path should be automatically recalculated as soon as possible + protected virtual bool shouldRecalculatePath { + get { + return !waitingForPathCalculation && autoRepath.ShouldRecalculatePath(position, radius, destination, Time.time); + } + } + + /// + /// Looks for any attached components like RVOController and CharacterController etc. + /// + /// This is done during . If you are adding/removing components during runtime you may want to call this function + /// to make sure that this script finds them. It is unfortunately prohibitive from a performance standpoint to look for components every frame. + /// + public virtual void FindComponents () { + tr = transform; + // GetComponent is a bit slow, so only call it if we don't know about the component already. + // This is important when selecting a lot of objects in the editor as OnDrawGizmos will call + // this method every frame when outside of play mode. + if (!seeker) TryGetComponent(out seeker); + if (!rvoController) TryGetComponent(out rvoController); + // Find attached movement components + if (!controller) TryGetComponent(out controller); + if (!rigid) TryGetComponent(out rigid); + if (!rigid2D) TryGetComponent(out rigid2D); + } + + /// Called when the component is enabled + protected virtual void OnEnable () { + FindComponents(); + onPathComplete = OnPathComplete; + Init(); + + // When using rigidbodies all movement is done inside FixedUpdate instead of Update + bool fixedUpdate = rigid != null || rigid2D != null; + BatchedEvents.Add(this, fixedUpdate ? BatchedEvents.Event.FixedUpdate : BatchedEvents.Event.Update, OnUpdate); + } + + /// + /// Called every frame. + /// This may be called during FixedUpdate or Update depending on if a rigidbody is attached to the GameObject. + /// + static void OnUpdate (AIBase[] components, int count, TransformAccessArray transforms, BatchedEvents.Event ev) { + // Sync transforms to ensure raycasts will hit the correct colliders + Physics.SyncTransforms(); + Physics2D.SyncTransforms(); + + float dt = ev == BatchedEvents.Event.FixedUpdate ? Time.fixedDeltaTime : Time.deltaTime; + + var simulator = RVOSimulator.active?.GetSimulator(); + if (simulator != null) { + int agentsWithRVOControllers = 0; + for (int i = 0; i < count; i++) agentsWithRVOControllers += (components[i].rvoController != null && components[i].rvoController.enabled ? 1 : 0); + RVODestinationCrowdedBehavior.JobDensityCheck densityJobData = new RVODestinationCrowdedBehavior.JobDensityCheck(agentsWithRVOControllers, dt, simulator); + + for (int i = 0, j = 0; i < count; i++) { + var agent = components[i]; + if (agent.rvoController != null && agent.rvoController.enabled) { + densityJobData.Set(j, agent.rvoController.rvoAgent.AgentIndex, agent.endOfPath, agent.rvoDensityBehavior.densityThreshold, agent.rvoDensityBehavior.progressAverage); + j++; + } + } + + var readLock = simulator.LockSimulationDataReadOnly(); + var densityJob = densityJobData.ScheduleBatch(agentsWithRVOControllers, agentsWithRVOControllers / 16, readLock.dependency); + readLock.UnlockAfter(densityJob); + densityJob.Complete(); + + for (int i = 0, j = 0; i < count; i++) { + var agent = components[i]; + if (agent.rvoController != null && agent.rvoController.enabled) { + agent.rvoDensityBehavior.ReadJobResult(ref densityJobData, j); + j++; + } + } + + densityJobData.Dispose(); + } + + for (int i = 0; i < count; i++) { + var agent = components[i]; + UnityEngine.Profiling.Profiler.BeginSample("OnUpdate"); + agent.OnUpdate(dt); + UnityEngine.Profiling.Profiler.EndSample(); + } + + if (count > 0 && components[0] is AIPathAlignedToSurface) { + AIPathAlignedToSurface.UpdateMovementPlanes(components as AIPathAlignedToSurface[], count); + } + + Physics.SyncTransforms(); + Physics2D.SyncTransforms(); + } + + /// Called every frame + protected virtual void OnUpdate (float dt) { + // If gravity is used depends on a lot of things. + // For example when a non-kinematic rigidbody is used then the rigidbody will apply the gravity itself + // Note that the gravity can contain NaN's, which is why the comparison uses !(a==b) instead of just a!=b. + usingGravity = !(gravity == Vector3.zero) && (!updatePosition || ((rigid == null || rigid.isKinematic) && (rigid2D == null || rigid2D.bodyType == RigidbodyType2D.Kinematic))); + + if (shouldRecalculatePath) SearchPath(); + + if (simulateMovement) { + MovementUpdate(dt, out var nextPosition, out var nextRotation); + UnityEngine.Profiling.Profiler.BeginSample("Finalize"); + FinalizeMovement(nextPosition, nextRotation); + UnityEngine.Profiling.Profiler.EndSample(); + } + } + + /// + /// Starts searching for paths. + /// If you override this method you should in most cases call base.Start () at the start of it. + /// See: + /// + protected virtual void Start () { + startHasRun = true; + Init(); + } + + void Init () { + if (startHasRun) { + // Clamp the agent to the navmesh (which is what the Teleport call will do essentially. Though only some movement scripts require this, like RichAI). + // The Teleport call will also make sure some variables are properly initialized (like #prevPosition1 and #prevPosition2) + if (simulateMovement) Teleport(position, false); + autoRepath.Reset(); + if (shouldRecalculatePath) SearchPath(); + } + } + + /// \copydocref{IAstarAI.Teleport} + public virtual void Teleport (Vector3 newPosition, bool clearPath = true) { + if (clearPath) ClearPath(); + prevPosition1 = prevPosition2 = simulatedPosition = newPosition; + if (updatePosition) tr.position = newPosition; + if (rvoController != null) rvoController.Move(Vector3.zero); + if (clearPath) SearchPath(); + } + + protected void CancelCurrentPathRequest () { + waitingForPathCalculation = false; + // Abort calculation of the current path + if (seeker != null) seeker.CancelCurrentPathRequest(); + } + + protected virtual void OnDisable () { + BatchedEvents.Remove(this); + ClearPath(); + + velocity2D = Vector3.zero; + accumulatedMovementDelta = Vector3.zero; + verticalVelocity = 0f; + lastDeltaTime = 0; + } + + /// \copydocref{IAstarAI.MovementUpdate} + public void MovementUpdate (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + lastDeltaTime = deltaTime; + MovementUpdateInternal(deltaTime, out nextPosition, out nextRotation); + } + + /// Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not + protected abstract void MovementUpdateInternal(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation); + + /// + /// Outputs the start point and end point of the next automatic path request. + /// This is a separate method to make it easy for subclasses to swap out the endpoints + /// of path requests. For example the script which requires the endpoints + /// to be transformed to graph space first. + /// + protected virtual void CalculatePathRequestEndpoints (out Vector3 start, out Vector3 end) { + start = GetFeetPosition(); + end = destination; + } + + /// \copydocref{IAstarAI.SearchPath} + public virtual void SearchPath () { + if (float.IsPositiveInfinity(destination.x)) return; + if (onSearchPath != null) onSearchPath(); + + // Find out where we are and where we are going + Vector3 start, end; + CalculatePathRequestEndpoints(out start, out end); + + // Request a path to be calculated from our current position to the destination + ABPath p = ABPath.Construct(start, end, null); + SetPath(p, false); + } + + /// + /// Position of the base of the character. + /// This is used for pathfinding as the character's pivot point is sometimes placed + /// at the center of the character instead of near the feet. In a building with multiple floors + /// the center of a character may in some scenarios be closer to the navmesh on the floor above + /// than to the floor below which could cause an incorrect path to be calculated. + /// To solve this the start point of the requested paths is always at the base of the character. + /// + public virtual Vector3 GetFeetPosition () { + return position; + } + + /// Called when a requested path has been calculated + protected abstract void OnPathComplete(Path newPath); + + /// + /// Clears the current path of the agent. + /// + /// Usually invoked using SetPath(null). + /// + /// See: + /// See: + /// + protected abstract void ClearPath(); + + /// \copydocref{IAstarAI.SetPath} + public void SetPath (Path path, bool updateDestinationFromPath = true) { + if (updateDestinationFromPath && path is ABPath abPath && abPath.endPointKnownBeforeCalculation) { + this.destination = abPath.originalEndPoint; + } + + if (path == null) { + CancelCurrentPathRequest(); + ClearPath(); + } else if (path.PipelineState == PathState.Created) { + // Path has not started calculation yet + waitingForPathCalculation = true; + seeker.CancelCurrentPathRequest(); + seeker.StartPath(path, onPathComplete); + autoRepath.DidRecalculatePath(destination, Time.time); + } else if (path.PipelineState >= PathState.Returning) { + // Path has already been calculated + + // We might be calculating another path at the same time, and we don't want that path to override this one. So cancel it. + if (seeker.GetCurrentPath() != path) seeker.CancelCurrentPathRequest(); + + OnPathComplete(path); + } else { + // Path calculation has been started, but it is not yet complete. Cannot really handle this. + throw new System.ArgumentException("You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished."); + } + } + + /// + /// Accelerates the agent downwards. + /// See: + /// See: + /// + protected virtual void ApplyGravity (float deltaTime) { + // Apply gravity + if (usingGravity) { + float verticalGravity; + velocity2D += movementPlane.ToPlane(deltaTime * (float.IsNaN(gravity.x) ? Physics.gravity : gravity), out verticalGravity); + verticalVelocity += verticalGravity; + } else { + verticalVelocity = 0; + } + } + + /// Calculates how far to move during a single frame + protected Vector2 CalculateDeltaToMoveThisFrame (Vector3 position, float distanceToEndOfPath, float deltaTime) { + if (rvoController != null && rvoController.enabled) { + // Use RVOController to get a processed delta position + // such that collisions will be avoided if possible + return movementPlane.ToPlane(rvoController.CalculateMovementDelta(position, deltaTime)); + } + // Direction and distance to move during this frame + return Vector2.ClampMagnitude(velocity2D * deltaTime, distanceToEndOfPath); + } + + /// + /// Simulates rotating the agent towards the specified direction and returns the new rotation. + /// + /// Note that this only calculates a new rotation, it does not change the actual rotation of the agent. + /// Useful when you are handling movement externally using but you want to use the built-in rotation code. + /// + /// See: + /// + /// Direction in world space to rotate towards. + /// Maximum number of degrees to rotate this frame. + public Quaternion SimulateRotationTowards (Vector3 direction, float maxDegrees) { + return SimulateRotationTowards(movementPlane.ToPlane(direction), maxDegrees, maxDegrees); + } + + /// + /// Simulates rotating the agent towards the specified direction and returns the new rotation. + /// + /// Note that this only calculates a new rotation, it does not change the actual rotation of the agent. + /// + /// See: + /// See: + /// + /// Direction in the movement plane to rotate towards. + /// Maximum number of degrees to rotate this frame around the character's main axis. This is rotating left and right as a character normally does. + /// Maximum number of degrees to rotate this frame around other axes. This is used to ensure the character's up direction is correct. + /// It is only used for non-planar worlds where the up direction changes depending on the position of the character. + /// More precisely a faster code path which ignores this parameter is used whenever the current #movementPlane is exactly the XZ or XY plane. + /// This must be at least as large as maxDegreesMainAxis. + protected Quaternion SimulateRotationTowards (Vector2 direction, float maxDegreesMainAxis, float maxDegreesOffAxis = float.PositiveInfinity) { + Quaternion targetRotation; + + if (movementPlane.isXY || movementPlane.isXZ) { + if (direction == Vector2.zero) return simulatedRotation; + + // Common fast path. + // A standard XY or XZ movement plane indicates that the character is moving in a normal planar world. + // We will use a much faster code path for this case since we don't have to deal with changing the 'up' direction of the character all the time. + // This code path mostly works for non-planar worlds too, but it will fail in some cases. + // In particular it will not be able to adjust the up direction of the character while it is standing still (because then a zero maxDegreesMainAxis is usually passed). + // That case may be important, especially when the character has just been spawned and does not have a destination yet. + targetRotation = Quaternion.LookRotation(movementPlane.ToWorld(direction, 0), movementPlane.ToWorld(Vector2.zero, 1)); + maxDegreesOffAxis = maxDegreesMainAxis; + } else { + // Decompose the rotation into two parts: a rotation around the main axis of the character, and a rotation around the other axes. + // Then limit the rotation speed along those two components separately. + var forwardInPlane = movementPlane.ToPlane(rotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward)); + + // Can happen if the character is perpendicular to the plane + if (forwardInPlane == Vector2.zero) forwardInPlane = Vector2.right; + + var rotationVectorAroundMainAxis = VectorMath.ComplexMultiplyConjugate(direction, forwardInPlane); + + // Note: If the direction is zero, then angle will also be zero since atan2(0,0) = 0 + var angle = Mathf.Atan2(rotationVectorAroundMainAxis.y, rotationVectorAroundMainAxis.x) * Mathf.Rad2Deg; + var rotationAroundMainAxis = Quaternion.AngleAxis(-Mathf.Min(Mathf.Abs(angle), maxDegreesMainAxis) * Mathf.Sign(angle), Vector3.up); + + targetRotation = Quaternion.LookRotation(movementPlane.ToWorld(forwardInPlane, 0), movementPlane.ToWorld(Vector2.zero, 1)); + targetRotation = targetRotation * rotationAroundMainAxis; + } + + // This causes the character to only rotate around the Z axis + if (orientation == OrientationMode.YAxisForward) targetRotation *= Quaternion.Euler(90, 0, 0); + return Quaternion.RotateTowards(simulatedRotation, targetRotation, maxDegreesOffAxis); + } + + /// \copydocref{IAstarAI.Move} + public virtual void Move (Vector3 deltaPosition) { + accumulatedMovementDelta += deltaPosition; + } + + /// + /// Moves the agent to a position. + /// + /// This is used if you want to override how the agent moves. For example if you are using + /// root motion with Mecanim. + /// + /// This will use a CharacterController, Rigidbody, Rigidbody2D or the Transform component depending on what options + /// are available. + /// + /// The agent will be clamped to the navmesh after the movement (if such information is available, generally this is only done by the RichAI component). + /// + /// See: for some example code. + /// See: , , + /// + /// New position of the agent. + /// New rotation of the agent. If #enableRotation is false then this parameter will be ignored. + public virtual void FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) { + if (enableRotation) FinalizeRotation(nextRotation); + FinalizePosition(nextPosition); + } + + void FinalizeRotation (Quaternion nextRotation) { + simulatedRotation = nextRotation; + if (updateRotation) { + if (rigid != null) rigid.MoveRotation(nextRotation); + else if (rigid2D != null) rigid2D.MoveRotation(nextRotation.eulerAngles.z); + else tr.rotation = nextRotation; + } + } + + void FinalizePosition (Vector3 nextPosition) { + // Use a local variable, it is significantly faster + Vector3 currentPosition = simulatedPosition; + bool positionDirty1 = false; + + if (controller != null && controller.enabled && updatePosition) { + // Use CharacterController + // The Transform may not be at #position if it was outside the navmesh and had to be moved to the closest valid position + tr.position = currentPosition; + controller.Move((nextPosition - currentPosition) + accumulatedMovementDelta); + // Grab the position after the movement to be able to take physics into account + // TODO: Add this into the clampedPosition calculation below to make RVO better respond to physics + currentPosition = tr.position; + if (controller.isGrounded) verticalVelocity = 0; + } else { + // Use Transform, Rigidbody, Rigidbody2D or nothing at all (if updatePosition = false) + float lastElevation; + movementPlane.ToPlane(currentPosition, out lastElevation); + currentPosition = nextPosition + accumulatedMovementDelta; + + // Position the character on the ground + if (usingGravity) currentPosition = RaycastPosition(currentPosition, lastElevation); + positionDirty1 = true; + } + + // Clamp the position to the navmesh after movement is done + bool positionDirty2 = false; + currentPosition = ClampToNavmesh(currentPosition, out positionDirty2); + + // Assign the final position to the character if we haven't already set it (mostly for performance, setting the position can be slow) + if ((positionDirty1 || positionDirty2) && updatePosition) { + // Note that rigid.MovePosition may or may not move the character immediately. + // Check the Unity documentation for the special cases. + if (rigid != null) rigid.MovePosition(currentPosition); + else if (rigid2D != null) rigid2D.MovePosition(currentPosition); + else tr.position = currentPosition; + } + + accumulatedMovementDelta = Vector3.zero; + simulatedPosition = currentPosition; + UpdateVelocity(); + } + + protected void UpdateVelocity () { + prevPosition2 = prevPosition1; + prevPosition1 = position; + } + + /// + /// Constrains the character's position to lie on the navmesh. + /// Not all movement scripts have support for this. + /// + /// Returns: New position of the character that has been clamped to the navmesh. + /// + /// Current position of the character. + /// True if the character's position was modified by this method. + protected virtual Vector3 ClampToNavmesh (Vector3 position, out bool positionChanged) { + positionChanged = false; + return position; + } + + /// + /// Hit info from the last raycast done for ground placement. + /// Will not update unless gravity is used (if no gravity is used, then raycasts are disabled). + /// + /// See: + /// + protected RaycastHit lastRaycastHit; + + /// + /// Checks if the character is grounded and prevents ground penetration. + /// + /// Sets to zero if the character is grounded. + /// + /// Returns: The new position of the character. + /// + /// Position of the character in the world. + /// Elevation coordinate before the agent was moved. This is along the 'up' axis of the #movementPlane. + protected Vector3 RaycastPosition (Vector3 position, float lastElevation) { + float elevation; + + movementPlane.ToPlane(position, out elevation); + float rayLength = tr.localScale.y * height * 0.5f + Mathf.Max(0, lastElevation-elevation); + Vector3 rayOffset = movementPlane.ToWorld(Vector2.zero, rayLength); + + if (Physics.Raycast(position + rayOffset, -rayOffset, out lastRaycastHit, rayLength, groundMask, QueryTriggerInteraction.Ignore)) { + // Grounded + // Make the vertical velocity fall off exponentially. This is reasonable from a physical standpoint as characters + // are not completely stiff and touching the ground will not immediately negate all velocity downwards. The AI will + // stop moving completely due to the raycast penetration test but it will still *try* to move downwards. This helps + // significantly when moving down along slopes as if the vertical velocity would be set to zero when the character + // was grounded it would lead to a kind of 'bouncing' behavior (try it, it's hard to explain). Ideally this should + // use a more physically correct formula but this is a good approximation and is much more performant. The constant + // '5' in the expression below determines how quickly it converges but high values can lead to too much noise. + verticalVelocity *= System.Math.Max(0, 1 - 5 * lastDeltaTime); + return lastRaycastHit.point; + } + return position; + } + + protected virtual void OnDrawGizmosSelected () { + // When selected in the Unity inspector it's nice to make the component react instantly if + // any other components are attached/detached or enabled/disabled. + // We don't want to do this normally every frame because that would be expensive. + if (Application.isPlaying) FindComponents(); + } + + public static readonly Color ShapeGizmoColor = new Color(240/255f, 213/255f, 30/255f); + + public override void DrawGizmos () { + if (!Application.isPlaying || !enabled || tr == null) FindComponents(); + + var color = ShapeGizmoColor; + if (rvoController != null && rvoController.locked) color *= 0.5f; + if (orientation == OrientationMode.YAxisForward) { + Draw.WireCylinder(position, Vector3.forward, 0, radius * tr.localScale.x, color); + } else { + Draw.WireCylinder(position, rotation * Vector3.up, tr.localScale.y * height, radius * tr.localScale.x, color); + } + + if (!float.IsPositiveInfinity(destination.x) && Application.isPlaying) Draw.Circle(destination, movementPlane.rotation * Vector3.up, 0.2f, Color.blue); + + autoRepath.DrawGizmos(Draw.editor, position, radius, new NativeMovementPlane(movementPlane.rotation)); + } + + protected override void Reset () { + ResetShape(); + base.Reset(); + } + + void ResetShape () { + if (TryGetComponent(out CharacterController cc)) { + radius = cc.radius; + height = Mathf.Max(radius*2, cc.height); + } + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) { + // Version == 5 is the 4.2 branch. + if (legacyVersion <= 2 || legacyVersion == 5) rvoDensityBehavior.enabled = false; + + if (legacyVersion <= 3) { + repathRate = repathRateCompatibility; + canSearch = canSearchCompability; + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs.meta new file mode 100644 index 0000000..b9f18be --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a08f67bbe580e4ddfaebd06363c9cc97 +timeCreated: 1496932372 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 100 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs new file mode 100644 index 0000000..83e0d30 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs @@ -0,0 +1,726 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Util; + + /// + /// Linearly interpolating movement script. + /// + /// This movement script will follow the path exactly, it uses linear interpolation to move between the waypoints in the path. + /// This is desirable for some types of games. + /// It also works in 2D. + /// + /// See: You can see an example of this script in action in the example scene called Example15_2D. + /// + /// \section rec Configuration + /// \subsection rec-snapped Recommended setup for movement along connections + /// + /// This depends on what type of movement you are aiming for. + /// If you are aiming for movement where the unit follows the path exactly and move only along the graph connections on a grid/point graph. + /// I recommend that you adjust the StartEndModifier on the Seeker component: set the 'Start Point Snapping' field to 'NodeConnection' and the 'End Point Snapping' field to 'SnapToNode'. + /// [Open online documentation to see images] + /// [Open online documentation to see images] + /// + /// \subsection rec-smooth Recommended setup for smooth movement + /// If you on the other hand want smoother movement I recommend setting 'Start Point Snapping' and 'End Point Snapping' to 'ClosestOnNode' and to add the Simple Smooth Modifier to the GameObject as well. + /// Alternatively you can use the which works better on navmesh/recast graphs or the . + /// + /// You should not combine the Simple Smooth Modifier or the Funnel Modifier with the NodeConnection snapping mode. This may lead to very odd behavior. + /// + /// [Open online documentation to see images] + /// [Open online documentation to see images] + /// You may also want to tweak the . + /// + [RequireComponent(typeof(Seeker))] + [AddComponentMenu("Pathfinding/AI/AILerp (2D,3D)")] + [UniqueComponent(tag = "ai")] + [DisallowMultipleComponent] + [HelpURL("https://arongranberg.com/astar/documentation/stable/ailerp.html")] + public class AILerp : VersionedMonoBehaviour, IAstarAI { + /// + /// Determines how often it will search for new paths. + /// If you have fast moving targets or AIs, you might want to set it to a lower value. + /// The value is in seconds between path requests. + /// + /// Deprecated: This has been renamed to . + /// See: + /// + public float repathRate { + get { + return this.autoRepath.period; + } + set { + this.autoRepath.period = value; + } + } + + /// + /// \copydoc Pathfinding::IAstarAI::canSearch + /// Deprecated: This has been superseded by . + /// + public bool canSearch { + get { + return this.autoRepath.mode != AutoRepathPolicy.Mode.Never; + } + set { + this.autoRepath.mode = value ? AutoRepathPolicy.Mode.EveryNSeconds : AutoRepathPolicy.Mode.Never; + } + } + + /// + /// Determines how the agent recalculates its path automatically. + /// This corresponds to the settings under the "Recalculate Paths Automatically" field in the inspector. + /// + public AutoRepathPolicy autoRepath = new AutoRepathPolicy(); + + /// \copydocref{IAstarAI.simulateMovement} + [UnityEngine.Serialization.FormerlySerializedAs("canMove")] + public bool simulateMovement = true; + + /// \copydocref{IAstarAI.canMove} + [System.Obsolete("Renamed to simulateMovement")] + public bool canMove { + get => simulateMovement; + set => simulateMovement = value; + } + + /// Speed in world units + public float speed = 3; + + /// + /// Determines which direction the agent moves in. + /// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. + /// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games. + /// + /// Using the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane + /// if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph) + /// will all tell the agent which movement plane it is supposed to use. + /// + /// [Open online documentation to see images] + /// + [UnityEngine.Serialization.FormerlySerializedAs("rotationIn2D")] + public OrientationMode orientation = OrientationMode.ZAxisForward; + + /// + /// If true, the AI will rotate to face the movement direction. + /// See: + /// + public bool enableRotation = true; + + /// How quickly to rotate + public float rotationSpeed = 10; + + /// + /// If true, some interpolation will be done when a new path has been calculated. + /// This is used to avoid short distance teleportation. + /// See: + /// + public bool interpolatePathSwitches = true; + + /// + /// How quickly to interpolate to the new path. + /// See: + /// + public float switchPathInterpolationSpeed = 5; + + /// True if the end of the current path has been reached + public bool reachedEndOfPath { get; private set; } + + /// \copydoc Pathfinding::IAstarAI::reachedDestination + public bool reachedDestination { + get { + if (!reachedEndOfPath || !interpolator.valid) return false; + // Note: distanceToSteeringTarget is the distance to the end of the path when approachingPathEndpoint is true + var dir = destination - interpolator.endPoint; + // Ignore either the y or z coordinate depending on if we are using 2D mode or not + if (orientation == OrientationMode.YAxisForward) dir.z = 0; + else dir.y = 0; + + // Check against using a very small margin + // In theory a check against 0 should be done, but this will be a bit more resilient against targets that move slowly or maybe jitter around due to floating point errors. + if (remainingDistance + dir.magnitude >= 0.05f) return false; + + return true; + } + } + + public Vector3 destination { get; set; } + + /// \copydoc Pathfinding::IAstarAI::movementPlane + public NativeMovementPlane movementPlane { + get { + if (path != null && path.path.Count > 0) { + var graph = path.path[0].Graph; + if (graph is NavmeshBase navmeshBase) { + return new NativeMovementPlane(navmeshBase.transform.ToSimpleMovementPlane()); + } else if (graph is GridGraph gg) { + return new NativeMovementPlane(gg.transform.ToSimpleMovementPlane()); + } + } + return new NativeMovementPlane(Unity.Mathematics.quaternion.identity); + } + } + + /// + /// Determines if the character's position should be coupled to the Transform's position. + /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not move + /// instead only the property will change. + /// + /// See: which in contrast to this field will disable all movement calculations. + /// See: + /// + public bool updatePosition { get; set; } = true; + + /// + /// Determines if the character's rotation should be coupled to the Transform's rotation. + /// If false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate + /// instead only the property will change. + /// + /// See: + /// + public bool updateRotation { get; set; } = true; + + /// + /// Cached delegate for the method. + /// + /// Caching this avoids allocating a new one every time a path is calculated, which reduces GC pressure. + /// + protected OnPathDelegate onPathComplete; + + /// \copydoc Pathfinding::IAstarAI::position + public Vector3 position { get { return updatePosition ? tr.position : simulatedPosition; } } + + /// \copydoc Pathfinding::IAstarAI::rotation + public Quaternion rotation { + get { return updateRotation ? tr.rotation : simulatedRotation; } + set { + if (updateRotation) { + tr.rotation = value; + } else { + simulatedRotation = value; + } + } + } + + /// \copydoc Pathfinding::IAstarAI::endOfPath + public Vector3 endOfPath { + get { + if (interpolator.valid) return interpolator.endPoint; + if (float.IsFinite(destination.x)) return destination; + return position; + } + } + + #region IAstarAI implementation + + /// \copydoc Pathfinding::IAstarAI::Move + void IAstarAI.Move (Vector3 deltaPosition) { + // This script does not know the concept of being away from the path that it is following + // so this call will be ignored (as is also mentioned in the documentation). + } + + /// \copydoc Pathfinding::IAstarAI::radius + float IAstarAI.radius { get { return 0; } set {} } + + /// \copydoc Pathfinding::IAstarAI::height + float IAstarAI.height { get { return 0; } set {} } + + /// \copydoc Pathfinding::IAstarAI::maxSpeed + float IAstarAI.maxSpeed { get { return speed; } set { speed = value; } } + + /// \copydoc Pathfinding::IAstarAI::canSearch + bool IAstarAI.canSearch { get { return canSearch; } set { canSearch = value; } } + + /// \copydoc Pathfinding::IAstarAI::simulateMovement + bool IAstarAI.simulateMovement { get { return simulateMovement; } set { simulateMovement = value; } } + + /// \copydoc Pathfinding::IAstarAI::velocity + public Vector3 velocity { + get { + return Time.deltaTime > 0.00001f ? (previousPosition1 - previousPosition2) / Time.deltaTime : Vector3.zero; + } + } + + Vector3 IAstarAI.desiredVelocity { + get { + // The AILerp script sets the position every frame. It does not take into account physics + // or other things. So the velocity should always be the same as the desired velocity. + return (this as IAstarAI).velocity; + } + } + + Vector3 IAstarAI.desiredVelocityWithoutLocalAvoidance { + get { + // The AILerp script sets the position every frame. It does not take into account physics + // or other things. So the velocity should always be the same as the desired velocity. + return (this as IAstarAI).velocity; + } + set { + throw new System.InvalidOperationException("The AILerp component does not support setting the desiredVelocityWithoutLocalAvoidance property since it does not make sense for how its movement works."); + } + } + + /// \copydoc Pathfinding::IAstarAI::steeringTarget + Vector3 IAstarAI.steeringTarget { + get { + // AILerp doesn't use steering at all, so we will just return a point ahead of the agent in the direction it is moving. + return interpolator.valid ? interpolator.position + interpolator.tangent : simulatedPosition; + } + } + + #endregion + + /// \copydoc Pathfinding::IAstarAI::remainingDistance + public float remainingDistance { + get { + return interpolator.valid ? Mathf.Max(interpolator.remainingDistance, 0) : float.PositiveInfinity; + } + set { + if (!interpolator.valid) throw new System.InvalidOperationException("Cannot set the remaining distance on the AILerp component because it doesn't have a path to follow."); + interpolator.remainingDistance = Mathf.Max(value, 0); + } + } + + /// \copydoc Pathfinding::IAstarAI::hasPath + public bool hasPath { + get { + return interpolator.valid; + } + } + + /// \copydoc Pathfinding::IAstarAI::pathPending + public bool pathPending { + get { + return !canSearchAgain; + } + } + + /// \copydoc Pathfinding::IAstarAI::isStopped + public bool isStopped { get; set; } + + /// \copydoc Pathfinding::IAstarAI::onSearchPath + public System.Action onSearchPath { get; set; } + + /// Cached Seeker component + protected Seeker seeker; + + /// Cached Transform component + protected Transform tr; + + /// Current path which is followed + protected ABPath path; + + /// Only when the previous path has been returned should a search for a new path be done + protected bool canSearchAgain = true; + + /// + /// When a new path was returned, the AI was moving along this ray. + /// Used to smoothly interpolate between the previous movement and the movement along the new path. + /// The speed is equal to movement direction. + /// + protected Vector3 previousMovementOrigin; + protected Vector3 previousMovementDirection; + + /// + /// Time since the path was replaced by a new path. + /// See: + /// + protected float pathSwitchInterpolationTime = 0; + + protected PathInterpolator.Cursor interpolator; + protected PathInterpolator interpolatorPath = new PathInterpolator(); + + + /// + /// Holds if the Start function has been run. + /// Used to test if coroutines should be started in OnEnable to prevent calculating paths + /// in the awake stage (or rather before start on frame 0). + /// + bool startHasRun = false; + + Vector3 previousPosition1, previousPosition2, simulatedPosition; + Quaternion simulatedRotation; + + [SerializeField] + [HideInInspector] + [UnityEngine.Serialization.FormerlySerializedAs("repathRate")] + float repathRateCompatibility = float.NaN; + + [SerializeField] + [HideInInspector] + [UnityEngine.Serialization.FormerlySerializedAs("canSearch")] + bool canSearchCompability = false; + + protected AILerp () { + // Note that this needs to be set here in the constructor and not in e.g Awake + // because it is possible that other code runs and sets the destination property + // before the Awake method on this script runs. + destination = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + } + + /// + /// Initializes reference variables. + /// If you override this function you should in most cases call base.Awake () at the start of it. + /// + protected override void Awake () { + base.Awake(); + //This is a simple optimization, cache the transform component lookup + tr = transform; + + seeker = GetComponent(); + + // Tell the StartEndModifier to ask for our exact position when post processing the path This + // is important if we are using prediction and requesting a path from some point slightly ahead + // of us since then the start point in the path request may be far from our position when the + // path has been calculated. This is also good because if a long path is requested, it may take + // a few frames for it to be calculated so we could have moved some distance during that time + seeker.startEndModifier.adjustStartPoint = () => simulatedPosition; + } + + /// + /// Starts searching for paths. + /// If you override this function you should in most cases call base.Start () at the start of it. + /// See: + /// + protected virtual void Start () { + startHasRun = true; + Init(); + } + + /// Called when the component is enabled + protected virtual void OnEnable () { + onPathComplete = OnPathComplete; + Init(); + } + + void Init () { + if (startHasRun) { + // The Teleport call will make sure some variables are properly initialized (like #prevPosition1 and #prevPosition2) + Teleport(position, false); + autoRepath.Reset(); + if (shouldRecalculatePath) SearchPath(); + } + } + + public void OnDisable () { + ClearPath(); + } + + /// \copydocref{IAstarAI.GetRemainingPath(List,bool)} + public void GetRemainingPath (List buffer, out bool stale) { + buffer.Clear(); + if (!interpolator.valid) { + buffer.Add(position); + stale = true; + return; + } + + stale = false; + interpolator.GetRemainingPath(buffer); + // The agent is almost always at interpolation.position (which is buffer[0]) + // but sometimes - in particular when interpolating between two paths - the agent might at a slightly different position. + // So we replace the first point with the actual position of the agent. + buffer[0] = position; + } + + /// \copydocref{IAstarAI.GetRemainingPath(List,List,bool)} + public void GetRemainingPath (List buffer, List partsBuffer, out bool stale) { + GetRemainingPath(buffer, out stale); + // This movement script doesn't keep track of path parts, so we just add the whole path as a single part + if (partsBuffer != null) { + partsBuffer.Clear(); + partsBuffer.Add(new PathPartWithLinkInfo { startIndex = 0, endIndex = buffer.Count - 1 }); + } + } + + public void Teleport (Vector3 position, bool clearPath = true) { + if (clearPath) ClearPath(); + simulatedPosition = previousPosition1 = previousPosition2 = position; + if (updatePosition) tr.position = position; + reachedEndOfPath = false; + if (clearPath) SearchPath(); + } + + /// True if the path should be automatically recalculated as soon as possible + protected virtual bool shouldRecalculatePath { + get { + return canSearchAgain && autoRepath.ShouldRecalculatePath(position, 0.0f, destination, Time.time); + } + } + + /// Requests a path to the target. + public virtual void SearchPath () { + if (float.IsPositiveInfinity(destination.x)) return; + if (onSearchPath != null) onSearchPath(); + + // This is where the path should start to search from + var currentPosition = GetFeetPosition(); + + // If we are following a path, start searching from the node we will + // reach next this can prevent odd turns right at the start of the path + /*if (interpolator.valid) { + var prevDist = interpolator.distance; + // Move to the end of the current segment + interpolator.MoveToSegment(interpolator.segmentIndex, 1); + currentPosition = interpolator.position; + // Move back to the original position + interpolator.distance = prevDist; + }*/ + + canSearchAgain = false; + + // Create a new path request + // The OnPathComplete method will later be called with the result + SetPath(ABPath.Construct(currentPosition, destination, null), false); + } + + /// + /// The end of the path has been reached. + /// If you want custom logic for when the AI has reached it's destination + /// add it here. + /// You can also create a new script which inherits from this one + /// and override the function in that script. + /// + /// Deprecated: Avoid overriding this method. Instead poll the or properties. + /// + public virtual void OnTargetReached () { + } + + /// + /// Called when a requested path has finished calculation. + /// A path is first requested by , it is then calculated, probably in the same or the next frame. + /// Finally it is returned to the seeker which forwards it to this function. + /// + protected virtual void OnPathComplete (Path _p) { + ABPath p = _p as ABPath; + + if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types"); + + canSearchAgain = true; + + // Increase the reference count on the path. + // This is used for path pooling + p.Claim(this); + + // Path couldn't be calculated of some reason. + // More info in p.errorLog (debug string) + if (p.error) { + p.Release(this); + return; + } + + if (interpolatePathSwitches) { + ConfigurePathSwitchInterpolation(); + } + + + // Replace the old path + var oldPath = path; + + path = p; + reachedEndOfPath = false; + + // The RandomPath and MultiTargetPath do not have a well defined destination that could have been + // set before the paths were calculated. So we instead set the destination here so that some properties + // like #reachedDestination and #remainingDistance work correctly. + if (path is RandomPath rpath) { + destination = rpath.originalEndPoint; + } else if (path is MultiTargetPath mpath) { + destination = mpath.originalEndPoint; + } + + // Just for the rest of the code to work, if there + // is only one waypoint in the path add another one + if (path.vectorPath != null && path.vectorPath.Count == 1) { + path.vectorPath.Insert(0, GetFeetPosition()); + } + + // Reset some variables + ConfigureNewPath(); + + // Release the previous path + // This is used for path pooling. + // This is done after the interpolator has been configured in the ConfigureNewPath method + // as this method would otherwise invalidate the interpolator + // since the vectorPath list (which the interpolator uses) will be pooled. + if (oldPath != null) oldPath.Release(this); + + if (interpolator.remainingDistance < 0.0001f && !reachedEndOfPath) { + reachedEndOfPath = true; + OnTargetReached(); + } + } + + /// + /// Clears the current path of the agent. + /// + /// Usually invoked using (null) + /// + /// See: + /// See: + /// + protected virtual void ClearPath () { + // Abort any calculations in progress + if (seeker != null) seeker.CancelCurrentPathRequest(); + canSearchAgain = true; + reachedEndOfPath = false; + + // Release current path so that it can be pooled + if (path != null) path.Release(this); + path = null; + interpolatorPath.SetPath(null); + } + + /// \copydoc Pathfinding::IAstarAI::SetPath + public void SetPath (Path path, bool updateDestinationFromPath = true) { + if (updateDestinationFromPath && path is ABPath abPath && !(path is RandomPath)) { + this.destination = abPath.originalEndPoint; + } + + if (path == null) { + ClearPath(); + } else if (path.PipelineState == PathState.Created) { + // Path has not started calculation yet + canSearchAgain = false; + seeker.CancelCurrentPathRequest(); + seeker.StartPath(path, onPathComplete); + autoRepath.DidRecalculatePath(destination, Time.time); + } else if (path.PipelineState >= PathState.Returning) { + // Path has already been calculated + + // We might be calculating another path at the same time, and we don't want that path to override this one. So cancel it. + if (seeker.GetCurrentPath() != path) seeker.CancelCurrentPathRequest(); + + OnPathComplete(path); + } else { + // Path calculation has been started, but it is not yet complete. Cannot really handle this. + throw new System.ArgumentException("You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished."); + } + } + + protected virtual void ConfigurePathSwitchInterpolation () { + bool reachedEndOfPreviousPath = interpolator.valid && interpolator.remainingDistance < 0.0001f; + + if (interpolator.valid && !reachedEndOfPreviousPath) { + previousMovementOrigin = interpolator.position; + previousMovementDirection = interpolator.tangent.normalized * interpolator.remainingDistance; + pathSwitchInterpolationTime = 0; + } else { + previousMovementOrigin = Vector3.zero; + previousMovementDirection = Vector3.zero; + pathSwitchInterpolationTime = float.PositiveInfinity; + } + } + + public virtual Vector3 GetFeetPosition () { + return position; + } + + /// Finds the closest point on the current path and configures the + protected virtual void ConfigureNewPath () { + var hadValidPath = interpolator.valid; + var prevTangent = hadValidPath ? interpolator.tangent : Vector3.zero; + + interpolatorPath.SetPath(path.vectorPath); + interpolator = interpolatorPath.start; + interpolator.MoveToClosestPoint(GetFeetPosition()); + + if (interpolatePathSwitches && switchPathInterpolationSpeed > 0.01f && hadValidPath) { + var correctionFactor = Mathf.Max(-Vector3.Dot(prevTangent.normalized, interpolator.tangent.normalized), 0); + interpolator.distance -= speed*correctionFactor*(1f/switchPathInterpolationSpeed); + } + } + + protected virtual void Update () { + if (shouldRecalculatePath) SearchPath(); + if (simulateMovement) { + Vector3 nextPosition; + Quaternion nextRotation; + MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation); + FinalizeMovement(nextPosition, nextRotation); + } + } + + /// \copydoc Pathfinding::IAstarAI::MovementUpdate + public void MovementUpdate (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + if (updatePosition) simulatedPosition = tr.position; + if (updateRotation) simulatedRotation = tr.rotation; + + Vector3 direction; + + nextPosition = CalculateNextPosition(out direction, isStopped ? 0f : deltaTime); + + if (enableRotation) nextRotation = SimulateRotationTowards(direction, deltaTime); + else nextRotation = simulatedRotation; + } + + /// \copydoc Pathfinding::IAstarAI::FinalizeMovement + public void FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) { + previousPosition2 = previousPosition1; + previousPosition1 = simulatedPosition = nextPosition; + simulatedRotation = nextRotation; + if (updatePosition) tr.position = nextPosition; + if (updateRotation) tr.rotation = nextRotation; + } + + Quaternion SimulateRotationTowards (Vector3 direction, float deltaTime) { + // Rotate unless we are really close to the target + if (direction != Vector3.zero) { + Quaternion targetRotation = Quaternion.LookRotation(direction, orientation == OrientationMode.YAxisForward ? Vector3.back : Vector3.up); + // This causes the character to only rotate around the Z axis + if (orientation == OrientationMode.YAxisForward) targetRotation *= Quaternion.Euler(90, 0, 0); + return Quaternion.Slerp(simulatedRotation, targetRotation, deltaTime * rotationSpeed); + } + return simulatedRotation; + } + + /// Calculate the AI's next position (one frame in the future). + /// The tangent of the segment the AI is currently traversing. Not normalized. + /// The time to simulate into the future. + protected virtual Vector3 CalculateNextPosition (out Vector3 direction, float deltaTime) { + if (!interpolator.valid) { + direction = Vector3.zero; + return simulatedPosition; + } + + interpolator.distance += deltaTime * speed; + + if (interpolator.remainingDistance < 0.0001f && !reachedEndOfPath) { + reachedEndOfPath = true; + OnTargetReached(); + } + + direction = interpolator.tangent; + pathSwitchInterpolationTime += deltaTime; + var alpha = switchPathInterpolationSpeed * pathSwitchInterpolationTime; + + if (interpolatePathSwitches && alpha < 1f) { + // Find the approximate position we would be at if we + // would have continued to follow the previous path + Vector3 positionAlongPreviousPath = previousMovementOrigin + Vector3.ClampMagnitude(previousMovementDirection, speed * pathSwitchInterpolationTime); + + // Interpolate between the position on the current path and the position + // we would have had if we would have continued along the previous path. + return Vector3.Lerp(positionAlongPreviousPath, interpolator.position, alpha); + } else { + return interpolator.position; + } + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) { + if (legacyVersion <= 3) { + repathRate = repathRateCompatibility; + canSearch = canSearchCompability; + } + } + } + + public override void DrawGizmos () { + tr = transform; + autoRepath.DrawGizmos(Pathfinding.Drawing.Draw.editor, this.position, 0.0f, new NativeMovementPlane(orientation == OrientationMode.YAxisForward ? Quaternion.Euler(-90, 0, 0) : Quaternion.identity)); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs.meta new file mode 100644 index 0000000..f22788b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AILerp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 847a14d4dc9cc43679ab34fc78e0182f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f2e81a0445323b64f973d2f5b5c56e15, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs new file mode 100644 index 0000000..e45a257 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs @@ -0,0 +1,543 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.Drawing; + + /// + /// AI for following paths. + /// + /// This AI is the default movement script which comes with the A* Pathfinding Project. + /// It is in no way required by the rest of the system, so feel free to write your own. But I hope this script will make it easier + /// to set up movement for the characters in your game. + /// This script works well for many types of units, but if you need the highest performance (for example if you are moving hundreds of characters) you + /// may want to customize this script or write a custom movement script to be able to optimize it specifically for your game. + /// + /// This script will try to move to a given . At , the path to the destination will be recalculated. + /// If you want to make the AI to follow a particular object you can attach the component. + /// Take a look at the getstarted (view in online documentation for working links) tutorial for more instructions on how to configure this script. + /// + /// Here is a video of this script being used move an agent around (it also uses the component to drive the animations): + /// [Open online documentation to see videos] + /// + /// \section variables Quick overview of the variables + /// In the inspector in Unity, you will see a bunch of variables. You can view detailed information further down, but here's a quick overview. + /// + /// The determines how often it will search for new paths, if you have fast moving targets, you might want to set it to a lower value. + /// The field is where the AI will try to move, it can be a point on the ground where the player has clicked in an RTS for example. + /// Or it can be the player object in a zombie game. + /// The is self-explanatory, as is . however might require some explanation: + /// It is the approximate distance from the target where the AI will start to slow down. Setting it to a large value will make the AI slow down very gradually. + /// determines the distance to the point the AI will move to (see image below). + /// + /// Below is an image illustrating several variables that are exposed by this class (, , + /// [Open online documentation to see images] + /// + /// This script has many movement fallbacks. + /// If it finds an RVOController attached to the same GameObject as this component, it will use that. If it finds a character controller it will also use that. + /// If it finds a rigidbody it will use that. Lastly it will fall back to simply modifying Transform.position which is guaranteed to always work and is also the most performant option. + /// + /// \section how-aipath-works How it works + /// In this section I'm going to go over how this script is structured and how information flows. + /// This is useful if you want to make changes to this script or if you just want to understand how it works a bit more deeply. + /// However you do not need to read this section if you are just going to use the script as-is. + /// + /// This script inherits from the class. The movement happens either in Unity's standard Update or FixedUpdate method. + /// They are both defined in the AIBase class. Which one is actually used depends on if a rigidbody is used for movement or not. + /// Rigidbody movement has to be done inside the FixedUpdate method while otherwise it is better to do it in Update. + /// + /// From there a call is made to the method (which in turn calls . + /// This method contains the main bulk of the code and calculates how the AI *wants* to move. However it doesn't do any movement itself. + /// Instead it returns the position and rotation it wants the AI to move to have at the end of the frame. + /// The Update (or FixedUpdate) method then passes these values to the method which is responsible for actually moving the character. + /// That method also handles things like making sure the AI doesn't fall through the ground using raycasting. + /// + /// The AI recalculates its path regularly. This happens in the Update method which checks , and if that returns true it will call . + /// The method will prepare a path request and send it to the component, which should be attached to the same GameObject as this script. + /// + [AddComponentMenu("Pathfinding/AI/AIPath (2D,3D)")] + [UniqueComponent(tag = "ai")] + [DisallowMultipleComponent] + public partial class AIPath : AIBase, IAstarAI { + /// + /// How quickly the agent accelerates. + /// Positive values represent an acceleration in world units per second squared. + /// Negative values are interpreted as an inverse time of how long it should take for the agent to reach its max speed. + /// For example if it should take roughly 0.4 seconds for the agent to reach its max speed then this field should be set to -1/0.4 = -2.5. + /// For a negative value the final acceleration will be: -acceleration*maxSpeed. + /// This behaviour exists mostly for compatibility reasons. + /// + /// In the Unity inspector there are two modes: Default and Custom. In the Default mode this field is set to -2.5 which means that it takes about 0.4 seconds for the agent to reach its top speed. + /// In the Custom mode you can set the acceleration to any positive value. + /// + public float maxAcceleration = -2.5f; + + /// + /// Rotation speed in degrees per second. + /// Rotation is calculated using Quaternion.RotateTowards. This variable represents the rotation speed in degrees per second. + /// The higher it is, the faster the character will be able to rotate. + /// + [UnityEngine.Serialization.FormerlySerializedAs("turningSpeed")] + public float rotationSpeed = 360; + + /// Distance from the end of the path where the AI will start to slow down + public float slowdownDistance = 0.6F; + + /// + /// How far the AI looks ahead along the path to determine the point it moves to. + /// In world units. + /// If you enable the toggle this value will be visualized in the scene view as a blue circle around the agent. + /// [Open online documentation to see images] + /// + /// Here are a few example videos showing some typical outcomes with good values as well as how it looks when this value is too low and too high. + /// + /// + /// + /// + /// + /// + ///
[Open online documentation to see videos]\xmlonly Too low
\endxmlonly A too low value and a too low acceleration will result in the agent overshooting a lot and not managing to follow the path well.
[Open online documentation to see videos]\xmlonly Ok
\endxmlonly A low value but a high acceleration works decently to make the AI follow the path more closely. Note that the component is better suited if you want the agent to follow the path without any deviations.
[Open online documentation to see videos]\xmlonly Ok
\endxmlonly A reasonable value in this example.
[Open online documentation to see videos]\xmlonly Ok
\endxmlonly A reasonable value in this example, but the path is followed slightly more loosely than in the previous video.
[Open online documentation to see videos]\xmlonly Too high
\endxmlonly A too high value will make the agent follow the path too loosely and may cause it to try to move through obstacles.
+ ///
+ public float pickNextWaypointDist = 2; + + /// Draws detailed gizmos constantly in the scene view instead of only when the agent is selected and settings are being modified + public bool alwaysDrawGizmos; + + /// + /// Slow down when not facing the target direction. + /// Incurs at a small performance overhead. + /// + /// This setting only has an effect if is enabled. + /// + public bool slowWhenNotFacingTarget = true; + + /// + /// Prevent the velocity from being too far away from the forward direction of the character. + /// If the character is ordered to move in the opposite direction from where it is facing + /// then enabling this will cause it to make a small loop instead of turning on the spot. + /// + /// This setting only has an effect if is enabled. + /// + public bool preventMovingBackwards = false; + + /// + /// Ensure that the character is always on the traversable surface of the navmesh. + /// When this option is enabled a query will be done every frame to find the closest node that the agent can walk on + /// and if the agent is not inside that node, then the agent will be moved to it. + /// + /// This is especially useful together with local avoidance in order to avoid agents pushing each other into walls. + /// See: local-avoidance (view in online documentation for working links) for more info about this. + /// + /// This option also integrates with local avoidance so that if the agent is say forced into a wall by other agents the local avoidance + /// system will be informed about that wall and can take that into account. + /// + /// Enabling this has some performance impact depending on the graph type (pretty fast for grid graphs, slightly slower for navmesh/recast graphs). + /// If you are using a navmesh/recast graph you may want to switch to the movement script which is specifically written for navmesh/recast graphs and + /// does this kind of clamping out of the box. In many cases it can also follow the path more smoothly around sharp bends in the path. + /// + /// It is not recommended that you use this option together with the funnel modifier on grid graphs because the funnel modifier will make the path + /// go very close to the border of the graph and this script has a tendency to try to cut corners a bit. This may cause it to try to go slightly outside the + /// traversable surface near corners and that will look bad if this option is enabled. + /// + /// Warning: This option makes no sense to use on point graphs because point graphs do not have a surface. + /// Enabling this option when using a point graph will lead to the agent being snapped to the closest node every frame which is likely not what you want. + /// + /// Below you can see an image where several agents using local avoidance were ordered to go to the same point in a corner. + /// When not constraining the agents to the graph they are easily pushed inside obstacles. + /// [Open online documentation to see images] + /// + public bool constrainInsideGraph = false; + + /// Current path which is followed + protected Path path; + + /// Represents the current steering target for the agent + protected PathInterpolator.Cursor interpolator; + /// Helper which calculates points along the current path + protected PathInterpolator interpolatorPath = new PathInterpolator(); + + #region IAstarAI implementation + + /// \copydoc Pathfinding::IAstarAI::Teleport + public override void Teleport (Vector3 newPosition, bool clearPath = true) { + reachedEndOfPath = false; + base.Teleport(newPosition, clearPath); + } + + /// \copydoc Pathfinding::IAstarAI::remainingDistance + public float remainingDistance => interpolator.valid ? interpolator.remainingDistance + movementPlane.ToPlane(interpolator.position - position).magnitude : float.PositiveInfinity; + + /// \copydoc Pathfinding::IAstarAI::reachedDestination + public override bool reachedDestination { + get { + if (!reachedEndOfPath) return false; + if (!interpolator.valid || remainingDistance + movementPlane.ToPlane(destination - interpolator.endPoint).magnitude > endReachedDistance) return false; + + // Don't do height checks in 2D mode + if (orientation != OrientationMode.YAxisForward) { + // Check if the destination is above the head of the character or far below the feet of it + movementPlane.ToPlane(destination - position, out float yDifference); + var h = tr.localScale.y * height; + if (yDifference > h || yDifference < -h*0.5) return false; + } + + return true; + } + } + + /// \copydoc Pathfinding::IAstarAI::reachedEndOfPath + public bool reachedEndOfPath { get; protected set; } + + /// \copydoc Pathfinding::IAstarAI::hasPath + public bool hasPath => interpolator.valid; + + /// \copydoc Pathfinding::IAstarAI::pathPending + public bool pathPending => waitingForPathCalculation; + + /// \copydoc Pathfinding::IAstarAI::steeringTarget + public Vector3 steeringTarget => interpolator.valid ? interpolator.position : position; + + /// \copydoc Pathfinding::IAstarAI::endOfPath + public override Vector3 endOfPath { + get { + if (interpolator.valid) return interpolator.endPoint; + if (float.IsFinite(destination.x)) return destination; + return position; + } + } + + /// \copydoc Pathfinding::IAstarAI::radius + float IAstarAI.radius { get => radius; set => radius = value; } + + /// \copydoc Pathfinding::IAstarAI::height + float IAstarAI.height { get => height; set => height = value; } + + /// \copydoc Pathfinding::IAstarAI::maxSpeed + float IAstarAI.maxSpeed { get => maxSpeed; set => maxSpeed = value; } + + /// \copydoc Pathfinding::IAstarAI::canSearch + bool IAstarAI.canSearch { get => canSearch; set => canSearch = value; } + + /// \copydoc Pathfinding::IAstarAI::simulateMovement + bool IAstarAI.simulateMovement { get => simulateMovement; set => simulateMovement = value; } + + /// \copydoc Pathfinding::IAstarAI::movementPlane + NativeMovementPlane IAstarAI.movementPlane => new NativeMovementPlane(movementPlane); + + #endregion + + /// \copydocref{IAstarAI.GetRemainingPath(List,bool)} + public void GetRemainingPath (List buffer, out bool stale) { + buffer.Clear(); + buffer.Add(position); + if (!interpolator.valid) { + stale = true; + return; + } + + stale = false; + interpolator.GetRemainingPath(buffer); + } + + /// \copydocref{IAstarAI.GetRemainingPath(List,List,bool)} + public void GetRemainingPath (List buffer, List partsBuffer, out bool stale) { + GetRemainingPath(buffer, out stale); + // This movement script doesn't keep track of path parts, so we just add the whole path as a single part + if (partsBuffer != null) { + partsBuffer.Clear(); + partsBuffer.Add(new PathPartWithLinkInfo { startIndex = 0, endIndex = buffer.Count - 1 }); + } + } + + protected override void OnDisable () { + // This will, among other things call ClearPath + base.OnDisable(); + rotationFilterState = Vector2.zero; + rotationFilterState2 = Vector2.zero; + } + + /// + /// The end of the path has been reached. + /// If you want custom logic for when the AI has reached it's destination add it here. You can + /// also create a new script which inherits from this one and override the function in that script. + /// + /// This method will be called again if a new path is calculated as the destination may have changed. + /// So when the agent is close to the destination this method will typically be called every seconds. + /// + /// Deprecated: Avoid overriding this method. Instead poll the or properties. + /// + public virtual void OnTargetReached () { + } + + protected virtual void UpdateMovementPlane () { + if (path.path == null || path.path.Count == 0) return; + var graph = AstarData.GetGraph(path.path[0]) as ITransformedGraph; + IMovementPlane graphTransform = graph != null ? graph.transform : (orientation == OrientationMode.YAxisForward ? new GraphTransform(Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90, 270, 90), Vector3.one)) : GraphTransform.identityTransform); + + movementPlane = graphTransform.ToSimpleMovementPlane(); + } + + /// + /// Called when a requested path has been calculated. + /// A path is first requested by , it is then calculated, probably in the same or the next frame. + /// Finally it is returned to the seeker which forwards it to this function. + /// + protected override void OnPathComplete (Path newPath) { + ABPath p = newPath as ABPath; + + if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types"); + + waitingForPathCalculation = false; + + // Increase the reference count on the new path. + // This is used for object pooling to reduce allocations. + p.Claim(this); + + // Path couldn't be calculated of some reason. + // More info in p.errorLog (debug string) + if (p.error) { + p.Release(this); + SetPath(null); + return; + } + + // Release the previous path. + if (path != null) path.Release(this); + + // Replace the old path + path = p; + + // The RandomPath and MultiTargetPath do not have a well defined destination that could have been + // set before the paths were calculated. So we instead set the destination here so that some properties + // like #reachedDestination and #remainingDistance work correctly. + if (!p.endPointKnownBeforeCalculation) { + destination = p.originalEndPoint; + } + + // Make sure the path contains at least 2 points + if (path.vectorPath.Count == 1) path.vectorPath.Add(path.vectorPath[0]); + interpolatorPath.SetPath(path.vectorPath); + interpolator = interpolatorPath.start; + + UpdateMovementPlane(); + + // Reset some variables + reachedEndOfPath = false; + + // Simulate movement from the point where the path was requested + // to where we are right now. This reduces the risk that the agent + // gets confused because the first point in the path is far away + // from the current position (possibly behind it which could cause + // the agent to turn around, and that looks pretty bad). + interpolator.MoveToLocallyClosestPoint((GetFeetPosition() + p.originalStartPoint) * 0.5f); + interpolator.MoveToLocallyClosestPoint(GetFeetPosition()); + + // Update which point we are moving towards. + // Note that we need to do this here because otherwise the remainingDistance field might be incorrect for 1 frame. + // (due to interpolator.remainingDistance being incorrect). + interpolator.MoveToCircleIntersection2D(position, pickNextWaypointDist, movementPlane); + + var distanceToEnd = remainingDistance; + + if (distanceToEnd <= endReachedDistance) { + reachedEndOfPath = true; + OnTargetReached(); + } + } + + protected override void ClearPath () { + CancelCurrentPathRequest(); + // Release current path so that it can be pooled + if (path != null) path.Release(this); + path = null; + interpolatorPath.SetPath(null); + reachedEndOfPath = false; + } + + /// Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not + protected override void MovementUpdateInternal (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + float currentAcceleration = maxAcceleration; + + // If negative, calculate the acceleration from the max speed + if (currentAcceleration < 0) currentAcceleration *= -maxSpeed; + + if (updatePosition) { + // Get our current position. We read from transform.position as few times as possible as it is relatively slow + // (at least compared to a local variable) + simulatedPosition = tr.position; + } + if (updateRotation) simulatedRotation = tr.rotation; + + var currentPosition = simulatedPosition; + + // Normalized direction of where the agent is looking + var forwards = movementPlane.ToPlane(simulatedRotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward)); + + // Check if we have a valid path to follow and some other script has not stopped the character + bool stopped = isStopped || (reachedDestination && whenCloseToDestination == CloseToDestinationMode.Stop); + + if (rvoController != null) rvoDensityBehavior.Update(rvoController.enabled, reachedDestination, ref stopped, ref rvoController.priorityMultiplier, ref rvoController.flowFollowingStrength, currentPosition); + + float speedLimitFactor = 0; + float distanceToEnd; + // Check if we have a path to follow + if (interpolator.valid) { + // Update which point we are moving towards + interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane); + var dir = movementPlane.ToPlane(steeringTarget - currentPosition); + + // Calculate the distance to the end of the path + distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance); + + // Check if we have reached the target + var prevTargetReached = reachedEndOfPath; + reachedEndOfPath = distanceToEnd <= endReachedDistance; + if (!prevTargetReached && reachedEndOfPath) OnTargetReached(); + + if (!stopped) { + // How fast to move depending on the distance to the destination. + // Move slower as the character gets closer to the destination. + // This is always a value between 0 and 1. + speedLimitFactor = distanceToEnd < slowdownDistance? Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1; + velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized*maxSpeed, velocity2D, currentAcceleration, rotationSpeed, maxSpeed, forwards) * deltaTime; + } + } else { + reachedEndOfPath = false; + distanceToEnd = float.PositiveInfinity; + } + + if (!interpolator.valid || stopped) { + // Slow down as quickly as possible + velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime); + // We are already slowing down as quickly as possible. Avoid limiting the speed in other ways. + speedLimitFactor = 1; + } + + velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, speedLimitFactor, slowWhenNotFacingTarget && enableRotation, preventMovingBackwards, forwards); + + ApplyGravity(deltaTime); + bool avoidingOtherAgents = false; + + if (rvoController != null && rvoController.enabled) { + // Send a message to the RVOController that we want to move + // with this velocity. In the next simulation step, this + // velocity will be processed and it will be fed back to the + // rvo controller and finally it will be used by this script + // when calling the CalculateMovementDelta method below + + // Make sure that we don't move further than to the end point + // of the path. If the RVO simulation FPS is low and we did + // not do this, the agent might overshoot the target a lot. + var rvoTarget = currentPosition + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEnd), 0f); + rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed, endOfPath); + avoidingOtherAgents = rvoController.AvoidingAnyAgents; + } + + // Set how much the agent wants to move during this frame + var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(currentPosition, distanceToEnd, deltaTime); + nextPosition = currentPosition + movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime); + CalculateNextRotation(speedLimitFactor, avoidingOtherAgents, out nextRotation); + } + + Vector2 rotationFilterState, rotationFilterState2; + + protected virtual void CalculateNextRotation (float slowdown, bool avoidingOtherAgents, out Quaternion nextRotation) { + if (lastDeltaTime > 0.00001f && enableRotation) { + // Rotate towards the direction we are moving in + // Filter out noise in the movement direction + // This is especially important when the agent is almost standing still and when using local avoidance + float noiseThreshold = radius * tr.localScale.x * 0.2f; + float rotationSpeedFactor = MovementUtilities.FilterRotationDirection(ref rotationFilterState, ref rotationFilterState2, lastDeltaPosition, noiseThreshold, lastDeltaTime, avoidingOtherAgents); + nextRotation = SimulateRotationTowards(rotationFilterState, rotationSpeed * lastDeltaTime * rotationSpeedFactor, rotationSpeed * lastDeltaTime); + } else { + // TODO: simulatedRotation + nextRotation = rotation; + } + } + + protected override Vector3 ClampToNavmesh (Vector3 position, out bool positionChanged) { + if (constrainInsideGraph) { + var constraint = NearestNodeConstraint.Walkable; + constraint.tags = seeker.traversableTags; + constraint.graphMask = seeker.graphMask; + constraint.distanceMetric = DistanceMetric.ClosestAsSeenFromAboveSoft(); + // Note: We don't want to set nn.constrainDistance = false (i.e. allow finding nodes arbitrarily far away), because that can lead to harsh + // performance cliffs if agents for example fall through the ground or get thrown off the map, or something like that (it's bound to happen in some games). + var nearestOnNavmesh = AstarPath.active.GetNearest(position, constraint); + + if (nearestOnNavmesh.node == null) { + // Found no valid node to constrain to. This can happen if there are no valid nodes close enough to the agent. + positionChanged = false; + return position; + } + + var clampedPosition = nearestOnNavmesh.position; + + if (rvoController != null && rvoController.enabled) { + // Inform the RVO system about the edges of the navmesh which will allow + // it to better keep inside the navmesh in the first place. + rvoController.SetObstacleQuery(nearestOnNavmesh.node); + } + + // We cannot simply check for equality because some precision may be lost + // if any coordinate transformations are used. + var difference = movementPlane.ToPlane(clampedPosition - position); + float sqrDifference = difference.sqrMagnitude; + if (sqrDifference > 0.001f*0.001f) { + // The agent was outside the navmesh. Remove that component of the velocity + // so that the velocity only goes along the direction of the wall, not into it + velocity2D -= difference * Vector2.Dot(difference, velocity2D) / sqrDifference; + + positionChanged = true; + // Return the new position, but ignore any changes in the y coordinate from the ClampToNavmesh method as the y coordinates in the navmesh are rarely very accurate + return position + movementPlane.ToWorld(difference); + } + } + + positionChanged = false; + return position; + } + +#if UNITY_EDITOR + [System.NonSerialized] + int gizmoHash = 0; + + [System.NonSerialized] + float lastChangedTime = float.NegativeInfinity; + + protected static readonly Color GizmoColor = new Color(46.0f/255, 104.0f/255, 201.0f/255); + + public override void DrawGizmos () { + base.DrawGizmos(); + + // If alwaysDrawGizmos is false, gizmos are only visible for a short while after the user changes any settings on this component + var newGizmoHash = pickNextWaypointDist.GetHashCode() ^ slowdownDistance.GetHashCode() ^ endReachedDistance.GetHashCode(); + + if (newGizmoHash != gizmoHash && gizmoHash != 0) lastChangedTime = Time.realtimeSinceStartup; + gizmoHash = newGizmoHash; + float alpha = alwaysDrawGizmos ? 1 : Mathf.SmoothStep(1, 0, (Time.realtimeSinceStartup - lastChangedTime - 5f)/0.5f) * (GizmoContext.selectionSize == 1 ? 1 : 0); + + if (alpha > 0) { + // Make sure the scene view is repainted while the gizmos are visible + if (!alwaysDrawGizmos) UnityEditor.SceneView.RepaintAll(); + Draw.Line(position, steeringTarget, GizmoColor * new Color(1, 1, 1, alpha)); + using (Draw.WithMatrix(Matrix4x4.TRS(position, transform.rotation * (orientation == OrientationMode.YAxisForward ? Quaternion.Euler(-90, 0, 0) : Quaternion.identity), Vector3.one))) { + Draw.xz.Circle(Vector3.zero, pickNextWaypointDist, GizmoColor * new Color(1, 1, 1, alpha)); + Draw.xz.Circle(Vector3.zero, slowdownDistance, Color.Lerp(GizmoColor, Color.red, 0.5f) * new Color(1, 1, 1, alpha)); + Draw.xz.Circle(Vector3.zero, endReachedDistance, Color.Lerp(GizmoColor, Color.red, 0.8f) * new Color(1, 1, 1, alpha)); + } + } + } +#endif + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.IsLegacyFormat) { + // Approximately convert from a damping value to a degrees per second value. + if (migrations.LegacyVersion < 1) rotationSpeed *= 90; + // The base call will migrate the legacy format further + } + base.OnUpgradeSerializedData(ref migrations, unityThread); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs.meta new file mode 100644 index 0000000..435260d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/AIPath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6eb1402c17e84a9282a7f0f62eb584f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f2e81a0445323b64f973d2f5b5c56e15, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs new file mode 100644 index 0000000..1727783 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs @@ -0,0 +1,1825 @@ +#pragma warning disable CS0282 // "There is no defined ordering between fields in multiple declarations of partial struct" +#if MODULE_ENTITIES +using UnityEngine; +using Unity.Mathematics; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Entities; +using Unity.Transforms; +using Unity.Profiling; + +namespace Pathfinding { + using Pathfinding.Drawing; + using Pathfinding.Util; + using System; + using Pathfinding.PID; + using Pathfinding.ECS.RVO; + using Pathfinding.ECS; + + /// + /// Movement script that uses ECS. + /// + /// This script is a replacement for the and scripts. + /// + /// This script is a movement script. It takes care of moving an agent along a path, updating the path, and so on. + /// + /// \section followerentity-contents Contents + /// \toc + /// + /// \section followerentity-intro Introduction + /// + /// The intended way to use this script is to use these two components: + /// - + /// - (optional, you can instead set the property manually) + /// + /// Of note is that this component shouldn't be used with a component. + /// It has its own settings for pathfinding instead, which are stored in the field. + /// + /// When used with local avoidance, it also has its own settings for local avoidance which are stored in the field, + /// instead of using a separate component. + /// + /// Other than that, you can use it just like the other movement scripts in this package. + /// + /// \section followerentity-features Features + /// + /// - Uses Unity's ECS (Entity Component System) to move the agent. This means it is highly-performant and is able to utilize multiple threads. + /// You do not need to know anything about ECS to be able to use it. + /// - Supports local avoidance (see local-avoidance) (view in online documentation for working links). + /// - Supports movement in both 2D and 3D games. + /// - Supports movement on spherical on non-planar worlds (see spherical) (view in online documentation for working links). + /// - Supports movement on grid graphs as well as navmesh/recast graphs. + /// - Not recommended on hexagonal graphs at the moment (though it does mostly work). + /// - Does not support movement on point graphs at the moment. This may be added in a future update. + /// - Supports time-scales greater than 1. The agent will automatically run multiple simulation steps per frame if the time-scale is greater than 1, to ensure stability. + /// - Supports off-mesh links. See offmeshlinks (view in online documentation for working links) for more info. + /// - Knows which node it is traversing at all times (see . + /// - Automatically stops when trying to reach a crowded destination when using local avoidance. + /// - Clamps the agent to the navmesh at all times. + /// - Follows paths very smoothly. + /// - Can keep a desired distance to walls. + /// - Can approach its destination with a desired facing direction. + /// + /// \section followerentity-inspector Inspector + /// + /// Shape + /// \inspectorField{radius; Radius} + /// \inspectorField{height; Height} + /// \inspectorField{orientation; Orientation} + /// + /// Movement + /// \inspectorField{maxSpeed; Speed} + /// \inspectorField{rotationSpeed; Rotation Speed} + /// \inspectorField{maxRotationSpeed; Max Rotation Speed} + /// \inspectorField{movementSettings.follower.allowRotatingOnSpot; Allow Rotating On The Spot} + /// \inspectorField{movementSettings.follower.maxOnSpotRotationSpeed; Max On Spot Rotation Speed} + /// \inspectorField{movementSettings.follower.slowdownTimeWhenTurningOnSpot; Slowdown Time When Turning On Spot} + /// \inspectorField{positionSmoothing; Position Smoothing} + /// \inspectorField{rotationSmoothing; Rotation Smoothing} + /// \inspectorField{movementSettings.follower.slowdownTime; Slowdown Time} + /// \inspectorField{movementSettings.stopDistance; Stop Distance} + /// \inspectorField{movementSettings.follower.leadInRadiusWhenApproachingDestination; Lead In Radius When Approaching Destination} + /// \inspectorField{movementSettings.follower.desiredWallDistance; Desired Wall Distance} + /// \inspectorField{enableGravity; Gravity} + /// \inspectorField{groundMask; Raycast Ground Mask} + /// \inspectorField{movementPlaneSource; Movement Plane Source} + /// + /// %Pathfinding + /// \inspectorField{pathfindingSettings.graphMask; Traversable Graphs} + /// \inspectorField{pathfindingSettings.tagCostMultipliers; Tags/Cost per world unit} + /// \inspectorField{pathfindingSettings.traversableTags; Tags/Traversable} + /// \inspectorField{autoRepath.mode; Recalculate Paths Automatically} + /// \inspectorField{autoRepath.period; Period} + /// + /// Local Avoidance + /// \inspectorField{enableLocalAvoidance; Enable Local Avoidance} + /// \inspectorField{rvoSettings.agentTimeHorizon; Agent Time Horizon} + /// \inspectorField{rvoSettings.obstacleTimeHorizon; Obstacle Time Horizon} + /// \inspectorField{rvoSettings.maxNeighbours; Max Neighbours} + /// \inspectorField{rvoSettings.layer; Layer} + /// \inspectorField{rvoSettings.collidesWith; Collides With} + /// \inspectorField{rvoSettings.priority; Priority} + /// \inspectorField{rvoSettings.locked; Locked} + /// + /// Debug + /// \inspectorField{debugFlags; Movement Debug Rendering} + /// \inspectorField{rvoSettings.debug; Local Avoidance Debug Rendering} + /// + /// Debug Info Foldout + /// \inspectorField{reachedDestination; Reached Destination} + /// \inspectorField{reachedEndOfPath; Reached End Of Path} + /// \inspectorField{reachedCrowdedEndOfPath; Reached (maybe crowded) End Of Path} + /// \inspectorField{hasPath; Has Path} + /// \inspectorField{pathPending; Path Pending} + /// \inspectorField{destination; Destination} + /// \inspectorField{remainingDistance; Remaining Distance} + /// \inspectorField{velocity; Speed} + /// + /// \section followerentity-ecs ECS + /// + /// This script uses Unity's ECS (Entity Component System) to move the agent. This means it is highly-performant and is able to utilize multiple threads. + /// Internally, an entity is created for the agent with the following components: + /// + /// - LocalTransform + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - - actually created during the first frame by + /// - (if local avoidance is enabled) + /// - - tag component (if is enabled) + /// - - tag component + /// - - tag component + /// - - tag component + /// - - tag component (if is enabled) + /// - - tag component (if is enabled) + /// - - tag component (if is .YAxisForward) + /// - - tag component, always exists, but enabled if the agent is about to start traversing an off-mesh link + /// - - tag component, always exists, but enabled internally if the agent should recalculate its path. + /// - - shared component + /// - - shared component + /// + /// Then this script barely does anything by itself. It is a thin wrapper around the ECS components. + /// Instead, actual movement calculations are carried out by the following systems: + /// + /// - - Updates the entity's transform from the GameObject. + /// - - Updates the agent's movement plane. + /// - - Updates the destination point if the destination transform moves. + /// - - Schedules path calculations when necessary. + /// - - Handles off-mesh link traversal. + /// - - Keeps the agent's path up to date. + /// - - Calculates how the agent wants to move. + /// - - Local avoidance calculations. + /// - - NOOP system for if local avoidance is disabled. + /// - - Performs the actual movement. + /// + /// In fact, as long as you create the appropriate ECS components, you do not even need this script. You can use the systems directly. + /// + /// This component can be baked or used as a normal monobehaviour, depending on if it is in a subscene or not. + /// When used outside a subscene, this script will continue to work even in standalone games. It is designed to be easily used + /// without having to care too much about the underlying ECS implementation. + /// + /// See: ecs (view in online documentation for working links) + /// + /// \section followerentity-subscenes Usage in ECS subscenes + /// This component can be used in subscenes and it will automatically be baked into a high-performance ECS entity. + /// Using a subscene will give you the highest performance, as you'll avoid the performance and memory overhead of keeping a GameObject and this component around. + /// However, it makes it a bit harder to use, since you cannot use the MonoBehaviour API. + /// + /// I recommend using subscenes if you have a lot of agents (>1000) and you need the best performance possible. + /// However, in the prototyping stage, using a MonoBehaviour is often far easier, so I recommend using this component as a MonoBehaviour in the beginning. + /// + /// Even when not using a subscene, ECS will be used behind the scenes, so you'll still get most of the performance benefits of using ECS. + /// + /// If you have the component on the same GameObject, it will be automatically converted to a component in the subscene. + /// + /// Note: When used in a subscene, you cannot use any properties, fields, or methods on this component in play mode, since this component is baked away and won't even exist at runtime. + /// Instead, you can either use ECS components directly, or you can use the wrapper which acts almost identically to this component, but uses an entity as the source instead. + /// + /// + /// var follower = new FollowerEntityProxy(world, entity); + /// follower.maxSpeed = 5; + /// follower.destination = new Vector3(1, 2, 3); + /// + /// if (follower.currentNode.Tag == 1) { + /// Debug.Log("The agent is right now traversing a node with tag 1"); + /// } + /// + /// + /// See: + /// See: https://docs.unity3d.com/Packages/com.unity.entities@1.4/manual/conversion-subscenes.html + /// See: ecs (view in online documentation for working links) + /// + /// \section followerentity-differences Differences compared to AIPath and RichAI + /// + /// This movement script has been written to remedy several inconsistency issues with other movement scrips, to provide very smooth movement, + /// and "just work" for most games. + /// + /// For example, it goes to great lengths to ensure + /// that the and properties are as accurate as possible at all times, even before it has had time to recalculate its path to account for a new . + /// It does this by locally repairing the path (if possible) immediately when the destination changes instead of waiting for a path recalculation. + /// This also has a bonus effect that the agent can often work just fine with moving targets, even if it almost never recalculates its path (though the repaired path may not always be optimal), + /// and it leads to very responsive movement. + /// + /// In contrast to other movement scripts, this movement script does not use path modifiers at all. + /// Instead, this script contains its own internal which it uses to simplify the path before it follows it. + /// In also doesn't use a separate component for local avoidance, but instead it stores local avoidance settings in . + /// + /// \section followerentity-bestpractices Best practices for good performance + /// + /// Here are some tips for how to improve performance when using this script. + /// As always, make sure to profile your game first, to see what is actually causing performance problems. + /// + /// Disable unused features + /// This script has some optional parts. Local avoidance, for example. Local avoidance is used to make sure that agents do not overlap with each other. + /// However, if you do not need it, you can disable it to improve performance. + /// + /// Don't change the destination unnecessarily + /// Repairing the path each frame can be a significant part of the movement calculation time. The FollowerEntity will perform better + /// if the is static, or moves seldom. For example, updating the destination every 10 frames will be faster than updating it every frame, + /// but to the player, both will look basically the same. + /// + /// Note: Repairing the path is different from recalculating it from scratch. The agent will recalculate the path from scratch relatively seldom, + /// but it will repair it every frame, if necessary, to account for small changes in the agent's position and destination. + /// + /// Disable debug rendering + /// Debug rendering has some performance costs in the Unity Editor. Disable all and to improve performance. + /// However, in standalone builds, these are automatically disabled and have no cost. + /// + /// Be aware of property access costs + /// Using ECS components has some downsides. Accessing properties on this script is significantly slower compared to accessing properties on other movement scripts. + /// This is because on each property access, the script has to make sure no jobs are running concurrently, which is a relatively expensive operation. + /// Slow is a relative term, though. This only starts to matter if you have lots of agents, maybe a hundred or so. So don't be scared of using it. + /// + /// But if you have a lot of agents, it is recommended to not access properties on this script more often than required. Avoid setting fields to the same value over and over again every frame, for example. + /// If you have a moving target, try to use the component instead of setting the property manually, as that is faster than setting the property every frame. + /// + /// You can instead write custom ECS systems to access the properties on the ECS components directly. This is much faster. + /// For example, if you want to make the agent follow a particular entity, you could create a new DestinationEntity component which just holds an entity reference, + /// and then create a system that every frame copies that entity's position to the field (a component that this entity will always have). + /// + /// Use subscenes + /// If you have a lot of agents, moving the GameObject into a subscene will give you the best performance. + /// This is because subscenes are baked into ECS entities, which have a much lower memory overhead than GameObjects, and avoids other GameObject overhead. + /// + /// See: ecs (view in online documentation for working links) + /// See: followerentity-subscenes (view in online documentation for working links) + /// + /// \section followerentity-timescale Time scaling + /// This component will automatically run multiple simulation steps per frame if the time scale is greater than 1. + /// This is done to ensure that the movement remains stable even at high time scales. + /// One case when this happens is when fast-forwarding games, which is common in some types of city builders and other types of simulation games. + /// This will impact performance at high time scales, but it is necessary to ensure that the movement remains stable. + /// + [AddComponentMenu("Pathfinding/AI/Follower Entity (2D,3D)")] + [UniqueComponent(tag = "ai")] + [UniqueComponent(tag = "rvo")] + [DisallowMultipleComponent] + public sealed partial class FollowerEntity : VersionedMonoBehaviour, IAstarAI, ISerializationCallbackReceiver { + [SerializeField] + AgentCylinderShape shape = new AgentCylinderShape { + height = 2, + radius = 0.5f, + }; + [SerializeField] + MovementSettings movement = new MovementSettings { + follower = new PIDMovement { + rotationSpeed = 600, + speed = 5, + maxRotationSpeed = 720, + maxOnSpotRotationSpeed = 720, + slowdownTime = 0.5f, + desiredWallDistance = 0.5f, + allowRotatingOnSpot = true, + leadInRadiusWhenApproachingDestination = 1f, + }, + stopDistance = 0.2f, + rotationSmoothing = 0f, + groundMask = -1, + isStopped = false, + debugFlags = PIDMovement.DebugFlags.Path, + }; + + #pragma warning disable CS0618 // Type or member is obsolete + [SerializeField] + ManagedState managedState = new ManagedState { + enableLocalAvoidance = false, + pathfindingSettings = PathRequestSettings.Default, + }; + #pragma warning restore CS0618 // Type or member is obsolete + + [SerializeField] + ManagedSettings managedSettings = new ManagedSettings { + pathfindingSettings = PathRequestSettings.Default, + }; + + [SerializeField] + bool enableLocalAvoidanceBacking = false; + + [SerializeField] + bool enableGravityBacking = true; + + [SerializeField] + RVOAgent rvoSettingsBacking = RVOAgent.Default; + + [SerializeField] + ECS.AutoRepathPolicy autoRepathBacking = ECS.AutoRepathPolicy.Default; + + /// + /// Determines which direction the agent moves in. + /// + /// See: + /// + [SerializeField] + OrientationMode orientationBacking; + [SerializeField] + MovementPlaneSource movementPlaneSourceBacking = MovementPlaneSource.Graph; + + /// \copydocref{updatePosition} + [SerializeField] + bool syncPosition = true; + + /// \copydocref{updateRotation} + [SerializeField] + bool syncRotation = true; + + /// Cached transform component + Transform tr; + + /// + /// Proxy for the entity which this movement script represents. + /// + /// An entity will be created when this script is enabled, and destroyed when this script is disabled. + /// + /// Check the class documentation to see which components it usually has, and what systems typically affect it. + /// + FollowerEntityProxy proxy; + + /// + /// Entity which this movement script represents. + /// + /// This is the same as proxy.entity, but is provided for convenience. + /// It is only valid when this script is enabled. + /// + /// See: FollowerEntityProxy + /// + public Entity entity { + [IgnoredByDeepProfiler] + get => proxy.entity; + } + + /// + /// The world in which the resides. + /// + /// This is the same as proxy.world, but is provided for convenience. + /// It is only valid when this script is enabled. + /// + /// See: FollowerEntityProxy + /// + public World world => proxy.world; + + static EntityArchetype archetype; + static World archetypeWorld; + +#if !UNITY_2023_1_OR_NEWER + bool didStart; +#endif + + void OnEnable () { + FindComponents(); + var entity = CreateEntity(tr.position, tr.rotation, tr.localScale.x, ref shape, ref movement, ref autoRepathBacking, managedState, orientationBacking, movementPlaneSourceBacking, syncPosition, syncRotation, enableGravityBacking, enableLocalAvoidanceBacking, rvoSettingsBacking, managedSettings, PhysicsSceneExtensions.GetPhysicsScene(gameObject.scene)); + proxy = new FollowerEntityProxy(World.DefaultGameObjectInjectionWorld, entity); + + // Register with the BatchedEvents system + // This is used not for the events, but because it keeps track of a TransformAccessArray + // of all components. This is then used by the SyncTransformsToEntitiesSystem. + BatchedEvents.Add(this, BatchedEvents.Event.None, (components, ev) => {}); + + var runtimeBakers = GetComponents(); + for (int i = 0; i < runtimeBakers.Length; i++) if (((MonoBehaviour)runtimeBakers[i]).enabled) runtimeBakers[i].OnCreatedEntity(World.DefaultGameObjectInjectionWorld, entity); + + // Make sure Start runs every time after OnEnable. + // When the game starts we don't want it to run immediately, because the graphs may not be scanned. + // But if the component is enabled at some later point in the game, it can run at the same time as OnEnable. + if (didStart) Start(); + } + + /// + /// Creates an entity with the given data. + /// + /// If you don't want to use the FollowerEntity MonoBehaviour, you can use this method to create an equivalent entity directly. + /// + public static Entity CreateEntity (float3 position, quaternion rotation, float scale, ref AgentCylinderShape shape, ref MovementSettings movement, ref ECS.AutoRepathPolicy autoRepath, ManagedState managedState, OrientationMode orientation, MovementPlaneSource movementPlaneSource, bool updatePosition, bool updateRotation, bool enableGravity, bool enableLocalAvoidance, RVOAgent rvoSettings, ManagedSettings managedSettings, PhysicsScene physicsScene) { + var world = World.DefaultGameObjectInjectionWorld; + if (!archetype.Valid || archetypeWorld != world) { + if (world == null) throw new Exception("World.DefaultGameObjectInjectionWorld is null. Has the world been destroyed?"); + archetypeWorld = world; + archetype = world.EntityManager.CreateArchetype( + typeof(LocalTransform), + typeof(MovementState), + typeof(MovementSettings), + typeof(ECS.AutoRepathPolicy), + typeof(MovementControl), + typeof(ManagedState), + typeof(ManagedSettings), + typeof(SearchState), + typeof(MovementStatistics), + typeof(AgentCylinderShape), + typeof(ResolvedMovement), + typeof(DestinationPoint), + typeof(AgentMovementPlane), + typeof(GravityState), + typeof(SimulateMovement), + typeof(SimulateMovementRepair), + typeof(SimulateMovementControl), + typeof(SimulateMovementFinalize), + typeof(SyncPositionWithTransform), + typeof(SyncRotationWithTransform), + typeof(ReadyToTraverseOffMeshLink), + typeof(AgentMovementPlaneSource), + typeof(PhysicsSceneRef), + typeof(AgentShouldRecalculatePath) + ); + } + + if (orientation == OrientationMode.YAxisForward) rotation = math.mul(rotation, SyncTransformsToEntitiesSystem.YAxisForwardToZAxisForward); + + var entityManager = world.EntityManager; + var entity = entityManager.CreateEntity(archetype); + // This GameObject may be in a hierarchy, but the entity will not be. So we copy the world orientation to the entity's local transform component + entityManager.SetComponentData(entity, LocalTransform.FromPositionRotationScale(position, rotation, scale)); + entityManager.SetComponentData(entity, new MovementState(position)); +#if UNITY_EDITOR + entityManager.SetName(entity, "Follower Entity"); +#endif + entityManager.SetComponentData(entity, new DestinationPoint { + destination = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity), + }); + autoRepath.Reset(); + entityManager.SetComponentData(entity, autoRepath); + entityManager.SetComponentData(entity, movement); + if (!managedState.pathTracer.isCreated) { + managedState.pathTracer = new PathTracer(Allocator.Persistent); + } + entityManager.SetComponentData(entity, managedState); + entityManager.SetComponentData(entity, managedSettings); + entityManager.SetComponentData(entity, new MovementStatistics { + estimatedVelocity = float3.zero, + lastPosition = position, + }); + entityManager.SetComponentData(entity, shape); + entityManager.SetComponentEnabled(entity, enableGravity); + if (orientation == OrientationMode.YAxisForward) { + entityManager.AddComponent(entity); + } + entityManager.SetComponentEnabled(entity, false); + entityManager.SetSharedComponent(entity, new AgentMovementPlaneSource { value = movementPlaneSource }); + entityManager.SetSharedComponent(entity, new PhysicsSceneRef { physicsScene = physicsScene }); + if (enableLocalAvoidance) { + entityManager.AddComponentData(entity, rvoSettings); + } + FollowerEntityProxy.ToggleComponent(world, entity, updatePosition, true); + FollowerEntityProxy.ToggleComponent(world, entity, updateRotation, true); + + ResolvedMovement resolvedMovement = default; + MovementControl movementControl = default; + AgentMovementPlane movementPlane = new AgentMovementPlane(rotation); + FollowerEntityProxy.ResetControl(ref resolvedMovement, ref movementControl, ref movementPlane, position, rotation, position); + + // Set the initial movement plane. This will be overriden before the first simulation loop runs. + entityManager.SetComponentData(entity, movementPlane); + + entityManager.SetComponentData(entity, resolvedMovement); + entityManager.SetComponentData(entity, movementControl); + entityManager.SetComponentEnabled(entity, false); + + return entity; + } + + internal void RegisterRuntimeBaker (IRuntimeBaker baker) { + if (entityExists) baker.OnCreatedEntity(World.DefaultGameObjectInjectionWorld, proxy.entity); + } + + void Start () { +#if !UNITY_2023_1_OR_NEWER + didStart = true; +#endif + // This will attach the entity to the navmesh (if one exists), to make things like #currentNode and being snapped to the graph surface work immediately. + // Otherwise, we'd have to wait for the first path calculation to finish. + Teleport(position, false); + } + + /// + /// Called when the component is disabled or about to be destroyed. + /// + /// This is also called by Unity when an undo/redo event is performed. This means that + /// when an undo event happens the entity will get destroyed and then re-created. + /// + void OnDisable () { + BatchedEvents.Remove(this); + CancelCurrentPathRequest(); + + // Destroy the entity and all components associated with it. + // Note: The entity itself may actually live for another frame to handle cleanup components. + // But we want to completely forget about it here. + proxy.Destroy(); + // Make sure the managed state gets disposed, even if no entity exists. If an entity exists, this will be automatically called. + managedState.Dispose(); + } + + /// \copydoc Pathfinding::IAstarAI::radius + public float radius { + get => shape.radius; + set { + shape.radius = value; + proxy.radius = value; + } + } + + /// + /// Height of the agent in world units. + /// This is visualized in the scene view as a yellow cylinder around the character. + /// + /// This value is used for various heuristics, and for visualization purposes. + /// For example, the destination is only considered reached if the destination is not above the agent's head, and it's not more than half the agent's height below its feet. + /// + /// If local lavoidance is enabled, this is also used to filter out collisions with agents and obstacles that are too far above or below the agent. + /// + public float height { + get => shape.height; + set { + shape.height = value; + proxy.height = value; + } + } + + /// + /// %Pathfinding settings. + /// + /// The settings in this struct controls how the agent calculates paths to its destination. + /// + /// This is analogous to the component used for other movement scripts. + /// + /// See: + /// + public ref PathRequestSettings pathfindingSettings { + get { + // Complete any job dependencies + // Need RW because this getter has a ref return. + if (proxy.world != null) FollowerEntityProxy.movementStateAccessRW.Update(proxy.world.EntityManager); + return ref managedSettings.pathfindingSettings; + } + } + + /// Local avoidance settings + public RVOAgent rvoSettings { + get => entityExists ? proxy.rvoSettings : rvoSettingsBacking; + set { + rvoSettingsBacking = value; + proxy.rvoSettings = value; + } + } + + /// \copydoc Pathfinding::IAstarAI::position + public Vector3 position { + get => entityExists ? proxy.position : transform.position; + set { + if (proxy.entityExists) { + proxy.position = value; + if (proxy.updatePosition) transform.position = value; + } else { + transform.position = value; + } + } + } + + /// + /// True if the agent is currently traversing an off-mesh link. + /// + /// See: offmeshlinks (view in online documentation for working links) + /// See: + /// See: + /// + public bool isTraversingOffMeshLink => proxy.isTraversingOffMeshLink; + + /// + /// The off-mesh link that the agent is currently traversing. + /// + /// This will be a default if the agent is not traversing an off-mesh link (the field will be null). + /// + /// Note: If the off-mesh link is destroyed while the agent is traversing it, this property will still return the link. + /// But be careful about accessing properties like , as that may refer to a destroyed gameObject. + /// + /// See: offmeshlinks (view in online documentation for working links) + /// See: + /// See: + /// See: + /// + public OffMeshLinks.OffMeshLinkTracer offMeshLink => proxy.offMeshLink; + + /// + /// The off-mesh link that the agent will traverse next, or the one it is traversing (if any). + /// + /// The next link in the path will be returned regardless of the distance to it. + /// + /// This will be a default if the agent is not traversing an off-mesh link, and there are no remaining off-mesh links in its path (the field will be null). + /// + /// Note: If the off-mesh link is destroyed while the agent is traversing it, this property will still return the link. + /// But be careful about accessing properties like , as that may refer to a destroyed gameObject. + /// + /// See: offmeshlinks (view in online documentation for working links) + /// See: + /// See: + /// See: + /// See: + /// + public OffMeshLinks.OffMeshLinkTracer nextOffMeshLink => proxy.nextOffMeshLink; + + /// + /// Callback to be called when an agent starts traversing an off-mesh link. + /// + /// The handler will be called when the agent starts traversing an off-mesh link. + /// It allows you to to control the agent for the full duration of the link traversal. + /// + /// Use the passed context struct to get information about the link and to control the agent. + /// + /// + /// using UnityEngine; + /// using Pathfinding; + /// using System.Collections; + /// using Pathfinding.ECS; + /// + /// namespace Pathfinding.Examples { + /// public class FollowerJumpLink : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine { + /// // Register this class as the handler for off-mesh links when the component is enabled + /// void OnEnable() => GetComponent().onTraverseOffMeshLink = this; + /// void OnDisable() => GetComponent().onTraverseOffMeshLink = null; + /// + /// IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this; + /// + /// void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) { + /// Debug.Log("An agent finished traversing an off-mesh link"); + /// } + /// + /// void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () { + /// Debug.Log("An agent aborted traversing an off-mesh link"); + /// } + /// + /// IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) { + /// var start = (Vector3)ctx.link.relativeStart; + /// var end = (Vector3)ctx.link.relativeEnd; + /// var dir = end - start; + /// + /// // Disable local avoidance while traversing the off-mesh link. + /// // If it was enabled, it will be automatically re-enabled when the agent finishes traversing the link. + /// ctx.DisableLocalAvoidance(); + /// + /// // Move and rotate the agent to face the other side of the link. + /// // When reaching the off-mesh link, the agent may be facing the wrong direction. + /// while (!ctx.MoveTowards( + /// position: start, + /// rotation: Quaternion.LookRotation(dir, ctx.movementPlane.up), + /// gravity: true, + /// slowdown: true).reached) { + /// yield return null; + /// } + /// + /// var bezierP0 = start; + /// var bezierP1 = start + Vector3.up*5; + /// var bezierP2 = end + Vector3.up*5; + /// var bezierP3 = end; + /// var jumpDuration = 1.0f; + /// + /// // Animate the AI to jump from the start to the end of the link + /// for (float t = 0; t < jumpDuration; t += ctx.deltaTime) { + /// ctx.transform.Position = AstarSplines.CubicBezier(bezierP0, bezierP1, bezierP2, bezierP3, Mathf.SmoothStep(0, 1, t / jumpDuration)); + /// yield return null; + /// } + /// } + /// } + /// } + /// + /// + /// Warning: Off-mesh links can be destroyed or disabled at any moment. The built-in code will attempt to make the agent continue following the link even if it is destroyed, + /// but if you write your own traversal code, you should be aware of this. + /// + /// You can alternatively set the corresponding property property on the off-mesh link () to specify a callback for a specific off-mesh link. + /// + /// Note: The agent's off-mesh link handler takes precedence over the link's off-mesh link handler, if both are set. + /// + /// See: offmeshlinks (view in online documentation for working links) for more details and example code + /// See: + /// + public IOffMeshLinkHandler onTraverseOffMeshLink { + get => managedSettings.onTraverseOffMeshLink; + set { + // Complete any job dependencies + if (proxy.world != null) FollowerEntityProxy.managedSettingsAccessRW.Update(proxy.world.EntityManager); + managedSettings.onTraverseOffMeshLink = value; + } + } + + /// + /// Node which the agent is currently traversing. + /// + /// You can, for example, use this to make the agent use a different animation when traversing nodes with a specific tag. + /// + /// Note: Will be null if the agent does not have a path, or if the node under the agent has just been destroyed by a graph update. + /// + /// When traversing an off-mesh link, this will return the final non-link node in the path before the agent started traversing the link. + /// + public GraphNode currentNode => proxy.currentNode; + + /// + /// Border of the navmesh closest to the agent's current position. + /// + /// The border of the navmesh is what separates the walkable and unwalkable part of the world. + /// + /// [Open online documentation to see images] + /// [Open online documentation to see videos] + /// + /// If no border could be found (for example if the agent has no path), then the hit.point field will be positive infinity. + /// + /// Note: hit.node will always be null, as it is currently not possible for the code to figure out which border belongs to what node. You can call with hit.point if you need this information. + /// + /// See: + /// See: + /// + public GraphHitInfo nearestNavmeshBorder => proxy.nearestNavmeshBorder; + + /// + /// Rotation of the agent. + /// In world space. + /// + /// The entity internally always treats the Z axis as forward, but this property respects the field. So it + /// will return either a rotation with the Y axis as forward, or Z axis as forward, depending on the field. + /// + /// Note: if is true (which is the default), this will also set the transform's rotation. + /// If is false, only the agent's internal rotation will be set. + /// + /// This will return the agent's rotation even if is false. + /// + /// See: + /// + public Quaternion rotation { + get { + if (!proxy.entityExists) { + // If the entity does not exist, return the transform's rotation + return transform.rotation; + } + var r = proxy.rotation; + if (proxy.orientation == OrientationMode.YAxisForward) { + // If the orientation is YAxisForward, convert the internal rotation to Y axis forward + r = math.mul(r, SyncTransformsToEntitiesSystem.ZAxisForwardToYAxisForward); + } + return r; + } + set { + if (!proxy.entityExists) { + // If the entity does not exist, set the transform's rotation + if (syncRotation) { + transform.rotation = value; + } else { + Debug.LogWarning("Cannot set agent rotation because updateRotation is false and the FollowerEntity component has not been enabled yet. Therefore, the internal entity does not exist, and there's no rotation to set.", this); + } + } else { + if (syncRotation) { + // If the entity exists and updateRotation is true, set the transform's rotation as well + transform.rotation = value; + } + if (orientation == OrientationMode.YAxisForward) value = math.mul(value, SyncTransformsToEntitiesSystem.YAxisForwardToZAxisForward); + proxy.rotation = value; + } + } + } + + /// + /// How to calculate which direction is "up" for the agent. + /// + /// In almost all cases, you should use the Graph option. This will make the agent use the graph's natural "up" direction. + /// However, if you are using a spherical world, or a world with some other strange shape, then you may want to use the NavmeshNormal or Raycast options. + /// + /// See: spherical (view in online documentation for working links) + /// + public MovementPlaneSource movementPlaneSource { + get => movementPlaneSourceBacking; + set { + movementPlaneSourceBacking = value; + proxy.movementPlaneSource = value; + } + } + + /// + /// Determines which layers the agent will stand on. + /// + /// The agent will use a raycast each frame to check if it should stop falling. + /// + /// This layer mask should ideally not contain the agent's own layer, if the agent has a collider, + /// as this may cause it to try to stand on top of itself. + /// + public LayerMask groundMask { + get => movement.groundMask; + set { + movement.groundMask = value; + proxy.groundMask = value; + } + } + + /// + /// Enables or disables debug drawing for this agent. + /// + /// This is a bitmask with multiple flags so that you can choose exactly what you want to debug. + /// + /// See: + /// See: bitmasks (view in online documentation for working links) + /// + public PIDMovement.DebugFlags debugFlags { + get => movement.debugFlags; + set { + movement.debugFlags = value; + proxy.debugFlags = value; + } + } + + /// \copydoc Pathfinding::IAstarAI::maxSpeed + public float maxSpeed { + get => movement.follower.speed; + set { + movement.follower.speed = value; + proxy.maxSpeed = value; + } + } + + /// \copydocref{PIDMovement.rotationSpeed} + public float rotationSpeed { + get => movement.follower.rotationSpeed; + set { + movement.follower.rotationSpeed = value; + proxy.rotationSpeed = value; + } + } + + /// \copydocref{PIDMovement.maxRotationSpeed} + public float maxRotationSpeed { + get => movement.follower.maxRotationSpeed; + set { + movement.follower.maxRotationSpeed = value; + proxy.maxRotationSpeed = value; + } + } + + /// + /// Actual velocity that the agent is moving with, including gravity. + /// In world units per second. + /// + /// This is useful for, for example, selecting which animations to play, and at what speeds. + /// + /// Note: Any value set here will be overriden during the next simulation step. Nevertheless, it can be useful to set this value if you have disabled the agent's movement logic using e.g. . + /// This value is only an output statistic. It is not used to control the agent's movement. + /// + /// See: + /// + public Vector3 velocity { + get => proxy.velocity; + set { + proxy.velocity = value; + } + } + + /// \copydoc Pathfinding::IAstarAI::desiredVelocity + public Vector3 desiredVelocity => proxy.desiredVelocity; + + /// \copydoc Pathfinding::IAstarAI::desiredVelocityWithoutLocalAvoidance + public Vector3 desiredVelocityWithoutLocalAvoidance { + get => proxy.desiredVelocityWithoutLocalAvoidance; + set { + proxy.desiredVelocityWithoutLocalAvoidance = value; + } + } + + /// + /// Approximate remaining distance along the current path to the end of the path. + /// + /// This is an approximation for performance reasons, and the agent also does not know the true distance at all times. + /// It tends to be a bit lower than the true distance. It will get more accurate as the agent gets closer to the end of the path. + /// If you want a more accurate distance (at a higher performance cost), you can call GetRemainingPath and sum the length of it. + /// + /// Note: This is the distance to the end of the path, which may or may not be the same as the destination. + /// If the character cannot reach the destination it will try to move as close as possible to it. + /// + /// This value will update immediately if the property is changed, or if the agent is moved using the property or the method. + /// + /// If the agent has no path, or if the current path is stale (e.g. if the graph has been updated close to the agent, and it hasn't had time to recalculate its path), this will return positive infinity. + /// + /// See: + /// See: + /// See: + /// + public float remainingDistance => proxy.remainingDistance; + + /// \copydocref{MovementSettings.stopDistance} + public float stopDistance { + get => movement.stopDistance; + set { + if (movement.stopDistance != value) { + movement.stopDistance = value; + proxy.stopDistance = value; + } + } + } + + /// \copydocref{MovementSettings.positionSmoothing} + public float positionSmoothing { + get => movement.positionSmoothing; + set { + if (movement.positionSmoothing != value) { + movement.positionSmoothing = value; + proxy.positionSmoothing = value; + } + } + } + + /// \copydocref{MovementSettings.rotationSmoothing} + public float rotationSmoothing { + get => movement.rotationSmoothing; + set { + if (movement.rotationSmoothing != value) { + movement.rotationSmoothing = value; + proxy.rotationSmoothing = value; + } + } + } + + /// + /// True if the ai has reached the . + /// + /// The agent considers the destination reached when it is within world units from the . + /// Additionally, the destination must not be above the agent's head, and it must not be more than half the agent's height below its feet. + /// + /// If a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation. + /// + /// This value will be updated immediately when the is changed. + /// + /// + /// IEnumerator Start () { + /// ai.destination = somePoint; + /// // Start to search for a path to the destination immediately + /// ai.SearchPath(); + /// // Wait until the agent has reached the destination + /// while (!ai.reachedDestination) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + /// Note: The agent may not be able to reach the destination. In that case this property may never become true. Sometimes is more appropriate. + /// + /// See: + /// See: + /// See: + /// See: + /// + public bool reachedDestination => proxy.reachedDestination; + + /// + /// True if the agent has reached the end of the current path. + /// + /// The agent considers the end of the path reached when it is within world units from the end of the path. + /// Additionally, the end of the path must not be above the agent's head, and it must not be more than half the agent's height below its feet. + /// + /// If a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation. + /// + /// This value will be updated immediately when the is changed. + /// + /// Note: Reaching the end of the path does not imply that it has reached its desired destination, as the destination may not even be possible to reach. + /// Sometimes is more appropriate. + /// + /// See: + /// See: + /// See: + /// + public bool reachedEndOfPath => proxy.reachedEndOfPath; + + /// + /// Like , but will also return true if the end of the path is crowded, and this agent has stopped because it cannot get closer. + /// + /// This is only relevant if the agent is using local avoidance. Otherwise, this will be identical to . + /// + /// If the agent has a stale path (e.g. because the destination changed significantly, or a graph update happened near the agent), false will be returned + /// until the path has been recalculated (typically in the next one or two frames). + /// + /// You can see a visualization of this state by enabling "Reached State" in the field. + /// + /// Note: The agent may not be completely stopped when this is true. It knows that there are other agents in the way, but it might still be able to slowly make some progress. + /// Check the property to see if the agent is actually moving. + /// + /// In the video below, the agents will get a red ring around them when this property is true. + /// + /// [Open online documentation to see videos] + /// + /// See: local-avoidance (view in online documentation for working links). + /// See: + /// + public bool reachedCrowdedEndOfPath => proxy.reachedCrowdedEndOfPath; + + /// + /// End point of path the agent is currently following. + /// If the agent has no path (or if it's not calculated yet), this will return the instead. + /// If the agent has no destination it will return the agent's current position. + /// + /// The end of the path is usually identical or very close to the , but it may differ + /// if the path for example was blocked by a wall, so that the agent couldn't get any closer. + /// + /// See: + /// + public Vector3 endOfPath => entityExists ? proxy.endOfPath : position; + + /// + /// Position in the world that this agent should move to. + /// + /// If no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned. + /// + /// Setting this property will immediately try to repair the path if the agent already has a path. + /// This will also immediately update properties like , and . + /// + /// The agent may do a full path recalculation if the local repair was not sufficient, + /// but this will at earliest happen in the next simulation step. + /// + /// + /// IEnumerator Start () { + /// ai.destination = somePoint; + /// // Wait until the AI has reached the destination + /// while (!ai.reachedEndOfPath) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + /// See: , which also allows you to set a facing direction for the agent. + /// + public Vector3 destination { + get => proxy.destination; + set => proxy.destination = value; + } + + /// + /// Direction the agent will try to face when it reaches the destination. + /// + /// If this is zero, the agent will not try to face any particular direction. + /// + /// The following video shows three agents, one with no facing direction set, and then two agents with varying values of the . + /// [Open online documentation to see videos] + /// + /// To set this, use . + /// + /// See: + /// See: + /// + public Vector3 destinationFacingDirection => proxy.destinationFacingDirection; + + /// + /// Set the position in the world that this agent should move to. + /// + /// This method will immediately try to repair the path if the agent already has a path. + /// This will also immediately update properties like , and . + /// The agent may do a full path recalculation if the local repair was not sufficient, + /// but this will at earliest happen in the next simulation step. + /// + /// If you are setting a destination and want to know when the agent has reached that destination, + /// then you could use either or . + /// + /// You may also set a facing direction for the agent. If set, the agent will try to approach the destination point + /// with the given heading. and will only return true once the agent is approximately facing the correct direction. + /// The field controls how wide an arc the agent will try to use when approaching the destination. + /// + /// The following video shows three agents, one with no facing direction set, and then two agents with varying values of the . + /// [Open online documentation to see videos] + /// + /// + /// IEnumerator Start () { + /// ai.SetDestination(somePoint, Vector3.right); + /// // Wait until the AI has reached the destination and is rotated to the right in world space + /// while (!ai.reachedEndOfPath) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + /// See: + /// + public void SetDestination (float3 destination, float3 facingDirection = default) { + proxy.SetDestination(destination, facingDirection); + } + + /// + /// Policy for when the agent recalculates its path. + /// + /// See: + /// + public ECS.AutoRepathPolicy autoRepath { + get => autoRepathBacking; + set { + autoRepathBacking = value; + proxy.autoRepath = value; + } + } + + /// + /// \copydoc Pathfinding::IAstarAI::canSearch + /// Deprecated: This has been superseded by . + /// + [System.Obsolete("This has been superseded by autoRepath.mode")] + public bool canSearch { + get { + return autoRepathBacking.mode != AutoRepathPolicy.Mode.Never; + } + set { + if (value) { + if (autoRepathBacking.mode == AutoRepathPolicy.Mode.Never) { + autoRepathBacking.mode = AutoRepathPolicy.Mode.EveryNSeconds; + } + } else { + autoRepathBacking.mode = AutoRepathPolicy.Mode.Never; + } + // Ensure the entity data is up to date + autoRepath = autoRepathBacking; + } + } + + /// + /// Enables or disables movement completely. + /// If you want the agent to stand still, but still react to local avoidance and use gravity: use instead. + /// + /// Disabling this will remove the component from the entity, which prevents + /// most systems from running for this entity. + /// + /// When disabled, the property, and many other informational properties, will no longer update. + /// + /// See: + /// See: + /// + public bool simulateMovement { + get => entityExists ? proxy.simulateMovement : true; + set => proxy.simulateMovement = value; + } + + /// \copydocref{IAstarAI.canMove} + [System.Obsolete("Renamed to simulateMovement")] + public bool canMove { + get => simulateMovement; + set => simulateMovement = value; + } + + /// \copydoc Pathfinding::IAstarAI::movementPlane + public NativeMovementPlane movementPlane => entityExists ? proxy.movementPlane : new NativeMovementPlane(rotation); + + /// + /// Enables or disables gravity. + /// + /// If gravity is enabled, the agent will accelerate downwards, and use a raycast to check if it should stop falling. + /// + /// This has no effect if the agent's is set to YAxisForward (2D mode). + /// Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode. + /// + /// See: + /// + public bool enableGravity { + get => entityExists ? proxy.enableGravity : enableGravityBacking; + set { + if (enableGravityBacking != value) { + enableGravityBacking = value; + proxy.enableGravity = value; + } + } + } + + /// + /// \copydocref{ManagedState.enableLocalAvoidance} + /// + /// See: + /// + public bool enableLocalAvoidance { + get => entityExists ? proxy.enableLocalAvoidance : enableLocalAvoidanceBacking; + set { + enableLocalAvoidanceBacking = value; + if (entityExists) { + var entityManager = proxy.world.EntityManager; + if (entityManager.HasComponent(proxy.entity) != value) { + if (value) { + entityManager.AddComponentData(proxy.entity, rvoSettingsBacking); + } else { + entityManager.RemoveComponent(proxy.entity); + } + } + } + } + } + + /// + /// True if the agent's local avoidance is temporarily disabled, due to traversing an off-mesh link. + /// + /// When traversing an off-mesh link, the traversal code may temporarily disable local avoidance to allow the agent to traverse the link without being pushed around by other agents. + /// + /// See: + /// See: + /// + /// When the agent is not traversing an off-mesh link, this will always return false. + /// + /// Note: A false value does not imply that the agent is actually using local avoidance. See for that. + /// + public bool localAvoidanceTemporarilyDisabled => proxy.localAvoidanceTemporarilyDisabled; + + /// + /// Determines if the character's position should be coupled to the Transform's position. + /// If false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. + /// Instead, only the property and the internal entity's position will change. + /// + /// This is useful if you want to control the movement of the character using some other means, such + /// as root motion, but still want the AI to move freely. + /// + /// Note: This has no effect when the agent is baked in a subscene, since there is no Transform to sync with. + /// + /// See: which in contrast to this field will disable all movement calculations. + /// See: + /// + public bool updatePosition { + get => syncPosition; + set { + syncPosition = value; + proxy.updatePosition = value; + } + } + + /// + /// Determines if the character's rotation should be coupled to the Transform's rotation. + /// If false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. + /// Instead, only the property and the internal entity's rotation will change. + /// + /// This is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed + /// sprite to indicate the direction the character is facing. + /// + /// You can enable .Rotation in to draw a gizmos arrow in the scene view to indicate the agent's internal rotation. + /// + /// Note: This has no effect when the agent is baked in a subscene, since there is no Transform to sync with. + /// + /// See: + /// See: + /// See: + /// + public bool updateRotation { + get => syncRotation; + set { + syncRotation = value; + proxy.updateRotation = value; + } + } + + /// + /// Determines which direction the agent moves in. + /// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. + /// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games. + /// + /// When using ZAxisForward, the +Z axis will be the forward direction of the agent, +Y will be upwards, and +X will be the right direction. + /// When using YAxisForward, the +Y axis will be the forward direction of the agent, +Z will be upwards, and +X will be the right direction. + /// + /// Note: This only affects the rotation of the GameObject's Transform, not the entity's internal rotation. + /// Thus it has no effect when the agent is baked in a subscene, since there is no Transform object in that case. + /// + /// [Open online documentation to see images] + /// + public OrientationMode orientation { + get => orientationBacking; + set { + if (orientationBacking != value) { + orientationBacking = value; + proxy.orientation = value; + } + } + } + + /// + /// True if this agent currently has a valid path that it follows. + /// + /// This is true if the agent has a path and the path is not stale. + /// + /// A path may become stale if the graph is updated close to the agent and it hasn't had time to recalculate its path yet. + /// + public bool hasPath => proxy.hasPath; + + /// \copydoc Pathfinding::IAstarAI::pathPending + public bool pathPending => proxy.pathPending; + + /// \copydoc Pathfinding::IAstarAI::isStopped + public bool isStopped { + get => entityExists ? proxy.isStopped : movement.isStopped; + set { + if (movement.isStopped != value) { + movement.isStopped = value; + proxy.isStopped = value; + } + } + } + + /// + /// Various movement settings. + /// + /// Some of these settings are exposed on the FollowerEntity directly. For example . + /// + /// Note: The return value is a struct. If you want to change some settings, you'll need to modify the returned struct and then assign it back to this property. + /// + public MovementSettings movementSettings { + get => movement; + set { + movement = value; + proxy.movementSettings = value; + } + } + + /// \copydoc Pathfinding::IAstarAI::steeringTarget + public Vector3 steeringTarget => entityExists ? proxy.steeringTarget : position; + + /// \copydoc Pathfinding::IAstarAI::onSearchPath + Action IAstarAI.onSearchPath { + get => null; + set => throw new NotImplementedException("The FollowerEntity does not support this property."); + } + + /// + /// Provides callbacks during various parts of the movement calculations. + /// + /// With this property you can register callbacks that will be called during various parts of the movement calculations. + /// These can be used to modify movement of the agent. + /// + /// The following example demonstrates how one can hook into one of the available phases and modify the agent's movement. + /// In this case, the movement is modified to become wavy. + /// + /// [Open online documentation to see videos] + /// + /// + /// using Pathfinding; + /// using Pathfinding.ECS; + /// using Unity.Entities; + /// using Unity.Mathematics; + /// using Unity.Transforms; + /// using UnityEngine; + /// + /// public class MovementModifierNoise : MonoBehaviour { + /// /** How much noise to apply */ + /// public float strength = 1; + /// /** How fast the noise should change */ + /// public float frequency = 1; + /// float phase; + /// + /// public void Start () { + /// // Register a callback to modify the movement. + /// // This will be called during every simulation step for the agent. + /// // This may be called multiple times per frame if the time scale is high or fps is low, + /// // or less than once per frame, if the fps is very high. + /// GetComponent().movementOverrides.AddBeforeControlCallback(MovementOverride); + /// + /// // Randomize a phase, to make different agents behave differently + /// phase = UnityEngine.Random.value * 1000; + /// } + /// + /// public void OnDisable () { + /// // Remove the callback when the component is disabled + /// GetComponent().movementOverrides.RemoveBeforeControlCallback(MovementOverride); + /// } + /// + /// public void MovementOverride (Entity entity, float dt, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings) { + /// // Rotate the next corner the agent is moving towards around the agent by a random angle. + /// // This will make the agent appear to move in a drunken fashion. + /// + /// // Don't modify the movement as much if we are very close to the end of the path + /// var strengthMultiplier = Mathf.Min(1, movementState.remainingDistanceToEndOfPart / Mathf.Max(shape.radius, movementSettings.follower.slowdownTime * movementSettings.follower.speed)); + /// strengthMultiplier *= strengthMultiplier; + /// + /// // Generate a smoothly varying rotation angle + /// var rotationAngleRad = strength * strengthMultiplier * (Mathf.PerlinNoise1D(Time.time * frequency + phase) - 0.5f); + /// // Clamp it to at most plus or minus 90 degrees + /// rotationAngleRad = Mathf.Clamp(rotationAngleRad, -math.PI*0.5f, math.PI*0.5f); + /// + /// // Convert the rotation angle to a world-space quaternion. + /// // We use the movement plane to rotate around the agent's up axis, + /// // making this code work in both 2D and 3D games. + /// var rotation = movementPlane.value.ToWorldRotation(rotationAngleRad); + /// + /// // Rotate the direction to the next corner around the agent + /// movementState.nextCorner = localTransform.Position + math.mul(rotation, movementState.nextCorner - localTransform.Position); + /// } + /// } + /// + /// + /// There are a few different phases that you can register callbacks for: + /// + /// - BeforeControl: Called before the agent's movement is calculated. At this point, the agent has a valid path, and the next corner that is moving towards has been calculated. + /// - AfterControl: Called after the agent's desired movement is calculated. The agent has stored its desired movement in the component. Local avoidance has not yet run. + /// - BeforeMovement: Called right before the agent's movement is applied. At this point the agent's final movement (including local avoidance) is stored in the component, which you may modify. + /// + /// Warning: If any agent has a callback registered here, a sync point will be created for all agents when the callback runs. + /// This can make the simulation not able to utilize multiple threads as effectively. If you have a lot of agents, consider using a custom entity component system instead. + /// But as always, profile first to see if this is actually a problem for your game. + /// + /// The callbacks may be called multiple times per frame, if the fps is low, or if the time scale is high. + /// It may also be called less than once per frame if the fps is very high. + /// Each callback is provided with a dt parameter, which is the time in seconds since the last simulation step. You should prefer using this instead of Time.deltaTime. + /// + /// See: + /// See: + /// See: + /// + /// Note: This API is unstable. It may change in future versions. + /// + public ManagedMovementOverrides movementOverrides => proxy.movementOverrides; + + /// \copydoc Pathfinding::IAstarAI::FinalizeMovement + void IAstarAI.FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) { + throw new InvalidOperationException("The FollowerEntity component does not support FinalizeMovement. Use an ECS system to override movement instead, or use the movementOverrides property. If you just want to move the agent to a position, set ai.position or call ai.Teleport."); + } + + /// + /// Fills buffer with the remaining path. + /// + /// If the agent traverses off-mesh links, the buffer will still contain the whole path. Off-mesh links will be represented by a single line segment. + /// You can use the overload to get more detailed information about the different parts of the path. + /// + /// Note: The agent will apply a steering behavior on top of this path as it is following it, to make its movement smoother. + /// So it may not follow this exact path. For example, things like are steering behaviors and will not be reflected in this path. + /// + /// + /// var buffer = new List(); + /// + /// ai.GetRemainingPath(buffer, out bool stale); + /// for (int i = 0; i < buffer.Count - 1; i++) { + /// Debug.DrawLine(buffer[i], buffer[i+1], Color.red); + /// } + /// + /// [Open online documentation to see images] + /// + /// The buffer will be cleared and replaced with the path. The first point is the current position of the agent. + /// May be true if the path is invalid in some way. For example if the agent has no path or if the agent has detected that some nodes in the path have been destroyed. + public void GetRemainingPath (List buffer, out bool stale) { + proxy.GetRemainingPath(buffer, out stale); + } + + /// + /// Fills buffer with the remaining path. + /// + /// + /// var buffer = new List(); + /// var parts = new List(); + /// + /// ai.GetRemainingPath(buffer, parts, out bool stale); + /// foreach (var part in parts) { + /// for (int i = part.startIndex; i < part.endIndex; i++) { + /// Debug.DrawLine(buffer[i], buffer[i+1], part.type == Funnel.PartType.NodeSequence ? Color.red : Color.green); + /// } + /// } + /// + /// [Open online documentation to see images] + /// + /// Note: The simplifies its path continuously as it moves along it. This means that the agent may not follow this exact path, if it manages to simplify the path later. + /// Furthermore, the agent will apply a steering behavior on top of this path, to make its movement smoother. + /// + /// The buffer will be cleared and replaced with the path. The first point is the current position of the agent. + /// If not null, this list will be filled with information about the different parts of the path. A part is a sequence of nodes or an off-mesh link. + /// May be true if the path is invalid in some way. For example if the agent has no path or if the agent has detected that some nodes in the path have been destroyed. + public void GetRemainingPath (List buffer, List partsBuffer, out bool stale) { + proxy.GetRemainingPath(buffer, partsBuffer, out stale); + } + + /// \copydoc Pathfinding::IAstarAI::Move + public void Move (Vector3 deltaPosition) { + position += deltaPosition; + } + + /// + /// Calculate how the agent will move during this frame. + /// + /// Warning: The FollowerEntity component does not support MovementUpdate. Use an ECS system to override movement instead, or use the property. + /// + void IAstarAI.MovementUpdate (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + throw new InvalidOperationException("The FollowerEntity component does not support MovementUpdate. Use an ECS system to override movement instead, or use the movementOverrides property"); + } + + /// \copydoc Pathfinding::IAstarAI::SearchPath + public void SearchPath () { + proxy.SearchPath(); + } + + /// + /// True if this component's entity exists. + /// + /// This is typically true if the component is active and enabled and the game is running. + /// + /// See: + /// + public bool entityExists => proxy.entityExists; + + void CancelCurrentPathRequest () { + proxy.CancelCurrentPathRequest(); + } + + /// + /// Make the AI follow the specified path. + /// + /// In case the path has not been calculated, the script will schedule the path to be calculated. + /// This means the AI may not actually start to follow the path until in a few frames when the path has been calculated. + /// The field will, as usual, return true while the path is being calculated. + /// + /// In case the path has already been calculated, it will immediately replace the current path the AI is following. + /// + /// If you pass null path, then the current path will be cleared and the agent will stop moving. + /// Note than unless you have also disabled , then the agent will soon recalculate its path and start moving again. + /// + /// Note: Stopping the agent by passing a null path works. But this will stop the agent instantly, and it will not be able to use local avoidance or know its place on the navmesh. + /// Usually it's better to set to false, which will make the agent slow down smoothly. + /// + /// You can disable the automatic path recalculation by setting the field to false. + /// + /// Note: This call will be ignored if the agent is currently traversing an off-mesh link. + /// Furthermore, when an agent starts traversing an off-mesh link, the current path request will be canceled (if one is currently in progress). + /// + /// + /// IEnumerator Start () { + /// var pointToAvoid = enemy.position; + /// // Make the AI flee from an enemy. + /// // The path will be about 20 world units long (the default cost of moving 1 world unit is 1000). + /// var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20); + /// + /// // Make the path use the same traversable tags and other pathfinding settings as set in the FollowerEntity inspector + /// path.UseSettings(ai.pathfindingSettings); + /// + /// ai.SetPath(path); + /// + /// while (!ai.reachedEndOfPath) { + /// yield return null; + /// } + /// } + /// + /// + /// See: calling-pathfinding (view in online documentation for working links) + /// See: example_path_types (view in online documentation for working links) + /// + /// The path to follow. + /// If true, the \reflink{destination} property will be set to the end point of the path. If false, the previous destination value will be kept. + /// If you pass a path which has no well defined destination before it is calculated (e.g. a MultiTargetPath or RandomPath), then the destination will be first be cleared, but once the path has been calculated, it will be set to the end point of the path. + public void SetPath(Path path, bool updateDestinationFromPath = true) => proxy.SetPath(path, updateDestinationFromPath); + + /// + /// Instantly move the agent to a new position. + /// + /// The current path will be cleared by default. + /// + /// This method is preferred for long distance teleports. If you only move the agent a very small distance (so that it is reasonable that it can keep its current path), + /// then setting the property is preferred. + /// Setting the property very far away from the agent could cause the agent to fail to move the full distance, as it can get blocked by the navmesh. + /// + /// See: Works similarly to Unity's NavmeshAgent.Warp. + /// See: + /// See: + /// + public void Teleport (Vector3 newPosition, bool clearPath = true) { + if (entityExists) { + proxy.Teleport(newPosition, clearPath); + if (proxy.updatePosition) transform.position = proxy.position; + } else position = newPosition; + } + + void FindComponents () { + tr = transform; + } + + public override void DrawGizmos () { + // Don't draw gizmos here during play mode. + // If the entity is in a subscene, these gizmos wouldn't match the real entity anyway. + // And in any case, we can just get better performance by allowing an ECS system to draw the gizmos. + // Gizmos are drawn by the AIMoveSystem instead, which runs JobDrawFollowerGizmosBase asynchronously and in parallel. + if (Application.isPlaying) return; + + // This may be called before the component has been enabled. + // For example outside of play mode, or even in play mode when in prefab isolation mode + if (tr == null) FindComponents(); + + var trans = LocalTransform.FromPositionRotationScale( + tr.position, + orientation == OrientationMode.YAxisForward ? math.mul(tr.rotation, SyncTransformsToEntitiesSystem.ZAxisForwardToYAxisForward) : tr.rotation, + tr.localScale.x + ); + var dest = new DestinationPoint { + destination = this.destination, + facingDirection = this.destinationFacingDirection, + }; + var draw = Draw.editor; + JobDrawFollowerGizmosBase.DrawGizmos(ref draw, in trans, in shape, in dest, orientation == OrientationMode.YAxisForward); + } + + [System.Flags] + enum FollowerEntityMigrations { + MigratePathfindingSettings = 1 << 0, + MigrateMovementPlaneSource = 1 << 1, + MigrateAutoRepathPolicy = 1 << 2, + MigrateManagedSettings = 1 << 3, + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.TryMigrateFromLegacyFormat(out var _legacyVersion)) { + // Only 1 version of the previous version format existed for this component + if (this.pathfindingSettings.tagEntryCosts.Length != 0) migrations.MarkMigrationFinished((int)FollowerEntityMigrations.MigratePathfindingSettings); + } + + if (migrations.AddAndMaybeRunMigration((int)FollowerEntityMigrations.MigratePathfindingSettings, unityThread)) { + if (TryGetComponent(out var seeker)) { + this.pathfindingSettings = new PathRequestSettings { + graphMask = seeker.graphMask, + traversableTags = seeker.traversableTags, + tagCostMultipliers = seeker.tagCostMultipliers, + tagEntryCosts = seeker.tagEntryCosts, + }; + UnityEngine.Object.DestroyImmediate(seeker); + } else { + this.pathfindingSettings = PathRequestSettings.Default; + } + } + // Old migrations that cannot run anymore + migrations.AddAndMaybeRunMigration((int)FollowerEntityMigrations.MigrateMovementPlaneSource); + migrations.AddAndMaybeRunMigration((int)FollowerEntityMigrations.MigrateAutoRepathPolicy); + + if (migrations.AddAndMaybeRunMigration((int)FollowerEntityMigrations.MigrateManagedSettings)) { + #pragma warning disable CS0618 // Type or member is obsolete + this.managedSettings.onTraverseOffMeshLink = this.managedState.onTraverseOffMeshLink; + this.managedSettings.pathfindingSettings = this.managedState.pathfindingSettings; + this.enableGravityBacking = this.managedState.enableGravity; + this.enableLocalAvoidanceBacking = this.managedState.enableLocalAvoidance; + this.rvoSettingsBacking = this.managedState.rvoSettings; + #pragma warning restore CS0618 // Type or member is obsolete + } + } + +#if UNITY_EDITOR + /// \cond IGNORE_IN_DOCS + + /// + /// Copies all settings from this component to the entity's components. + /// + /// Note: This is an internal method and you should never need to use it yourself. + /// Typically it is used by the editor to keep the entity's state in sync with the component's state. + /// + public void SyncWithEntity () { + if (!FollowerEntityProxy.entityStorageCache.Update(proxy.world, proxy.entity, out var entityManager, out var storage)) return; + + proxy.position = transform.position; + proxy.autoRepath = autoRepathBacking; + FollowerEntityProxy.movementSettingsAccessRW.Update(entityManager); + FollowerEntityProxy.managedStateAccessRW.Update(entityManager); + FollowerEntityProxy.agentCylinderShapeAccessRW.Update(entityManager); + FollowerEntityProxy.managedSettingsAccessRW.Update(entityManager); + + SyncWithEntity(FollowerEntityProxy.managedStateAccessRW[storage], FollowerEntityProxy.managedSettingsAccessRW[storage], ref FollowerEntityProxy.agentCylinderShapeAccessRW[storage], ref FollowerEntityProxy.movementSettingsAccessRW[storage]); + + // Structural changes + proxy.enableGravity = enableGravityBacking; + proxy.orientation = orientationBacking; + proxy.updatePosition = syncPosition; + proxy.updateRotation = syncRotation; + proxy.movementPlaneSource = movementPlaneSourceBacking; + this.enableLocalAvoidance = enableLocalAvoidanceBacking; + proxy.rvoSettings = rvoSettingsBacking; + } + + /// + /// Copies all settings from this component to the entity's components. + /// + /// Note: This is an internal method and you should never need to use it yourself. + /// + public void SyncWithEntity (ManagedState managedState, ManagedSettings managedSettings, ref AgentCylinderShape shape, ref MovementSettings movementSettings) { + movementSettings = this.movement; + shape = this.shape; + // Copy all serialized fields to the managed state object. + // This excludes the PathTracer, onTraverseOffMeshLink and pathfindingSettings.traversalProvider, since they are not serialized + var traversalProvider = managedSettings.pathfindingSettings.traversalProvider; + managedSettings.pathfindingSettings = this.managedSettings.pathfindingSettings; + managedSettings.pathfindingSettings.traversalProvider = traversalProvider; + // Replace this instance of the managed state with the entity component + this.managedState = managedState; + this.managedSettings = managedSettings; + // Note: RVO settings are copied every frame automatically before local avoidance simulations + } + + static List needsSyncWithEntityList = new List(); + + void ISerializationCallbackReceiver.OnBeforeSerialize () {} + + void ISerializationCallbackReceiver.OnAfterDeserialize () { + UpgradeSerializedData(false); + + // This is (among other times) called after an undo or redo event has happened. + // In that case, the entity's state might be out of sync with this component's state, + // so we need to sync the two together. Unfortunately this method is called + // from Unity's separate serialization thread, so we cannot access the entity directly. + // Instead we add this component to a list and make sure to process them in the next + // editor update. + lock (needsSyncWithEntityList) { + needsSyncWithEntityList.Add(this); + if (needsSyncWithEntityList.Count == 1) { + UnityEditor.EditorApplication.update += SyncWithEntities; + } + } + } + + static void SyncWithEntities () { + lock (needsSyncWithEntityList) { + for (int i = 0; i < needsSyncWithEntityList.Count; i++) { + needsSyncWithEntityList[i].SyncWithEntity(); + } + needsSyncWithEntityList.Clear(); + UnityEditor.EditorApplication.update -= SyncWithEntities; + } + } + +#if UNITY_EDITOR + public class FollowerEntityBaker : Baker { + public override void Bake (FollowerEntity authoring) { + var entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.WorldSpace); + + var position = authoring.transform.position; + var rotation = authoring.transform.rotation; + var shape = authoring.shape; + var movement = authoring.movement; + var autoRepath = authoring.autoRepath; + var orientation = authoring.orientation; + var movementPlaneSource = authoring.movementPlaneSource; + var enableGravity = authoring.enableGravityBacking; + var enableLocalAvoidance = authoring.enableLocalAvoidanceBacking; + var rvoSettings = authoring.rvoSettingsBacking; + PhysicsScene physicsScene = Physics.defaultPhysicsScene; + if (authoring.gameObject.scene.IsValid()) { + // If the object is a prefab, then the scene will be invalid, and we cannot get the physics scene. + physicsScene = PhysicsSceneExtensions.GetPhysicsScene(authoring.gameObject.scene); + } + + + // The simplification replaces some arrays with null if they are all default values. + // This saves some memory and makes the entity smaller. + // This has a side effect of making live-patching of entities in the editor quite a lot faster + var managedSettings = authoring.managedSettings.CloneAndSimplifyDefaults(true); + + if (orientation == OrientationMode.YAxisForward) { + rotation = math.mul(rotation, SyncTransformsToEntitiesSystem.YAxisForwardToZAxisForward); + AddComponent(entity); + } + + AddComponent(entity, new MovementState(position)); + AddComponent(entity, new DestinationPoint { + destination = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity), + }); + autoRepath.Reset(); + AddComponent(entity, autoRepath); + AddComponent(entity, movement); + AddComponentObject(entity, managedSettings); + AddComponent(entity, new MovementStatistics { + estimatedVelocity = float3.zero, + lastPosition = position, + }); + AddComponent(entity, shape); + AddComponent(entity); + SetComponentEnabled(entity, enableGravity); + AddComponent(entity); + SetComponentEnabled(entity, false); + AddSharedComponent(entity, new AgentMovementPlaneSource { value = movementPlaneSource }); + AddSharedComponent(entity, new PhysicsSceneRef { physicsScene = physicsScene }); + + if (enableLocalAvoidance) { + AddComponent(entity, rvoSettings); + } + + ResolvedMovement resolvedMovement = default; + MovementControl movementControl = default; + AgentMovementPlane movementPlane = new AgentMovementPlane(rotation); + FollowerEntityProxy.ResetControl(ref resolvedMovement, ref movementControl, ref movementPlane, position, rotation, position); + + // Set the initial movement plane and control state. This will be overriden during the first simulation loop. + AddComponent(entity, movementPlane); + AddComponent(entity, resolvedMovement); + AddComponent(entity, movementControl); + + AddComponent(entity); + AddComponent(entity); + AddComponent(entity); + AddComponent(entity); + AddComponent(entity); + AddComponent(entity); + SetComponentEnabled(entity, false); + } + } +#endif + + /// \endcond +#endif + } +} +#else +namespace Pathfinding { + public sealed partial class FollowerEntity : VersionedMonoBehaviour { + public void Start () { + UnityEngine.Debug.LogError("The FollowerEntity component requires at least version 1.0 of the 'Entities' package to be installed. You can install it using the Unity package manager."); + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + // Since most of the code for this component is stripped out, we should just preserve the current state, + // and not try to migrate anything. + // If we don't do this, then the base code will log an error about an unknown migration already being done. + migrations.IgnoreMigrationAttempt(); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs.meta new file mode 100644 index 0000000..6e40ab6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cfe9431ea8ad072f2aecd3041b1524dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f2e81a0445323b64f973d2f5b5c56e15, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs new file mode 100644 index 0000000..220c747 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs @@ -0,0 +1,1066 @@ +#if MODULE_ENTITIES +using Pathfinding; +using Unity.Entities; +using Unity.Profiling; +using UnityEngine; +using Unity.Mathematics; +using UnityEngine.Profiling; +using Unity.Collections; +using Unity.Transforms; +using Pathfinding.RVO; +using Pathfinding.ECS.RVO; +using Pathfinding.Util; +using Pathfinding.PID; +using System.Collections.Generic; +using Pathfinding.Collections; +using UnityEngine.Assertions; + +namespace Pathfinding.ECS { + /// + /// Proxy for a FollowerEntity using pure ECS components. + /// + /// This struct behaves almost identically to the MonoBehaviour component, but it wraps an Entity in a given world instead. + /// It is used primarily when you are using subscenes, and therefore cannot use the component at runtime. + /// + /// This struct gives you the same high-level API as the component, but without the overhead of dealing with GameObjects and MonoBehaviours. + /// + /// Warning: This struct can only be used on the main thread outside of ECS jobs. When accessing ECS components from within jobs, you must use the ECS api directly. + /// + /// \section followerentityproxy-usage Usage + /// + /// -# Create a subscene (see unity entities documentation). + /// -# Create an agent GameObject with a component within that subscene. + /// -# At runtime, the GameObject will be automatically converted to an entity. + /// -# You can then use the struct to interact with the entity. + /// + /// var follower = new FollowerEntityProxy(world, entity); + /// follower.maxSpeed = 5; + /// follower.destination = new Vector3(1, 2, 3); + /// + /// if (follower.currentNode.Tag == 1) { + /// Debug.Log("The agent is right now traversing a node with tag 1"); + /// } + /// + /// + /// See: + /// + public struct FollowerEntityProxy : IAstarAI { + public Entity entity { [IgnoredByDeepProfiler] get; private set; } + public World world { [IgnoredByDeepProfiler] get; private set; } + + class EntityDoesNotExistException : System.Exception { + public EntityDoesNotExistException() : base("The entity does not exist. This can happen if the component is not enabled, or if the game is not running.") { } + } + + static NativeList nextCornersScratch; + static NativeArray indicesScratch; + + internal static EntityAccess destinationPointAccessRW = new EntityAccess(false); + internal static EntityAccess destinationPointAccessRO = new EntityAccess(true); + internal static EntityAccess movementPlaneAccessRW = new EntityAccess(false); + internal static EntityAccess movementPlaneAccessRO = new EntityAccess(true); + internal static EntityAccess movementStateAccessRW = new EntityAccess(false); + internal static EntityAccess movementStateAccessRO = new EntityAccess(true); + internal static EntityAccess movementOutputAccessRW = new EntityAccess(false); + internal static EntityAccess resolvedMovementAccessRO = new EntityAccess(true); + internal static EntityAccess resolvedMovementAccessRW = new EntityAccess(false); + internal static EntityAccess movementControlAccessRO = new EntityAccess(true); + internal static EntityAccess movementControlAccessRW = new EntityAccess(false); + internal static EntityAccess movementStatisticsAccessRW = new EntityAccess(false); + internal static EntityAccess movementStatisticsAccessRO = new EntityAccess(true); + internal static ManagedEntityAccess managedStateAccessRO = new ManagedEntityAccess(true); + internal static ManagedEntityAccess managedStateAccessRW = new ManagedEntityAccess(false); + internal static ManagedEntityAccess managedSettingsAccessRO = new ManagedEntityAccess(true); + internal static ManagedEntityAccess managedSettingsAccessRW = new ManagedEntityAccess(false); + internal static EntityAccess autoRepathPolicyRW = new EntityAccess(false); + internal static EntityAccess localTransformAccessRO = new EntityAccess(true); + internal static EntityAccess localTransformAccessRW = new EntityAccess(false); + internal static EntityAccess agentCylinderShapeAccessRO = new EntityAccess(true); + internal static EntityAccess agentCylinderShapeAccessRW = new EntityAccess(false); + internal static EntityAccess movementSettingsAccessRO = new EntityAccess(true); + internal static EntityAccess movementSettingsAccessRW = new EntityAccess(false); + internal static EntityAccess agentOffMeshLinkTraversalRO = new EntityAccess(true); + internal static EntityAccess readyToTraverseOffMeshLinkRW = new EntityAccess(false); + internal static EntityAccess rvoSettingsAccessRO = new EntityAccess(true); + internal static EntityAccess rvoSettingsAccessRW = new EntityAccess(false); + + internal static EntityStorageCache entityStorageCache; + + static void InitScratchData ( ) { + if (!nextCornersScratch.IsCreated) { + if (!Application.isPlaying) throw new System.InvalidOperationException("Cannot initialize scratch data outside of play mode."); + nextCornersScratch = new NativeList(16, Allocator.Persistent); + indicesScratch = new NativeArray(16, Allocator.Persistent); + Application.quitting += DisposeScratchData; + } + } + + static void DisposeScratchData () { + if (nextCornersScratch.IsCreated) nextCornersScratch.Dispose(); + if (indicesScratch.IsCreated) indicesScratch.Dispose(); + Application.quitting -= DisposeScratchData; + } + + public FollowerEntityProxy (World world, Entity entity) { + this.world = world; + this.entity = entity; + } + + /// + /// True if the exists. + /// + /// This is typically true if the component is active and enabled and the game is running. + /// + /// See: + /// + public bool entityExists => world != null && world.IsCreated && world.EntityManager.Exists(entity); + + /// + /// True if the entity seems to have the required components. + /// + /// This is used to detect entities which still exist, but which have had all their FollowerEntity components removed. + /// + internal bool likelyHasReasonableComponents => entityExists && world.EntityManager.HasComponent(entity) && world.EntityManager.HasComponent(entity); + + void AssertEntityExists () { + if (world == null || !world.IsCreated || !world.EntityManager.Exists(entity)) throw new System.InvalidOperationException("Entity does not exist. You can only access this if the component is active and enabled."); + } + + /// \copydocref{FollowerEntity.radius} + public float radius { + get => entityStorageCache.GetComponentData(world, entity, ref agentCylinderShapeAccessRW, out var shape) ? shape.value.radius : 0; + set { + if (entityStorageCache.GetComponentData(world, entity, ref agentCylinderShapeAccessRW, out var shape)) { + shape.value.radius = value; + } + } + } + + /// \copydocref{FollowerEntity.height} + public float height { + get => entityStorageCache.GetComponentData(world, entity, ref agentCylinderShapeAccessRO, out var shape) ? shape.value.height : 0; + set { + if (entityStorageCache.GetComponentData(world, entity, ref agentCylinderShapeAccessRW, out var shape)) { + shape.value.height = value; + } + } + } + + /// \copydocref{FollowerEntity.pathfindingSettings} + public ref PathRequestSettings pathfindingSettings { + get { + // Complete any job dependencies + // Need RW because this getter has a ref return. + if (entityStorageCache.GetComponentData(world, entity, ref managedSettingsAccessRW, out var managedSettings)) { + return ref managedSettings.pathfindingSettings; + } else { + throw new EntityDoesNotExistException(); + } + } + } + + /// \copydocref{FollowerEntity.rvoSettings} + public RVOAgent rvoSettings { + get { + // Note: Cannot use GetComponentData, because it assumes that the component exists on the entity, but the RVOAgent component is optional + if (entityStorageCache.Update(world, entity, out var entityManager, out var storage)) { + rvoSettingsAccessRO.Update(entityManager); + if (rvoSettingsAccessRO.HasComponent(storage)) { + return rvoSettingsAccessRO[storage]; + } + } + throw new System.Exception("Local avoidance is not enabled for the entity, cannot get local avoidance settings. The RVOAgent component is missing on the entity."); + } + set { + if (entityStorageCache.Update(world, entity, out var entityManager, out var storage)) { + rvoSettingsAccessRW.Update(entityManager); + if (rvoSettingsAccessRW.HasComponent(storage)) { + rvoSettingsAccessRW[storage] = value; + } + } + } + } + + /// \copydocref{FollowerEntity.position} + public Vector3 position { + // Make sure we are not waiting for a job to update the world position + get => entityStorageCache.GetComponentData(world, entity, ref localTransformAccessRO, out var localTransform) ? localTransform.value.Position : default; + set { + if (entityStorageCache.Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage)) { + movementStateAccessRW.Update(entityManager); + managedStateAccessRW.Update(entityManager); + agentCylinderShapeAccessRO.Update(entityManager); + movementSettingsAccessRO.Update(entityManager); + destinationPointAccessRO.Update(entityManager); + movementPlaneAccessRO.Update(entityManager); + localTransformAccessRW.Update(entityManager); + readyToTraverseOffMeshLinkRW.Update(entityManager); + autoRepathPolicyRW.Update(entityManager); + + ref var localTransform = ref localTransformAccessRW[storage]; + localTransform.Position = value; + ref var movementState = ref movementStateAccessRW[storage]; + movementState.positionOffset = float3.zero; + var managedState = managedStateAccessRW[storage]; + if (managedState.pathTracer.hasPath) { + Profiler.BeginSample("RepairStart"); + ref var movementPlane = ref movementPlaneAccessRO[storage]; + var oldVersion = managedState.pathTracer.version; + managedState.pathTracer.UpdateStart(value, PathTracer.RepairQuality.High, movementPlane.value); + Profiler.EndSample(); + if (managedState.pathTracer.version != oldVersion) { + Profiler.BeginSample("EstimateNative"); + ref var shape = ref agentCylinderShapeAccessRO[storage]; + ref var movementSettings = ref movementSettingsAccessRO[storage]; + ref var destinationPoint = ref destinationPointAccessRO[storage]; + ref var autoRepath = ref autoRepathPolicyRW[storage]; + var readyToTraverseOffMeshLink = storage.Chunk.GetEnabledMask(ref readyToTraverseOffMeshLinkRW.handle).GetEnabledRefRW(storage.IndexInChunk); + InitScratchData(); + JobRepairPath.Execute( + ref localTransform, + ref movementState, + ref shape, + ref movementPlane, + ref autoRepath, + ref destinationPoint, + readyToTraverseOffMeshLink, + managedState, + in movementSettings, + nextCornersScratch, + ref indicesScratch, + Allocator.Persistent, + false + ); + Profiler.EndSample(); + } + } + } + } + } + + /// \copydocref{FollowerEntity.isTraversingOffMeshLink} + public bool isTraversingOffMeshLink { + get => entityExists && world.EntityManager.HasComponent(entity); + } + + + /// \copydocref{FollowerEntity.offMeshLink} + public OffMeshLinks.OffMeshLinkTracer offMeshLink { + get { + if (entityStorageCache.Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage) && entityManager.HasComponent(entity)) { + agentOffMeshLinkTraversalRO.Update(entityManager); + var linkTraversal = agentOffMeshLinkTraversalRO[storage]; + var linkTraversalManaged = entityManager.GetComponentData(entity); + return new OffMeshLinks.OffMeshLinkTracer(linkTraversalManaged.context.concreteLink, linkTraversal.relativeStart, linkTraversal.relativeEnd, linkTraversal.isReverse); + } else { + return default; + } + } + } + + /// \copydocref{FollowerEntity.nextOffMeshLink} + public OffMeshLinks.OffMeshLinkTracer nextOffMeshLink { + get { + var l = offMeshLink; + if (l.link != null) return l; + + if (entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState)) { + if (managedState.pathTracer.isNextPartValidLink) { + return managedState.pathTracer.GetLinkInfo(1); + } + } + return default; + } + } + + /// \copydocref{FollowerEntity.onTraverseOffMeshLink} + public IOffMeshLinkHandler onTraverseOffMeshLink { + get => entityStorageCache.GetComponentData(world, entity, ref managedSettingsAccessRO, out var managedSettings) ? managedSettings.onTraverseOffMeshLink : null; + set { + // Complete any job dependencies + if (entityStorageCache.GetComponentData(world, entity, ref managedSettingsAccessRO, out var managedSettings)) { + managedSettings.onTraverseOffMeshLink = value; + } + } + } + + /// \copydocref{FollowerEntity.currentNode} + public GraphNode currentNode { + get { + if (entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState)) { + var node = managedState.pathTracer.startNode; + if (node == null || node.Destroyed) return null; + return node; + } else { + return null; + } + } + } + + /// \copydocref{FollowerEntity.nearestNavmeshBorder} + public GraphHitInfo nearestNavmeshBorder { + get { + if (AstarPath.active != null) { + AstarPath.active.GetNearestBorder(position, currentNode, out var hit); + return hit; + } else { + GraphHitInfo hit = default; + hit.origin = position; + hit.point = Vector3.positiveInfinity; + return hit; + } + } + } + + /// \copydocref{FollowerEntity.rotation} + public Quaternion rotation { + get => entityStorageCache.GetComponentData(world, entity, ref localTransformAccessRO, out var localTransform) ? localTransform.value.Rotation : Quaternion.identity; + set { + if (entityStorageCache.GetComponentData(world, entity, ref localTransformAccessRW, out var localTransform)) { + localTransform.value.Rotation = value; + } + } + } + + /// \copydocref{FollowerEntity.movementPlaneSource} + public MovementPlaneSource movementPlaneSource { + get => entityExists ? world.EntityManager.GetSharedComponent(entity).value : default; + set { + if (entityExists) { + world.EntityManager.SetSharedComponent(entity, new AgentMovementPlaneSource { value = value }); + } + } + } + + /// \copydocref{FollowerEntity.groundMask} + public LayerMask groundMask { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.groundMask : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.groundMask = value; + } + } + } + + /// \copydocref{FollowerEntity.debugFlags} + public PIDMovement.DebugFlags debugFlags { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.debugFlags : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.debugFlags = value; + } + } + } + + /// \copydocref{FollowerEntity.maxSpeed} + public float maxSpeed { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.follower.speed : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.follower.speed = value; + } + } + } + + /// \copydocref{FollowerEntity.rotationSpeed} + public float rotationSpeed { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.follower.rotationSpeed : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.follower.rotationSpeed = value; + } + } + } + + /// \copydocref{FollowerEntity.maxRotationSpeed} + public float maxRotationSpeed { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.follower.maxRotationSpeed : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.follower.maxRotationSpeed = value; + } + } + } + + /// \copydocref{FollowerEntity.velocity} + public Vector3 velocity { + get { + return entityStorageCache.GetComponentData(world, entity, ref movementStatisticsAccessRO, out var statistics) ? (Vector3)statistics.value.estimatedVelocity : Vector3.zero; + } + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementStatisticsAccessRW, out var statistics)) { + statistics.value.estimatedVelocity = (float3)value; + } + } + } + + /// \copydocref{FollowerEntity.desiredVelocity} + public Vector3 desiredVelocity { + get { + if (entityStorageCache.GetComponentData(world, entity, ref resolvedMovementAccessRO, out var resolvedMovement)) { + var dt = Mathf.Max(Time.deltaTime, 0.0001f); + return Vector3.ClampMagnitude((Vector3)resolvedMovement.value.targetPoint - position, dt * resolvedMovement.value.speed) / dt; + } else { + return Vector3.zero; + } + } + } + + /// \copydocref{FollowerEntity.desiredVelocityWithoutLocalAvoidance} + public Vector3 desiredVelocityWithoutLocalAvoidance { + get { + if (entityStorageCache.GetComponentData(world, entity, ref movementControlAccessRO, out var movementControl)) { + var dt = Mathf.Max(Time.deltaTime, 0.0001f); + return Vector3.ClampMagnitude((Vector3)movementControl.value.targetPoint - position, dt * movementControl.value.speed) / dt; + } else { + return Vector3.zero; + } + } + set => throw new System.NotImplementedException("The FollowerEntity does not support setting this property. If you want to override the movement, you'll need to write a custom entity component system."); + } + + /// \copydocref{FollowerEntity.remainingDistance} + public float remainingDistance { + get { + if (!entityStorageCache.Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage)) return float.PositiveInfinity; + + movementStateAccessRO.Update(entityManager); + managedStateAccessRO.Update(entityManager); + var managedState = managedStateAccessRO[storage]; + // TODO: Should this perhaps only check if the start/end points are stale, and ignore the case when the graph is updated and some nodes are destroyed? + if (managedState.pathTracer.hasPath && !managedState.pathTracer.isStale) { + ref var movementState = ref movementStateAccessRO[storage]; + return movementState.remainingDistanceToEndOfPart + Vector3.Distance(managedState.pathTracer.endPointOfFirstPart, managedState.pathTracer.endPoint); + } else { + return float.PositiveInfinity; + } + } + } + + /// \copydocref{FollowerEntity.stopDistance} + public float stopDistance { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.stopDistance : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.stopDistance = value; + } + } + } + + /// \copydocref{FollowerEntity.positionSmoothing} + public float positionSmoothing { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.positionSmoothing : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.positionSmoothing = value; + } + } + } + + /// \copydocref{FollowerEntity.rotationSmoothing} + public float rotationSmoothing { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.rotationSmoothing : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.rotationSmoothing = value; + } + } + } + + /// \copydocref{FollowerEntity.reachedDestination} + public bool reachedDestination => entityStorageCache.GetComponentData(world, entity, ref movementStateAccessRW, out var movementState) ? movementState.value.reachedDestinationAndOrientation : false; + + /// \copydocref{FollowerEntity.reachedEndOfPath} + public bool reachedEndOfPath => entityStorageCache.GetComponentData(world, entity, ref movementStateAccessRW, out var movementState) ? movementState.value.reachedEndOfPathAndOrientation : false; + + /// \copydocref{FollowerEntity.reachedCrowdedEndOfPath} + public bool reachedCrowdedEndOfPath { + get { + if (reachedEndOfPath) return true; + if (!hasPath) return false; + + var entityManager = world.EntityManager; + if (RVOSimulator.active != null && entityManager.HasComponent(entity)) { + var agentIndex = entityManager.GetComponentData(entity); + var simulator = RVOSimulator.active.GetSimulator(); + if (agentIndex.TryGetIndex(ref simulator.simulationData, out var index)) { + var effectivelyReachedDestination = simulator.outputData.effectivelyReachedDestination[index]; + if (effectivelyReachedDestination == ReachedEndOfPath.Reached) { + managedStateAccessRO.Update(entityManager); + var managedState = managedStateAccessRO[entityManager.GetStorageInfo(entity)]; + + // Check if the RVO simulator state is roughly in sync with the path tracer + var rvoEndOfPath = (Vector3)simulator.simulationData.endOfPath[index]; + var endOfPath = managedState.pathTracer.endPoint; + const float MaxChangeRadians = 0.1f; + if ((rvoEndOfPath - endOfPath).sqrMagnitude < (endOfPath - position).sqrMagnitude*MaxChangeRadians*MaxChangeRadians) { + return true; + } else { + // The RVO simulator has a different end of path than the path tracer. + // This can happen if the destination has just changed, but the rvo simulation has not yet run another iteration. + // In that case we should not consider it reached. + } + } + } + } + return false; + } + } + + /// \copydocref{FollowerEntity.endOfPath} + public Vector3 endOfPath { + get { + if (entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState)) { + if (managedState.pathTracer.hasPath) { + return managedState.pathTracer.endPoint; + } else { + var d = destination; + if (float.IsFinite(d.x)) return d; + } + } + return position; + } + } + + /// \copydocref{FollowerEntity.destination} + public Vector3 destination { + get => entityStorageCache.GetComponentData(world, entity, ref destinationPointAccessRO, out var destination) ? (Vector3)destination.value.destination : Vector3.positiveInfinity; + set => SetDestination(value, default); + } + + /// \copydocref{FollowerEntity.destinationFacingDirection} + public Vector3 destinationFacingDirection { + get => entityStorageCache.GetComponentData(world, entity, ref destinationPointAccessRO, out var destination) ? (Vector3)destination.value.facingDirection : Vector3.zero; + } + + /// \copydocref{FollowerEntity.SetDestination} + public void SetDestination (float3 destination, float3 facingDirection = default) { + AssertEntityExists(); + var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; + movementStateAccessRW.Update(entityManager); + managedStateAccessRW.Update(entityManager); + agentCylinderShapeAccessRO.Update(entityManager); + movementSettingsAccessRO.Update(entityManager); + localTransformAccessRO.Update(entityManager); + autoRepathPolicyRW.Update(entityManager); + destinationPointAccessRW.Update(entityManager); + movementPlaneAccessRO.Update(entityManager); + readyToTraverseOffMeshLinkRW.Update(entityManager); + + var storage = entityManager.GetStorageInfo(entity); + destinationPointAccessRW[storage] = new DestinationPoint { + destination = destination, + facingDirection = facingDirection, + }; + + var managedState = managedStateAccessRW[storage]; + + // If we already have a path, we try to repair it immediately. + // This ensures that the #reachedDestination and #reachedEndOfPath flags are as up to date as possible. + if (managedState.pathTracer.hasPath) { + Profiler.BeginSample("RepairEnd"); + ref var movementPlane = ref movementPlaneAccessRO[storage]; + managedState.pathTracer.UpdateEnd(destination, PathTracer.RepairQuality.High, movementPlane.value); + Profiler.EndSample(); + ref var movementState = ref movementStateAccessRW[storage]; + if (movementState.pathTracerVersion != managedState.pathTracer.version) { + Profiler.BeginSample("EstimateNative"); + ref var shape = ref agentCylinderShapeAccessRO[storage]; + ref var movementSettings = ref movementSettingsAccessRO[storage]; + ref var localTransform = ref localTransformAccessRO[storage]; + ref var autoRepath = ref autoRepathPolicyRW[storage]; + ref var destinationPoint = ref destinationPointAccessRW[storage]; + var readyToTraverseOffMeshLink = storage.Chunk.GetEnabledMask(ref readyToTraverseOffMeshLinkRW.handle).GetEnabledRefRW(storage.IndexInChunk); + InitScratchData(); + JobRepairPath.Execute( + ref localTransform, + ref movementState, + ref shape, + ref movementPlane, + ref autoRepath, + ref destinationPoint, + readyToTraverseOffMeshLink, + managedState, + in movementSettings, + nextCornersScratch, + ref indicesScratch, + Allocator.Persistent, + false + ); + Profiler.EndSample(); + } + } + } + + /// \copydocref{FollowerEntity.autoRepath} + public ECS.AutoRepathPolicy autoRepath { + get => entityStorageCache.GetComponentData(world, entity, ref autoRepathPolicyRW, out var component) ? component.value : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref autoRepathPolicyRW, out var component)) { + component.value = value; + } + } + } + + /// + /// \copydoc Pathfinding::IAstarAI::canSearch + /// Deprecated: This has been superseded by . + /// + [System.Obsolete("This has been superseded by autoRepath.mode")] + public bool canSearch { + get { + return autoRepath.mode != Pathfinding.AutoRepathPolicy.Mode.Never; + } + set { + if (value) { + var v = autoRepath; + if (v.mode == Pathfinding.AutoRepathPolicy.Mode.Never) { + v.mode = Pathfinding.AutoRepathPolicy.Mode.EveryNSeconds; + autoRepath = v; + } + } else { + var v = autoRepath; + v.mode = Pathfinding.AutoRepathPolicy.Mode.Never; + autoRepath = v; + } + } + } + + /// \copydocref{FollowerEntity.simulateMovement} + public bool simulateMovement { + get => entityExists && world.EntityManager.HasComponent(entity); + set => ToggleComponent(world, entity, value, true); + } + + /// \copydocref{FollowerEntity.movementPlane} + public NativeMovementPlane movementPlane => entityStorageCache.GetComponentData(world, entity, ref movementPlaneAccessRO, out var movementPlane) ? movementPlane.value.value : new NativeMovementPlane(rotation); + + /// \copydocref{FollowerEntity.enableGravity} + public bool enableGravity { + get => entityExists && world.EntityManager.IsComponentEnabled(entity); + set { + ToggleComponentEnabled(world, entity, value, false); + } + } + + /// + /// \copydocref{ManagedState.enableLocalAvoidance} + /// + /// Note: Setting this property cannot be done via this proxy. Instead, you must add or remove the component from the entity. + /// + /// See: , which supports both get and set. + /// See: + /// + public bool enableLocalAvoidance { + get => entityExists && world.EntityManager.HasComponent(entity); + } + + /// \copydocref{FollowerEntity.localAvoidanceTemporarilyDisabled} + public bool localAvoidanceTemporarilyDisabled { + get => entityExists && world.EntityManager.HasComponent(entity) && world.EntityManager.IsComponentEnabled(entity); + } + + /// \copydocref{FollowerEntity.updatePosition} + public bool updatePosition { + get => entityExists && world.EntityManager.HasComponent(entity); + set { + ToggleComponent(world, entity, value, false); + } + } + + /// \copydocref{FollowerEntity.updateRotation} + + public bool updateRotation { + get => entityExists && world.EntityManager.HasComponent(entity); + set { + ToggleComponent(world, entity, value, false); + } + } + + /// \copydocref{FollowerEntity.orientation} + public OrientationMode orientation { + get => entityExists && world.EntityManager.HasComponent(entity) ? OrientationMode.YAxisForward : OrientationMode.ZAxisForward; + set { + ToggleComponent(world, entity, value == OrientationMode.YAxisForward, false); + } + } + + /// \copydocref{FollowerEntity.hasPath} + public bool hasPath { + get => entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState) && !managedState.pathTracer.isStale; + } + + /// \copydocref{FollowerEntity.pathPending} + public bool pathPending { + get => entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState) && managedState.pendingPath != null; + } + + /// \copydocref{FollowerEntity.isStopped} + public bool isStopped { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value.isStopped : false; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value.isStopped = value; + } + } + } + + /// \copydocref{FollowerEntity.movementSettings} + public MovementSettings movementSettings { + get => entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRO, out var movementSettings) ? movementSettings.value : default; + set { + if (entityStorageCache.GetComponentData(world, entity, ref movementSettingsAccessRW, out var movementSettings)) { + movementSettings.value = value; + } + } + } + + /// \copydocref{FollowerEntity.steeringTarget} + public Vector3 steeringTarget => entityStorageCache.GetComponentData(world, entity, ref movementStateAccessRO, out var movementState) ? (Vector3)movementState.value.nextCorner : position; + + /// \copydocref{FollowerEntity.movementOverrides} + public ManagedMovementOverrides movementOverrides => new ManagedMovementOverrides(entity, world); + + /// \copydoc Pathfinding::IAstarAI::onSearchPath + System.Action IAstarAI.onSearchPath { + get => null; + set => throw new System.NotImplementedException("The FollowerEntity does not support this property."); + } + + /// \copydoc Pathfinding::IAstarAI::FinalizeMovement + void IAstarAI.FinalizeMovement (Vector3 nextPosition, Quaternion nextRotation) { + throw new System.InvalidOperationException("The FollowerEntity component does not support FinalizeMovement. Use an ECS system to override movement instead, or use the movementOverrides property. If you just want to move the agent to a position, set ai.position or call ai.Teleport."); + } + + /// \copydocref{FollowerEntity.GetRemainingPath(List,bool)} + public void GetRemainingPath (List buffer, out bool stale) { + GetRemainingPath(buffer, null, out stale); + } + + /// \copydocref{FollowerEntity.GetRemainingPath(List,List,bool)} + public void GetRemainingPath (List buffer, List partsBuffer, out bool stale) { + buffer.Clear(); + if (partsBuffer != null) partsBuffer.Clear(); + if (!entityExists) { + buffer.Add(position); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo { startIndex = 0, endIndex = 0 }); + stale = true; + return; + } + + var ms = world.EntityManager.GetComponentData(entity); + stale = false; + if (ms.pathTracer.hasPath) { + var nativeBuffer = new NativeList(Allocator.Temp); + var scratch = new NativeArray(8, Allocator.Temp); + ms.pathTracer.GetNextCorners(nativeBuffer, int.MaxValue, ref scratch, Allocator.Temp); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(0, nativeBuffer.Length - 1)); + + if (ms.pathTracer.partCount > 1) { + // There are more parts in the path. We need to create a new PathTracer to get the other parts. + // This can be comparatively expensive, since it needs to generate all the other types from scratch. + var pathTracer = ms.pathTracer.Clone(); + while (pathTracer.partCount > 1) { + pathTracer.PopParts(1); + var startIndex = nativeBuffer.Length; + if (pathTracer.GetPartType() == Funnel.PartType.NodeSequence) { + pathTracer.GetNextCorners(nativeBuffer, int.MaxValue, ref scratch, Allocator.Temp); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(startIndex, nativeBuffer.Length - 1)); + } else { + // If the link contains destroyed nodes, we cannot get a valid link object. + // In that case, we stop here and mark the path as stale. + if (pathTracer.PartContainsDestroyedNodes()) { + stale = true; + break; + } + // Note: startIndex will refer to the last point in the previous part, and endIndex will refer to the first point in the next part + Assert.IsTrue(startIndex > 0); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(startIndex - 1, startIndex, pathTracer.GetLinkInfo())); + } + // We need to check if the path is stale after each part because the path tracer may have realized that some nodes are destroyed + stale |= pathTracer.isStale; + } + pathTracer.Dispose(); + } + + nativeBuffer.AsUnsafeSpan().Reinterpret().CopyTo(buffer); + } else { + buffer.Add(position); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo { startIndex = 0, endIndex = 0 }); + } + stale |= ms.pathTracer.isStale; + } + + /// \copydocref{FollowerEntity.Move} + public void Move (Vector3 deltaPosition) { + position += deltaPosition; + } + + /// + /// Calculate how the agent will move during this frame. + /// + /// Warning: The FollowerEntity component does not support MovementUpdate. Use an ECS system to override movement instead, or use the property. + /// + void IAstarAI.MovementUpdate (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + throw new System.InvalidOperationException("The FollowerEntity component does not support MovementUpdate. Use an ECS system to override movement instead, or use the movementOverrides property"); + } + + /// \copydocref{FollowerEntity.SearchPath} + public void SearchPath () { + var dest = destination; + if (!float.IsFinite(dest.x)) return; + + if (entityStorageCache.GetComponentData(world, entity, ref managedSettingsAccessRO, out var managedSettings)) { + var movementPlane = this.movementPlane; + var path = ABPath.Construct(position, dest, null); + path.UseSettings(managedSettings.pathfindingSettings); + SetPath(path, false); + } + } + + internal void CancelCurrentPathRequest () { + if (entityStorageCache.GetComponentData(world, entity, ref managedStateAccessRO, out var managedState)) { + managedState.CancelCurrentPathRequest(); + } + } + + internal void ClearPath () { + if (entityStorageCache.Update(world, entity, out var entityManager, out var storage)) { + agentOffMeshLinkTraversalRO.Update(entityManager); + + if (agentOffMeshLinkTraversalRO.HasComponent(storage)) { + // Agent is traversing an off-mesh link. We must abort this link traversal. + var managedInfo = entityManager.GetComponentData(entity); + if (managedInfo.stateMachine != null) managedInfo.stateMachine.OnAbortTraversingOffMeshLink(); + managedInfo.context.Restore(); + entityManager.RemoveComponent(entity); + entityManager.RemoveComponent(entity); + // We need to get the storage info again, because the entity will have been moved to another chunk + entityStorageCache.Update(world, entity, out entityManager, out storage); + } + + entityManager.SetComponentEnabled(entity, false); + + managedStateAccessRW.Update(entityManager); + movementStateAccessRW.Update(entityManager); + localTransformAccessRO.Update(entityManager); + movementPlaneAccessRO.Update(entityManager); + resolvedMovementAccessRW.Update(entityManager); + movementControlAccessRW.Update(entityManager); + + ref var movementState = ref movementStateAccessRW[storage]; + ref var localTransform = ref localTransformAccessRO[storage]; + ref var movementPlane = ref movementPlaneAccessRO[storage]; + ref var resolvedMovement = ref resolvedMovementAccessRW[storage]; + ref var controlOutput = ref movementControlAccessRW[storage]; + var managedState = managedStateAccessRW[storage]; + + managedState.ClearPath(); + managedState.CancelCurrentPathRequest(); + movementState.SetPathIsEmpty(localTransform.Position); + + // This emulates what JobControl does when the agent has no path. + // This ensures that properties like #desiredVelocity return the correct value immediately after the path has been cleared. + ResetControl(ref resolvedMovement, ref controlOutput, ref movementPlane, localTransform.Position, localTransform.Rotation, movementState.endOfPath); + } + } + + /// Adds or removes a component from an entity + internal static void ToggleComponent(World world, Entity entity, bool enabled, bool mustExist) where T : struct, IComponentData { + if (world == null || !world.IsCreated || !world.EntityManager.Exists(entity)) { + if (!mustExist) throw new System.InvalidOperationException("Entity does not exist. You can only access this if the component is active and enabled."); + return; + } + if (enabled) { + world.EntityManager.AddComponent(entity); + } else { + world.EntityManager.RemoveComponent(entity); + } + } + + /// Enables or disables a component on an entity + internal static void ToggleComponentEnabled(World world, Entity entity, bool enabled, bool mustExist) where T : struct, IComponentData, IEnableableComponent { + if (world == null || !world.IsCreated || !world.EntityManager.Exists(entity)) { + if (!mustExist) throw new System.InvalidOperationException("Entity does not exist. You can only access this if the component is active and enabled."); + return; + } + world.EntityManager.SetComponentEnabled(entity, enabled); + } + + internal static void ResetControl (ref ResolvedMovement resolvedMovement, ref MovementControl controlOutput, ref AgentMovementPlane movementPlane, float3 position, quaternion rotation, float3 endOfPath) { + resolvedMovement.targetPoint = position; + resolvedMovement.speed = 0; + resolvedMovement.targetRotation = resolvedMovement.targetRotationHint = controlOutput.targetRotation = controlOutput.targetRotationHint = movementPlane.value.ToPlane(rotation); + controlOutput.endOfPath = endOfPath; + controlOutput.speed = 0f; + controlOutput.targetPoint = position; + controlOutput.hierarchicalNodeIndex = -1; + } + + /// \copydocref{FollowerEntity.SetPath} + public void SetPath (Path path, bool updateDestinationFromPath = true) { + var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; + if (!entityManager.Exists(entity)) throw new System.InvalidOperationException("Entity does not exist. You can only assign a path if the component is active and enabled."); + + managedStateAccessRW.Update(entityManager); + movementPlaneAccessRO.Update(entityManager); + agentOffMeshLinkTraversalRO.Update(entityManager); + movementStateAccessRW.Update(entityManager); + localTransformAccessRO.Update(entityManager); + destinationPointAccessRW.Update(entityManager); + autoRepathPolicyRW.Update(entityManager); + + var storage = entityManager.GetStorageInfo(entity); + + bool isTraversingOffMeshLink = agentOffMeshLinkTraversalRO.HasComponent(storage); + if (isTraversingOffMeshLink) { + // Agent is traversing an off-mesh link. We ignore any path updates during this time. + // TODO: Race condition when adding off mesh link component? + // TODO: If passing null, should we clear the whole path after the off-mesh link? + return; + } + + if (path == null) { + ClearPath(); + return; + } + + var managedState = managedStateAccessRW[storage]; + ref var movementPlane = ref movementPlaneAccessRO[storage]; + ref var movementState = ref movementStateAccessRW[storage]; + ref var localTransform = ref localTransformAccessRO[storage]; + ref var destination = ref destinationPointAccessRW[storage]; + ref var autoRepathPolicy = ref autoRepathPolicyRW[storage]; + + if (updateDestinationFromPath && path is ABPath abPath) { + // If the user supplies a new ABPath manually, they probably want the agent to move to that point. + // So by default we update the destination to match the path. + if (abPath.endPointKnownBeforeCalculation || abPath.IsDone()) { + destination = new DestinationPoint { destination = abPath.originalEndPoint, facingDirection = default }; + } else { + // If the destination is not known, we set it to positive infinity. + // This is the case for MultiTargetPath and RandomPath, for example. + destination = new DestinationPoint { destination = Vector3.positiveInfinity, facingDirection = default }; + } + } + + // The FollowerEntity works best with a ClosestAsSeenFromAboveSoft distance metric + path.nearestNodeDistanceMetric = DistanceMetric.ClosestAsSeenFromAboveSoft(movementPlane.value.up); + + autoRepathPolicy.OnScheduledPathRecalculation(destination.destination, (float)World.DefaultGameObjectInjectionWorld.Time.ElapsedTime); + if (path.IsDone()) autoRepathPolicy.OnPathCalculated(path.error); + ManagedState.SetPath(path, managedState, in movementPlane, ref destination); + + // Check if we have started to follow the path. + // If it wasn't calculated yet, it will have just been scheduled to be calculated, and will be applied later. + if (managedState.activePath == path) { + agentCylinderShapeAccessRO.Update(entityManager); + movementSettingsAccessRO.Update(entityManager); + readyToTraverseOffMeshLinkRW.Update(entityManager); + + // This remaining part ensures that the path tracer is fully up to date immediately after the path has been assigned. + // So that things like GetRemainingPath, and various properties like reachedDestination are up to date immediately. + managedState.pathTracer.UpdateStart(localTransform.Position, PathTracer.RepairQuality.High, movementPlane.value); + managedState.pathTracer.UpdateEnd(destination.destination, PathTracer.RepairQuality.High, movementPlane.value); + + if (movementState.pathTracerVersion != managedState.pathTracer.version) { + InitScratchData(); + ref var shape = ref agentCylinderShapeAccessRO[storage]; + ref var movementSettings = ref movementSettingsAccessRO[storage]; + var readyToTraverseOffMeshLink = storage.Chunk.GetEnabledMask(ref readyToTraverseOffMeshLinkRW.handle).GetEnabledRefRW(storage.IndexInChunk); + JobRepairPath.Execute( + ref localTransform, + ref movementState, + ref shape, + ref movementPlane, + ref autoRepathPolicy, + ref destination, + readyToTraverseOffMeshLink, + managedState, + in movementSettings, + nextCornersScratch, + ref indicesScratch, + Allocator.Persistent, + false + ); + } + } + } + + /// \copydocref{FollowerEntity.Teleport} + public void Teleport (Vector3 newPosition, bool clearPath = true) { + if (!entityExists) return; + + if (clearPath) ClearPath(); + + var entityManager = world.EntityManager; + movementOutputAccessRW.Update(entityManager); + managedStateAccessRW.Update(entityManager); + managedSettingsAccessRO.Update(entityManager); + movementPlaneAccessRW.Update(entityManager); + resolvedMovementAccessRW.Update(entityManager); + movementControlAccessRW.Update(entityManager); + var storage = entityManager.GetStorageInfo(entity); + + ref var movementOutput = ref movementOutputAccessRW[storage]; + movementOutput.lastPosition = newPosition; + var managedState = managedStateAccessRW[storage]; + var managedSettings = managedSettingsAccessRO[storage]; + if (clearPath) managedState.CancelCurrentPathRequest(); + + if (!managedState.pathTracer.hasPath && AstarPath.active != null) { + // Since we haven't calculated a path yet, + var nearest = AstarPath.active.GetNearest(newPosition, managedSettings.pathfindingSettings.ToNearestNodeConstraint()); + + if (nearest.node != null) { + ref var movementPlane = ref movementPlaneAccessRW[storage]; + ref var resolvedMovement = ref resolvedMovementAccessRW[storage]; + ref var movementControl = ref movementControlAccessRW[storage]; + + // If we are using the graph's natural movement plane, we need to update our movement plane from the graph + // before we start repairing the path. Otherwise the agent can get snapped to a weird point on the navmesh, + // if its initial rotation was not aligned with the graph. + if (movementPlaneSource == MovementPlaneSource.Graph) { + // The target rotations are relative to the movement plane, so we need to patch it, to make sure it stays constant in world space. + // This is important when the agent starts with isStopped=true, because the targetRotation will not be recalculated every frame. + // TODO: Alternatively we could make sure to always make the new movement plane as similar as possible to the old one, + // but this has a minor performance impact every frame. + var targetRotation = movementPlane.value.ToWorldRotation(resolvedMovement.targetRotation); + var targetRotationHint = movementPlane.value.ToWorldRotation(resolvedMovement.targetRotationHint); + var targetRotation2 = movementPlane.value.ToWorldRotation(movementControl.targetRotation); + var targetRotationHint2 = movementPlane.value.ToWorldRotation(movementControl.targetRotationHint); + + movementPlane = new AgentMovementPlane(MovementPlaneFromGraphSystem.MovementPlaneFromGraph(nearest.node.Graph)); + // TODO: Do we need to do a similar thing for the raycast and navmesh normal cases? + + resolvedMovement.targetRotation = movementPlane.value.ToPlane(targetRotation); + resolvedMovement.targetRotationHint = movementPlane.value.ToPlane(targetRotationHint); + movementControl.targetRotation = movementPlane.value.ToPlane(targetRotation2); + movementControl.targetRotationHint = movementPlane.value.ToPlane(targetRotationHint2); + } + + // Make the agent's path consist of a single node at the current position. + // This is temporary and will be replaced by the actual path when it is calculated. + // This allows it to be clamped to the navmesh immediately, instead of waiting for a destination to be set and a path to be calculated. + managedState.pathTracer.SetFromSingleNode(nearest.node, nearest.position, movementPlane.value, managedSettings.pathfindingSettings.ToTraversalConstraint(), managedSettings.pathfindingSettings.ToTraversalCosts()); + } + } + + // Note: Since we are starting from a completely new path, + // setting the position will also cause the path tracer to repair the destination. + // Therefore we don't have to also set the destination here. + position = newPosition; + } + + /// + /// Destroys the entity and clears the proxy. + /// + /// If the entity does not exist, this does nothing. + /// + public void Destroy () { + if (entityExists) world.EntityManager.DestroyEntity(entity); + this = default; // Clear the proxy to avoid dangling references + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs.meta new file mode 100644 index 0000000..08760e7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/FollowerEntityProxy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bff690f261cf294283acc5c5bea901f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs new file mode 100644 index 0000000..b59a4ac --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs @@ -0,0 +1,531 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; + +namespace Pathfinding { + /// + /// Common interface for all movement scripts in the A* Pathfinding Project. + /// See: + /// See: + /// See: + /// + public interface IAstarAI { + /// + /// Radius of the agent in world units. + /// This is visualized in the scene view as a yellow cylinder around the character. + /// + /// Note that this does not affect pathfinding in any way. + /// The graph used completely determines where the agent can move. + /// + /// Note: The script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script. + /// + float radius { get; set; } + + /// + /// Height of the agent in world units. + /// This is visualized in the scene view as a yellow cylinder around the character. + /// + /// This value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. + /// However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. + /// That said, it may be used for something in a future release. + /// + /// Note: The script doesn't really have any use of knowing the radius or the height of the character, so this property will always return 0 in that script. + /// + float height { get; set; } + + /// + /// Position of the agent. + /// In world space. + /// See: + /// + /// If you want to move the agent you may use or . + /// + Vector3 position { get; } + + /// + /// Rotation of the agent. + /// In world space. + /// See: + /// + Quaternion rotation { get; set; } + + /// Max speed in world units per second + float maxSpeed { get; set; } + + /// + /// Actual velocity that the agent is moving with. + /// In world units per second. + /// + /// See: + /// + Vector3 velocity { get; } + + /// + /// Velocity that this agent wants to move with. + /// Includes gravity and local avoidance if applicable. + /// In world units per second. + /// + /// See: + /// + /// Note: The movement script doesn't use local avoidance or gravity so this property will always be identical to on that component. + /// + Vector3 desiredVelocity { get; } + + /// + /// Velocity that this agent wants to move with before taking local avoidance into account. + /// + /// Includes gravity. + /// In world units per second. + /// + /// Setting this property will set the current velocity that the agent is trying to move with, including gravity. + /// This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way. + /// + /// + /// // Set the velocity to zero, but keep the current gravity + /// var newVelocity = new Vector3(0, ai.desiredVelocityWithoutLocalAvoidance.y, 0); + /// + /// ai.desiredVelocityWithoutLocalAvoidance = newVelocity; + /// + /// + /// Note: The movement script doesn't use local avoidance so this property will always be identical to on that component. + /// + /// Warning: Trying to set this property on an AILerp component will throw an exception since its velocity cannot meaningfully be changed abitrarily. + /// + /// If you are not using local avoidance then this property will in almost all cases be identical to plus some noise due to floating point math. + /// + /// See: + /// See: + /// See: + /// See: + /// + Vector3 desiredVelocityWithoutLocalAvoidance { get; set; } + + /// + /// Approximate remaining distance along the current path to the end of the path. + /// The RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. + /// However it will be accurate when the agent is within 1 corner of the destination. + /// You can use to calculate the actual remaining path more precisely. + /// + /// The AIPath and AILerp scripts use a more accurate distance calculation at all times. + /// + /// If the agent does not currently have a path, then positive infinity will be returned. + /// + /// Note: This is the distance to the end of the path, which may or may not be at the . If the character cannot reach the destination it will try to move as close as possible to it. + /// + /// Warning: Since path requests are asynchronous, there is a small delay between a path request being sent and this value being updated with the new calculated path. + /// + /// See: + /// See: + /// See: + /// + float remainingDistance { get; } + + /// + /// True if the ai has reached the . + /// This is a best effort calculation to see if the has been reached. + /// For the AIPath/RichAI scripts, this is when the character is within world units from the . + /// For the AILerp script it is when the character is at the destination (±a very small margin). + /// + /// This value will be updated immediately when the is changed (in contrast to , however since path requests are asynchronous + /// it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance + /// from the end of the path to the (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total + /// distance is less than . This property is therefore only a best effort, but it will work well for almost all use cases. + /// + /// Furthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the of the character below its feet + /// (so if you have a multilevel building, it is important that you configure the of the character correctly). + /// + /// The cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. + /// During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), + /// even though it may actually be quite a long way around the wall to the other side. + /// + /// In contrast to , this property is immediately updated when the is changed. + /// + /// + /// IEnumerator Start () { + /// ai.destination = somePoint; + /// // Start to search for a path to the destination immediately + /// ai.SearchPath(); + /// // Wait until the agent has reached the destination + /// while (!ai.reachedDestination) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + /// See: + /// See: + /// See: + /// + bool reachedDestination { get; } + + /// + /// True if the agent has reached the end of the current path. + /// + /// Note that setting the does not immediately update the path, nor is there any guarantee that the + /// AI will actually be able to reach the destination that you set. The AI will try to get as close as possible. + /// Often you want to use instead which is easier to work with. + /// + /// It is very hard to provide a method for detecting if the AI has reached the that works across all different games + /// because the destination may not even lie on the navmesh and how that is handled differs from game to game (see also the code snippet in the docs for ). + /// + /// See: + /// See: + /// + bool reachedEndOfPath { get; } + + /// + /// End point of path the agent is currently following. + /// If the agent has no path (or it might not be calculated yet), this will return the instead. + /// If the agent has no destination it will return the agent's current position. + /// + /// The end of the path is usually identical or very close to the , but it may differ + /// if the path for example was blocked by a wall so that the agent couldn't get any closer. + /// + /// This is only updated when the path is recalculated. + /// + Vector3 endOfPath { get; } + + /// + /// Position in the world that this agent should move to. + /// + /// If no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned. + /// + /// Note that setting this property does not immediately cause the agent to recalculate its path. + /// So it may take some time before the agent starts to move towards this point. + /// Most movement scripts have a repathRate field which indicates how often the agent looks + /// for a new path. You can also call the method to immediately + /// start to search for a new path. Paths are calculated asynchronously so when an agent starts to + /// search for path it may take a few frames (usually 1 or 2) until the result is available. + /// During this time the property will return true. + /// + /// If you are setting a destination and then want to know when the agent has reached that destination + /// then you could either use (recommended) or check both and . + /// Check the documentation for the respective fields to learn about their differences. + /// + /// + /// IEnumerator Start () { + /// ai.destination = somePoint; + /// // Start to search for a path to the destination immediately + /// ai.SearchPath(); + /// // Wait until the agent has reached the destination + /// while (!ai.reachedDestination) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + /// IEnumerator Start () { + /// ai.destination = somePoint; + /// // Start to search for a path to the destination immediately + /// // Note that the result may not become available until after a few frames + /// // ai.pathPending will be true while the path is being calculated + /// ai.SearchPath(); + /// // Wait until we know for sure that the agent has calculated a path to the destination we set above + /// while (ai.pathPending || !ai.reachedEndOfPath) { + /// yield return null; + /// } + /// // The agent has reached the destination now + /// } + /// + /// + Vector3 destination { get; set; } + + /// + /// Enables or disables recalculating the path at regular intervals. + /// Setting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path. + /// + /// Note that this only disables automatic path recalculations. If you call the method a path will still be calculated. + /// + /// See: + /// See: + /// + bool canSearch { get; set; } + + /// + /// Enables or disables movement simulations completely. + /// + /// This is primarily useful if you want to override the default movement behavior. + /// If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. . + /// Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement. + /// + /// If you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use instead. + /// If you want to make the agent stop immediately, disable the whole component instead. + /// + /// See: + /// See: + /// See: + /// + bool simulateMovement { get; set; } + + /// + /// Enables or disables movement completely. + /// If you want the agent to stand still, but still react to local avoidance and use gravity: use instead. + /// + /// This is also useful if you want to have full control over when the movement calculations run. + /// Take a look at + /// + /// See: + /// See: + /// Deprecated: Renamed to to avoid confusion with the property. + /// + [System.Obsolete("Renamed to simulateMovement")] + bool canMove { + get => simulateMovement; + set { + simulateMovement = value; + } + } + + /// True if this agent currently has a path that it follows + bool hasPath { get; } + + /// True if a path is currently being calculated + bool pathPending { get; } + + /// + /// Determines if the character's position should be coupled to the Transform's position. + /// If false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. + /// Instead, only the property will change. + /// + /// This is useful if you want to control the movement of the character using some other means, such + /// as root motion, but still want the AI to move freely. + /// + /// See: which in contrast to this field will disable all movement calculations. + /// See: + /// + bool updatePosition { get; set; } + + /// + /// Determines if the character's rotation should be coupled to the Transform's rotation. + /// If false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. + /// Instead, only the property will change. + /// + /// This is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed + /// sprite to indicate the direction the character is facing. + /// + /// See: + /// See: + /// + bool updateRotation { get; set; } + + /// + /// Gets or sets if the agent should stop moving. + /// If this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. + /// The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction. + /// + /// The current path of the agent will not be cleared, so when this is set + /// to false again the agent will continue moving along the previous path. + /// + /// This is a purely user-controlled parameter, so for example it is not set automatically when the agent stops + /// moving because it has reached the target. Use for that. + /// + /// If this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will + /// continue traversing the link and stop once it has completed it. + /// + /// Note: This is not the same as the setting which some movement scripts have. The setting + /// disables movement calculations completely (which among other things makes it not be affected by local avoidance or gravity). + /// For the AILerp movement script which doesn't use gravity or local avoidance anyway changing this property is very similar to + /// changing . + /// + /// The property will continue to indicate the point which the agent would move towards if it would not be stopped. + /// + /// For most movement scripts, this will make the agent slow down quickly, but not instantly stop. + /// If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), + /// or you could set to Vector3.zero, which will stop it immediately. + /// + bool isStopped { get; set; } + + /// + /// Point on the path which the agent is currently moving towards. + /// This is usually a point a small distance ahead of the agent + /// or the end of the path. + /// + /// If the agent does not have a path at the moment, then the agent's current position will be returned. + /// + Vector3 steeringTarget { get; } + + /// + /// Called when the agent recalculates its path. + /// This is called both for automatic path recalculations (see and manual ones (see . + /// + /// See: Take a look at the source code for an example of how it can be used. + /// + System.Action onSearchPath { get; set; } + + /// + /// The plane the agent is moving in. + /// + /// This is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. + /// Ultimately it depends on the graph orientation. + /// + /// If you are doing pathfinding on a spherical world (see spherical) (view in online documentation for working links), the the movement plane will be the tangent plane of the sphere at the agent's position. + /// + NativeMovementPlane movementPlane { get; } + /// + /// Fills buffer with the remaining path. + /// + /// + /// var buffer = new List(); + /// + /// ai.GetRemainingPath(buffer, out bool stale); + /// for (int i = 0; i < buffer.Count - 1; i++) { + /// Debug.DrawLine(buffer[i], buffer[i+1], Color.red); + /// } + /// + /// [Open online documentation to see images] + /// + /// The buffer will be cleared and replaced with the path. The first point is the current position of the agent. + /// May be true if the path is invalid in some way. For example if the agent has no path or (for the RichAI/FollowerEntity components only) if the agent has detected that some nodes in the path have been destroyed. + void GetRemainingPath(List buffer, out bool stale); + + /// + /// Fills buffer with the remaining path. + /// + /// + /// var buffer = new List(); + /// var parts = new List(); + /// + /// ai.GetRemainingPath(buffer, parts, out bool stale); + /// foreach (var part in parts) { + /// for (int i = part.startIndex; i < part.endIndex; i++) { + /// Debug.DrawLine(buffer[i], buffer[i+1], part.type == Funnel.PartType.NodeSequence ? Color.red : Color.green); + /// } + /// } + /// + /// [Open online documentation to see images] + /// + /// Note: The and movement scripts do not know about off-mesh links, so the partsBuffer will always be filled with a single node-sequence part. + /// + /// The buffer will be cleared and replaced with the path. The first point is the current position of the agent. + /// If not null, this list will be cleared and filled with information about the different parts of the path. A part is a sequence of nodes or an off-mesh link. + /// May be true if the path is invalid in some way. For example if the agent has no path or (for the RichAI/FollowerEntity components only) if the agent has detected that some nodes in the path have been destroyed. + void GetRemainingPath(List buffer, List partsBuffer, out bool stale); + + /// + /// Recalculate the current path. + /// You can for example use this if you want very quick reaction times when you have changed the + /// so that the agent does not have to wait until the next automatic path recalculation (see . + /// + /// If there is an ongoing path calculation, it will be canceled, so make sure you leave time for the paths to get calculated before calling this function again. + /// A canceled path will show up in the log with the message "Canceled by script" (see ). + /// + /// If no has been set yet then nothing will be done. + /// + /// Note: The path result may not become available until after a few frames. + /// During the calculation time the property will return true. + /// + /// See: + /// + void SearchPath(); + + /// + /// Make the AI follow the specified path. + /// + /// In case the path has not been calculated, the script will call seeker.StartPath to calculate it. + /// This means the AI may not actually start to follow the path until in a few frames when the path has been calculated. + /// The field will as usual return true while the path is being calculated. + /// + /// In case the path has already been calculated it will immediately replace the current path the AI is following. + /// This is useful if you want to replace how the AI calculates its paths. + /// + /// If you pass null as a parameter then the current path will be cleared and the agent will stop moving. + /// Note than unless you have also disabled then the agent will soon recalculate its path and start moving again. + /// It is not recommended to use this as a way of stopping the agent, since it will also cause it to loose its connection to the navmesh and will thus no longer be able + /// to clamp itself to the surface of the navmesh (important for the and movement scripts). + /// Instead use . + /// + /// You can disable the automatic path recalculation by setting the field to false. + /// + /// Note: This call will be ignored if the agent is currently traversing an off-mesh link. Furthermore, if the agent starts traversing an off-mesh link, the current path request will be canceled (if one is currently in progress). + /// + /// + /// // Disable the automatic path recalculation + /// ai.canSearch = false; + /// var pointToAvoid = enemy.position; + /// // Make the AI flee from the enemy. + /// // The path will be about 20 world units long (the default cost of moving 1 world unit is 1000). + /// var path = FleePath.Construct(ai.position, pointToAvoid, 1000 * 20); + /// ai.SetPath(path); + /// + /// + /// The path to follow. + /// If true, the \reflink{destination} property will be set to the end point of the path. If false, the previous destination value will be kept. + void SetPath(Path path, bool updateDestinationFromPath = true); + + /// + /// Instantly move the agent to a new position. + /// This will trigger a path recalculation (if clearPath is true, which is the default) so if you want to teleport the agent and change its + /// it is recommended that you set the before calling this method. + /// + /// The current path will be cleared by default. + /// + /// This method is preferred for long distance teleports. If you only move the agent a very small distance (so that it is reasonable that it can keep its current path), + /// then setting the property is preferred. + /// When using the movement script, setting the property when using a long distance teleport could cause the agent to fail to move the full distance, as it can get blocked by the navmesh. + /// + /// See: Works similarly to Unity's NavmeshAgent.Warp. + /// See: + /// + void Teleport(Vector3 newPosition, bool clearPath = true); + + /// + /// Move the agent. + /// + /// This is intended for external movement forces such as those applied by wind, conveyor belts, knockbacks etc. + /// + /// Some movement scripts may ignore this completely (notably the script) if it does not have + /// any concept of being moved externally. + /// + /// For the and movement scripts, the agent will not be moved immediately when calling this method. Instead this offset will be stored and then + /// applied the next time the agent runs its movement calculations (which is usually later this frame or the next frame). + /// If you want to move the agent immediately then call: + /// + /// ai.Move(someVector); + /// ai.FinalizeMovement(ai.position, ai.rotation); + /// + /// + /// The movement script will, on the other hand, move the agent immediately. + /// + /// Direction and distance to move the agent in world space. + void Move(Vector3 deltaPosition); + + /// + /// Calculate how the character wants to move during this frame. + /// + /// Note that this does not actually move the character. You need to call for that. + /// This is called automatically unless is false. + /// + /// To handle movement yourself you can disable and call this method manually. + /// This code will replicate the normal behavior of the component: + /// + /// void Update () { + /// // Disable the AIs own movement code + /// ai.simulateMovement = false; + /// Vector3 nextPosition; + /// Quaternion nextRotation; + /// // Calculate how the AI wants to move + /// ai.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation); + /// // Modify nextPosition and nextRotation in any way you wish + /// // Actually move the AI + /// ai.FinalizeMovement(nextPosition, nextRotation); + /// } + /// + /// + /// time to simulate movement for. Usually set to Time.deltaTime. + /// the position that the agent wants to move to during this frame. + /// the rotation that the agent wants to rotate to during this frame. + void MovementUpdate(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation); + + /// + /// Move the agent. + /// To be called as the last step when you are handling movement manually. + /// + /// The movement will be clamped to the navmesh if applicable (this is done for the RichAI movement script). + /// + /// See: for a code example. + /// + void FinalizeMovement(Vector3 nextPosition, Quaternion nextRotation); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs.meta new file mode 100644 index 0000000..b7106e3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/IAstarAI.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b7438f3f6b9404f05ab7f584f92aa7d5 +timeCreated: 1495013922 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs new file mode 100644 index 0000000..076f8a4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs @@ -0,0 +1,73 @@ +using UnityEngine; +namespace Pathfinding.Examples { + using Pathfinding.Util; + + /// + /// RichAI for local space (pathfinding on moving graphs). + /// + /// What this script does is that it fakes graph movement. + /// It can be seen in the example scene called 'Moving' where + /// a character is pathfinding on top of a moving ship. + /// The graph does not actually move in that example + /// instead there is some 'cheating' going on. + /// + /// When requesting a path, we first transform + /// the start and end positions of the path request + /// into local space for the object we are moving on + /// (e.g the ship in the example scene), then when we get the + /// path back, they will still be in these local coordinates. + /// When following the path, we will every frame transform + /// the coordinates of the waypoints in the path to global + /// coordinates so that we can follow them. + /// + /// At the start of the game (when the graph is scanned) the + /// object we are moving on should be at a valid position on the graph and + /// you should attach the component to it. The + /// component will store the position and orientation of the object right there are the start + /// and then we can use that information to transform coordinates back to that region of the graph + /// as if the object had not moved at all. + /// + /// This functionality is only implemented for the RichAI + /// script, however it should not be hard to + /// use the same approach for other movement scripts. + /// + [HelpURL("https://arongranberg.com/astar/documentation/stable/localspacerichai.html")] + public class LocalSpaceRichAI : RichAI { + /// Root of the object we are moving on + public LocalSpaceGraph graph; + + protected override Vector3 ClampPositionToGraph (Vector3 newPosition) { + RefreshTransform(); + // Clamp the new position to the navmesh + // First we need to transform the position to the same space that the graph is in though. + var nearest = AstarPath.active != null? AstarPath.active.GetNearest(graph.transformation.InverseTransform(newPosition)) : new NNInfo(); + float elevation; + + movementPlane.ToPlane(newPosition, out elevation); + return movementPlane.ToWorld(movementPlane.ToPlane(nearest.node != null ? graph.transformation.Transform(nearest.position) : newPosition), elevation); + } + + void RefreshTransform () { + graph.Refresh(); + richPath.transform = graph.transformation; + movementPlane = graph.transformation.ToSimpleMovementPlane(); + } + + protected override void Start () { + RefreshTransform(); + base.Start(); + } + + protected override void CalculatePathRequestEndpoints (out Vector3 start, out Vector3 end) { + RefreshTransform(); + base.CalculatePathRequestEndpoints(out start, out end); + start = graph.transformation.InverseTransform(start); + end = graph.transformation.InverseTransform(end); + } + + protected override void OnUpdate (float dt) { + RefreshTransform(); + base.OnUpdate(dt); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs.meta new file mode 100644 index 0000000..0c09569 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/LocalSpaceRichAI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e342e9f54c9d04f05b77eff70a36605e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f2e81a0445323b64f973d2f5b5c56e15, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs new file mode 100644 index 0000000..3252c29 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs @@ -0,0 +1,646 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.RVO; + using Pathfinding.Util; + using Pathfinding.Drawing; + + [AddComponentMenu("Pathfinding/AI/RichAI (3D, for navmesh)")] + [UniqueComponent(tag = "ai")] + [DisallowMultipleComponent] + /// + /// Advanced AI for navmesh based graphs. + /// + /// See: movementscripts (view in online documentation for working links) + /// + public partial class RichAI : AIBase, IAstarAI { + /// + /// Max acceleration of the agent. + /// In world units per second per second. + /// + public float acceleration = 5; + + /// + /// Max rotation speed of the agent. + /// In degrees per second. + /// + public float rotationSpeed = 360; + + /// + /// How long before reaching the end of the path to start to slow down. + /// A lower value will make the agent stop more abruptly. + /// + /// Note: The agent may require more time to slow down if + /// its maximum is not high enough. + /// + /// If set to zero the agent will not even attempt to slow down. + /// This can be useful if the target point is not a point you want the agent to stop at + /// but it might for example be the player and you want the AI to slam into the player. + /// + /// Note: A value of zero will behave differently from a small but non-zero value (such as 0.0001). + /// When it is non-zero the agent will still respect its when determining if it needs + /// to slow down, but if it is zero it will disable that check. + /// This is useful if the is not a point where you want the agent to stop. + /// + /// \htmlonly \endhtmlonly + /// + public float slowdownTime = 0.5f; + + /// + /// Force to avoid walls with. + /// The agent will try to steer away from walls slightly. + /// + /// See: + /// + public float wallForce = 3; + + /// + /// Walls within this range will be used for avoidance. + /// Setting this to zero disables wall avoidance and may improve performance slightly + /// + /// See: + /// + public float wallDist = 1; + + /// + /// Use funnel simplification. + /// On tiled navmesh maps, but sometimes on normal ones as well, it can be good to simplify + /// the funnel as a post-processing step to make the paths straighter. + /// + /// This has a moderate performance impact during frames when a path calculation is completed. + /// + /// The RichAI script uses its own internal funnel algorithm, so you never + /// need to attach the FunnelModifier component. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + public bool funnelSimplification = false; + + /// + /// Slow down when not facing the target direction. + /// Incurs at a small performance overhead. + /// + /// This setting only has an effect if is enabled. + /// + public bool slowWhenNotFacingTarget = true; + + /// + /// Prevent the velocity from being too far away from the forward direction of the character. + /// If the character is ordered to move in the opposite direction from where it is facing + /// then enabling this will cause it to make a small loop instead of turning on the spot. + /// + /// This setting only has an effect if is enabled. + /// + public bool preventMovingBackwards = false; + + /// + /// Called when the agent starts to traverse an off-mesh link. + /// Register to this callback to handle off-mesh links in a custom way. + /// + /// If this event is set to null then the agent will fall back to traversing + /// off-mesh links using a very simple linear interpolation. + /// + /// + /// void OnEnable () { + /// ai = GetComponent(); + /// if (ai != null) ai.onTraverseOffMeshLink += TraverseOffMeshLink; + /// } + /// + /// void OnDisable () { + /// if (ai != null) ai.onTraverseOffMeshLink -= TraverseOffMeshLink; + /// } + /// + /// IEnumerator TraverseOffMeshLink (RichSpecial link) { + /// // Traverse the link over 1 second + /// float startTime = Time.time; + /// + /// while (Time.time < startTime + 1) { + /// transform.position = Vector3.Lerp(link.first.position, link.second.position, Time.time - startTime); + /// yield return null; + /// } + /// transform.position = link.second.position; + /// } + /// + /// + public System.Func onTraverseOffMeshLink; + + /// Holds the current path that this agent is following + protected readonly RichPath richPath = new RichPath(); + + protected bool delayUpdatePath; + protected bool lastCorner; + + /// Internal state used for filtering out noise in the agent's rotation + Vector2 rotationFilterState; + Vector2 rotationFilterState2; + + /// Distance to in the movement plane + protected float distanceToSteeringTarget = float.PositiveInfinity; + + protected readonly List nextCorners = new List(); + protected readonly List wallBuffer = new List(); + + public bool traversingOffMeshLink { get; protected set; } + + /// \copydoc Pathfinding::IAstarAI::remainingDistance + public float remainingDistance { + get { + return distanceToSteeringTarget + Vector3.Distance(steeringTarget, richPath.Endpoint); + } + } + + /// \copydoc Pathfinding::IAstarAI::reachedEndOfPath + public bool reachedEndOfPath { get { return approachingPathEndpoint && distanceToSteeringTarget < endReachedDistance; } } + + /// \copydoc Pathfinding::IAstarAI::reachedDestination + public override bool reachedDestination { + get { + if (!reachedEndOfPath) return false; + // Distance from our position to the current steering target + + // Distance from the steering target to the end of the path + + // distance from the end of the path to the destination. + // Note that most distance checks are done only in the movement plane (which means in most cases that the y coordinate differences are discarded). + // This is because those coordinates are often not very accurate. + // A separate check is done below to make sure that the destination y coordinate is correct + if (distanceToSteeringTarget + movementPlane.ToPlane(steeringTarget - richPath.Endpoint).magnitude + movementPlane.ToPlane(destination - richPath.Endpoint).magnitude > endReachedDistance) return false; + + // Don't do height checks in 2D mode + if (orientation != OrientationMode.YAxisForward) { + // Check if the destination is above the head of the character or far below the feet of it + float yDifference; + movementPlane.ToPlane(destination - position, out yDifference); + var h = tr.localScale.y * height; + if (yDifference > h || yDifference < -h*0.5) return false; + } + + return true; + } + } + + /// \copydoc Pathfinding::IAstarAI::hasPath + public bool hasPath { get { return richPath.GetCurrentPart() != null; } } + + /// \copydoc Pathfinding::IAstarAI::pathPending + public bool pathPending { get { return waitingForPathCalculation || delayUpdatePath; } } + + /// \copydoc Pathfinding::IAstarAI::steeringTarget + public Vector3 steeringTarget { get; protected set; } + + /// \copydoc Pathfinding::IAstarAI::radius + float IAstarAI.radius { get { return radius; } set { radius = value; } } + + /// \copydoc Pathfinding::IAstarAI::height + float IAstarAI.height { get { return height; } set { height = value; } } + + /// \copydoc Pathfinding::IAstarAI::maxSpeed + float IAstarAI.maxSpeed { get { return maxSpeed; } set { maxSpeed = value; } } + + /// \copydoc Pathfinding::IAstarAI::canSearch + bool IAstarAI.canSearch { get { return canSearch; } set { canSearch = value; } } + + /// \copydoc Pathfinding::IAstarAI::simulateMovement + bool IAstarAI.simulateMovement { get { return simulateMovement; } set { simulateMovement = value; } } + + /// \copydoc Pathfinding::IAstarAI::movementPlane + NativeMovementPlane IAstarAI.movementPlane => new NativeMovementPlane(movementPlane); + + /// + /// True if approaching the last waypoint in the current part of the path. + /// Path parts are separated by off-mesh links. + /// + /// See: + /// + public bool approachingPartEndpoint { + get { + return lastCorner && nextCorners.Count == 1; + } + } + + /// + /// True if approaching the last waypoint of all parts in the current path. + /// Path parts are separated by off-mesh links. + /// + /// See: + /// + public bool approachingPathEndpoint { + get { + return approachingPartEndpoint && richPath.IsLastPart; + } + } + + /// \copydoc Pathfinding::IAstarAI::endOfPath + public override Vector3 endOfPath { + get { + if (hasPath) return richPath.Endpoint; + if (float.IsFinite(destination.x)) return destination; + return position; + } + } + + public override Quaternion rotation { + get { + return base.rotation; + } + set { + base.rotation = value; + // Make the agent keep this rotation instead of just rotating back to whatever it used before + rotationFilterState = Vector2.zero; + rotationFilterState2 = Vector2.zero; + } + } + + /// + /// \copydoc Pathfinding::IAstarAI::Teleport + /// + /// When setting transform.position directly the agent + /// will be clamped to the part of the navmesh it can + /// reach, so it may not end up where you wanted it to. + /// This ensures that the agent can move to any part of the navmesh. + /// + public override void Teleport (Vector3 newPosition, bool clearPath = true) { + base.Teleport(ClampPositionToGraph(newPosition), clearPath); + } + + protected virtual Vector3 ClampPositionToGraph (Vector3 newPosition) { + // Clamp the new position to the navmesh + var nearest = AstarPath.active != null? AstarPath.active.GetNearest(newPosition) : new NNInfo(); + float elevation; + + movementPlane.ToPlane(newPosition, out elevation); + return movementPlane.ToWorld(movementPlane.ToPlane(nearest.node != null ? nearest.position : newPosition), elevation); + } + + /// Called when the component is disabled + protected override void OnDisable () { + base.OnDisable(); + traversingOffMeshLink = false; + // Stop the off mesh link traversal coroutine + StopAllCoroutines(); + rotationFilterState = Vector2.zero; + rotationFilterState2 = Vector2.zero; + } + + protected override bool shouldRecalculatePath { + get { + // Don't automatically recalculate the path in the middle of an off-mesh link + return base.shouldRecalculatePath && !traversingOffMeshLink; + } + } + + public override void SearchPath () { + // Calculate paths after the current off-mesh link has been completed + if (traversingOffMeshLink) { + delayUpdatePath = true; + } else { + base.SearchPath(); + } + } + + protected override void OnPathComplete (Path p) { + waitingForPathCalculation = false; + p.Claim(this); + + if (p.error) { + p.Release(this); + return; + } + + if (traversingOffMeshLink) { + delayUpdatePath = true; + } else { + // The RandomPath and MultiTargetPath do not have a well defined destination that could have been + // set before the paths were calculated. So we instead set the destination here so that some properties + // like #reachedDestination and #remainingDistance work correctly. + if (p is ABPath abPath && !abPath.endPointKnownBeforeCalculation) { + destination = abPath.originalEndPoint; + } + + richPath.Initialize(seeker, p, true, funnelSimplification); + + // Check if we have already reached the end of the path + // We need to do this here to make sure that the #reachedEndOfPath + // property is up to date. + var part = richPath.GetCurrentPart() as RichFunnel; + if (part != null) { + if (updatePosition) simulatedPosition = tr.position; + + // Note: UpdateTarget has some side effects like setting the nextCorners list and the lastCorner field + var localPosition = movementPlane.ToPlane(UpdateTarget(part)); + + // Target point + steeringTarget = nextCorners[0]; + Vector2 targetPoint = movementPlane.ToPlane(steeringTarget); + distanceToSteeringTarget = (targetPoint - localPosition).magnitude; + + if (lastCorner && nextCorners.Count == 1 && distanceToSteeringTarget <= endReachedDistance) { + NextPart(); + } + } + } + p.Release(this); + } + + protected override void ClearPath () { + CancelCurrentPathRequest(); + richPath.Clear(); + lastCorner = false; + delayUpdatePath = false; + distanceToSteeringTarget = float.PositiveInfinity; + } + + /// + /// Declare that the AI has completely traversed the current part. + /// This will skip to the next part, or call OnTargetReached if this was the last part + /// + protected void NextPart () { + if (!richPath.CompletedAllParts) { + if (!richPath.IsLastPart) lastCorner = false; + richPath.NextPart(); + if (richPath.CompletedAllParts) { + OnTargetReached(); + } + } + } + + /// \copydocref{IAstarAI.GetRemainingPath(List,bool)} + public void GetRemainingPath (List buffer, out bool stale) { + richPath.GetRemainingPath(buffer, null, simulatedPosition, out stale); + } + + /// \copydocref{IAstarAI.GetRemainingPath(List,List,bool)} + public void GetRemainingPath (List buffer, List partsBuffer, out bool stale) { + richPath.GetRemainingPath(buffer, partsBuffer, simulatedPosition, out stale); + } + + /// + /// Called when the end of the path is reached. + /// + /// Deprecated: Avoid overriding this method. Instead poll the or properties. + /// + protected virtual void OnTargetReached () { + } + + protected virtual Vector3 UpdateTarget (RichFunnel fn) { + nextCorners.Clear(); + + // This method assumes simulatedPosition is up to date as our current position. + // We read and write to tr.position as few times as possible since doing so + // is much slower than to read and write from/to a local/member variable. + bool requiresRepath; + Vector3 position = fn.Update(simulatedPosition, nextCorners, 2, out lastCorner, out requiresRepath); + + if (requiresRepath && !waitingForPathCalculation && canSearch) { + // TODO: What if canSearch is false? How do we notify other scripts that might be handling the path calculation that a new path needs to be calculated? + SearchPath(); + } + + return position; + } + + /// Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not + protected override void MovementUpdateInternal (float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + if (updatePosition) simulatedPosition = tr.position; + if (updateRotation) simulatedRotation = tr.rotation; + + RichPathPart currentPart = richPath.GetCurrentPart(); + + if (currentPart is RichSpecial) { + // Start traversing the off mesh link if we haven't done it yet + if (!traversingOffMeshLink && !richPath.CompletedAllParts) { + StartCoroutine(TraverseSpecial(currentPart as RichSpecial)); + } + + nextPosition = steeringTarget = simulatedPosition; + nextRotation = rotation; + } else { + var funnel = currentPart as RichFunnel; + + // Check if we have a valid path to follow and some other script has not stopped the character + bool stopped = isStopped || (reachedDestination && whenCloseToDestination == CloseToDestinationMode.Stop); + if (rvoController != null) rvoDensityBehavior.Update(rvoController.enabled, reachedDestination, ref stopped, ref rvoController.priorityMultiplier, ref rvoController.flowFollowingStrength, simulatedPosition); + + if (funnel != null && !stopped) { + TraverseFunnel(funnel, deltaTime, out nextPosition, out nextRotation); + } else { + // Unknown, null path part, or the character is stopped + // Slow down as quickly as possible + velocity2D -= Vector2.ClampMagnitude(velocity2D, acceleration * deltaTime); + FinalMovement(simulatedPosition, deltaTime, float.PositiveInfinity, 1f, out nextPosition, out nextRotation); + if (funnel == null || isStopped) { + steeringTarget = simulatedPosition; + } + } + } + } + + void TraverseFunnel (RichFunnel fn, float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { + // Clamp the current position to the navmesh + // and update the list of upcoming corners in the path + // and store that in the 'nextCorners' field + var position3D = UpdateTarget(fn); + float elevation; + Vector2 position = movementPlane.ToPlane(position3D, out elevation); + + // Only find nearby walls every 5th frame to improve performance + if (Time.frameCount % 5 == 0 && wallForce > 0 && wallDist > 0) { + wallBuffer.Clear(); + fn.FindWalls(wallBuffer, wallDist); + } + + // Target point + steeringTarget = nextCorners[0]; + Vector2 targetPoint = movementPlane.ToPlane(steeringTarget); + // Direction to target + Vector2 dir = targetPoint - position; + + // Normalized direction to the target + Vector2 normdir = VectorMath.Normalize(dir, out distanceToSteeringTarget); + // Calculate force from walls + Vector2 wallForceVector = CalculateWallForce(position, elevation, normdir); + Vector2 targetVelocity; + + if (approachingPartEndpoint) { + targetVelocity = slowdownTime > 0 ? Vector2.zero : normdir * maxSpeed; + + // Reduce the wall avoidance force as we get closer to our target + wallForceVector *= System.Math.Min(distanceToSteeringTarget/0.5f, 1); + + if (distanceToSteeringTarget <= endReachedDistance) { + // Reached the end of the path or an off mesh link + NextPart(); + } + } else { + var nextNextCorner = nextCorners.Count > 1 ? movementPlane.ToPlane(nextCorners[1]) : position + 2*dir; + targetVelocity = (nextNextCorner - targetPoint).normalized * maxSpeed; + } + + var forwards = movementPlane.ToPlane(simulatedRotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward)); + + // Update the velocity using the acceleration + Vector2 accel = MovementUtilities.CalculateAccelerationToReachPoint(targetPoint - position, targetVelocity, velocity2D, acceleration, rotationSpeed, maxSpeed, forwards); + velocity2D += (accel + wallForceVector*wallForce)*deltaTime; + + // Distance to the end of the path (almost as the crow flies) + var distanceToEndOfPath = distanceToSteeringTarget + Vector3.Distance(steeringTarget, fn.exactEnd); + + // How fast to move depending on the distance to the destination. + // Move slower as the character gets closer to the destination. + // This is always a value between 0 and 1. + var speedLimitFactor = distanceToEndOfPath < maxSpeed * slowdownTime? Mathf.Sqrt(distanceToEndOfPath / (maxSpeed * slowdownTime)) : 1; + + FinalMovement(position3D, deltaTime, distanceToEndOfPath, speedLimitFactor, out nextPosition, out nextRotation); + } + + void FinalMovement (Vector3 position3D, float deltaTime, float distanceToEndOfPath, float speedLimitFactor, out Vector3 nextPosition, out Quaternion nextRotation) { + var forwards = movementPlane.ToPlane(simulatedRotation * (orientation == OrientationMode.YAxisForward ? Vector3.up : Vector3.forward)); + + ApplyGravity(deltaTime); + + velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, speedLimitFactor, slowWhenNotFacingTarget && enableRotation, preventMovingBackwards, forwards); + bool avoidingAnyAgents = false; + + if (rvoController != null && rvoController.enabled) { + // Send a message to the RVOController that we want to move + // with this velocity. In the next simulation step, this + // velocity will be processed and it will be fed back to the + // rvo controller and finally it will be used by this script + // when calling the CalculateMovementDelta method below + + // Make sure that we don't move further than to the end point + // of the path. If the RVO simulation FPS is low and we did + // not do this, the agent might overshoot the target a lot. + var rvoTarget = position3D + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEndOfPath)); + rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed, endOfPath); + avoidingAnyAgents = rvoController.AvoidingAnyAgents; + } + + // Direction and distance to move during this frame + var deltaPosition = lastDeltaPosition = CalculateDeltaToMoveThisFrame(position3D, distanceToEndOfPath, deltaTime); + + if (enableRotation) { + // Rotate towards the direction we are moving in + // Filter out noise in the movement direction + // This is especially important when the agent is almost standing still and when using local avoidance + float noiseThreshold = radius * tr.localScale.x * 0.2f; + float rotationSpeedFactor = MovementUtilities.FilterRotationDirection(ref rotationFilterState, ref rotationFilterState2, deltaPosition, noiseThreshold, deltaTime, avoidingAnyAgents); + nextRotation = SimulateRotationTowards(rotationFilterState, rotationSpeed * deltaTime * rotationSpeedFactor, rotationSpeed * deltaTime); + } else { + nextRotation = simulatedRotation; + } + + nextPosition = position3D + movementPlane.ToWorld(deltaPosition, verticalVelocity * deltaTime); + } + + protected override Vector3 ClampToNavmesh (Vector3 position, out bool positionChanged) { + if (richPath != null) { + var funnel = richPath.GetCurrentPart() as RichFunnel; + if (funnel != null) { + var clampedPosition = funnel.ClampToNavmesh(position); + + // Inform the RVO system about the edges of the navmesh which will allow + // it to better keep inside the navmesh in the first place. + if (rvoController != null && rvoController.enabled) rvoController.SetObstacleQuery(funnel.CurrentNode); + + // We cannot simply check for equality because some precision may be lost + // if any coordinate transformations are used. + var difference = movementPlane.ToPlane(clampedPosition - position); + float sqrDifference = difference.sqrMagnitude; + if (sqrDifference > 0.001f*0.001f) { + // The agent was outside the navmesh. Remove that component of the velocity + // so that the velocity only goes along the direction of the wall, not into it + velocity2D -= difference * Vector2.Dot(difference, velocity2D) / sqrDifference; + positionChanged = true; + // Return the new position, but ignore any changes in the y coordinate from the ClampToNavmesh method as the y coordinates in the navmesh are rarely very accurate + return position + movementPlane.ToWorld(difference); + } + } + } + + positionChanged = false; + return position; + } + + Vector2 CalculateWallForce (Vector2 position, float elevation, Vector2 directionToTarget) { + if (wallForce <= 0 || wallDist <= 0) return Vector2.zero; + + float wLeft = 0; + float wRight = 0; + + var position3D = movementPlane.ToWorld(position, elevation); + for (int i = 0; i < wallBuffer.Count; i += 2) { + Vector3 closest = VectorMath.ClosestPointOnSegment(wallBuffer[i], wallBuffer[i+1], position3D); + float dist = (closest-position3D).sqrMagnitude; + + if (dist > wallDist*wallDist) continue; + + Vector2 tang = movementPlane.ToPlane(wallBuffer[i+1]-wallBuffer[i]).normalized; + + // Using the fact that all walls are laid out clockwise (looking from inside the obstacle) + // Then left and right (ish) can be figured out like this + float dot = Vector2.Dot(directionToTarget, tang); + float weight = 1 - System.Math.Max(0, (2*(dist / (wallDist*wallDist))-1)); + if (dot > 0) wRight = System.Math.Max(wRight, dot * weight); + else wLeft = System.Math.Max(wLeft, -dot * weight); + } + + Vector2 normal = new Vector2(directionToTarget.y, -directionToTarget.x); + return normal*(wRight-wLeft); + } + + /// Traverses an off-mesh link + protected virtual IEnumerator TraverseSpecial (RichSpecial link) { + traversingOffMeshLink = true; + // The current path part is a special part, for example a link + // Movement during this part of the path is handled by the TraverseSpecial coroutine + velocity2D = Vector3.zero; + var offMeshLinkCoroutine = onTraverseOffMeshLink != null? onTraverseOffMeshLink(link) : TraverseOffMeshLinkFallback(link); + yield return StartCoroutine(offMeshLinkCoroutine); + + // Off-mesh link traversal completed + traversingOffMeshLink = false; + NextPart(); + + // If a path completed during the time we traversed the special connection, we need to recalculate it + if (delayUpdatePath) { + delayUpdatePath = false; + // TODO: What if canSearch is false? How do we notify other scripts that might be handling the path calculation that a new path needs to be calculated? + if (canSearch) SearchPath(); + } + } + + /// + /// Fallback for traversing off-mesh links in case is not set. + /// This will do a simple linear interpolation along the link. + /// + protected IEnumerator TraverseOffMeshLinkFallback (RichSpecial link) { + float duration = maxSpeed > 0 ? Vector3.Distance(link.second.position, link.first.position) / maxSpeed : 1; + float startTime = Time.time; + + while (true) { + var pos = Vector3.Lerp(link.first.position, link.second.position, Mathf.InverseLerp(startTime, startTime + duration, Time.time)); + if (updatePosition) tr.position = pos; + else simulatedPosition = pos; + + if (Time.time >= startTime + duration) break; + yield return null; + } + } + + protected static readonly Color GizmoColorPath = new Color(8.0f/255, 78.0f/255, 194.0f/255); + + public override void DrawGizmos () { + base.DrawGizmos(); + + if (tr != null) { + Vector3 lastPosition = position; + for (int i = 0; i < nextCorners.Count; lastPosition = nextCorners[i], i++) { + Draw.Line(lastPosition, nextCorners[i], GizmoColorPath); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs.meta new file mode 100644 index 0000000..d2bc561 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichAI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce11ea984e202491d9271f53021d8b89 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: f2e81a0445323b64f973d2f5b5c56e15, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs new file mode 100644 index 0000000..7aaa4eb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs @@ -0,0 +1,803 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; +using Pathfinding.Pooling; +using UnityEngine.Assertions; + +namespace Pathfinding { + public class RichPath { + int currentPart; + readonly List parts = new List(); + + public Seeker seeker; + + /// + /// Transforms points from path space to world space. + /// If null the identity transform will be used. + /// + /// This is used when the world position of the agent does not match the + /// corresponding position on the graph. This is the case in the example + /// scene called 'Moving'. + /// + /// See: + /// + public ITransform transform; + + public RichPath () { + Clear(); + } + + public void Clear () { + parts.Clear(); + currentPart = 0; + Endpoint = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + } + + /// Use this for initialization. + /// Optionally provide in order to take tag penalties into account. May be null if you do not use a Seeker\ + /// Path to follow + /// If true, then adjacent parts that the path is split up in will + /// try to use the same start/end points. For example when using a link on a navmesh graph + /// Instead of first following the path to the center of the node where the link is and then + /// follow the link, the path will be adjusted to go to the exact point where the link starts + /// which usually makes more sense. + /// The path can optionally be simplified. This can be a bit expensive for long paths. + public void Initialize (Seeker seeker, Path path, bool mergePartEndpoints, bool simplificationMode) { + if (path.error) throw new System.ArgumentException("Path has an error"); + + List nodes = path.path; + if (nodes.Count == 0) throw new System.ArgumentException("Path traverses no nodes"); + + this.seeker = seeker; + // Release objects back to object pool + // Yeah, I know, it's casting... but this won't be called much + for (int i = 0; i < parts.Count; i++) { + var funnelPart = parts[i] as RichFunnel; + var specialPart = parts[i] as RichSpecial; + if (funnelPart != null) ObjectPool.Release(ref funnelPart); + else if (specialPart != null) ObjectPool.Release(ref specialPart); + } + + Clear(); + + // Initialize new + Endpoint = path.vectorPath[path.vectorPath.Count-1]; + + //Break path into parts + for (int i = 0; i < nodes.Count; i++) { + if (nodes[i] is TriangleMeshNode) { + var graph = AstarData.GetGraph(nodes[i]) as NavmeshBase; + if (graph == null) throw new System.Exception("Found a TriangleMeshNode that was not in a NavmeshBase graph"); + + RichFunnel f = ObjectPool.Claim().Initialize(this, graph); + + f.funnelSimplification = simplificationMode; + + int sIndex = i; + uint currentGraphIndex = nodes[sIndex].GraphIndex; + + + for (; i < nodes.Count; i++) { + if (nodes[i].GraphIndex != currentGraphIndex && !(nodes[i] is NodeLink3Node)) { + break; + } + } + i--; + + if (sIndex == 0) { + f.exactStart = path.vectorPath[0]; + } else { + f.exactStart = (Vector3)nodes[mergePartEndpoints ? sIndex-1 : sIndex].position; + } + + if (i == nodes.Count-1) { + f.exactEnd = path.vectorPath[path.vectorPath.Count-1]; + } else { + f.exactEnd = (Vector3)nodes[mergePartEndpoints ? i+1 : i].position; + } + + f.BuildFunnelCorridor(nodes, sIndex, i); + + parts.Add(f); + } else if (nodes[i] is LinkNode nl) { + int sIndex = i; + uint currentGraphIndex = nodes[sIndex].GraphIndex; + + while (i < nodes.Count && nodes[i].GraphIndex == currentGraphIndex) i++; + i--; + + if (i - sIndex > 1) { + throw new System.Exception("NodeLink2 path length greater than two (2) nodes. " + (i - sIndex)); + } else if (i - sIndex == 0) { + // The link is a single node. + // Just ignore it. It can happen in very rare circumstances with some path types. + // For example, a RandomPath can stop at the first node of a node link, without including the other end of the link + continue; + } + Assert.AreEqual(nl.linkConcrete, (nodes[i] as LinkNode).linkConcrete); + + RichSpecial rps = ObjectPool.Claim().Initialize(nl.linkConcrete.GetTracer(nl)); + parts.Add(rps); + } else if (!(nodes[i] is PointNode)) { + // Some other graph type which we do not have support for + throw new System.InvalidOperationException("The RichAI movment script can only be used on recast/navmesh graphs. A node of type " + nodes[i].GetType().Name + " was in the path."); + } + } + } + + public Vector3 Endpoint { get; private set; } + + /// True if we have completed (called NextPart for) the last part in the path + public bool CompletedAllParts { + get { + return currentPart >= parts.Count; + } + } + + /// True if we are traversing the last part of the path + public bool IsLastPart { + get { + return currentPart >= parts.Count - 1; + } + } + + public void NextPart () { + currentPart = Mathf.Min(currentPart + 1, parts.Count); + } + + public RichPathPart GetCurrentPart () { + if (parts.Count == 0) return null; + return currentPart < parts.Count ? parts[currentPart] : parts[parts.Count - 1]; + } + + /// + /// Replaces the buffer with the remaining path. + /// See: + /// + public void GetRemainingPath (List buffer, List partsBuffer, Vector3 currentPosition, out bool requiresRepath) { + buffer.Clear(); + buffer.Add(currentPosition); + requiresRepath = false; + for (int i = currentPart; i < parts.Count; i++) { + var part = parts[i]; + if (part is RichFunnel funnel) { + bool lastCorner; + var startIndex = buffer.Count; + if (i != 0) buffer.Add(funnel.exactStart); + funnel.Update(i == 0 ? currentPosition : funnel.exactStart, buffer, int.MaxValue, out lastCorner, out requiresRepath); + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(startIndex, buffer.Count-1)); + if (requiresRepath) { + return; + } + } else if (part is RichSpecial rs) { + // By adding all points above the link will look like just a stright line, which is reasonable + // The part's start/end indices refer to the last point in previous part and first point in the next part, respectively + if (partsBuffer != null) partsBuffer.Add(new PathPartWithLinkInfo(buffer.Count-1, buffer.Count, rs.nodeLink)); + } + } + } + } + + public abstract class RichPathPart : IAstarPooledObject { + public abstract void OnEnterPool(); + } + + public class RichFunnel : RichPathPart { + readonly List left; + readonly List right; + List nodes; + public Vector3 exactStart; + public Vector3 exactEnd; + NavmeshBase graph; + int currentNode; + Vector3 currentPosition; + int checkForDestroyedNodesCounter; + RichPath path; + int[] triBuffer = new int[3]; + + /// Post process the funnel corridor or not + public bool funnelSimplification = true; + + public RichFunnel () { + left = Pathfinding.Pooling.ListPool.Claim(); + right = Pathfinding.Pooling.ListPool.Claim(); + nodes = new List(); + this.graph = null; + } + + /// Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining + public RichFunnel Initialize (RichPath path, NavmeshBase graph) { + if (graph == null) throw new System.ArgumentNullException("graph"); + if (this.graph != null) throw new System.InvalidOperationException("Trying to initialize an already initialized object. " + graph); + + this.graph = graph; + this.path = path; + return this; + } + + public override void OnEnterPool () { + left.Clear(); + right.Clear(); + nodes.Clear(); + graph = null; + currentNode = 0; + checkForDestroyedNodesCounter = 0; + } + + public TriangleMeshNode CurrentNode { + get { + var node = nodes[currentNode]; + if (!node.Destroyed) { + return node; + } + return null; + } + } + + /// + /// Build a funnel corridor from a node list slice. + /// The nodes are assumed to be of type TriangleMeshNode. + /// + /// Nodes to build the funnel corridor from + /// Start index in the nodes list + /// End index in the nodes list, this index is inclusive + public void BuildFunnelCorridor (List nodes, int start, int end) { + //Make sure start and end points are on the correct nodes + exactStart = (nodes[start] as MeshNode).ClosestPointOnNode(exactStart); + exactEnd = (nodes[end] as MeshNode).ClosestPointOnNode(exactEnd); + + left.Clear(); + right.Clear(); + left.Add(exactStart); + right.Add(exactStart); + + this.nodes.Clear(); + + if (funnelSimplification) { + List tmp = ListPool.Claim(end-start); + + // TODO: Save from path? + var traversalConstraint = TraversalConstraint.None; + traversalConstraint.tags = path.seeker != null ? path.seeker.traversableTags : -1; + var traversalCosts = new TraversalCosts(); + traversalCosts.tagCostMultipliers = path.seeker != null ? path.seeker.tagCostMultipliers : null; + traversalCosts.tagEntryCosts = path.seeker != null ? path.seeker.tagEntryCosts : null; + + Funnel.Simplify(new Funnel.PathPart { + startIndex = start, + endIndex = end, + startPoint = exactStart, + endPoint = exactEnd, + type = Funnel.PartType.NodeSequence, + }, graph, nodes, tmp, ref traversalCosts, ref traversalConstraint); + + if (this.nodes.Capacity < tmp.Count) this.nodes.Capacity = tmp.Count; + + for (int i = 0; i < tmp.Count; i++) { + // Guaranteed to be TriangleMeshNodes since they are all in the same graph + var node = tmp[i] as TriangleMeshNode; + if (node != null) this.nodes.Add(node); + } + + ListPool.Release(ref tmp); + } else { + if (this.nodes.Capacity < end-start) this.nodes.Capacity = (end-start); + for (int i = start; i <= end; i++) { + // Guaranteed to be TriangleMeshNodes since they are all in the same graph + var node = nodes[i] as TriangleMeshNode; + if (node != null) this.nodes.Add(node); + } + } + + for (int i = 0; i < this.nodes.Count-1; i++) { + if (this.nodes[i].GetPortal(this.nodes[i+1], out var leftP, out var rightP)) { + left.Add(leftP); + right.Add(rightP); + } else { + // Can happen in case custom connections have been added + left.Add((Vector3)this.nodes[i].position); + right.Add((Vector3)this.nodes[i].position); + left.Add((Vector3)this.nodes[i+1].position); + right.Add((Vector3)this.nodes[i+1].position); + } + } + + left.Add(exactEnd); + right.Add(exactEnd); + } + + /// + /// Split funnel at node index splitIndex and throw the nodes up to that point away and replace with prefix. + /// Used when the AI has happened to get sidetracked and entered a node outside the funnel. + /// + void UpdateFunnelCorridor (int splitIndex, List prefix) { + nodes.RemoveRange(0, splitIndex); + nodes.InsertRange(0, prefix); + + left.Clear(); + right.Clear(); + left.Add(exactStart); + right.Add(exactStart); + + for (int i = 0; i < nodes.Count-1; i++) { + if (nodes[i].GetPortal(nodes[i+1], out var leftP, out var rightP)) { + left.Add(leftP); + right.Add(rightP); + } + } + + left.Add(exactEnd); + right.Add(exactEnd); + } + + /// True if any node in the path is destroyed + bool CheckForDestroyedNodes () { + // Loop through all nodes and check if they are destroyed + // If so, we really need a recalculation of our path quickly + // since there might be an obstacle blocking our path after + // a graph update or something similar + for (int i = 0, t = nodes.Count; i < t; i++) { + if (nodes[i].Destroyed) { + return true; + } + } + return false; + } + + /// + /// Approximate distance (as the crow flies) to the endpoint of this path part. + /// See: + /// + public float DistanceToEndOfPath { + get { + var currentNode = CurrentNode; + Vector3 closestOnNode = currentNode != null? currentNode.ClosestPointOnNode(currentPosition) : currentPosition; + return (exactEnd - closestOnNode).magnitude; + } + } + + /// + /// Clamps the position to the navmesh and repairs the path if the agent has moved slightly outside it. + /// You should not call this method with anything other than the agent's position. + /// + public Vector3 ClampToNavmesh (Vector3 position) { + if (path.transform != null) position = path.transform.InverseTransform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + ClampToNavmeshInternal(ref position); + if (path.transform != null) position = path.transform.Transform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + return position; + } + + /// + /// Find the next points to move towards and clamp the position to the navmesh. + /// + /// Returns: The position of the agent clamped to make sure it is inside the navmesh. + /// + /// The position of the agent. + /// Will be filled with up to numCorners points which are the next points in the path towards the target. + /// See buffer. + /// True if the buffer contains the end point of the path. + /// True if nodes along the path have been destroyed and a path recalculation is necessary. + public Vector3 Update (Vector3 position, List buffer, int numCorners, out bool lastCorner, out bool requiresRepath) { + if (path.transform != null) position = path.transform.InverseTransform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + + lastCorner = false; + requiresRepath = false; + + // Only check for destroyed nodes every 10 frames + if (checkForDestroyedNodesCounter >= 10) { + checkForDestroyedNodesCounter = 0; + requiresRepath |= CheckForDestroyedNodes(); + } else { + checkForDestroyedNodesCounter++; + } + + bool nodesDestroyed = ClampToNavmeshInternal(ref position); + + currentPosition = position; + + if (nodesDestroyed) { + // Some nodes on the path have been destroyed + // we need to recalculate the path immediately + requiresRepath = true; + lastCorner = false; + buffer.Add(position); + } else if (!FindNextCorners(position, currentNode, buffer, numCorners, out lastCorner)) { + Debug.LogError("Failed to find next corners in the path"); + buffer.Add(position); + } + + if (path.transform != null) { + for (int i = 0; i < buffer.Count; i++) { + buffer[i] = path.transform.Transform(buffer[i]); + } + + position = path.transform.Transform(position); + UnityEngine.Assertions.Assert.IsFalse(float.IsNaN(position.x)); + } + + return position; + } + + /// Cached object to avoid unnecessary allocations + static Queue navmeshClampQueue = new Queue(); + /// Cached object to avoid unnecessary allocations + static List navmeshClampList = new List(); + /// Cached object to avoid unnecessary allocations + static Dictionary navmeshClampDict = new Dictionary(); + + /// + /// Searches for the node the agent is inside. + /// This will also clamp the position to the navmesh + /// and repair the funnel cooridor if the agent moves slightly outside it. + /// + /// Returns: True if nodes along the path have been destroyed so that a path recalculation is required + /// + bool ClampToNavmeshInternal (ref Vector3 position) { + var previousNode = nodes[currentNode]; + + if (previousNode.Destroyed) { + return true; + } + + // Check if we are in the same node as we were in during the last frame and otherwise do a more extensive search + if (previousNode.ContainsPoint(position)) { + return false; + } + + // This part of the code is relatively seldom called + // Most of the time we are still on the same node as during the previous frame + + var que = navmeshClampQueue; + var allVisited = navmeshClampList; + var parent = navmeshClampDict; + previousNode.TemporaryFlag1 = true; + parent[previousNode] = null; + que.Enqueue(previousNode); + allVisited.Add(previousNode); + + float bestDistance = float.PositiveInfinity; + Vector3 bestPoint = position; + TriangleMeshNode bestNode = null; + + while (que.Count > 0) { + var node = que.Dequeue(); + + // Snap to the closest point in XZ space (keep the Y coordinate) + // If we would have snapped to the closest point in 3D space, the agent + // might slow down when traversing slopes + var closest = node.ClosestPointOnNodeXZ(position); + var dist = VectorMath.MagnitudeXZ(closest - position); + + // Check if this node is any closer than the previous best node. + // Allow for a small margin to both avoid floating point errors and to allow + // moving past very small local minima. + if (dist <= bestDistance * 1.05f + 0.001f) { + if (dist < bestDistance) { + bestDistance = dist; + bestPoint = closest; + bestNode = node; + } + + for (int i = 0; i < node.connections.Length; i++) { + if (!node.connections[i].isOutgoing) continue; + var neighbour = node.connections[i].node as TriangleMeshNode; + if (neighbour != null && !neighbour.TemporaryFlag1) { + neighbour.TemporaryFlag1 = true; + parent[neighbour] = node; + que.Enqueue(neighbour); + allVisited.Add(neighbour); + } + } + } + } + + UnityEngine.Assertions.Assert.IsNotNull(bestNode); + + for (int i = 0; i < allVisited.Count; i++) allVisited[i].TemporaryFlag1 = false; + allVisited.ClearFast(); + + var closestNodeInPath = nodes.IndexOf(bestNode); + + // Move the x and z coordinates of the chararacter but not the y coordinate + // because the navmesh surface may not line up with the ground + position.x = bestPoint.x; + position.z = bestPoint.z; + + // Check if the closest node + // was on the path already or if we need to adjust it + if (closestNodeInPath == -1) { + // Reuse this list, because why not. + var prefix = navmeshClampList; + + while (closestNodeInPath == -1) { + prefix.Add(bestNode); + bestNode = parent[bestNode]; + closestNodeInPath = nodes.IndexOf(bestNode); + } + + // We have found a node containing the position, but it is outside the funnel + // Recalculate the funnel to include this node + exactStart = position; + UpdateFunnelCorridor(closestNodeInPath, prefix); + + prefix.ClearFast(); + + // Restart from the first node in the updated path + currentNode = 0; + } else { + currentNode = closestNodeInPath; + } + + parent.Clear(); + // Do a quick check to see if the next node in the path has been destroyed + // If that is the case then we should plan a new path immediately + return currentNode + 1 < nodes.Count && nodes[currentNode+1].Destroyed; + } + + /// + /// Fill wallBuffer with all navmesh wall segments close to the current position. + /// A wall segment is a node edge which is not shared by any other neighbour node, i.e an outer edge on the navmesh. + /// + public void FindWalls (List wallBuffer, float range) { + FindWalls(currentNode, wallBuffer, currentPosition, range); + } + + void FindWalls (int nodeIndex, List wallBuffer, Vector3 position, float range) { + if (range <= 0) return; + + bool negAbort = false; + bool posAbort = false; + + range *= range; + + //Looping as 0,-1,1,-2,2,-3,3,-4,4 etc. Avoids code duplication by keeping it to one loop instead of two + for (int i = 0; !negAbort || !posAbort; i = i < 0 ? -i : -i-1) { + if (i < 0 && negAbort) continue; + if (i > 0 && posAbort) continue; + + if (i < 0 && nodeIndex+i < 0) { + negAbort = true; + continue; + } + + if (i > 0 && nodeIndex+i >= nodes.Count) { + posAbort = true; + continue; + } + + TriangleMeshNode prev = nodeIndex+i-1 < 0 ? null : nodes[nodeIndex+i-1]; + TriangleMeshNode node = nodes[nodeIndex+i]; + TriangleMeshNode next = nodeIndex+i+1 >= nodes.Count ? null : nodes[nodeIndex+i+1]; + + if (node.Destroyed) { + break; + } + + var dir = node.ClosestPointOnNodeXZ(position)-position; + dir.y = 0; + if (dir.sqrMagnitude > range) { + if (i < 0) negAbort = true; + else posAbort = true; + continue; + } + + for (int j = 0; j < 3; j++) triBuffer[j] = 0; + + for (int j = 0; j < node.connections.Length; j++) { + var other = node.connections[j].node as TriangleMeshNode; + if (other == null) continue; + + int va = -1; + for (int a = 0; a < 3; a++) { + for (int b = 0; b < 3; b++) { + if (node.GetVertex(a) == other.GetVertex((b+1) % 3) && node.GetVertex((a+1) % 3) == other.GetVertex(b)) { + va = a; + a = 3; + break; + } + } + } + if (va == -1) { + //No direct connection + } else { + triBuffer[va] = other == prev || other == next ? 2 : 1; + } + } + + for (int j = 0; j < 3; j++) { + //Tribuffer values + // 0 : Navmesh border, outer edge + // 1 : Inner edge, to node inside funnel + // 2 : Inner edge, to node outside funnel + if (triBuffer[j] == 0) { + //Add edge to list of walls + wallBuffer.Add((Vector3)node.GetVertex(j)); + wallBuffer.Add((Vector3)node.GetVertex((j+1) % 3)); + } + } + } + + if (path.transform != null) { + for (int i = 0; i < wallBuffer.Count; i++) { + wallBuffer[i] = path.transform.Transform(wallBuffer[i]); + } + } + } + + bool FindNextCorners (Vector3 origin, int startIndex, List funnelPath, int numCorners, out bool lastCorner) { + lastCorner = false; + + if (left == null) throw new System.Exception("left list is null"); + if (right == null) throw new System.Exception("right list is null"); + if (funnelPath == null) throw new System.ArgumentNullException("funnelPath"); + + if (left.Count != right.Count) throw new System.ArgumentException("left and right lists must have equal length"); + + int diagonalCount = left.Count; + + if (diagonalCount == 0) throw new System.ArgumentException("no diagonals"); + + if (diagonalCount-startIndex < 3) { + //Direct path + funnelPath.Add(left[diagonalCount-1]); + lastCorner = true; + return true; + } + +#if ASTARDEBUG + for (int i = startIndex; i < left.Count-1; i++) { + Debug.DrawLine(left[i], left[i+1], Color.red); + Debug.DrawLine(right[i], right[i+1], Color.magenta); + Debug.DrawRay(right[i], Vector3.up, Color.magenta); + } + for (int i = 0; i < left.Count; i++) { + Debug.DrawLine(right[i], left[i], Color.cyan); + } +#endif + + //Remove identical vertices + while (left[startIndex+1] == left[startIndex+2] && right[startIndex+1] == right[startIndex+2]) { + //System.Console.WriteLine ("Removing identical left and right"); + //left.RemoveAt (1); + //right.RemoveAt (1); + startIndex++; + + if (diagonalCount-startIndex <= 3) { + return false; + } + } + + Vector3 swPoint = left[startIndex+2]; + if (swPoint == left[startIndex+1]) { + swPoint = right[startIndex+2]; + } + + + //Test + while (VectorMath.IsColinearXZ(origin, left[startIndex+1], right[startIndex+1]) || VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], swPoint) == VectorMath.RightOrColinearXZ(left[startIndex+1], right[startIndex+1], origin)) { +#if ASTARDEBUG + Debug.DrawLine(left[startIndex+1], right[startIndex+1], new Color(0, 0, 0, 0.5F)); + Debug.DrawLine(origin, swPoint, new Color(0, 0, 0, 0.5F)); +#endif + //left.RemoveAt (1); + //right.RemoveAt (1); + startIndex++; + + if (diagonalCount-startIndex < 3) { + //Debug.Log ("#2 " + left.Count + " - " + startIndex + " = " + (left.Count-startIndex)); + //Direct path + funnelPath.Add(left[diagonalCount-1]); + lastCorner = true; + return true; + } + + swPoint = left[startIndex+2]; + if (swPoint == left[startIndex+1]) { + swPoint = right[startIndex+2]; + } + } + + + //funnelPath.Add (origin); + + Vector3 portalApex = origin; + Vector3 portalLeft = left[startIndex+1]; + Vector3 portalRight = right[startIndex+1]; + + int apexIndex = startIndex+0; + int rightIndex = startIndex+1; + int leftIndex = startIndex+1; + + for (int i = startIndex+2; i < diagonalCount; i++) { + if (funnelPath.Count >= numCorners) { + return true; + } + + if (funnelPath.Count > 2000) { + Debug.LogWarning("Avoiding infinite loop. Remove this check if you have this long paths."); + break; + } + + Vector3 pLeft = left[i]; + Vector3 pRight = right[i]; + + /*Debug.DrawLine (portalApex,portalLeft,Color.red); + * Debug.DrawLine (portalApex,portalRight,Color.yellow); + * Debug.DrawLine (portalApex,left,Color.cyan); + * Debug.DrawLine (portalApex,right,Color.cyan);*/ + + if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pRight) >= 0) { + if (portalApex == portalRight || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pRight) <= 0) { + portalRight = pRight; + rightIndex = i; + } else { + funnelPath.Add(portalLeft); + portalApex = portalLeft; + apexIndex = leftIndex; + + portalLeft = portalApex; + portalRight = portalApex; + + leftIndex = apexIndex; + rightIndex = apexIndex; + + i = apexIndex; + + continue; + } + } + + if (VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalLeft, pLeft) <= 0) { + if (portalApex == portalLeft || VectorMath.SignedTriangleAreaTimes2XZ(portalApex, portalRight, pLeft) >= 0) { + portalLeft = pLeft; + leftIndex = i; + } else { + funnelPath.Add(portalRight); + portalApex = portalRight; + apexIndex = rightIndex; + + portalLeft = portalApex; + portalRight = portalApex; + + leftIndex = apexIndex; + rightIndex = apexIndex; + + i = apexIndex; + + continue; + } + } + } + + lastCorner = true; + funnelPath.Add(left[diagonalCount-1]); + + return true; + } + } + + public struct FakeTransform { + public Vector3 position; + public Quaternion rotation; + } + + public class RichSpecial : RichPathPart { + public OffMeshLinks.OffMeshLinkTracer nodeLink; + public FakeTransform first => new FakeTransform { position = nodeLink.relativeStart, rotation = nodeLink.isReverse ? nodeLink.link.end.rotation : nodeLink.link.start.rotation }; + public FakeTransform second => new FakeTransform { position = nodeLink.relativeEnd, rotation = nodeLink.isReverse ? nodeLink.link.start.rotation : nodeLink.link.end.rotation }; + public bool reverse => nodeLink.isReverse; + + public override void OnEnterPool () { + nodeLink = default; + } + + /// Works like a constructor, but can be used even for pooled objects. Returns this for easy chaining + public RichSpecial Initialize (OffMeshLinks.OffMeshLinkTracer nodeLink) { + this.nodeLink = nodeLink; + return this; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs.meta new file mode 100644 index 0000000..bad8489 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/RichPath.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6865b0d3859fe4641ad0839b829e00d2 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs new file mode 100644 index 0000000..aacb338 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs @@ -0,0 +1,831 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; +using UnityEngine.Profiling; + +namespace Pathfinding { + using Pathfinding.Drawing; + + /// + /// Handles path calls for a single unit. + /// + /// This is a component which is meant to be attached to a single unit (AI, Robot, Player, whatever) to handle its pathfinding calls. + /// It also handles post-processing of paths using modifiers. + /// + /// \section seeker-inspector Inspector + /// + /// \inspectorField{drawGizmos; Draw Gizmos} + /// \inspectorField{detailedGizmos; Detailed Gizmos} + /// \inspectorField{startEndModifier.exactStartPoint; Start End Modifier → Start Point Snapping} + /// \inspectorField{startEndModifier.exactEndPoint; Start End Modifier → End Point Snapping} + /// \inspectorField{startEndModifier.addPoints; Start End Modifier → Add Points} + /// \inspectorField{graphMask; Traversable Graphs} + /// \inspectorField{traversableTags; Tags → Traversable} + /// \inspectorField{tagCostMultipliers; Tags → Cost per world unit} + /// \inspectorField{tagEntryCosts; Tags → Cost per node} + /// + /// See: calling-pathfinding (view in online documentation for working links) + /// See: modifiers (view in online documentation for working links) + /// + [AddComponentMenu("Pathfinding/Seeker")] + [DisallowMultipleComponent] + [HelpURL("https://arongranberg.com/astar/documentation/stable/seeker.html")] + public class Seeker : VersionedMonoBehaviour { + /// + /// Enables drawing of the last calculated path using Gizmos. + /// The path will show up in green. + /// + /// See: OnDrawGizmos + /// + public bool drawGizmos = true; + + /// + /// Enables drawing of the non-postprocessed path using Gizmos. + /// The path will show up in orange. + /// + /// Requires that is true. + /// + /// This will show the path before any post processing such as smoothing is applied. + /// + /// See: drawGizmos + /// See: OnDrawGizmos + /// + public bool detailedGizmos; + + /// Path modifier which tweaks the start and end points of a path + public StartEndModifier startEndModifier = new StartEndModifier(); + + /// + /// The tags which the Seeker can traverse. + /// + /// This is a bitmask, i.e bit 0 indicates that tag 0 is traversable, bit 3 indicates tag 3 is traversable etc. + /// + /// By default all tags are traversable, which corresponds to the value -1. + /// + /// [Open online documentation to see images] + /// + /// + /// // Allows the first and third tags to be searched, but not the rest + /// seeker.traversableTags = (1 << 0) | (1 << 2); + /// + /// // Allows the two named tags to be searched, but not the rest + /// seeker.traversableTags = PathfindingTag.FromName("Grass").ToMask() | PathfindingTag.FromName("Lava").ToMask(); + /// + /// + /// See: + /// See: tags (view in online documentation for working links) + /// See: bitmasks (view in online documentation for working links) + /// See: + /// See: + /// See: + /// + public int traversableTags = -1; + + /// + /// Penalties for each tag. + /// Tag 0 which is the default tag, will have added a penalty of tagPenalties[0]. + /// These should only be positive values since the A* algorithm cannot handle negative penalties. + /// + /// The length of this array should be exactly 32, one for each tag. + /// + /// See: Pathfinding.Path.tagCostMultipliers + /// Deprecated: Use or instead. + /// + [System.Obsolete("Use tagEntryCosts or tagCostMultipliers instead", false)] + public uint[] tagPenalties { + get => tagEntryCosts; + set { + tagEntryCosts = value; + } + } + + /// \copydocref{TraversalCosts.tagCostMultipliers} + public float[] tagCostMultipliers; + + /// \copydocref{TraversalCosts.tagEntryCosts} + [UnityEngine.Serialization.FormerlySerializedAs("tagPenalties")] + public uint[] tagEntryCosts = new uint[32]; + + /// + /// %Graphs that this Seeker can use. + /// This field determines which graphs will be considered when searching for the start and end nodes of a path. + /// It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units. + /// + /// This is a bitmask so if you for example want to make the agent only use graph index 3 then you can set this to: + /// seeker.graphMask = GraphMask.FromGraphIndex(3); + /// + /// See: bitmasks (view in online documentation for working links) + /// + /// Note that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering + /// then this mask may no longer be correct. + /// + /// If you know the name of the graph you can use the method: + /// + /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph"); + /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph"); + /// + /// NearestNodeConstraint nn = NearestNodeConstraint.Walkable; + /// + /// nn.graphMask = mask1 | mask2; + /// + /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph' + /// var info = AstarPath.active.GetNearest(somePoint, nn); + /// + /// + /// Some overloads of the methods take a graphMask parameter. If those overloads are used then they + /// will override the graph mask for that path request. + /// + /// [Open online documentation to see images] + /// + /// See: multiple-agent-types (view in online documentation for working links) + /// + public GraphMask graphMask = GraphMask.everything; + + /// + /// Custom traversal provider to calculate which nodes are traversable and their penalties. + /// + /// This can be used to override the built-in pathfinding logic. + /// + /// + /// seeker.traversalProvider = new MyCustomTraversalProvider(); + /// + /// + /// See: + /// See: traversal_provider (view in online documentation for working links) + /// + public ITraversalProvider traversalProvider; + + /// Used for serialization backwards compatibility + [UnityEngine.Serialization.FormerlySerializedAs("graphMask")] + int graphMaskCompatibility = -1; + + /// + /// Callback for when a path is completed. + /// Movement scripts should register to this delegate. + /// A temporary callback can also be set when calling StartPath, but that delegate will only be called for that path + /// + /// + /// public void Start () { + /// // Assumes a Seeker component is attached to the GameObject + /// Seeker seeker = GetComponent(); + /// + /// // seeker.pathCallback is a OnPathDelegate, we add the function OnPathComplete to it so it will be called whenever a path has finished calculating on that seeker + /// seeker.pathCallback += OnPathComplete; + /// } + /// + /// public void OnPathComplete (Path p) { + /// Debug.Log("This is called when a path is completed on the seeker attached to this GameObject"); + /// } + /// + /// + /// Deprecated: Pass a callback every time to the StartPath method instead, or use ai.SetPath+ai.pathPending on the movement script. You can cache it in your own script if you want to avoid the GC allocation of creating a new delegate. + /// + [System.Obsolete("Pass a callback every time to the StartPath method instead, or use ai.SetPath+ai.pathPending on the movement script. You can cache it in your own script if you want to avoid the GC allocation of creating a new delegate.")] + public OnPathDelegate pathCallback; + + /// Called before pathfinding is started + public OnPathDelegate preProcessPath; + + /// Called after a path has been calculated, right before modifiers are executed. + public OnPathDelegate postProcessPath; + +#if UNITY_EDITOR + /// Used for drawing gizmos + [System.NonSerialized] + List lastCompletedVectorPath; + + /// Used for drawing gizmos + [System.NonSerialized] + List lastCompletedNodePath; +#endif + + /// The current path + [System.NonSerialized] + protected Path path; + + /// Previous path. Used to draw gizmos + [System.NonSerialized] + private Path prevPath; + + /// Cached delegate to avoid allocating one every time a path is started + private readonly OnPathDelegate onPathDelegate; + /// Cached delegate to avoid allocating one every time a path is started + private readonly OnPathDelegate onPartialPathDelegate; + + /// Temporary callback only called for the current path. This value is set by the StartPath functions + private OnPathDelegate tmpPathCallback; + + /// The path ID of the last path queried + protected uint lastPathID; + + /// Internal list of all modifiers + readonly List modifiers = new List(); + + public enum ModifierPass { + PreProcess, + // An obsolete item occupied index 1 previously + PostProcess = 2, + } + + public Seeker () { + onPathDelegate = OnPathComplete; + onPartialPathDelegate = OnPartialPathComplete; + tagCostMultipliers = new float[32]; + for (int i = 0; i < tagCostMultipliers.Length; i++) { + tagCostMultipliers[i] = 1; + } + } + + /// Initializes a few variables + protected override void Awake () { + base.Awake(); + startEndModifier.Awake(this); + } + + /// + /// Path that is currently being calculated or was last calculated. + /// You should rarely have to use this. Instead get the path when the path callback is called. + /// + /// See: + /// + public Path GetCurrentPath() => path; + + /// + /// Stop calculating the current path request. + /// If this Seeker is currently calculating a path it will be canceled. + /// The callback (usually to a method named OnPathComplete) will soon be called + /// with a path that has the 'error' field set to true. + /// + /// This does not stop the character from moving, it just aborts + /// the path calculation. + /// + /// If true then the path will be pooled when the pathfinding system is done with it. + public void CancelCurrentPathRequest (bool pool = true) { + if (!IsDone()) { + path.FailWithError("Canceled by script (Seeker.CancelCurrentPathRequest)"); + if (pool) { + // Make sure the path has had its reference count incremented and decremented once. + // If this is not done the system will think no pooling is used at all and will not pool the path. + // The particular object that is used as the parameter (in this case 'path') doesn't matter at all + // it just has to be *some* object. + path.Claim(path); + path.Release(path); + } + } + } + + /// + /// Cleans up some variables. + /// Releases any eventually claimed paths. + /// Calls OnDestroy on the . + /// + /// See: + /// See: + /// + void OnDestroy () { + ReleaseClaimedPath(); + startEndModifier.OnDestroy(this); + } + + /// + /// Releases the path used for gizmos (if any). + /// The seeker keeps the latest path claimed so it can draw gizmos. + /// In some cases this might not be desireable and you want it released. + /// In that case, you can call this method to release it (not that path gizmos will then not be drawn). + /// + /// If you didn't understand anything from the description above, you probably don't need to use this method. + /// + /// See: pooling (view in online documentation for working links) + /// + void ReleaseClaimedPath () { + if (prevPath != null) { + prevPath.Release(this, true); + prevPath = null; + } + } + + /// Called by modifiers to register themselves + public void RegisterModifier (IPathModifier modifier) { + // Modifier might already be registered if pathfinding is used outside of play mode + if (!modifiers.Contains(modifier)) { + modifiers.Add(modifier); + + // Sort the modifiers based on their specified order + modifiers.Sort((a, b) => a.Order.CompareTo(b.Order)); + } + } + + /// Called by modifiers when they are disabled or destroyed + public void DeregisterModifier (IPathModifier modifier) { + modifiers.Remove(modifier); + } + + void ForceRegisterModifiers () { + GetComponents(modifiers); + modifiers.Sort((a, b) => a.Order.CompareTo(b.Order)); + } + + /// + /// Post-processes a path. + /// This will run any modifiers attached to this GameObject on the path. + /// This is identical to calling RunModifiers(ModifierPass.PostProcess, path) + /// See: + /// + public void PostProcess (Path path) { + RunModifiers(ModifierPass.PostProcess, path); + } + + /// Runs modifiers on a path + public void RunModifiers (ModifierPass pass, Path path) { + if (!Application.isPlaying) ForceRegisterModifiers(); + + if (pass == ModifierPass.PreProcess) { + if (preProcessPath != null) preProcessPath(path); + + for (int i = 0; i < modifiers.Count; i++) modifiers[i].PreProcess(path); + } else if (pass == ModifierPass.PostProcess) { + Profiler.BeginSample("Running Path Modifiers"); + // Call delegates if they exist + if (postProcessPath != null) postProcessPath(path); + + // Loop through all modifiers and apply post processing + for (int i = 0; i < modifiers.Count; i++) modifiers[i].Apply(path); + Profiler.EndSample(); + } + } + + /// + /// Is the current path done calculating. + /// Returns true if the current has been returned or if the is null. + /// + /// Note: Do not confuse this with Pathfinding.Path.IsDone. They usually return the same value, but not always. + /// The path might be completely calculated, but has not yet been processed by the Seeker. + /// + /// Inside the OnPathComplete callback this method will return true. + /// + /// Version: Before version 4.2.13 this would return false inside the OnPathComplete callback. However, this behaviour was unintuitive. + /// + public bool IsDone() => path == null || path.PipelineState >= PathState.Returning; + + /// Called when a path has completed + void OnPathComplete (Path path) { + OnPathComplete(path, true, true); + } + + /// + /// Called when a path has completed. + /// Will post process it and return it by calling + /// + void OnPathComplete (Path p, bool runModifiers, bool sendCallbacks) { + if (p != null && p != path && sendCallbacks) { + return; + } + + if (this == null || p == null || p != path) + return; + + if (!path.error && runModifiers) { + // This will send the path for post processing to modifiers attached to this Seeker + RunModifiers(ModifierPass.PostProcess, path); + } + + if (sendCallbacks) { + p.Claim(this); + +#if UNITY_EDITOR + lastCompletedNodePath = p.path; + lastCompletedVectorPath = p.vectorPath; +#endif + + #pragma warning disable 618 + if (tmpPathCallback == null && pathCallback == null) { +#if UNITY_EDITOR + // This checks for a common error that people make when they upgrade from an older version + // This will be removed in a future version to avoid the slight performance cost. + if (TryGetComponent(out var ai)) { + Debug.LogWarning("A path was calculated, but no callback was specified when calling StartPath. If you wanted a movement script to use this path, use ai.SetPath instead of calling StartPath on the Seeker directly. The path will be forwarded to the attached movement script, but this behavior will be removed in the future.", this); + ai.SetPath(p); + } +#endif + } else { + // This will send the path to the callback (if any) specified when calling StartPath + if (tmpPathCallback != null) { + tmpPathCallback(p); + } + + // This will send the path to any script which has registered to the callback + if (pathCallback != null) { + pathCallback(p); + } + } + #pragma warning restore 618 + + // Note: it is important that #prevPath is kept alive (i.e. not pooled) + // if we are drawing gizmos. + // It is also important that #path is kept alive since it can be returned + // from the GetCurrentPath method. + // Since #path will be copied to #prevPath it is sufficient that #prevPath + // is kept alive until it is replaced. + + // Recycle the previous path to reduce the load on the GC + if (prevPath != null) { + prevPath.Release(this, true); + } + + prevPath = p; + } + } + + /// + /// Called for each path in a MultiTargetPath. + /// Only post processes the path, does not return it. + /// + void OnPartialPathComplete (Path p) { + OnPathComplete(p, true, false); + } + + /// Called once for a MultiTargetPath. Only returns the path, does not post process. + void OnMultiPathComplete (Path p) { + OnPathComplete(p, false, true); + } + + /// + /// Queue a path to be calculated. + /// Since this method does not take a callback parameter, you should set the field before calling this method. + /// + /// + /// void Start () { + /// // Get the seeker component attached to this GameObject + /// var seeker = GetComponent(); + /// + /// // Schedule a new path request from the current position to a position 10 units forward. + /// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request + /// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete); + /// + /// // Note that the path is NOT calculated at this point + /// // It has just been queued for calculation + /// } + /// + /// void OnPathComplete (Path path) { + /// // The path is now calculated! + /// + /// if (path.error) { + /// Debug.LogError("Path failed: " + path.errorLog); + /// return; + /// } + /// + /// // Cast the path to the path type we were using + /// var abPath = path as ABPath; + /// + /// // Draw the path in the scene view for 10 seconds + /// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) { + /// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10); + /// } + /// } + /// + /// + /// Deprecated: Use instead. + /// + /// The start point of the path + /// The end point of the path + [System.Obsolete("Use the overload that takes a callback instead")] + public Path StartPath (Vector3 start, Vector3 end) { + return StartPath(start, end, null); + } + + /// + /// Queue a path to be calculated. + /// + /// The callback will be called when the path has been calculated (which may be several frames into the future). + /// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// + /// void Start () { + /// // Get the seeker component attached to this GameObject + /// var seeker = GetComponent(); + /// + /// // Schedule a new path request from the current position to a position 10 units forward. + /// // When the path has been calculated, the OnPathComplete method will be called, unless it was canceled by another path request + /// seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete); + /// + /// // Note that the path is NOT calculated at this point + /// // It has just been queued for calculation + /// } + /// + /// void OnPathComplete (Path path) { + /// // The path is now calculated! + /// + /// if (path.error) { + /// Debug.LogError("Path failed: " + path.errorLog); + /// return; + /// } + /// + /// // Cast the path to the path type we were using + /// var abPath = path as ABPath; + /// + /// // Draw the path in the scene view for 10 seconds + /// for (int i = 0; i < abPath.vectorPath.Count - 1; i++) { + /// Debug.DrawLine(abPath.vectorPath[i], abPath.vectorPath[i+1], Color.red, 10); + /// } + /// } + /// + /// + /// The start point of the path + /// The end point of the path + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback) { + return StartPath(ABPath.Construct(start, end, null), callback); + } + + /// + /// Queue a path to be calculated. + /// + /// The callback will be called when the path has been calculated (which may be several frames into the future). + /// Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// + /// // Schedule a path search that will only start searching the graphs with index 0 and 3 + /// seeker.StartPath(startPoint, endPoint, null, GraphMask.FromGraphIndex(0) | GraphMask.FromGraphIndex(3)); + /// + /// + /// The start point of the path + /// The end point of the path + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + /// Mask used to specify which graphs should be searched for close nodes. See \reflink{TraversalConstraint.graphMask}. This will override the #graphMask on this Seeker. + public Path StartPath (Vector3 start, Vector3 end, OnPathDelegate callback, GraphMask graphMask) { + return StartPath(ABPath.Construct(start, end, null), callback, graphMask); + } + + /// + /// Queue a path to be calculated. + /// + /// Version: Since 4.1.x this method will no longer overwrite the graphMask on the path unless it is explicitly passed as a parameter (see other overloads of this method). + /// + /// This overload takes no callback parameter. Instead, it is expected that you poll the path for completion, or block until it is completed. + /// + /// See: + /// See: + /// See: + /// + /// However, should not be used with the Seeker component. This is because while the path itself may be calculated, the Seeker may not have had time to run post processing modifiers on the path yet. + /// + /// The path to start calculating + public Path StartPath (Path p) { + return StartPath(p, null); + } + + /// + /// Queue a path to be calculated. + /// + /// The callback will be called when the path has been calculated (which may be several frames into the future). + /// The callback will not be called if a new path request is started before this path request has been calculated. + /// + /// The path to start calculating + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + public Path StartPath (Path p, OnPathDelegate callback) { + // Set the graph mask only if the user has not changed it from the default value. + // This is not perfect as the user may have wanted it to be precisely -1 + // however it is the best detection that I can do. + // The non-default check is primarily for compatibility reasons to avoid breaking peoples existing code. + // The StartPath overloads with an explicit graphMask field should be used instead to set the graphMask. + if (p.traversalConstraint.graphMask.containsAllGraphs) p.traversalConstraint.graphMask = graphMask; + StartPathInternal(p, callback); + return p; + } + + /// + /// Queue a path to be calculated. + /// + /// The callback will be called when the path has been calculated (which may be several frames into the future). + /// The callback will not be called if a new path request is started before this path request has been calculated. + /// + /// The path to start calculating + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + /// Mask used to specify which graphs should be searched for close nodes. See \reflink{GraphMask}. This will override the #graphMask on this Seeker. + public Path StartPath (Path p, OnPathDelegate callback, GraphMask graphMask) { + p.traversalConstraint.graphMask = graphMask; + StartPathInternal(p, callback); + return p; + } + + /// Internal method to start a path and mark it as the currently active path + void StartPathInternal (Path p, OnPathDelegate callback) { + var mtp = p as MultiTargetPath; + if (mtp != null) { + // TODO: Allocation, cache + var callbacks = new OnPathDelegate[mtp.targetPoints.Length]; + + for (int i = 0; i < callbacks.Length; i++) { + callbacks[i] = onPartialPathDelegate; + } + + mtp.callbacks = callbacks; + p.callback += OnMultiPathComplete; + } else { + p.callback += onPathDelegate; + } + + p.traversalConstraint.tags = traversableTags; + + // Unless the user has already set these fields, we will set them to the values on this Seeker. + if (p.traversalCosts.tagCostMultipliers == null) p.traversalCosts.tagCostMultipliers = tagCostMultipliers; + if (p.traversalCosts.tagEntryCosts == null) p.traversalCosts.tagEntryCosts = tagEntryCosts; + if (p.traversalConstraint.traversalProvider == null) p.traversalConstraint.traversalProvider = traversalProvider; + if (p.traversalCosts.traversalProvider == null) p.traversalCosts.traversalProvider = traversalProvider; + + // Cancel a previously requested path is it has not been processed yet and also make sure that it has not been recycled and used somewhere else + if (path != null && path.PipelineState <= PathState.Processing && path.CompleteState != PathCompleteState.Error && lastPathID == path.pathID) { + path.FailWithError("Canceled path because a new one was requested.\n"+ + "This happens when a new path is requested from the seeker when one was already being calculated.\n" + + "For example if a unit got a new order, you might request a new path directly instead of waiting for the now" + + " invalid path to be calculated. Which is probably what you want.\n" + + "If you are getting this a lot, you might want to consider how you are scheduling path requests."); + // No callback will be sent for the canceled path + } + + // Set p as the active path + path = p; + tmpPathCallback = callback; + + // Save the path id so we can make sure that if we cancel a path (see above) it should not have been recycled yet. + lastPathID = path.pathID; + + // Pre process the path + RunModifiers(ModifierPass.PreProcess, path); + + // Send the request to the pathfinder + AstarPath.StartPath(path); + } + + /// + /// Starts a Multi Target Path from one start point to multiple end points. + /// A Multi Target Path will search for all the end points in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false. + /// + /// callback will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// See: Pathfinding.MultiTargetPath + /// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths" + /// + /// + /// var endPoints = new Vector3[] { + /// transform.position + Vector3.forward * 5, + /// transform.position + Vector3.right * 10, + /// transform.position + Vector3.back * 15 + /// }; + /// // Start a multi target path, where endPoints is a Vector3[] array. + /// // The pathsForAll parameter specifies if a path to every end point should be searched for + /// // or if it should only try to find the shortest path to any end point. + /// var path = seeker.StartMultiTargetPath(transform.position, endPoints, pathsForAll: true, callback: null); + /// path.BlockUntilCalculated(); + /// + /// if (path.error) { + /// Debug.LogError("Error calculating path: " + path.errorLog); + /// return; + /// } + /// + /// Debug.Log("The closest target was index " + path.chosenTarget); + /// + /// // Draw the path to all targets + /// foreach (var subPath in path.vectorPaths) { + /// for (int i = 0; i < subPath.Count - 1; i++) { + /// Debug.DrawLine(subPath[i], subPath[i+1], Color.green, 10); + /// } + /// } + /// + /// // Draw the path to the closest target + /// for (int i = 0; i < path.vectorPath.Count - 1; i++) { + /// Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10); + /// } + /// + /// + /// The start point of the path + /// The end points of the path + /// Indicates whether or not a path to all end points should be searched for or only to the closest one + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + /// Mask used to specify which graphs should be searched for close nodes. See \reflink{TraversalConstraint.graphMask}. This will override the #graphMask on this Seeker. + public MultiTargetPath StartMultiTargetPath (Vector3 start, Vector3[] endPoints, bool pathsForAll, OnPathDelegate callback, GraphMask graphMask) { + MultiTargetPath p = MultiTargetPath.Construct(start, endPoints, null, null); + + p.pathsForAll = pathsForAll; + StartPath(p, callback, graphMask); + return p; + } + + /// + /// Starts a Multi Target Path from one start point to multiple end points. + /// A Multi Target Path will search for all the end points in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false. + /// + /// callback will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// See: Pathfinding.MultiTargetPath + /// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths" + /// + /// + /// var endPoints = new Vector3[] { + /// transform.position + Vector3.forward * 5, + /// transform.position + Vector3.right * 10, + /// transform.position + Vector3.back * 15 + /// }; + /// // Start a multi target path, where endPoints is a Vector3[] array. + /// // The pathsForAll parameter specifies if a path to every end point should be searched for + /// // or if it should only try to find the shortest path to any end point. + /// var path = seeker.StartMultiTargetPath(transform.position, endPoints, pathsForAll: true, callback: null); + /// path.BlockUntilCalculated(); + /// + /// if (path.error) { + /// Debug.LogError("Error calculating path: " + path.errorLog); + /// return; + /// } + /// + /// Debug.Log("The closest target was index " + path.chosenTarget); + /// + /// // Draw the path to all targets + /// foreach (var subPath in path.vectorPaths) { + /// for (int i = 0; i < subPath.Count - 1; i++) { + /// Debug.DrawLine(subPath[i], subPath[i+1], Color.green, 10); + /// } + /// } + /// + /// // Draw the path to the closest target + /// for (int i = 0; i < path.vectorPath.Count - 1; i++) { + /// Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10); + /// } + /// + /// + /// The start point of the path + /// The end points of the path + /// Indicates whether or not a path to all end points should be searched for or only to the closest one + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + public MultiTargetPath StartMultiTargetPath(Vector3 start, Vector3[] endPoints, bool pathsForAll, OnPathDelegate callback) => StartMultiTargetPath(start, endPoints, pathsForAll, callback, graphMask); + + /// + /// Starts a Multi Target Path from multiple start points to a single target point. + /// A Multi Target Path will search from all start points to the target point in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false. + /// + /// callback will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// See: Pathfinding.MultiTargetPath + /// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths" + /// + /// The start points of the path + /// The end point of the path + /// Indicates whether or not a path from all start points should be searched for or only to the closest one + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + /// Mask used to specify which graphs should be searched for close nodes. See \reflink{TraversalConstraint.graphMask}. This will override the #graphMask on this Seeker. + public MultiTargetPath StartMultiTargetPath (Vector3[] startPoints, Vector3 end, bool pathsForAll, OnPathDelegate callback, GraphMask graphMask) { + MultiTargetPath p = MultiTargetPath.Construct(startPoints, end, null, null); + + p.pathsForAll = pathsForAll; + StartPath(p, callback, graphMask); + return p; + } + + /// + /// Starts a Multi Target Path from multiple start points to a single target point. + /// A Multi Target Path will search from all start points to the target point in one search and will return all paths if pathsForAll is true, or only the shortest one if pathsForAll is false. + /// + /// callback will be called when the path has completed. Callback will not be called if the path is canceled (e.g when a new path is requested before the previous one has completed) + /// + /// See: Pathfinding.MultiTargetPath + /// See: MultiTargetPathExample.cs (view in online documentation for working links) "Example of how to use multi-target-paths" + /// + /// The start points of the path + /// The end point of the path + /// Indicates whether or not a path from all start points should be searched for or only to the closest one + /// The function to call when the path has been calculated. If you don't want a callback (e.g. if you instead poll path.IsDone or use a similar method) you can set this to null. + public MultiTargetPath StartMultiTargetPath(Vector3[] startPoints, Vector3 end, bool pathsForAll, OnPathDelegate callback) => StartMultiTargetPath(startPoints, end, pathsForAll, callback, graphMask); + +#if UNITY_EDITOR + /// Draws gizmos for the Seeker + public override void DrawGizmos () { + if (lastCompletedNodePath == null || !drawGizmos) { + return; + } + + if (detailedGizmos && lastCompletedNodePath != null) { + using (Draw.WithColor(new Color(0.7F, 0.5F, 0.1F, 0.5F))) { + for (int i = 0; i < lastCompletedNodePath.Count-1; i++) { + Draw.Line((Vector3)lastCompletedNodePath[i].position, (Vector3)lastCompletedNodePath[i+1].position); + } + } + } + + if (lastCompletedVectorPath != null) { + using (Draw.WithColor(new Color(0, 1F, 0, 1F))) { + for (int i = 0; i < lastCompletedVectorPath.Count-1; i++) { + Draw.Line(lastCompletedVectorPath[i], lastCompletedVectorPath[i+1]); + } + } + } + } +#endif + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (graphMaskCompatibility != -1) { + graphMask = new GraphMask((uint)graphMaskCompatibility); + graphMaskCompatibility = -1; + } + base.OnUpgradeSerializedData(ref migrations, unityThread); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs.meta new file mode 100644 index 0000000..30f5e18 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/Seeker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 373b52eb9bf8c40f785bb6947a1aee66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 2c41d544537d6ee4a8ecd487b2ac9724, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs new file mode 100644 index 0000000..2bce7cf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs @@ -0,0 +1,25 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding.Examples { + /// Helper script in the example scene 'Turn Based' + [HelpURL("https://arongranberg.com/astar/documentation/stable/turnbasedai.html")] + public class TurnBasedAI : VersionedMonoBehaviour { + public int movementPoints = 2; + public BlockManager blockManager; + public SingleNodeBlocker blocker; + public GraphNode targetNode; + public BlockManager.TraversalProvider traversalProvider; + + void Start () { + blocker.BlockAtCurrentPosition(); + } + + protected override void Awake () { + base.Awake(); + // Set the traversal provider to block all nodes that are blocked by a SingleNodeBlocker + // except the SingleNodeBlocker owned by this AI (we don't want to be blocked by ourself) + traversalProvider = new BlockManager.TraversalProvider(blockManager, BlockManager.BlockMode.AllExceptSelector, new List() { blocker }); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs.meta new file mode 100644 index 0000000..4fa7210 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AI/TurnBasedAI.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8f95b80c439d6408b9afac9d013922e4 +timeCreated: 1453035991 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs new file mode 100644 index 0000000..517daf9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs @@ -0,0 +1,809 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using Pathfinding.WindowsStore; +using Pathfinding.Serialization; +using Pathfinding.Util; +using Pathfinding.Sync; +using System; +using UnityEngine.Profiling; + +namespace Pathfinding { + [System.Serializable] + /// + /// Stores the navigation graphs for the A* Pathfinding System. + /// + /// An instance of this class is assigned to . From it you can access all graphs loaded through the variable. + /// This class also handles a lot of the high level serialization. + /// + public class AstarData { + /// The AstarPath component which owns this AstarData + AstarPath active; + + #region Fields + /// + /// Shortcut to the first + /// + /// Deprecated: Use instead + /// + [System.Obsolete("Use navmeshGraph instead")] + public NavMeshGraph navmesh => navmeshGraph; + + /// Shortcut to the first + public NavMeshGraph navmeshGraph { get; private set; } + +#if !ASTAR_NO_GRID_GRAPH + /// Shortcut to the first + public GridGraph gridGraph { get; private set; } + + /// Shortcut to the first . + public LayerGridGraph layerGridGraph { get; private set; } +#endif + +#if !ASTAR_NO_POINT_GRAPH + /// Shortcut to the first . + public PointGraph pointGraph { get; private set; } +#endif + + /// Shortcut to the first . + public RecastGraph recastGraph { get; private set; } + + /// Shortcut to the first . + public LinkGraph linkGraph { get; private set; } + + /// + /// All supported graph types. + /// Populated through reflection search + /// + public static System.Type[] graphTypes { get; private set; } + +#if ASTAR_FAST_NO_EXCEPTIONS || UNITY_WINRT + /// + /// Graph types to use when building with Fast But No Exceptions for iPhone. + /// If you add any custom graph types, you need to add them to this hard-coded list. + /// + public static readonly System.Type[] DefaultGraphTypes = new System.Type[] { +#if !ASTAR_NO_GRID_GRAPH + typeof(GridGraph), + typeof(LayerGridGraph), +#endif +#if !ASTAR_NO_POINT_GRAPH + typeof(PointGraph), +#endif + typeof(NavMeshGraph), + typeof(RecastGraph), + typeof(LinkGraph), + }; +#endif + + /// + /// All graphs. + /// This will be filled only after deserialization has completed. + /// May contain null entries if graph have been removed. + /// + [System.NonSerialized] + public NavGraph[] graphs = new NavGraph[0]; + + /// + /// Serialized data for all graphs and settings. + /// Stored as a base64 encoded string because otherwise Unity's Undo system would sometimes corrupt the byte data (because it only stores deltas). + /// + /// This can be accessed as a byte array from the property. + /// + [SerializeField] + string dataString; + + /// Serialized data for all graphs and settings + private byte[] data { + get { + var d = dataString != null? System.Convert.FromBase64String(dataString) : null; + // Unity can initialize the dataString to an empty string, but that's not a valid zip file + if (d != null && d.Length == 0) return null; + return d; + } + set { + dataString = value != null? System.Convert.ToBase64String(value) : null; + } + } + + /// + /// Serialized data for cached startup. + /// If set, and is enabled, graphs will be deserialized from this file when the game starts. + /// + /// [Open online documentation to see images] + /// + public TextAsset file_cachedStartup; + + /// + /// Should graph-data be cached. + /// Caching the startup means saving the whole graphs - not only the settings - to a file ( which can + /// be loaded when the game starts. This is usually much faster than scanning the graphs when the game starts. This is configured from the editor under the "Save & Load" tab. + /// + /// [Open online documentation to see images] + /// + /// See: save-load-graphs (view in online documentation for working links) + /// + [SerializeField] + public bool cacheStartup; + + List graphStructureLocked = new List(); + + static readonly Unity.Profiling.ProfilerMarker MarkerLoadFromCache = new Unity.Profiling.ProfilerMarker("LoadFromCache"); + static readonly Unity.Profiling.ProfilerMarker MarkerDeserializeGraphs = new Unity.Profiling.ProfilerMarker("DeserializeGraphs"); + static readonly Unity.Profiling.ProfilerMarker MarkerSerializeGraphs = new Unity.Profiling.ProfilerMarker("SerializeGraphs"); + static readonly Unity.Profiling.ProfilerMarker MarkerFindGraphTypes = new Unity.Profiling.ProfilerMarker("FindGraphTypes"); + + #endregion + + internal AstarData (AstarPath active) { + this.active = active; + } + + /// Get the serialized data for all graphs and their settings + public byte[] GetData() => data; + + /// + /// Set the serialized data for all graphs and their settings. + /// + /// During runtime you usually want to deserialize the graphs immediately, in which case you should use []) instead. + /// + public void SetData (byte[] data) { + this.data = data; + } + + /// Loads the graphs from memory, will load cached graphs if any exists + public void OnEnable () { + FindGraphTypes(); + + if (graphs == null) graphs = new NavGraph[0]; + + if (cacheStartup && file_cachedStartup != null && Application.isPlaying) { + LoadFromCache(); + } else { + DeserializeGraphs(); + } + } + + /// + /// Prevent the graph structure from changing during the time this lock is held. + /// This prevents graphs from being added or removed and also prevents graphs from being serialized or deserialized. + /// This is used when e.g an async scan is happening to ensure that for example a graph that is being scanned is not destroyed. + /// + /// Each call to this method *must* be paired with exactly one call to . + /// The calls may be nested. + /// + internal void LockGraphStructure (bool allowAddingGraphs = false) { + graphStructureLocked.Add(allowAddingGraphs); + } + + /// + /// Allows the graph structure to change again. + /// See: + /// + internal void UnlockGraphStructure () { + if (graphStructureLocked.Count == 0) throw new System.InvalidOperationException(); + graphStructureLocked.RemoveAt(graphStructureLocked.Count - 1); + } + + PathProcessor.GraphUpdateLock AssertSafe (bool onlyAddingGraph = false) { + if (graphStructureLocked.Count > 0) { + bool allowAdding = true; + for (int i = 0; i < graphStructureLocked.Count; i++) allowAdding &= graphStructureLocked[i]; + if (!(onlyAddingGraph && allowAdding)) throw new System.InvalidOperationException("Graphs cannot be added, removed or serialized while the graph structure is locked. This is the case when a graph is currently being scanned and when executing graph updates and work items.\nHowever as a special case, graphs can be added inside work items."); + } + + // Pause the pathfinding threads + var graphLock = active.PausePathfinding(); + if (!active.IsInsideWorkItem) { + // Make sure all graph updates and other callbacks are done + // Only do this if this code is not being called from a work item itself as that would cause a recursive wait that could never complete. + // There are some valid cases when this can happen. For example it may be necessary to add a new graph inside a work item. + active.FlushWorkItems(); + + // Paths that are already calculated and waiting to be returned to the Seeker component need to be + // processed immediately as their results usually depend on graphs that currently exist. If this was + // not done then after destroying a graph one could get a path result with destroyed nodes in it. + active.pathReturnQueue.ReturnPaths(false); + } + return graphLock; + } + + /// + /// Calls the callback with every node in all graphs. + /// This is the easiest way to iterate through every existing node. + /// + /// + /// AstarPath.active.data.GetNodes(node => { + /// Debug.Log("I found a node at position " + (Vector3)node.position); + /// }); + /// + /// + /// See: for getting the nodes of a single graph instead of all. + /// See: graph-updates (view in online documentation for working links) + /// + public void GetNodes (System.Action callback) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null) graphs[i].GetNodes(callback); + } + } + + /// + /// Updates shortcuts to the first graph of different types. + /// Hard coding references to some graph types is not really a good thing imo. I want to keep it dynamic and flexible. + /// But these references ease the use of the system, so I decided to keep them. + /// + public void UpdateShortcuts () { + navmeshGraph = (NavMeshGraph)FindGraphOfType(typeof(NavMeshGraph)); + +#if !ASTAR_NO_GRID_GRAPH + gridGraph = (GridGraph)FindGraphOfType(typeof(GridGraph)); + layerGridGraph = (LayerGridGraph)FindGraphOfType(typeof(LayerGridGraph)); +#endif + +#if !ASTAR_NO_POINT_GRAPH + pointGraph = (PointGraph)FindGraphOfType(typeof(PointGraph)); +#endif + + recastGraph = (RecastGraph)FindGraphOfType(typeof(RecastGraph)); + linkGraph = (LinkGraph)FindGraphOfType(typeof(LinkGraph)); + } + + /// Load from data from + public void LoadFromCache () { + using var _ = MarkerLoadFromCache.Auto(); + using (AssertSafe()) { + if (file_cachedStartup != null) { + var bytes = file_cachedStartup.bytes; + DeserializeGraphs(bytes); + + GraphModifier.TriggerEvent(GraphModifier.EventType.PostCacheLoad); + } else { + Debug.LogError("Can't load from cache since the cache is empty"); + } + } + } + + #region Serialization + + /// + /// Serializes all graphs settings to a byte array. + /// + /// + /// byte[] bytes = AstarPath.active.data.SerializeGraphs(); + /// + /// + /// See: DeserializeGraphs(byte[]) + /// + public byte[] SerializeGraphs () { + return SerializeGraphs(SerializeSettings.Settings); + } + + /// + /// Serializes all graphs settings and optionally node data to a byte array. + /// + /// + /// byte[] bytes = AstarPath.active.data.SerializeGraphs(Pathfinding.Serialization.SerializeSettings.NodesAndSettings); + /// + /// + /// See: DeserializeGraphs(byte[]) + /// See: + /// + public byte[] SerializeGraphs (SerializeSettings settings) { + return SerializeGraphs(settings, out var _); + } + + /// + /// Serializes all graphs to a byte array. + /// + /// You can later load the graphs using or . + /// + /// A similar function exists in the AstarPathEditor.cs script to save the state of the graph inspectors as well. + /// + public byte[] SerializeGraphs (SerializeSettings settings, out uint checksum) { + return SerializeGraphs(settings, graphs, out checksum); + } + + /// + /// Serializes a single graph to a byte array. + /// + /// You can later load the graph using or . + /// + /// All cross-graph connections between this graph and other graphs will be excluded from the serialized data. + /// + /// Warning: If you are using multiple graphs, and you are serializing and deserializing single graphs, the graph may not keep its original index in the array. + /// This means that things like graph masks (e.g. ) may not reference the correct graph after deserialization. + /// + public byte[] SerializeGraph (SerializeSettings settings, NavGraph graph) { + return SerializeGraph(settings, graph, out var _); + } + + /// + /// Serializes a single graph to a byte array. + /// + /// You can later load the graph using or . + /// + /// All cross-graph connections between this graph and other graphs will be excluded from the serialized data. + /// + /// Warning: If you are using multiple graphs, and you are serializing and deserializing single graphs, the graph may not keep its original index in the array. + /// This means that things like graph masks (e.g. ) may not reference the correct graph after deserialization. + /// + public byte[] SerializeGraph (SerializeSettings settings, NavGraph graph, out uint checksum) { + if (graph == null) throw new ArgumentNullException(nameof(graph)); + return SerializeGraphs(settings, new NavGraph[] { graph }, out checksum); + } + + /// + /// Serializes a set of graphs to a byte array. + /// + /// You can later load the graphs using or . + /// + /// All cross-graph connections between these graphs and other graphs will be excluded from the serialized data. + /// + /// Warning: If you are using multiple graphs, and you are serializing and deserializing subsets of graphs, the graphs may not keep their original indices in the array. + /// This means that things like graph masks (e.g. ) may not reference the correct graph after deserialization. + /// + public byte[] SerializeGraphs (SerializeSettings settings, NavGraph[] graphs, out uint checksum) { + MarkerSerializeGraphs.Begin(); + using (AssertSafe()) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] == null) continue; + var idx = Array.IndexOf(this.graphs, graphs[i]); + if (idx == -1) throw new InvalidOperationException("One of the specified graphs is not loaded. It cannot be serialized."); + if (idx != graphs[i].graphIndex) throw new InvalidOperationException("One of the specified graphs has an invalid graph index."); + } + + var sr = new AstarSerializer(this, settings, active.gameObject); + + sr.OpenSerialize(); + sr.SerializeGraphs(graphs); + sr.SerializeExtraInfo(); + byte[] bytes = sr.CloseSerialize(); + checksum = sr.GetChecksum(); +#if ASTARDEBUG + Debug.Log("Got a whole bunch of data, "+bytes.Length+" bytes"); +#endif + + MarkerSerializeGraphs.End(); + return bytes; + } + } + + /// + /// Deserializes graphs from . + /// + /// + /// AstarPath.active.data.DeserializeGraphs(bytes); + /// + /// + public void DeserializeGraphs () { + var dataBytes = data; + if (dataBytes != null) { + DeserializeGraphs(dataBytes); + } + } + + /// + /// Destroys all graphs and sets to null. + /// See: + /// + public void ClearGraphs () { + using (AssertSafe()) { + ClearGraphsInternal(); + } + } + + void ClearGraphsInternal () { + if (graphs == null) return; + Profiler.BeginSample("Destroy Graphs"); + using (AssertSafe()) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null) { + active.DirtyBounds(graphs[i].bounds); + ((IGraphInternals)graphs[i]).OnDestroy(); + graphs[i].active = null; + } + } + graphs = new NavGraph[0]; + UpdateShortcuts(); + } + Profiler.EndSample(); + } + + public void DisposeUnmanagedData () { + if (graphs == null) return; + using (AssertSafe()) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null) { + ((IGraphInternals)graphs[i]).DisposeUnmanagedData(); + } + } + } + } + + /// Makes all graphs become unscanned + internal void DestroyAllNodes () { + if (graphs == null) return; + using (AssertSafe()) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null) { + ((IGraphInternals)graphs[i]).DestroyAllNodes(); + } + } + } + } + + public void OnDestroy () { + ClearGraphsInternal(); + } + + /// + /// Deserializes and loads graphs from the specified byte array. + /// An error will be logged if deserialization fails. + /// + /// Returns: The deserialized graphs + /// + public NavGraph[] DeserializeGraphs (byte[] bytes) { + using (AssertSafe()) { + ClearGraphs(); + return DeserializeGraphsAdditive(bytes); + } + } + + /// + /// Deserializes and loads graphs from the specified byte array additively. + /// + /// All loaded graphs will be appended to the array. + /// + /// + /// AstarPath.active.data.DeserializeGraphsAdditive(bytes); + /// + /// + /// If the serialized graphs only contain settings, you may want to call to scan the graphs after deserialization. + /// + /// Returns: The deserialized graphs + /// + public NavGraph[] DeserializeGraphsAdditive (byte[] bytes) { + return DeserializeGraphsAdditive(bytes, true); + } + + NavGraph[] DeserializeGraphsAdditive (byte[] bytes, bool warnIfDuplicateGuids) { + using (AssertSafe()) { + try { + MarkerDeserializeGraphs.Begin(); + NavGraph[] result; + if (bytes != null) { + var sr = new AstarSerializer(this, active.gameObject); + + if (sr.OpenDeserialize(bytes)) { + result = DeserializeGraphsPartAdditive(sr, warnIfDuplicateGuids); + sr.CloseDeserialize(); + } else { + throw new System.ArgumentException("Invalid data file (cannot read zip).\nThe data is either corrupt or it was saved using a 3.0.x or earlier version of the system"); + } + } else { + throw new System.ArgumentNullException(nameof(bytes)); + } + UpdateShortcuts(); + GraphModifier.TriggerEvent(GraphModifier.EventType.PostGraphLoad); + return result; + } catch (System.Exception e) { + Debug.LogException(new System.Exception("Caught exception while deserializing data.", e)); + graphs = new NavGraph[0]; + UpdateShortcuts(); + throw; + } finally { + MarkerDeserializeGraphs.End(); + } + } + } + + /// Helper function for deserializing graphs + NavGraph[] DeserializeGraphsPartAdditive (AstarSerializer sr, bool warnIfDuplicateGuids) { + if (graphs == null) graphs = new NavGraph[0]; + + var gr = new List(graphs); + + // Trim nulls at the end + while (gr.Count > 0 && gr[gr.Count-1] == null) gr.RemoveAt(gr.Count-1); + var firstNewGraphIndex = gr.Count; + + FindGraphTypes(); + // This may be false if the user is editing a prefab, for example. + // If it is false, we must not try to load any nodes + bool astarInitialized = active == AstarPath.active; + int lastUsedGraphIndex = -1; + var newGraphs = sr.DeserializeGraphs(graphTypes, astarInitialized, () => { + // Find the index to insert the new graph at + // This is the first index which is not yet filled with a graph + lastUsedGraphIndex++; + while (lastUsedGraphIndex < gr.Count && gr[lastUsedGraphIndex] != null) { + lastUsedGraphIndex++; + } + return lastUsedGraphIndex; + }); + + for (int i = 0; i < newGraphs.Length; i++) { + while (gr.Count < (int)newGraphs[i].graphIndex + 1) gr.Add(null); + gr[(int)newGraphs[i].graphIndex] = newGraphs[i]; + } + + if (gr.Count > GraphNode.MaxGraphIndex + 1) { + throw new System.InvalidOperationException("Graph Count Limit Reached. You cannot have more than " + GraphNode.MaxGraphIndex + " graphs."); + } + + graphs = gr.ToArray(); + + // Assign correct graph indices. + bool anyScanned = false; + for (int i = firstNewGraphIndex; i < graphs.Length; i++) { + if (graphs[i] == null) continue; + graphs[i].GetNodes(node => node.GraphIndex = (uint)i); + anyScanned |= graphs[i].isScanned; + } + + for (int i = 0; i < graphs.Length; i++) { + for (int j = i+1; j < graphs.Length; j++) { + if (graphs[i] != null && graphs[j] != null && graphs[i].guid == graphs[j].guid) { + if (warnIfDuplicateGuids) Debug.LogWarning("Guid Conflict when importing graphs additively. Imported graph will get a new Guid.\nThis message is (relatively) harmless."); + graphs[i].guid = Pathfinding.Util.Guid.NewGuid(); + break; + } + } + } + + sr.PostDeserialization(); + + if (anyScanned) { + // This will refresh off-mesh links, + // and also recalculate the hierarchical graph if necessary. + // + // It's important that this does not run if no graphs are scanned, + // which is the case when just deserializing graph settings in the editor. + // This is because we may be in a prefab, and prefabs should never be able + // to actually load graphs with nodes. + active.AddWorkItem(ctx => { + for (int i = 0; i < newGraphs.Length; i++) { + if (newGraphs[i].isScanned) { + ctx.DirtyBounds(newGraphs[i].bounds); + } + } + }); + active.FlushWorkItems(); + } + return newGraphs; + } + + #endregion + + /// + /// Find all graph types supported in this build. + /// Using reflection, the assembly is searched for types which inherit from NavGraph. + /// + public void FindGraphTypes () { + if (graphTypes != null) return; + + MarkerFindGraphTypes.Begin(); +#if !ASTAR_FAST_NO_EXCEPTIONS && !UNITY_WINRT + graphTypes = AssemblySearcher.FindTypesInheritingFrom().ToArray(); +#else + graphTypes = DefaultGraphTypes; +#endif + MarkerFindGraphTypes.End(); + } + + #region GraphCreation + + /// Creates a new graph instance of type type + internal NavGraph CreateGraph (System.Type type) { + var graph = System.Activator.CreateInstance(type) as NavGraph; + + graph.active = active; + return graph; + } + + /// + /// Adds a graph of type T to the array. + /// See: runtime-graphs (view in online documentation for working links) + /// + public T AddGraph () where T : NavGraph => AddGraph(typeof(T)) as T; + + /// + /// Adds a graph of type type to the array. + /// See: runtime-graphs (view in online documentation for working links) + /// + public NavGraph AddGraph (System.Type type) { + NavGraph graph = null; + + for (int i = 0; i < graphTypes.Length; i++) { + if (System.Type.Equals(graphTypes[i], type)) { + graph = CreateGraph(graphTypes[i]); + } + } + + if (graph == null) { + Debug.LogError("No NavGraph of type '"+type+"' could be found, "+graphTypes.Length+" graph types are avaliable"); + return null; + } + + AddGraph(graph); + + return graph; + } + + /// Adds the specified graph to the array + void AddGraph (NavGraph graph) { + // Make sure to not interfere with pathfinding + using (AssertSafe(true)) { + // Try to fill in an empty position + int graphIndex = System.Array.IndexOf(graphs, null); + + if (graphIndex == -1) { + if (graphs.Length >= GraphNode.MaxGraphIndex) { + throw new System.Exception($"Graph Count Limit Reached. You cannot have more than {GraphNode.MaxGraphIndex} graphs."); + } + + // Add a new entry + Memory.Realloc(ref graphs, graphs.Length + 1); + graphIndex = graphs.Length-1; + } + graphs[graphIndex] = graph; + graph.graphIndex = (uint)graphIndex; + graph.active = active; + + UpdateShortcuts(); + } + } + + /// + /// Removes the specified graph from the array and Destroys it in a safe manner. + /// To avoid changing graph indices for the other graphs, the graph is simply nulled in the array instead + /// of actually removing it from the array. + /// The empty position will be reused if a new graph is added. + /// + /// Returns: True if the graph was sucessfully removed (i.e it did exist in the array). False otherwise. + /// + /// See: + /// + public bool RemoveGraph (NavGraph graph) { + if (graph == null) throw new System.ArgumentNullException(nameof(graph)); + + // Make sure the pathfinding threads are paused + using (AssertSafe()) { + active.DirtyBounds(graph.bounds); + ((IGraphInternals)graph).OnDestroy(); + graph.active = null; + + int i = System.Array.IndexOf(graphs, graph); + if (i != -1) graphs[i] = null; + + UpdateShortcuts(); + + // If we are working on a prefab, this may not be true + if (AstarPath.active == active) { + active.AddWorkItem(() => active.offMeshLinks.Refresh()); + active.FlushWorkItems(); + } + return i != -1; + } + } + + /// + /// Duplicates the given graph and adds the duplicate to the array. + /// + /// Note: Only graph settings are duplicated, not the nodes in the graph. You may want to scan the graph after duplicating it. + /// + /// Returns: The duplicated graph. + /// + public NavGraph DuplicateGraph (NavGraph graph) { + if (graph == null) throw new System.ArgumentNullException(nameof(graph)); + + int i = System.Array.IndexOf(graphs, graph); + if (i == -1) throw new System.ArgumentException("Graph doesn't exist"); + + var bytes = SerializeGraph(SerializeSettings.Settings, graph); + var newGraphs = DeserializeGraphsAdditive(bytes, false); + UnityEngine.Assertions.Assert.AreEqual(1, newGraphs.Length); + +#if UNITY_EDITOR + foreach (var g in newGraphs) { + var existingNames = new string[graphs.Length]; + for (int j = 0; j < graphs.Length; j++) existingNames[j] = graphs[j].name; + g.name = UnityEditor.ObjectNames.GetUniqueName(existingNames, g.name); + } +#endif + return newGraphs[0]; + } + + #endregion + + #region GraphUtility + + /// + /// Graph which contains the specified node. + /// The graph must be in the array. + /// + /// Returns: Returns the graph which contains the node. Null if the graph wasn't found + /// + public static NavGraph GetGraph (GraphNode node) { + if (node == null || node.Destroyed) return null; + + AstarPath script = AstarPath.active; + if (System.Object.ReferenceEquals(script, null)) return null; + + AstarData data = script.data; + if (data == null || data.graphs == null) return null; + + uint graphIndex = node.GraphIndex; + return data.graphs[(int)graphIndex]; + } + + /// Returns the first graph which satisfies the predicate. Returns null if no graph was found. + public NavGraph FindGraph (System.Func predicate) { + if (graphs != null) { + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null && predicate(graphs[i])) { + return graphs[i]; + } + } + } + return null; + } + + /// Returns the first graph of type type found in the array. Returns null if no graph was found. + public NavGraph FindGraphOfType (System.Type type) { + return FindGraph(graph => System.Type.Equals(graph.GetType(), type)); + } + + /// Returns the first graph which inherits from the type type. Returns null if no graph was found. + public NavGraph FindGraphWhichInheritsFrom (System.Type type) { + return FindGraph(graph => WindowsStoreCompatibility.GetTypeInfo(type).IsAssignableFrom(WindowsStoreCompatibility.GetTypeInfo(graph.GetType()))); + } + + /// + /// Loop through this function to get all graphs of type 'type' + /// + /// foreach (GridGraph graph in AstarPath.data.FindGraphsOfType (typeof(GridGraph))) { + /// //Do something with the graph + /// } + /// + /// See: + /// + public IEnumerable FindGraphsOfType (System.Type type) { + if (graphs == null) yield break; + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null && System.Type.Equals(graphs[i].GetType(), type)) { + yield return graphs[i]; + } + } + } + + /// + /// All graphs which implements the UpdateableGraph interface + /// foreach (IUpdatableGraph graph in AstarPath.data.GetUpdateableGraphs ()) { + /// //Do something with the graph + /// } + /// See: + /// See: + /// + public IEnumerable GetUpdateableGraphs () { + if (graphs == null) yield break; + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] is IUpdatableGraph) { + yield return graphs[i]; + } + } + } + + /// Gets the index of the graph in the array + public int GetGraphIndex (NavGraph graph) { + if (graph == null) throw new System.ArgumentNullException("graph"); + if (graphs == null) throw new System.ArgumentException("No graphs exist"); + + var index = System.Array.IndexOf(graphs, graph); + if (index == -1) throw new System.ArgumentException("Graph doesn't exist"); + return index; + } + + #endregion + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs.meta new file mode 100644 index 0000000..eb5a46f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarData.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 38d211caa07cb44ef886481aa1cf755c +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs new file mode 100644 index 0000000..cc9ed72 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs @@ -0,0 +1,2131 @@ +using UnityEngine; +using System.Collections.Generic; +using System; + +namespace Pathfinding { + using Pathfinding.Util; + using Unity.Mathematics; + using Unity.Burst; + using Pathfinding.Collections; + using Pathfinding.Pooling; + + /// Contains various spline functions. + public static class AstarSplines { + public static Vector3 CatmullRom (Vector3 previous, Vector3 start, Vector3 end, Vector3 next, float elapsedTime) { + // References used: + // p.266 GemsV1 + // + // tension is often set to 0.5 but you can use any reasonable value: + // http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf + // + // bias and tension controls: + // http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/ + + float percentComplete = elapsedTime; + float percentCompleteSquared = percentComplete * percentComplete; + float percentCompleteCubed = percentCompleteSquared * percentComplete; + + return + previous * (-0.5F*percentCompleteCubed + + percentCompleteSquared - + 0.5F*percentComplete) + + + start * + (1.5F*percentCompleteCubed + + -2.5F*percentCompleteSquared + 1.0F) + + + end * + (-1.5F*percentCompleteCubed + + 2.0F*percentCompleteSquared + + 0.5F*percentComplete) + + + next * + (0.5F*percentCompleteCubed - + 0.5F*percentCompleteSquared); + } + + /// Returns a point on a cubic bezier curve. t is clamped between 0 and 1 + public static Vector3 CubicBezier (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { + t = Mathf.Clamp01(t); + float t2 = 1-t; + return t2*t2*t2 * p0 + 3 * t2*t2 * t * p1 + 3 * t2 * t*t * p2 + t*t*t * p3; + } + + /// Returns the derivative for a point on a cubic bezier curve. t is clamped between 0 and 1 + public static Vector3 CubicBezierDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { + t = Mathf.Clamp01(t); + float t2 = 1-t; + return 3*t2*t2*(p1-p0) + 6*t2*t*(p2 - p1) + 3*t*t*(p3 - p2); + } + + /// Returns the second derivative for a point on a cubic bezier curve. t is clamped between 0 and 1 + public static Vector3 CubicBezierSecondDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { + t = Mathf.Clamp01(t); + float t2 = 1-t; + return 6*t2*(p2 - 2*p1 + p0) + 6*t*(p3 - 2*p2 + p1); + } + } + + /// + /// Various vector math utility functions. + /// Version: A lot of functions in the Polygon class have been moved to this class + /// the names have changed slightly and everything now consistently assumes a left handed + /// coordinate system now instead of sometimes using a left handed one and sometimes + /// using a right handed one. This is why the 'Left' methods in the Polygon class redirect + /// to methods named 'Right'. The functionality is exactly the same. + /// + /// Note the difference between segments and lines. Lines are infinitely + /// long but segments have only a finite length. + /// + public static class VectorMath { + /// + /// Complex number multiplication. + /// Returns: a * b + /// + /// Used to rotate vectors in an efficient way. + /// + /// See: https://en.wikipedia.org/wiki/Complex_number + /// + public static Vector2 ComplexMultiply (Vector2 a, Vector2 b) { + return new Vector2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + } + + /// + /// Complex number multiplication. + /// Returns: a * b + /// + /// Used to rotate vectors in an efficient way. + /// + /// See: https://en.wikipedia.org/wiki/Complex_number + /// + public static float2 ComplexMultiply (float2 a, float2 b) { + return a.x*b + a.y*new float2(-b.y, b.x); + } + + /// + /// Complex number multiplication. + /// Returns: a * conjugate(b) + /// + /// Used to rotate vectors in an efficient way. + /// + /// See: https://en.wikipedia.org/wiki/Complex_number + /// See: https://en.wikipedia.org/wiki/Complex_conjugate + /// + public static float2 ComplexMultiplyConjugate (float2 a, float2 b) { + return new float2(a.x * b.x + a.y * b.y, a.y * b.x - a.x * b.y); + } + + /// + /// Complex number multiplication. + /// Returns: a * conjugate(b) + /// + /// Used to rotate vectors in an efficient way. + /// + /// See: https://en.wikipedia.org/wiki/Complex_number + /// See: https://en.wikipedia.org/wiki/Complex_conjugate + /// + public static Vector2 ComplexMultiplyConjugate (Vector2 a, Vector2 b) { + return new Vector2(a.x * b.x + a.y * b.y, a.y * b.x - a.x * b.y); + } + + /// + /// Returns the closest point on the line. + /// The line is treated as infinite. + /// See: ClosestPointOnSegment + /// See: ClosestPointOnLineFactor + /// + public static Vector3 ClosestPointOnLine (Vector3 lineStart, Vector3 lineEnd, Vector3 point) { + Vector3 lineDirection = Vector3.Normalize(lineEnd - lineStart); + float dot = Vector3.Dot(point - lineStart, lineDirection); + + return lineStart + (dot*lineDirection); + } + + /// + /// Returns the closest point on the line. + /// The line is treated as infinite. + /// See: ClosestPointOnSegment + /// See: ClosestPointOnLineFactor + /// + public static float3 ClosestPointOnLine (float3 lineStart, float3 lineEnd, float3 point) { + return lineStart + ClosestPointOnLineFactor(lineStart, lineEnd, point) * (lineEnd - lineStart); + } + + /// + /// Factor along the line which is closest to the point. + /// Returned value is in the range [0,1] if the point lies on the segment otherwise it just lies on the line. + /// The closest point can be calculated using (end-start)*factor + start. + /// + /// See: ClosestPointOnLine + /// See: ClosestPointOnSegment + /// + public static float ClosestPointOnLineFactor (Vector3 lineStart, Vector3 lineEnd, Vector3 point) { + var dir = lineEnd - lineStart; + float sqrMagn = dir.sqrMagnitude; + + if (sqrMagn <= 0.000001f) return 0; + + return Vector3.Dot(point - lineStart, dir) / sqrMagn; + } + + /// + /// Factor along the line which is closest to the point. + /// Returned value is in the range [0,1] if the point lies on the segment otherwise it just lies on the line. + /// The closest point can be calculated using (end-start)*factor + start + /// + public static float ClosestPointOnLineFactor (float3 lineStart, float3 lineEnd, float3 point) { + var lineDirection = lineEnd - lineStart; + var sqrMagn = math.dot(lineDirection, lineDirection); + return math.select(0, math.dot(point - lineStart, lineDirection) / sqrMagn, sqrMagn > 0.000001f); + } + + /// + /// Factor along the line which is closest to the point. + /// Returned value is in the range [0,1] if the point lies on the segment otherwise it just lies on the line. + /// The closest point can be calculated using (end-start)*factor + start + /// + public static float ClosestPointOnLineFactor (Int3 lineStart, Int3 lineEnd, Int3 point) { + var lineDirection = lineEnd - lineStart; + float magn = lineDirection.sqrMagnitude; + + float closestPoint = (float)Int3.DotLong(point - lineStart, lineDirection); + + if (magn != 0) closestPoint /= magn; + + return closestPoint; + } + + /// + /// Factor of the nearest point on the segment. + /// Returned value is in the range [0,1] if the point lies on the segment otherwise it just lies on the line. + /// The closest point can be calculated using (end-start)*factor + start; + /// + public static float ClosestPointOnLineFactor (Vector2Int lineStart, Vector2Int lineEnd, Vector2Int point) { + var lineDirection = lineEnd - lineStart; + double magn = (long)lineDirection.x*(long)lineDirection.x + (long)lineDirection.y*(long)lineDirection.y; + + var dirPoint = point - lineStart; + double closestPoint = (long)dirPoint.x*(long)lineDirection.x + (long)dirPoint.y*(long)lineDirection.y; + + if (magn != 0) closestPoint /= magn; + + return (float)closestPoint; + } + + /// + /// Returns the closest point on the segment. + /// The segment is NOT treated as infinite. + /// See: ClosestPointOnLine + /// See: ClosestPointOnSegmentXZ + /// + public static Vector3 ClosestPointOnSegment (Vector3 lineStart, Vector3 lineEnd, Vector3 point) { + var dir = lineEnd - lineStart; + float sqrMagn = dir.sqrMagnitude; + + if (sqrMagn <= 0.000001) return lineStart; + + float factor = Vector3.Dot(point - lineStart, dir) / sqrMagn; + return lineStart + Mathf.Clamp01(factor)*dir; + } + + /// + /// Returns the closest point on the segment in the XZ plane. + /// The y coordinate of the result will be the same as the y coordinate of the point parameter. + /// + /// The segment is NOT treated as infinite. + /// See: ClosestPointOnSegment + /// See: ClosestPointOnLine + /// + public static Vector3 ClosestPointOnSegmentXZ (Vector3 lineStart, Vector3 lineEnd, Vector3 point) { + lineStart.y = point.y; + lineEnd.y = point.y; + Vector3 fullDirection = lineEnd-lineStart; + Vector3 fullDirection2 = fullDirection; + fullDirection2.y = 0; + float magn = fullDirection2.magnitude; + Vector3 lineDirection = magn > float.Epsilon ? fullDirection2/magn : Vector3.zero; + + float closestPoint = Vector3.Dot((point-lineStart), lineDirection); + return lineStart+(Mathf.Clamp(closestPoint, 0.0f, fullDirection2.magnitude)*lineDirection); + } + + /// + /// Returns the approximate shortest squared distance between x,z and the segment p-q. + /// The segment is not considered infinite. + /// This function is not entirely exact, but it is about twice as fast as DistancePointSegment2. + /// TODO: Is this actually approximate? It looks exact. + /// + public static float SqrDistancePointSegmentApproximate (int x, int z, int px, int pz, int qx, int qz) { + float pqx = (float)(qx - px); + float pqz = (float)(qz - pz); + float dx = (float)(x - px); + float dz = (float)(z - pz); + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = px + t*pqx - x; + dz = pz + t*pqz - z; + + return dx*dx + dz*dz; + } + + /// + /// Returns the approximate shortest squared distance between x,z and the segment p-q. + /// The segment is not considered infinite. + /// This function is not entirely exact, but it is about twice as fast as DistancePointSegment2. + /// TODO: Is this actually approximate? It looks exact. + /// + public static float SqrDistancePointSegmentApproximate (Int3 a, Int3 b, Int3 p) { + float pqx = (float)(b.x - a.x); + float pqz = (float)(b.z - a.z); + float dx = (float)(p.x - a.x); + float dz = (float)(p.z - a.z); + float d = pqx*pqx + pqz*pqz; + float t = pqx*dx + pqz*dz; + + if (d > 0) + t /= d; + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + + dx = a.x + t*pqx - p.x; + dz = a.z + t*pqz - p.z; + + return dx*dx + dz*dz; + } + + /// + /// Returns the squared distance between p and the segment a-b. + /// The line is not considered infinite. + /// + public static float SqrDistancePointSegment (Vector3 a, Vector3 b, Vector3 p) { + var nearest = ClosestPointOnSegment(a, b, p); + + return (nearest-p).sqrMagnitude; + } + + /// + /// 3D minimum distance between 2 segments. + /// Input: two 3D line segments S1 and S2 + /// Returns: the shortest squared distance between S1 and S2 + /// + public static float SqrDistanceSegmentSegment (Vector3 s1, Vector3 e1, Vector3 s2, Vector3 e2) { + Vector3 dir1 = e1 - s1; + Vector3 dir2 = e2 - s2; + Vector3 startOffset = s1 - s2; + double dir1sq = Vector3.Dot(dir1, dir1); // always >= 0 + double b = Vector3.Dot(dir1, dir2); + double dir2sq = Vector3.Dot(dir2, dir2); // always >= 0 + double d = Vector3.Dot(dir1, startOffset); + double e = Vector3.Dot(dir2, startOffset); + double D = dir1sq*dir2sq - b*b; // always >= 0 + double sc, sN, sD = D; // sc = sN / sD, default sD = D >= 0 + double tc, tN, tD = D; // tc = tN / tD, default tD = D >= 0 + + // compute the line parameters of the two closest points + // D is approximately |dir1|^2|dir2|^2*(1-cos^2 alpha), where alpha is the angle between the lines + if (D < 0.000001 * dir1sq*dir2sq) { // the lines are almost parallel + sN = 0.0f; // force using point P0 on segment S1 + sD = 1.0f; // to prevent possible division by 0.0 later + tN = e; + tD = dir2sq; + } else { // get the closest points on the infinite lines + sN = (b*e - dir2sq*d); + tN = (dir1sq*e - b*d); + if (sN < 0.0) { // sc < 0 => the s=0 edge is visible + sN = 0.0; + tN = e; + tD = dir2sq; + } else if (sN > sD) { // sc > 1 => the s=1 edge is visible + sN = sD; + tN = e + b; + tD = dir2sq; + } + } + + if (tN < 0.0) { // tc < 0 => the t=0 edge is visible + tN = 0.0; + // recompute sc for this edge + if (-d < 0.0f) + sN = 0.0f; + else if (-d > dir1sq) + sN = sD; + else { + sN = -d; + sD = dir1sq; + } + } else if (tN > tD) { // tc > 1 => the t=1 edge is visible + tN = tD; + // recompute sc for this edge + if ((-d + b) < 0.0f) + sN = 0; + else if ((-d + b) > dir1sq) + sN = sD; + else { + sN = (-d + b); + sD = dir1sq; + } + } + + // finally do the division to get sc and tc + sc = (Math.Abs(sN) < 0.00001f ? 0.0 : sN / sD); + tc = (Math.Abs(tN) < 0.00001f ? 0.0 : tN / tD); + + // get the difference of the two closest points + Vector3 dP = startOffset + ((float)sc * dir1) - ((float)tc * dir2); // = S1(sc) - S2(tc) + + return dP.sqrMagnitude; // return the closest distance + } + + /// + /// Determinant of the 2x2 matrix [c1, c2]. + /// + /// This is useful for many things, like calculating distances between lines and points. + /// + /// Equivalent to Cross(new float3(c1, 0), new float 3(c2, 0)).z + /// + public static float Determinant (float2 c1, float2 c2) { + return c1.x*c2.y - c1.y*c2.x; + } + + /// Squared distance between two points in the XZ plane + public static float SqrDistanceXZ (Vector3 a, Vector3 b) { + var delta = a-b; + + return delta.x*delta.x+delta.z*delta.z; + } + + /// + /// Signed area of a triangle multiplied by 2. + /// This will be negative for clockwise triangles and positive for counter-clockwise ones + /// + public static long SignedTriangleAreaTimes2 (int2 a, int2 b, int2 c) { + return (long)(b.x - a.x) * (long)(c.y - a.y) - (long)(c.x - a.x) * (long)(b.y - a.y); + } + + /// + /// Signed area of a triangle in the XZ plane multiplied by 2. + /// This will be negative for clockwise triangles and positive for counter-clockwise ones + /// + public static long SignedTriangleAreaTimes2XZ (Int3 a, Int3 b, Int3 c) { + return (long)(b.x - a.x) * (long)(c.z - a.z) - (long)(c.x - a.x) * (long)(b.z - a.z); + } + + /// + /// Signed area of a triangle in the XZ plane multiplied by 2. + /// This will be negative for clockwise triangles and positive for counter-clockwise ones. + /// + public static float SignedTriangleAreaTimes2XZ (Vector3 a, Vector3 b, Vector3 c) { + return (b.x - a.x) * (c.z - a.z) - (c.x - a.x) * (b.z - a.z); + } + + /// + /// Returns if p lies on the right side of the line a - b. + /// Uses XZ space. Does not return true if the points are colinear. + /// + public static bool RightXZ (Vector3 a, Vector3 b, Vector3 p) { + return (b.x - a.x) * (p.z - a.z) - (p.x - a.x) * (b.z - a.z) < -float.Epsilon; + } + + /// + /// Returns if p lies on the right side of the line a - b. + /// Uses XZ space. Does not return true if the points are colinear. + /// + public static bool RightXZ (Int3 a, Int3 b, Int3 p) { + return (long)(b.x - a.x) * (long)(p.z - a.z) - (long)(p.x - a.x) * (long)(b.z - a.z) < 0; + } + + /// + /// Returns if p lies on the right side of the line a - b. + /// Does not return true if the points are colinear. + /// + public static bool Right (int2 a, int2 b, int2 p) { + return (long)(b.x - a.x) * (long)(p.y - a.y) - (long)(p.x - a.x) * (long)(b.y - a.y) < 0; + } + + /// + /// Returns which side of the line a - b that p lies on. + /// Uses XZ space. + /// + public static Side SideXZ (Int3 a, Int3 b, Int3 p) { + var s = (long)(b.x - a.x) * (long)(p.z - a.z) - (long)(p.x - a.x) * (long)(b.z - a.z); + + return s > 0 ? Side.Left : (s < 0 ? Side.Right : Side.Colinear); + } + + /// + /// Returns if p lies on the right side of the line a - b. + /// Also returns true if the points are colinear. + /// + public static bool RightOrColinear (Vector2 a, Vector2 b, Vector2 p) { + return (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y) <= 0; + } + + /// + /// Returns if p lies on the right side of the line a - b. + /// Also returns true if the points are colinear. + /// + public static bool RightOrColinear (Vector2Int a, Vector2Int b, Vector2Int p) { + return (long)(b.x - a.x) * (long)(p.y - a.y) - (long)(p.x - a.x) * (long)(b.y - a.y) <= 0; + } + + /// + /// Returns if p lies on the left side of the line a - b. + /// Uses XZ space. Also returns true if the points are colinear. + /// + public static bool RightOrColinearXZ (Vector3 a, Vector3 b, Vector3 p) { + return (b.x - a.x) * (p.z - a.z) - (p.x - a.x) * (b.z - a.z) <= 0; + } + + /// + /// Returns if p lies on the left side of the line a - b. + /// Uses XZ space. Also returns true if the points are colinear. + /// + public static bool RightOrColinearXZ (Int3 a, Int3 b, Int3 p) { + return (long)(b.x - a.x) * (long)(p.z - a.z) - (long)(p.x - a.x) * (long)(b.z - a.z) <= 0; + } + + /// + /// Returns if the points a in a clockwise order. + /// Will return true even if the points are colinear or very slightly counter-clockwise + /// (if the signed area of the triangle formed by the points has an area less than or equals to float.Epsilon) + /// + public static bool IsClockwiseMarginXZ (Vector3 a, Vector3 b, Vector3 c) { + return (b.x-a.x)*(c.z-a.z)-(c.x-a.x)*(b.z-a.z) <= float.Epsilon; + } + + /// Returns if the points a in a clockwise order + public static bool IsClockwiseXZ (Vector3 a, Vector3 b, Vector3 c) { + return (b.x-a.x)*(c.z-a.z)-(c.x-a.x)*(b.z-a.z) < 0; + } + + /// Returns if the points a in a clockwise order + public static bool IsClockwiseXZ (Int3 a, Int3 b, Int3 c) { + return RightXZ(a, b, c); + } + + /// Returns if the points a in a clockwise order + public static bool IsClockwise (int2 a, int2 b, int2 c) { + return Right(a, b, c); + } + + /// Returns true if the points a in a clockwise order or if they are colinear + public static bool IsClockwiseOrColinearXZ (Int3 a, Int3 b, Int3 c) { + return RightOrColinearXZ(a, b, c); + } + + /// Returns true if the points a in a clockwise order or if they are colinear + public static bool IsClockwiseOrColinear (Vector2Int a, Vector2Int b, Vector2Int c) { + return RightOrColinear(a, b, c); + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinear (Vector3 a, Vector3 b, Vector3 c) { + var lhs = b - a; + var rhs = c - a; + // Take the cross product of lhs and rhs + // The magnitude of the cross product will be zero if the points a,b,c are colinear + float x = lhs.y * rhs.z - lhs.z * rhs.y; + float y = lhs.z * rhs.x - lhs.x * rhs.z; + float z = lhs.x * rhs.y - lhs.y * rhs.x; + float v = x*x + y*y + z*z; + float lengthsq = lhs.sqrMagnitude * rhs.sqrMagnitude; + + // Epsilon not chosen with much thought, just that float.Epsilon was a bit too small. + return v <= math.sqrt(lengthsq) * 0.0001f || lengthsq == 0.0f; + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinear (Vector2 a, Vector2 b, Vector2 c) { + float v = (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y); + + // Epsilon not chosen with much thought, just that float.Epsilon was a bit too small. + return v <= 0.0001f && v >= -0.0001f; + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinear (int2 a, int2 b, int2 c) { + return (long)(b.x - a.x) * (long)(c.y - a.y) - (long)(c.x - a.x) * (long)(b.y - a.y) == 0; + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinearXZ (Int3 a, Int3 b, Int3 c) { + return (long)(b.x - a.x) * (long)(c.z - a.z) - (long)(c.x - a.x) * (long)(b.z - a.z) == 0; + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinearXZ (Vector3 a, Vector3 b, Vector3 c) { + float v = (b.x-a.x)*(c.z-a.z)-(c.x-a.x)*(b.z-a.z); + + // Epsilon not chosen with much thought, just that float.Epsilon was a bit too small. + return v <= 0.0000001f && v >= -0.0000001f; + } + + /// Returns if the points are colinear (lie on a straight line) + public static bool IsColinearAlmostXZ (Int3 a, Int3 b, Int3 c) { + long v = (long)(b.x - a.x) * (long)(c.z - a.z) - (long)(c.x - a.x) * (long)(b.z - a.z); + + return v > -1 && v < 1; + } + + /// + /// Returns if the line segment start2 - end2 intersects the line segment start1 - end1. + /// If only the endpoints coincide, the result is undefined (may be true or false). + /// + public static bool SegmentsIntersect (Vector2Int start1, Vector2Int end1, Vector2Int start2, Vector2Int end2) { + return RightOrColinear(start1, end1, start2) != RightOrColinear(start1, end1, end2) && RightOrColinear(start2, end2, start1) != RightOrColinear(start2, end2, end1); + } + + /// + /// Returns if the line segment start2 - end2 intersects the line segment start1 - end1. + /// If only the endpoints coincide, the result is undefined (may be true or false). + /// + /// Note: XZ space + /// + public static bool SegmentsIntersectXZ (Int3 start1, Int3 end1, Int3 start2, Int3 end2) { + return RightOrColinearXZ(start1, end1, start2) != RightOrColinearXZ(start1, end1, end2) && RightOrColinearXZ(start2, end2, start1) != RightOrColinearXZ(start2, end2, end1); + } + + /// + /// Returns if the two line segments intersects. The lines are NOT treated as infinite (just for clarification) + /// See: IntersectionPoint + /// + public static bool SegmentsIntersectXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2) { + Vector3 dir1 = end1-start1; + Vector3 dir2 = end2-start2; + + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + return false; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float nom2 = dir1.x*(start1.z-start2.z) - dir1.z * (start1.x - start2.x); + float u = nom/den; + float u2 = nom2/den; + + if (u < 0F || u > 1F || u2 < 0F || u2 > 1F) { + return false; + } + + return true; + } + + /// + /// Calculates the intersection points between a "capsule" (segment expanded by a radius), and a line. + /// + /// Returns: (t1, t2), the intersection points on the form lineStart + lineDir*t. Where t2 >= t1. If t2 < t1 then there are no intersections. + /// + /// Center of the capsule's first circle + /// Main axis of the capsule. Must be normalized. + /// Distance betwen the capsule's circle centers. + /// A point on the line + /// The (normalized) direction of the line. + /// The radius of the circle. + public static float2 CapsuleLineIntersectionFactors (float2 capsuleStart, float2 capsuleDir, float capsuleLength, float2 lineStart, float2 lineDir, float radius) { + var cosAlpha = math.dot(capsuleDir, lineDir); + var sinAlpha = math.sqrt(1.0f - cosAlpha*cosAlpha); + var tmin = float.PositiveInfinity; + var tmax = float.NegativeInfinity; + + if (LineCircleIntersectionFactors(lineStart - capsuleStart, lineDir, radius, out float t11, out float t12)) { + tmin = math.min(tmin, t11); + tmax = math.max(tmax, t12); + } + if (LineCircleIntersectionFactors(lineStart - (capsuleStart + capsuleDir*capsuleLength), lineDir, radius, out float t21, out float t22)) { + tmin = math.min(tmin, t21); + tmax = math.max(tmax, t22); + } + + if (LineLineIntersectionFactor(capsuleStart, capsuleDir, lineStart, lineDir, out float ucenter)) { + var normal = new float2(-capsuleDir.y, capsuleDir.x); + var offset = radius * cosAlpha / sinAlpha; + var side = math.sign(capsuleDir.y*lineDir.x - capsuleDir.x*lineDir.y); + var ustraight1 = ucenter + offset*side; + var ustraight2 = ucenter - offset*side; + if (ustraight1 >= 0 && ustraight1 <= capsuleLength) { + var p = capsuleStart + capsuleDir * ustraight1 - normal * radius; + var tstraight1 = math.dot(p - lineStart, lineDir); + tmin = math.min(tmin, tstraight1); + tmax = math.max(tmax, tstraight1); + } + if (ustraight2 >= 0 && ustraight2 <= capsuleLength) { + var p = capsuleStart + capsuleDir * ustraight2 + normal * radius; + var tstraight2 = math.dot(p - lineStart, lineDir); + tmin = math.min(tmin, tstraight2); + tmax = math.max(tmax, tstraight2); + } + } else { + // Parallel, or almost parallel. + // In this case we can just rely on the circle intersection checks. + } + + return new float2(tmin, tmax); + } + + /// + /// Calculates the point start1 + dir1*t where the two infinite lines intersect. + /// Returns false if the lines are close to parallel. + /// + public static bool LineLineIntersectionFactor (float2 start1, float2 dir1, float2 start2, float2 dir2, out float t) { + float den = dir2.y*dir1.x - dir2.x * dir1.y; + + if (math.abs(den) < 0.0001f) { + t = 0; + return false; + } + + float nom = dir2.x*(start1.y-start2.y) - dir2.y*(start1.x-start2.x); + t = nom/den; + return true; + } + + /// + /// Calculates the point start1 + dir1*factor1 == start2 + dir2*factor2 where the two infinite lines intersect. + /// Returns false if the lines are close to parallel. + /// + public static bool LineLineIntersectionFactors (float2 start1, float2 dir1, float2 start2, float2 dir2, out float factor1, out float factor2) { + float den = dir2.y*dir1.x - dir2.x * dir1.y; + + if (math.abs(den) < 0.0001f) { + factor1 = factor2 = 0; + return false; + } + + float nom1 = dir2.x*(start1.y-start2.y) - dir2.y*(start1.x-start2.x); + float nom2 = dir1.x*(start1.y-start2.y) - dir1.y*(start1.x - start2.x); + factor1 = nom1/den; + factor2 = nom2/den; + return true; + } + + /// + /// Intersection point between two infinite lines. + /// Note that start points and directions are taken as parameters instead of start and end points. + /// Lines are treated as infinite. If the lines are parallel 'start1' will be returned. + /// Intersections are calculated on the XZ plane. + /// + /// See: LineIntersectionPointXZ + /// + public static Vector3 LineDirIntersectionPointXZ (Vector3 start1, Vector3 dir1, Vector3 start2, Vector3 dir2) { + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + return start1; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float u = nom/den; + + return start1 + dir1*u; + } + + /// + /// Intersection point between two infinite lines. + /// Note that start points and directions are taken as parameters instead of start and end points. + /// Lines are treated as infinite. If the lines are parallel 'start1' will be returned. + /// Intersections are calculated on the XZ plane. + /// + /// See: LineIntersectionPointXZ + /// + public static Vector3 LineDirIntersectionPointXZ (Vector3 start1, Vector3 dir1, Vector3 start2, Vector3 dir2, out bool intersects) { + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + intersects = false; + return start1; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float u = nom/den; + + intersects = true; + return start1 + dir1*u; + } + + /// + /// Returns if the ray (start1, end1) intersects the segment (start2, end2). + /// false is returned if the lines are parallel. + /// Only the XZ coordinates are used. + /// TODO: Double check that this actually works + /// + public static bool RaySegmentIntersectXZ (Int3 start1, Int3 end1, Int3 start2, Int3 end2) { + Int3 dir1 = end1-start1; + Int3 dir2 = end2-start2; + + long den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + return false; + } + + long nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + long nom2 = dir1.x*(start1.z-start2.z) - dir1.z * (start1.x - start2.x); + + //factor1 < 0 + // If both have the same sign, then nom/den < 0 and thus the segment cuts the ray before the ray starts + if (!(nom < 0 ^ den < 0)) { + return false; + } + + //factor2 < 0 + if (!(nom2 < 0 ^ den < 0)) { + return false; + } + + if ((den >= 0 && nom2 > den) || (den < 0 && nom2 <= den)) { + return false; + } + + return true; + } + + /// + /// Returns the intersection factors for line 1 and line 2. The intersection factors is a distance along the line start - end where the other line intersects it. + /// intersectionPoint = start1 + factor1 * (end1-start1) + /// intersectionPoint2 = start2 + factor2 * (end2-start2) + /// Lines are treated as infinite. + /// false is returned if the lines are parallel and true if they are not. + /// Only the XZ coordinates are used. + /// + public static bool LineIntersectionFactorXZ (Int3 start1, Int3 end1, Int3 start2, Int3 end2, out float factor1, out float factor2) { + Int3 dir1 = end1-start1; + Int3 dir2 = end2-start2; + + long den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + factor1 = 0; + factor2 = 0; + return false; + } + + long nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + long nom2 = dir1.x*(start1.z-start2.z) - dir1.z * (start1.x - start2.x); + + factor1 = (float)nom/den; + factor2 = (float)nom2/den; + + return true; + } + + /// + /// Returns the intersection factors for line 1 and line 2. The intersection factors is a distance along the line start - end where the other line intersects it. + /// intersectionPoint = start1 + factor1 * (end1-start1) + /// intersectionPoint2 = start2 + factor2 * (end2-start2) + /// Lines are treated as infinite. + /// false is returned if the lines are parallel and true if they are not. + /// Only the XZ coordinates are used. + /// + public static bool LineIntersectionFactorXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2, out float factor1, out float factor2) { + Vector3 dir1 = end1-start1; + Vector3 dir2 = end2-start2; + + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den <= 0.00001f && den >= -0.00001f) { + factor1 = 0; + factor2 = 0; + return false; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float nom2 = dir1.x*(start1.z-start2.z) - dir1.z * (start1.x - start2.x); + + float u = nom/den; + float u2 = nom2/den; + + factor1 = u; + factor2 = u2; + + return true; + } + + /// + /// Returns the intersection factor for line 1 with ray 2. + /// The intersection factors is a factor distance along the line start - end where the other line intersects it. + /// intersectionPoint = start1 + factor * (end1-start1) + /// Lines are treated as infinite. + /// + /// The second "line" is treated as a ray, meaning only matches on start2 or forwards towards end2 (and beyond) will be returned + /// If the point lies on the wrong side of the ray start, Nan will be returned. + /// + /// NaN is returned if the lines are parallel. + /// + public static float LineRayIntersectionFactorXZ (Int3 start1, Int3 end1, Int3 start2, Int3 end2) { + Int3 dir1 = end1-start1; + Int3 dir2 = end2-start2; + + int den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + return float.NaN; + } + + int nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + int nom2 = dir1.x*(start1.z-start2.z) - dir1.z * (start1.x - start2.x); + + if ((float)nom2/den < 0) { + return float.NaN; + } + return (float)nom/den; + } + + /// + /// Returns the intersection factor for line 1 with line 2. + /// The intersection factor is a distance along the line start1 - end1 where the line start2 - end2 intersects it. + /// intersectionPoint = start1 + intersectionFactor * (end1-start1) . + /// Lines are treated as infinite. + /// -1 is returned if the lines are parallel (note that this is a valid return value if they are not parallel too) + /// + public static float LineIntersectionFactorXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2) { + Vector3 dir1 = end1-start1; + Vector3 dir2 = end2-start2; + + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + return -1; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float u = nom/den; + + return u; + } + + /// Returns the intersection point between the two lines. Lines are treated as infinite. start1 is returned if the lines are parallel + public static Vector3 LineIntersectionPointXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2) { + bool s; + + return LineIntersectionPointXZ(start1, end1, start2, end2, out s); + } + + /// Returns the intersection point between the two lines. Lines are treated as infinite. start1 is returned if the lines are parallel + public static Vector3 LineIntersectionPointXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2, out bool intersects) { + Vector3 dir1 = end1-start1; + Vector3 dir2 = end2-start2; + + float den = dir2.z*dir1.x - dir2.x * dir1.z; + + if (den == 0) { + intersects = false; + return start1; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + + float u = nom/den; + + intersects = true; + return start1 + dir1*u; + } + + /// Returns the intersection point between the two lines. Lines are treated as infinite. start1 is returned if the lines are parallel + public static Vector2 LineIntersectionPoint (Vector2 start1, Vector2 end1, Vector2 start2, Vector2 end2) { + bool s; + + return LineIntersectionPoint(start1, end1, start2, end2, out s); + } + + /// Returns the intersection point between the two lines. Lines are treated as infinite. start1 is returned if the lines are parallel + public static Vector2 LineIntersectionPoint (Vector2 start1, Vector2 end1, Vector2 start2, Vector2 end2, out bool intersects) { + Vector2 dir1 = end1-start1; + Vector2 dir2 = end2-start2; + + float den = dir2.y*dir1.x - dir2.x * dir1.y; + + if (den == 0) { + intersects = false; + return start1; + } + + float nom = dir2.x*(start1.y-start2.y)- dir2.y*(start1.x-start2.x); + + float u = nom/den; + + intersects = true; + return start1 + dir1*u; + } + + /// + /// Returns the intersection point between the two line segments in XZ space. + /// Lines are NOT treated as infinite. start1 is returned if the line segments do not intersect + /// The point will be returned along the line [start1, end1] (this matters only for the y coordinate). + /// + public static Vector3 SegmentIntersectionPointXZ (Vector3 start1, Vector3 end1, Vector3 start2, Vector3 end2, out bool intersects) { + Vector3 dir1 = end1-start1; + Vector3 dir2 = end2-start2; + + float den = dir2.z * dir1.x - dir2.x * dir1.z; + + if (den == 0) { + intersects = false; + return start1; + } + + float nom = dir2.x*(start1.z-start2.z)- dir2.z*(start1.x-start2.x); + float nom2 = dir1.x*(start1.z-start2.z) - dir1.z*(start1.x-start2.x); + float u = nom/den; + float u2 = nom2/den; + + if (u < 0F || u > 1F || u2 < 0F || u2 > 1F) { + intersects = false; + return start1; + } + + intersects = true; + return start1 + dir1*u; + } + + /// + /// Does the line segment intersect the bounding box. + /// The line is NOT treated as infinite. + /// \author Slightly modified code from http://www.3dkingdoms.com/weekly/weekly.php?a=21 + /// + public static bool SegmentIntersectsBounds (Bounds bounds, Vector3 a, Vector3 b) { + // Put segment in box space + a -= bounds.center; + b -= bounds.center; + + // Get line midpoint and extent + var LMid = (a + b) * 0.5F; + var L = (a - LMid); + var LExt = new Vector3(Math.Abs(L.x), Math.Abs(L.y), Math.Abs(L.z)); + + Vector3 extent = bounds.extents; + + // Use Separating Axis Test + // Separation vector from box center to segment center is LMid, since the line is in box space + if (Math.Abs(LMid.x) > extent.x + LExt.x) return false; + if (Math.Abs(LMid.y) > extent.y + LExt.y) return false; + if (Math.Abs(LMid.z) > extent.z + LExt.z) return false; + // Crossproducts of line and each axis + if (Math.Abs(LMid.y * L.z - LMid.z * L.y) > (extent.y * LExt.z + extent.z * LExt.y)) return false; + if (Math.Abs(LMid.x * L.z - LMid.z * L.x) > (extent.x * LExt.z + extent.z * LExt.x)) return false; + if (Math.Abs(LMid.x * L.y - LMid.y * L.x) > (extent.x * LExt.y + extent.y * LExt.x)) return false; + // No separating axis, the line intersects + return true; + } + + /// + /// Calculates the two intersection points (point + direction*t) on the line where it intersects with a circle at the origin. + /// + /// t1 will always be less than or equal to t2 if there are intersections. + /// + /// Returns false if there are no intersections. + /// + /// A point on the line + /// The normalized direction of the line + /// The radius of the circle at the origin. + /// The first intersection (if any). + /// The second intersection (if any). + public static bool LineCircleIntersectionFactors (float2 point, float2 direction, float radius, out float t1, out float t2) { + // Distance from the closest point on the line (from the origin) to line.point + float dot = math.dot(point, direction); + // Squared distance from the origin to the closest point on the line + float distanceToLine = math.lengthsq(point) - dot*dot; + // Calculate the intersection of the line with the circle. + // This is the squared length of half the chord that intersects the circle. + float discriminant = radius*radius - distanceToLine; + + if (discriminant < 0.0f) { + // The line is completely outside the circle + t1 = float.PositiveInfinity; + t2 = float.NegativeInfinity; + return false; + } + + var sqrtDiscriminant = math.sqrt(discriminant); + t1 = -dot - sqrtDiscriminant; + t2 = -dot + sqrtDiscriminant; + return true; + } + + /// + /// Calculates the two intersection points (lerp(point1, point2, t)) on the segment where it intersects with a circle at the origin. + /// + /// t1 will always be less than or equal to t2 if there are intersections. + /// + /// Returns false if there are no intersections. + /// + /// Start of the segment + /// End of the segment + /// The squared radius of the circle at the origin. + /// The first intersection (if any). Between 0 and 1. + /// The second intersection (if any). Between 0 and 1. + public static bool SegmentCircleIntersectionFactors (float2 point1, float2 point2, float radiusSq, out float t1, out float t2) { + // Distance from the closest point on the line (from the origin) to line.point + var dir = point2 - point1; + var dirSq = math.lengthsq(dir); + float dot = math.dot(point1, dir) / dirSq; + // Proportional to the squared distance from the origin to the closest point on the line + float distanceToLine = math.lengthsq(point1) / dirSq - dot*dot; + float discriminant = radiusSq/dirSq - distanceToLine; + + if (discriminant < 0.0f) { + // The line is completely outside the circle + t1 = float.PositiveInfinity; + t2 = float.NegativeInfinity; + return false; + } + + var sqrtDiscriminant = math.sqrt(discriminant); + t1 = -dot - sqrtDiscriminant; + t2 = -dot + sqrtDiscriminant; + t1 = math.max(0, t1); + t2 = math.min(1, t2); + + if (t1 >= 1 || t2 <= 0) return false; + return true; + } + + /// + /// Intersection of a line and a circle. + /// Returns the greatest t such that segmentStart+t*(segmentEnd-segmentStart) lies on the circle. + /// + /// In case the line does not intersect with the circle, the closest point on the line + /// to the circle will be returned. + /// + /// Note: Works for line and sphere in 3D space as well. + /// + /// See: http://mathworld.wolfram.com/Circle-LineIntersection.html + /// See: https://en.wikipedia.org/wiki/Intersection_(Euclidean_geometry) + /// + public static float LineCircleIntersectionFactor (Vector3 circleCenter, Vector3 linePoint1, Vector3 linePoint2, float radius) { + float segmentLength; + var normalizedDirection = Normalize(linePoint2 - linePoint1, out segmentLength); + var dirToStart = linePoint1 - circleCenter; + + var dot = Vector3.Dot(dirToStart, normalizedDirection); + var discriminant = dot * dot - (dirToStart.sqrMagnitude - radius*radius); + + if (discriminant < 0) { + // No intersection, pick closest point on segment + discriminant = 0; + } + + var t = -dot + Mathf.Sqrt(discriminant); + // Note: the default value of 1 is important for the PathInterpolator.MoveToCircleIntersection2D + // method to work properly. Maybe find some better abstraction where this default value is more obvious. + return segmentLength > 0.00001f ? t / segmentLength : 1f; + } + + /// + /// True if the matrix will reverse orientations of faces. + /// + /// Scaling by a negative value along an odd number of axes will reverse + /// the orientation of e.g faces on a mesh. This must be counter adjusted + /// by for example the recast rasterization system to be able to handle + /// meshes with negative scales properly. + /// + /// We can find out if they are flipped by finding out how the signed + /// volume of a unit cube is transformed when applying the matrix + /// + /// If the (signed) volume turns out to be negative + /// that also means that the orientation of it has been reversed. + /// + /// See: https://en.wikipedia.org/wiki/Normal_(geometry) + /// See: https://en.wikipedia.org/wiki/Parallelepiped + /// + public static bool ReversesFaceOrientations (Matrix4x4 matrix) { + var dX = matrix.MultiplyVector(new Vector3(1, 0, 0)); + var dY = matrix.MultiplyVector(new Vector3(0, 1, 0)); + var dZ = matrix.MultiplyVector(new Vector3(0, 0, 1)); + + // Calculate the signed volume of the parallelepiped + var volume = Vector3.Dot(Vector3.Cross(dX, dY), dZ); + + return volume < 0; + } + + /// + /// Normalize vector and also return the magnitude. + /// This is more efficient than calculating the magnitude and normalizing separately + /// + public static Vector3 Normalize (Vector3 v, out float magnitude) { + magnitude = v.magnitude; + // This is the same constant that Unity uses + if (magnitude > 1E-05f) { + return v / magnitude; + } else { + return Vector3.zero; + } + } + + /// + /// Normalize vector and also return the magnitude. + /// This is more efficient than calculating the magnitude and normalizing separately + /// + public static Vector2 Normalize (Vector2 v, out float magnitude) { + magnitude = v.magnitude; + // This is the same constant that Unity uses + if (magnitude > 1E-05f) { + return v / magnitude; + } else { + return Vector2.zero; + } + } + + /* Clamp magnitude along the X and Z axes. + * The y component will not be changed. + */ + public static Vector3 ClampMagnitudeXZ (Vector3 v, float maxMagnitude) { + float squaredMagnitudeXZ = v.x*v.x + v.z*v.z; + + if (squaredMagnitudeXZ > maxMagnitude*maxMagnitude && maxMagnitude > 0) { + var factor = maxMagnitude / Mathf.Sqrt(squaredMagnitudeXZ); + v.x *= factor; + v.z *= factor; + } + return v; + } + + /* Magnitude in the XZ plane */ + public static float MagnitudeXZ (Vector3 v) { + return Mathf.Sqrt(v.x*v.x + v.z*v.z); + } + + /// + /// Number of radians that this quaternion rotates around its axis of rotation. + /// Will be in the range [-PI, PI]. + /// + /// Note: A quaternion of q and -q represent the same rotation, but their axis of rotation point in opposite directions, so the angle will be different. + /// + public static float QuaternionAngle (quaternion rot) { + return 2 * math.atan2(math.length(rot.value.xyz), rot.value.w); + } + } + + /// + /// Utility functions for working with numbers and strings. + /// + /// See: Polygon + /// See: VectorMath + /// + public static class AstarMath { + static Unity.Mathematics.Random GlobalRandom = Unity.Mathematics.Random.CreateFromIndex(0); + static object GlobalRandomLock = new object(); + + public static float ThreadSafeRandomFloat () { + lock (GlobalRandomLock) { + return GlobalRandom.NextFloat(); + } + } + + public static float2 ThreadSafeRandomFloat2 () { + lock (GlobalRandomLock) { + return GlobalRandom.NextFloat2(); + } + } + + /// Converts a non-negative float to a long, saturating at long.MaxValue if the value is too large + public static long SaturatingConvertFloatToLong(float v) => v > (float)long.MaxValue ? long.MaxValue : (long)v; + + /// Maps a value between startMin and startMax to be between targetMin and targetMax + public static float MapTo (float startMin, float startMax, float targetMin, float targetMax, float value) { + return Mathf.Lerp(targetMin, targetMax, Mathf.InverseLerp(startMin, startMax, value)); + } + + /// + /// Returns bit number b from int a. The bit number is zero based. Relevant b values are from 0 to 31. + /// Equals to (a >> b) & 1 + /// + static int Bit (int a, int b) { + return (a >> b) & 1; + } + + /// + /// Returns a nice color from int i with alpha a. Got code from the open-source Recast project, works really well. + /// Seems like there are only 64 possible colors from studying the code + /// + public static Color IntToColor (int i, float a) { + int r = Bit(i, 2) + Bit(i, 3) * 2 + 1; + int g = Bit(i, 1) + Bit(i, 4) * 2 + 1; + int b = Bit(i, 0) + Bit(i, 5) * 2 + 1; + + return new Color(r*0.25F, g*0.25F, b*0.25F, a); + } + + /// + /// Converts an HSV color to an RGB color. + /// According to the algorithm described at http://en.wikipedia.org/wiki/HSL_and_HSV + /// + /// @author Wikipedia + /// @return the RGB representation of the color. + /// + public static Color HSVToRGB (float h, float s, float v) { + float r = 0, g = 0, b = 0; + + float Chroma = s * v; + float Hdash = h / 60.0f; + float X = Chroma * (1.0f - System.Math.Abs((Hdash % 2.0f) - 1.0f)); + + if (Hdash < 1.0f) { + r = Chroma; + g = X; + } else if (Hdash < 2.0f) { + r = X; + g = Chroma; + } else if (Hdash < 3.0f) { + g = Chroma; + b = X; + } else if (Hdash < 4.0f) { + g = X; + b = Chroma; + } else if (Hdash < 5.0f) { + r = X; + b = Chroma; + } else if (Hdash < 6.0f) { + r = Chroma; + b = X; + } + + float Min = v - Chroma; + + r += Min; + g += Min; + b += Min; + + return new Color(r, g, b); + } + + /// + /// Calculates the shortest difference between two given angles given in radians. + /// + /// The return value will be between -pi/2 and +pi/2. + /// + public static float DeltaAngle (float angle1, float angle2) { + float diff = (angle2 - angle1 + math.PI) % (2*math.PI) - math.PI; + return math.select(diff, diff + 2*math.PI, diff < -math.PI); + } + } + + /// + /// Utility functions for working with polygons, lines, and other vector math. + /// All functions which accepts Vector3s but work in 2D space uses the XZ space if nothing else is said. + /// + /// Version: A lot of functions in this class have been moved to the VectorMath class + /// the names have changed slightly and everything now consistently assumes a left handed + /// coordinate system now instead of sometimes using a left handed one and sometimes + /// using a right handed one. This is why the 'Left' methods redirect to methods + /// named 'Right'. The functionality is exactly the same. + /// + [BurstCompile] + public static class Polygon { + /// + /// Returns if the triangle ABC contains the point p in XZ space. + /// The triangle vertices are assumed to be laid out in clockwise order. + /// + public static bool ContainsPointXZ (Vector3 a, Vector3 b, Vector3 c, Vector3 p) { + return VectorMath.IsClockwiseMarginXZ(a, b, p) && VectorMath.IsClockwiseMarginXZ(b, c, p) && VectorMath.IsClockwiseMarginXZ(c, a, p); + } + + /// + /// Returns if the triangle ABC contains the point p. + /// The triangle vertices are assumed to be laid out in clockwise order. + /// + public static bool ContainsPointXZ (Int3 a, Int3 b, Int3 c, Int3 p) { + return VectorMath.IsClockwiseOrColinearXZ(a, b, p) && VectorMath.IsClockwiseOrColinearXZ(b, c, p) && VectorMath.IsClockwiseOrColinearXZ(c, a, p); + } + + /// + /// Returns if the triangle ABC contains the point p. + /// The triangle vertices are assumed to be laid out in clockwise order. + /// + public static bool ContainsPoint (Vector2Int a, Vector2Int b, Vector2Int c, Vector2Int p) { + return VectorMath.IsClockwiseOrColinear(a, b, p) && VectorMath.IsClockwiseOrColinear(b, c, p) && VectorMath.IsClockwiseOrColinear(c, a, p); + } + + /// + /// Checks if p is inside the polygon. + /// \author http://unifycommunity.com/wiki/index.php?title=PolyContainsPoint (Eric5h5) + /// + public static bool ContainsPoint (Vector2[] polyPoints, Vector2 p) { + int j = polyPoints.Length-1; + bool inside = false; + + for (int i = 0; i < polyPoints.Length; j = i++) { + if (((polyPoints[i].y <= p.y && p.y < polyPoints[j].y) || (polyPoints[j].y <= p.y && p.y < polyPoints[i].y)) && + (p.x < (polyPoints[j].x - polyPoints[i].x) * (p.y - polyPoints[i].y) / (polyPoints[j].y - polyPoints[i].y) + polyPoints[i].x)) + inside = !inside; + } + return inside; + } + + /// + /// Checks if p is inside the polygon (XZ space). + /// \author http://unifycommunity.com/wiki/index.php?title=PolyContainsPoint (Eric5h5) + /// + public static bool ContainsPointXZ (Vector3[] polyPoints, Vector3 p) { + int j = polyPoints.Length-1; + bool inside = false; + + for (int i = 0; i < polyPoints.Length; j = i++) { + if (((polyPoints[i].z <= p.z && p.z < polyPoints[j].z) || (polyPoints[j].z <= p.z && p.z < polyPoints[i].z)) && + (p.x < (polyPoints[j].x - polyPoints[i].x) * (p.z - polyPoints[i].z) / (polyPoints[j].z - polyPoints[i].z) + polyPoints[i].x)) + inside = !inside; + } + return inside; + } + + /// + /// Returns if the triangle contains the point p when projected on the movement plane. + /// The triangle vertices may be clockwise or counter-clockwise. + /// + /// This method is numerically robust, as in, if the point is contained in exactly one of two adjacent triangles, then this + /// function will return true for at least one of them (both if the point is exactly on the edge between them). + /// If it was less numerically robust, it could conceivably return false for both of them if the point was on the edge between them, which would be bad. + /// + [BurstCompile] + public static bool ContainsPoint (ref int3 aWorld, ref int3 bWorld, ref int3 cWorld, ref int3 pWorld, ref NativeMovementPlane movementPlane) { + // Extract the coordinate axes of the movement plane + var m = new float3x3(movementPlane.rotation.value); + var m2D = math.transpose(new float3x2(m.c0, m.c2)); + return ContainsPoint(ref aWorld, ref bWorld, ref cWorld, ref pWorld, in m2D); + } + + /// + /// Returns if the triangle contains the point p when projected on a plane using the given projection. + /// The triangle vertices may be clockwise or counter-clockwise. + /// + /// This method is numerically robust, as in, if the point is contained in exactly one of two adjacent triangles, then this + /// function will return true for at least one of them (both if the point is exactly on the edge between them). + /// If it was less numerically robust, it could conceivably return false for both of them if the point was on the edge between them, which would be bad. + /// + public static bool ContainsPoint (ref int3 aWorld, ref int3 bWorld, ref int3 cWorld, ref int3 pWorld, in float2x3 planeProjection) { + const int QUANTIZATION = 1024; + var m = new int2x3(planeProjection * QUANTIZATION); + // Project all the points onto the movement plane using SIMD + var xs = new int4(aWorld.x, bWorld.x, cWorld.x, pWorld.x); + var ys = new int4(aWorld.y, bWorld.y, cWorld.y, pWorld.y); + var zs = new int4(aWorld.z, bWorld.z, cWorld.z, pWorld.z); + // Subtract the first point from all the other points + // This ensures that large coordinates will not overflow due to using 32 bits here. + // Since we multiply all coordinates by QUANTIZATION, and Int3 coordinates are already multiplied by 1000, + // coordinates would otherwise be liable to start overflowing at unity world coordinates above around 2000. + // TODO: We could still get bad results if pWorld is very far away from the triangle (about 4000 units). + xs -= xs.x; + ys -= ys.x; + zs -= zs.x; + // Projected X and Y coordinates + var px = (xs * m.c0.x + ys * m.c1.x + zs * m.c2.x) / QUANTIZATION; + var py = (xs * m.c0.y + ys * m.c1.y + zs * m.c2.y) / QUANTIZATION; + + // Do 3 cross products to check if the point is inside the triangle + var v1 = px.yzx - px.xyz; + var v2 = py.www - py.xyz; + var v3 = px.www - px.xyz; + var v4 = py.yzx - py.xyz; + long check1 = (long)v1.x * (long)v2.x - (long)v3.x * (long)v4.x; + long check2 = (long)v1.y * (long)v2.y - (long)v3.y * (long)v4.y; + long check3 = (long)v1.z * (long)v2.z - (long)v3.z * (long)v4.z; + // Allow for both clockwise and counter-clockwise triangle layouts. + // This can be important sometimes on spherical worlds where the "upside-down" triangles + // will be seen as having the reverse winding order when projected onto a plane. + // We take care to include points right on the edge of the triangle. + return (check1 >= 0 & check2 >= 0 & check3 >= 0) | (check1 <= 0 & check2 <= 0 & check3 <= 0); + + // Note: It might be tempting to try to use SIMD-like code for this. But the following requires a lot more instructions, as it turns out. + // return math.all(new bool3(check1 >= 0, check2 >= 0, check3 >= 0)) || math.all(new bool3(check1 <= 0, check2 <= 0, check3 <= 0)); + } + + public struct BarycentricTriangleInterpolator { + int2 origin; + double2x2 barycentricMapping; + double3 thresholds, linear1, linear2, linear3, ys; + + public BarycentricTriangleInterpolator(Int3 p1, Int3 p2, Int3 p3) { + double signedTriangleAreaTimes2 = ((double)(p2.z - p3.z)) * (p1.x - p3.x) + ((double)(p3.x - p2.x)) * (p1.z - p3.z); + var d12 = new double2(p2.x - p1.x, p2.z - p1.z); + var d23 = new double2(p3.x - p2.x, p3.z - p2.z); + var d31 = new double2(p1.x - p3.x, p1.z - p3.z); + var l12 = math.lengthsq(d12); + var l23 = math.lengthsq(d23); + var l31 = math.lengthsq(d31); + + // When interpolating a point that is very close to the edge of the triangle (or even outside it) + // we snap it to the edge of the triangle. + // This is to improve robustness when there are two adjacent triangles: + // If the query point is almost exactly on the edge between them, we want + // SampleY to return the same y coordinate for both triangles. + // This may not be the case if using pure barycentric interpolation. + // Due to precision limitations, the integer coordinates may be slightly off + // from the precise mathematical edge of the triangle. + // A value of 1 seems to work fine in practice, but we use 2 for a bit of extra margin. + const double MaxEdgeSnappingDistance = 2; + // Thresholds for the barycentric coordinates. If they are smaller than this, then the point is very close to the edge of the triangle. + // This follows from https://en.wikipedia.org/wiki/Barycentric_coordinate_system#Barycentric_coordinates_on_triangles + // For barycentric coordinates (b1,b2,b3) it holds that + // b1 = area(p2,p3,p) / area(p1,p2,p3) + // = (distanceToLine(p2,p3,p)*length(p2,p3)/2) / area(p1,p2,p3) (since a triangle's area = base*height/2) + // = distanceToLine(p2,p3,p) * length(p2,p3) / (2*area(p1,p2,p3)) + // If we want to check if distanceToLine is smaller than MaxEdgeSnappingDistance, we get the threshold below + // for all the barycentric coordinates. + thresholds = math.sqrt(new double3(l23, l31, l12)) * (MaxEdgeSnappingDistance / math.abs(signedTriangleAreaTimes2)); + origin = new int2(p3.x, p3.z); + + // Linear mapping from 2D space (relative to origin) to barycentric coordinates + barycentricMapping = new double2x2( + -d23.y, d23.x, + -d31.y, d31.x + ) / signedTriangleAreaTimes2; + + ys = new double3(p1.y, p2.y, p3.y); + + // Creates a mapping m(p) from 2D projective space (x,z,1) relative to the origin, to a y coordinate for the point on the line a-b that is closest to (x,z). + double3 ProjectPointOnLine (Int3 a, Int3 b, double lengthSq, int2 origin) { + // m1(p) = dot((p + origin) - a, b - a) / lengthSq; + // = (dot(p, b - a) + dot(origin - a, b - a)) / lengthSq; + var m1 = new double3( + b.x - a.x, + b.z - a.z, + (double)(origin.x - a.x) * (b.x - a.x) + (double)(origin.y - a.z) * (b.z - a.z) + ) / lengthSq; + // m2(p) = lerp(a, b, m1(p)) = a + m1(p) * (b - a) + var m2 = m1 * (b.y - a.y) + new double3(0, 0, a.y); + return m2; + } + + linear1 = ProjectPointOnLine(p2, p3, l23, origin); + linear2 = ProjectPointOnLine(p3, p1, l31, origin); + linear3 = ProjectPointOnLine(p1, p2, l12, origin); + } + + public int SampleY (int2 p) { + p -= origin; + + double2 lambdas = math.mul(this.barycentricMapping, new double2(p.x, p.y)); + double3 lambdas2 = new double3(lambdas.x, lambdas.y, 1 - lambdas.x - lambdas.y); + + if (lambdas2.x < thresholds.x) { + // Interpolate only along the edge p2-p3 for numerical stability + return (int)math.round(math.dot(linear1, new double3(p.x, p.y, 1))); + } + if (lambdas2.y < thresholds.y) { + // Interpolate only along the edge p3-p1 for numerical stability + return (int)math.round(math.dot(linear2, new double3(p.x, p.y, 1))); + } + if (lambdas2.z < thresholds.z) { + // Interpolate only along the edge p1-p2 for numerical stability + return (int)math.round(math.dot(linear3, new double3(p.x, p.y, 1))); + } + return (int)math.round(math.dot(ys, lambdas2)); + } + } + + /// + /// Calculates convex hull in XZ space for the points. + /// Implemented using the very simple Gift Wrapping Algorithm + /// which has a complexity of O(nh) where n is the number of points and h is the number of points on the hull, + /// so it is in the worst case quadratic. + /// + public static Vector3[] ConvexHullXZ (Vector3[] points) { + if (points.Length == 0) return new Vector3[0]; + + var hull = Pathfinding.Pooling.ListPool.Claim(); + + int pointOnHull = 0; + for (int i = 1; i < points.Length; i++) if (points[i].x < points[pointOnHull].x) pointOnHull = i; + + int startpoint = pointOnHull; + int counter = 0; + + do { + hull.Add(points[pointOnHull]); + int endpoint = 0; + for (int i = 0; i < points.Length; i++) if (endpoint == pointOnHull || !VectorMath.RightOrColinearXZ(points[pointOnHull], points[endpoint], points[i])) endpoint = i; + + pointOnHull = endpoint; + + counter++; + if (counter > 10000) { + Debug.LogWarning("Infinite Loop in Convex Hull Calculation"); + break; + } + } while (pointOnHull != startpoint); + + var result = hull.ToArray(); + + // Return to pool + Pathfinding.Pooling.ListPool.Release(hull); + return result; + } + + /// + /// Closest point on the triangle abc to the point p. + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + public static Vector2 ClosestPointOnTriangle (Vector2 a, Vector2 b, Vector2 c, Vector2 p) { + // Check if p is in vertex region outside A + var ab = b - a; + var ac = c - a; + var ap = p - a; + + var d1 = Vector2.Dot(ab, ap); + var d2 = Vector2.Dot(ac, ap); + + // Barycentric coordinates (1,0,0) + if (d1 <= 0 && d2 <= 0) { + return a; + } + + // Check if p is in vertex region outside B + var bp = p - b; + var d3 = Vector2.Dot(ab, bp); + var d4 = Vector2.Dot(ac, bp); + + // Barycentric coordinates (0,1,0) + if (d3 >= 0 && d4 <= d3) { + return b; + } + + // Check if p is in edge region outside AB, if so return a projection of p onto AB + if (d1 >= 0 && d3 <= 0) { + var vc = d1 * d4 - d3 * d2; + if (vc <= 0) { + // Barycentric coordinates (1-v, v, 0) + var v = d1 / (d1 - d3); + return a + ab*v; + } + } + + // Check if p is in vertex region outside C + var cp = p - c; + var d5 = Vector2.Dot(ab, cp); + var d6 = Vector2.Dot(ac, cp); + + // Barycentric coordinates (0,0,1) + if (d6 >= 0 && d5 <= d6) { + return c; + } + + // Check if p is in edge region of AC, if so return a projection of p onto AC + if (d2 >= 0 && d6 <= 0) { + var vb = d5 * d2 - d1 * d6; + if (vb <= 0) { + // Barycentric coordinates (1-v, 0, v) + var v = d2 / (d2 - d6); + return a + ac*v; + } + } + + // Check if p is in edge region of BC, if so return projection of p onto BC + if ((d4 - d3) >= 0 && (d5 - d6) >= 0) { + var va = d3 * d6 - d5 * d4; + if (va <= 0) { + var v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + (c - b) * v; + } + } + + return p; + } + + /// + /// Closest point on the triangle abc to the point p when seen from above. + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + public static Vector3 ClosestPointOnTriangleXZ (Vector3 a, Vector3 b, Vector3 c, Vector3 p) { + // Check if p is in vertex region outside A + var ab = new Vector2(b.x - a.x, b.z - a.z); + var ac = new Vector2(c.x - a.x, c.z - a.z); + var ap = new Vector2(p.x - a.x, p.z - a.z); + + var d1 = Vector2.Dot(ab, ap); + var d2 = Vector2.Dot(ac, ap); + + // Barycentric coordinates (1,0,0) + if (d1 <= 0 && d2 <= 0) { + return a; + } + + // Check if p is in vertex region outside B + var bp = new Vector2(p.x - b.x, p.z - b.z); + var d3 = Vector2.Dot(ab, bp); + var d4 = Vector2.Dot(ac, bp); + + // Barycentric coordinates (0,1,0) + if (d3 >= 0 && d4 <= d3) { + return b; + } + + // Check if p is in edge region outside AB, if so return a projection of p onto AB + var vc = d1 * d4 - d3 * d2; + if (d1 >= 0 && d3 <= 0 && vc <= 0) { + // Barycentric coordinates (1-v, v, 0) + var v = d1 / (d1 - d3); + return (1-v)*a + v*b; + } + + // Check if p is in vertex region outside C + var cp = new Vector2(p.x - c.x, p.z - c.z); + var d5 = Vector2.Dot(ab, cp); + var d6 = Vector2.Dot(ac, cp); + + // Barycentric coordinates (0,0,1) + if (d6 >= 0 && d5 <= d6) { + return c; + } + + // Check if p is in edge region of AC, if so return a projection of p onto AC + var vb = d5 * d2 - d1 * d6; + if (d2 >= 0 && d6 <= 0 && vb <= 0) { + // Barycentric coordinates (1-v, 0, v) + var v = d2 / (d2 - d6); + return (1-v)*a + v*c; + } + + // Check if p is in edge region of BC, if so return projection of p onto BC + var va = d3 * d6 - d5 * d4; + if ((d4 - d3) >= 0 && (d5 - d6) >= 0 && va <= 0) { + var v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + (c - b) * v; + } else { + // P is inside the face region. Compute the point using its barycentric coordinates (u, v, w) + // Note that the x and z coordinates will be exactly the same as P's x and z coordinates + var denom = 1f / (va + vb + vc); + var v = vb * denom; + var w = vc * denom; + + return new Vector3(p.x, (1 - v - w)*a.y + v*b.y + w*c.y, p.z); + } + } + + /// + /// Closest point on the triangle abc to the point p. + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + public static float3 ClosestPointOnTriangle (float3 a, float3 b, float3 c, float3 p) { + ClosestPointOnTriangleByRef(in a, in b, in c, in p, out var output); + return output; + } + + /// + /// Closest point on the triangle abc to the point p. + /// + /// Takes arguments by reference to be able to be burst-compiled. + /// + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + /// Returns: True if the point is inside the triangle, false otherwise, after the point has been projected on the plane that the triangle is in. + /// + [BurstCompile] + public static bool ClosestPointOnTriangleByRef (in float3 a, in float3 b, in float3 c, in float3 p, [NoAlias] out float3 output) { + // Check if p is in vertex region outside A + var ab = b - a; + var ac = c - a; + var ap = p - a; + + var d1 = math.dot(ab, ap); + var d2 = math.dot(ac, ap); + + // Barycentric coordinates (1,0,0) + if (d1 <= 0 && d2 <= 0) { + output = a; + return false; + } + + // Check if p is in vertex region outside B + var bp = p - b; + var d3 = math.dot(ab, bp); + var d4 = math.dot(ac, bp); + + // Barycentric coordinates (0,1,0) + if (d3 >= 0 && d4 <= d3) { + output = b; + return false; + } + + // Check if p is in edge region outside AB, if so return a projection of p onto AB + var vc = d1 * d4 - d3 * d2; + if (d1 >= 0 && d3 <= 0 && vc <= 0) { + // Barycentric coordinates (1-v, v, 0) + var v = d1 / (d1 - d3); + output = a + ab * v; + return false; + } + + // Check if p is in vertex region outside C + var cp = p - c; + var d5 = math.dot(ab, cp); + var d6 = math.dot(ac, cp); + + // Barycentric coordinates (0,0,1) + if (d6 >= 0 && d5 <= d6) { + output = c; + return false; + } + + // Check if p is in edge region of AC, if so return a projection of p onto AC + var vb = d5 * d2 - d1 * d6; + if (d2 >= 0 && d6 <= 0 && vb <= 0) { + // Barycentric coordinates (1-v, 0, v) + var v = d2 / (d2 - d6); + output = a + ac * v; + return false; + } + + // Check if p is in edge region of BC, if so return projection of p onto BC + var va = d3 * d6 - d5 * d4; + if ((d4 - d3) >= 0 && (d5 - d6) >= 0 && va <= 0) { + var v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + output = b + (c - b) * v; + return false; + } else { + // P is inside the face region. Compute the point using its barycentric coordinates (u, v, w) + var denom = 1f / (va + vb + vc); + var v = vb * denom; + var w = vc * denom; + + // This is equal to: u*a + v*b + w*c, u = va*denom = 1 - v - w; + output = a + ab * v + ac * w; + return true; + } + } + + /// + /// Closest point on the triangle abc to the point p as barycentric coordinates. + /// + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + public static float3 ClosestPointOnTriangleBarycentric (float2 a, float2 b, float2 c, float2 p) { + // Check if p is in vertex region outside A + var ab = b - a; + var ac = c - a; + var ap = p - a; + + var d1 = math.dot(ab, ap); + var d2 = math.dot(ac, ap); + + // Barycentric coordinates (1,0,0) + if (d1 <= 0 && d2 <= 0) { + return new float3(1, 0, 0); + } + + // Check if p is in vertex region outside B + var bp = p - b; + var d3 = math.dot(ab, bp); + var d4 = math.dot(ac, bp); + + // Barycentric coordinates (0,1,0) + if (d3 >= 0 && d4 <= d3) { + return new float3(0, 1, 0); + } + + // Check if p is in edge region outside AB, if so return a projection of p onto AB + var vc = d1 * d4 - d3 * d2; + if (d1 >= 0 && d3 <= 0 && vc <= 0) { + // Barycentric coordinates (1-v, v, 0) + var v = d1 / (d1 - d3); + return new float3(1-v, v, 0); + } + + // Check if p is in vertex region outside C + var cp = p - c; + var d5 = math.dot(ab, cp); + var d6 = math.dot(ac, cp); + + // Barycentric coordinates (0,0,1) + if (d6 >= 0 && d5 <= d6) { + return new float3(0, 0, 1); + } + + // Check if p is in edge region of AC, if so return a projection of p onto AC + var vb = d5 * d2 - d1 * d6; + if (d2 >= 0 && d6 <= 0 && vb <= 0) { + // Barycentric coordinates (1-v, 0, v) + var v = d2 / (d2 - d6); + return new float3(1 - v, 0, v); + } + + // Check if p is in edge region of BC, if so return projection of p onto BC + var va = d3 * d6 - d5 * d4; + if ((d4 - d3) >= 0 && (d5 - d6) >= 0 && va <= 0) { + var v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return new float3(0, 1 - v, v); + } else { + // P is inside the face region. Compute the point using its barycentric coordinates (u, v, w) + var denom = 1f / (va + vb + vc); + var v = vb * denom; + var w = vc * denom; + return new float3(1 - v - w, v, w); + + // This is equal to: u*a + v*b + w*c, u = va*denom = 1 - v - w; + // return a + ab * v + ac * w; + } + } + + /// + /// Closest point on the triangle abc to the point p as barycentric coordinates. + /// + /// See: 'Real Time Collision Detection' by Christer Ericson, chapter 5.1, page 141 + /// + public static float3 ClosestPointOnTriangleBarycentric (float3 a, float3 b, float3 c, float3 p) { + // Check if p is in vertex region outside A + var ab = b - a; + var ac = c - a; + var ap = p - a; + + var d1 = math.dot(ab, ap); + var d2 = math.dot(ac, ap); + + // Barycentric coordinates (1,0,0) + if (d1 <= 0 && d2 <= 0) { + return new float3(1, 0, 0); + } + + // Check if p is in vertex region outside B + var bp = p - b; + var d3 = math.dot(ab, bp); + var d4 = math.dot(ac, bp); + + // Barycentric coordinates (0,1,0) + if (d3 >= 0 && d4 <= d3) { + return new float3(0, 1, 0); + } + + // Check if p is in edge region outside AB, if so return a projection of p onto AB + var vc = d1 * d4 - d3 * d2; + if (d1 >= 0 && d3 <= 0 && vc <= 0) { + // Barycentric coordinates (1-v, v, 0) + var v = d1 / (d1 - d3); + return new float3(1-v, v, 0); + } + + // Check if p is in vertex region outside C + var cp = p - c; + var d5 = math.dot(ab, cp); + var d6 = math.dot(ac, cp); + + // Barycentric coordinates (0,0,1) + if (d6 >= 0 && d5 <= d6) { + return new float3(0, 0, 1); + } + + // Check if p is in edge region of AC, if so return a projection of p onto AC + var vb = d5 * d2 - d1 * d6; + if (d2 >= 0 && d6 <= 0 && vb <= 0) { + // Barycentric coordinates (1-v, 0, v) + var v = d2 / (d2 - d6); + return new float3(1 - v, 0, v); + } + + // Check if p is in edge region of BC, if so return projection of p onto BC + var va = d3 * d6 - d5 * d4; + if ((d4 - d3) >= 0 && (d5 - d6) >= 0 && va <= 0) { + var v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return new float3(0, 1 - v, v); + } else { + // P is inside the face region. Compute the point using its barycentric coordinates (u, v, w) + var denom = 1f / (va + vb + vc); + var v = vb * denom; + var w = vc * denom; + return new float3(1 - v - w, v, w); + + // This is equal to: u*a + v*b + w*c, u = va*denom = 1 - v - w; + // return a + ab * v + ac * w; + } + } + + /// + /// Closest point on a triangle when one axis is scaled. + /// + /// Project the triangle onto the plane defined by the projection axis. + /// Then find the closest point on the triangle in the plane. + /// Calculate the distance to the closest point in the plane, call that D1. + /// Convert the closest point into 3D space, and calculate the distance to the + /// query point along the plane's normal, call that D2. + /// The final cost for a given point is D1 + D2 * distanceScaleAlongProjectionDirection. + /// + /// This will form a diamond shape of equivalent cost points around the query point (x). + /// The ratio of the width of this diamond to the height is equal to distanceScaleAlongProjectionDirection. + /// + /// ^ + /// / \ + /// / \ + /// / x \ + /// \ / + /// \ / + /// \ / + /// v + /// + /// See: + /// + /// First vertex of the triangle, in graph space. + /// Second vertex of the triangle, in graph space. + /// Third vertex of the triangle, in graph space. + /// Projection parameters that are for example constructed from a movement plane. + /// Point to find the closest point to. + /// Closest point on the triangle to the point. + /// Squared cost from the point to the closest point on the triangle. + /// Distance from the point to the closest point on the triangle along the projection axis. + [BurstCompile] + public static void ClosestPointOnTriangleProjected (ref Int3 vi1, ref Int3 vi2, ref Int3 vi3, ref BBTree.ProjectionParams projection, ref float3 point, [NoAlias] out float3 closest, [NoAlias] out float sqrDist, [NoAlias] out float distAlongProjection) { + var v1 = (float3)vi1; + var v2 = (float3)vi2; + var v3 = (float3)vi3; + var v1proj = math.mul(projection.planeProjection, v1); + var v2proj = math.mul(projection.planeProjection, v2); + var v3proj = math.mul(projection.planeProjection, v3); + // TODO: Can be cached + var pointProj = math.mul(projection.planeProjection, point); + var closestBarycentric = ClosestPointOnTriangleBarycentric(v1proj, v2proj, v3proj, pointProj); + closest = v1*closestBarycentric.x + v2*closestBarycentric.y + v3*closestBarycentric.z; + var closestProj = v1proj*closestBarycentric.x + v2proj*closestBarycentric.y + v3proj*closestBarycentric.z; + distAlongProjection = math.abs(math.dot(closest - point, projection.projectionAxis)); + var distInPlane = math.length(closestProj - pointProj); + if (distInPlane < 0.01f) { + // If we are very close to being inside the triangle, + // check if we are actually inside the triangle using a more numerically robust method. + // If we are, set the in-plane-distance to 0. + // This is particularly important if distanceScaleAlongProjectionAxis is zero, + // as otherwise tie breaking may not work due to numerical issues. + var ci1 = (int3)vi1; + var ci2 = (int3)vi2; + var ci3 = (int3)vi3; + // wow, ugly + var pi = (int3)(Int3)(Vector3)point; + if (ContainsPoint(ref ci1, ref ci2, ref ci3, ref pi, in projection.planeProjection)) { + distInPlane = 0; + } + } + var dist = distInPlane + distAlongProjection*projection.distanceScaleAlongProjectionAxis; + sqrDist = dist*dist; + } + + /// Cached dictionary to avoid excessive allocations + static readonly Dictionary cached_Int3_int_dict = new Dictionary(); + + /// + /// Compress the mesh by removing duplicate vertices. + /// + /// Vertices that differ by only 1 along the y coordinate will also be merged together. + /// Warning: This function is not threadsafe. It uses some cached structures to reduce allocations. + /// + /// Vertices of the input mesh + /// Triangles of the input mesh + /// Tags of the input mesh. One for each triangle. + /// Vertices of the output mesh. + /// Triangles of the output mesh. + /// Tags of the output mesh. One for each triangle. + public static void CompressMesh (List vertices, List triangles, List tags, out Int3[] outVertices, out int[] outTriangles, out uint[] outTags) { + Dictionary firstVerts = cached_Int3_int_dict; + + firstVerts.Clear(); + + // Use cached array to reduce memory allocations + int[] compressedPointers = ArrayPool.Claim(vertices.Count); + + // Map positions to the first index they were encountered at + int count = 0; + for (int i = 0; i < vertices.Count; i++) { + // Check if the vertex position has already been added + // Also check one position up and one down because rounding errors can cause vertices + // that should end up in the same position to be offset 1 unit from each other + // TODO: Check along X and Z axes as well? + int ind; + if (!firstVerts.TryGetValue(vertices[i], out ind) && !firstVerts.TryGetValue(vertices[i] + new Int3(0, 1, 0), out ind) && !firstVerts.TryGetValue(vertices[i] + new Int3(0, -1, 0), out ind)) { + firstVerts.Add(vertices[i], count); + compressedPointers[i] = count; + vertices[count] = vertices[i]; + count++; + } else { + compressedPointers[i] = ind; + } + } + + // Create the triangle array or reuse the existing buffer + outTriangles = new int[triangles.Count]; + + // Remap the triangles to the new compressed indices + for (int i = 0; i < outTriangles.Length; i++) { + outTriangles[i] = compressedPointers[triangles[i]]; + } + + // Create the vertex array or reuse the existing buffer + outVertices = new Int3[count]; + + for (int i = 0; i < count; i++) + outVertices[i] = vertices[i]; + + ArrayPool.Release(ref compressedPointers); + + outTags = tags.ToArray(); + } + + /// + /// Given a set of edges between vertices, follows those edges and returns them as chains and cycles. + /// + /// [Open online documentation to see images] + /// + /// outline[a] = b if there is an edge from a to b. + /// hasInEdge should contain b if outline[a] = b for any key a. + /// Will be called once for each contour with the contour as a parameter as well as a boolean indicating if the contour is a cycle or a chain (see image). + public static void TraceContours (Dictionary outline, HashSet hasInEdge, System.Action, bool> results) { + // Iterate through chains of the navmesh outline. + // I.e segments of the outline that are not loops + // we need to start these at the beginning of the chain. + // Then iterate over all the loops of the outline. + // Since they are loops, we can start at any point. + var obstacleVertices = ListPool.Claim(); + var outlineKeys = ListPool.Claim(); + + outlineKeys.AddRange(outline.Keys); + for (int k = 0; k <= 1; k++) { + bool cycles = k == 1; + for (int i = 0; i < outlineKeys.Count; i++) { + var startIndex = outlineKeys[i]; + + // Chains (not cycles) need to start at the start of the chain + // Cycles can start at any point + if (!cycles && hasInEdge.Contains(startIndex)) { + continue; + } + + var index = startIndex; + obstacleVertices.Clear(); + obstacleVertices.Add(index); + + while (outline.ContainsKey(index)) { + var next = outline[index]; + outline.Remove(index); + + obstacleVertices.Add(next); + + // We traversed a full cycle + if (next == startIndex) break; + + index = next; + } + + if (obstacleVertices.Count > 1) { + results(obstacleVertices, cycles); + } + } + } + + ListPool.Release(ref outlineKeys); + ListPool.Release(ref obstacleVertices); + } + + /// Divides each segment in the list into subSegments segments and fills the result list with the new points + public static void Subdivide (List points, List result, int subSegments) { + for (int i = 0; i < points.Count-1; i++) + for (int j = 0; j < subSegments; j++) + result.Add(Vector3.Lerp(points[i], points[i+1], j / (float)subSegments)); + + result.Add(points[points.Count-1]); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs.meta new file mode 100644 index 0000000..fe1bca0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarMath.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 960fd9020b1f74f939fee737c3c0f491 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs new file mode 100644 index 0000000..c818602 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs @@ -0,0 +1,2626 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using Pathfinding; +using Pathfinding.Drawing; +using UnityEngine.Profiling; +using Pathfinding.Util; +using Pathfinding.Graphs.Navmesh; +using Pathfinding.Graphs.Util; +using Pathfinding.Jobs; +using Pathfinding.Collections; +using Pathfinding.Sync; +using Unity.Jobs; + +#if NETFX_CORE +using Thread = Pathfinding.WindowsStore.Thread; +#else +using Thread = System.Threading.Thread; +#endif + +[ExecuteInEditMode] +[AddComponentMenu("Pathfinding/AstarPath")] +[DisallowMultipleComponent] +/// +/// Core component for the A* Pathfinding System. +/// This class handles all of the pathfinding system, calculates all paths and stores the info. +/// This class is a singleton class, meaning there should only exist at most one active instance of it in the scene. +/// +/// See: inspector (view in online documentation for working links) +/// See: calling-directly (view in online documentation for working links) +/// +[HelpURL("https://arongranberg.com/astar/documentation/stable/astarpath.html")] +public class AstarPath : VersionedMonoBehaviour { + /// The version number for the A* Pathfinding Project + public static readonly System.Version Version = new System.Version(5, 4, 6); + + /// Information about where the package was downloaded + public enum AstarDistribution { WebsiteDownload, AssetStore, PackageManager }; + + /// Used by the editor to guide the user to the correct place to download updates + public static readonly AstarDistribution Distribution = AstarDistribution.AssetStore; + + /// + /// Which branch of the A* Pathfinding Project is this release. + /// Used when checking for updates so that users of the development + /// versions can get notifications of development updates. + /// + public static readonly string Branch = "master"; + + /// Holds all graph data + [UnityEngine.Serialization.FormerlySerializedAs("astarData")] + public AstarData data; + + /// + /// Returns the active AstarPath object in the scene. + /// Note: This is only set if the AstarPath object has been initialized (which happens in Awake). + /// + public static AstarPath active; + + /// Shortcut to + public NavGraph[] graphs => data.graphs; + + bool hasScannedGraphAtStartup = false; + + #region InspectorDebug + /// + /// Visualize graphs in the scene view (editor only). + /// [Open online documentation to see images] + /// + /// See: + /// + public bool showNavGraphs = true; + + /// + /// Toggle to show unwalkable nodes. + /// + /// Note: Only relevant in the editor + /// + /// See: + /// + public bool showUnwalkableNodes = true; + + /// + /// The mode to use for drawing nodes in the sceneview. + /// + /// Note: Only relevant in the editor + /// + /// See: + /// + public GraphDebugMode debugMode; + + /// + /// How to visualize graph updates in the editor. + /// This is used to visualize the bounds of graph updates, and how they affect the graph. + /// + /// If is enabled, the visualization will also be shown in a standalone player. + /// + /// [Open online documentation to see images] + /// [Open online documentation to see images] + /// + /// See: for more info about the different modes. + /// + public GraphUpdateDebugMode graphUpdateDebugMode = GraphUpdateDebugMode.Nothing; + + /// + /// Low value to use for certain modes. + /// For example if is set to G, this value will determine when the node will be completely red. + /// + /// Note: Only relevant in the editor + /// + /// See: + /// See: + /// + public float debugFloor = 0; + + /// + /// High value to use for certain modes. + /// For example if is set to G, this value will determine when the node will be completely green. + /// + /// For the penalty debug mode, the nodes will be colored green when they have a penalty less than and red + /// when their penalty is greater or equal to this value and something between red and green otherwise. + /// + /// Note: Only relevant in the editor + /// + /// See: + /// See: + /// + public float debugRoof = 20000; + + /// + /// If set, the and values will not be automatically recalculated. + /// + /// Note: Only relevant in the editor + /// + public bool manualDebugFloorRoof = false; + + + /// + /// If enabled, nodes will draw a line to their 'parent'. + /// This will show the search tree for the latest path. + /// + /// Note: Only relevant in the editor + /// + public bool showSearchTree = false; + + /// + /// Size of the red cubes shown in place of unwalkable nodes. + /// + /// Note: Only relevant in the editor. Does not apply to grid graphs. + /// See: + /// + public float unwalkableNodeDebugSize = 0.3F; + + /// + /// The amount of debugging messages. + /// Use less debugging to improve performance (a bit) or just to get rid of the Console spamming. + /// Use more debugging (heavy) if you want more information about what the pathfinding scripts are doing. + /// The InGame option will display the latest path log using in-game GUI. + /// + /// [Open online documentation to see images] + /// + public PathLog logPathResults = PathLog.Normal; + + #endregion + + #region InspectorSettings + /// + /// Maximum distance to search for nodes. + /// When searching for the nearest node to a point, this is the limit (in world units) for how far away it is allowed to be. + /// + /// This is relevant if you try to request a path to a point that cannot be reached and it thus has to search for + /// the closest node to that point which can be reached (which might be far away). If it cannot find a node within this distance + /// then the path will fail. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + public float maxNearestNodeDistance = 100; + + /// + /// Max Nearest Node Distance Squared. + /// See: + /// + public float maxNearestNodeDistanceSqr => maxNearestNodeDistance*maxNearestNodeDistance; + + /// + /// If true, all graphs will be scanned when the game starts, during OnEnable. + /// If you disable this, you will have to call yourself to enable pathfinding. + /// Alternatively you could load a saved graph from a file. + /// + /// If a startup cache has been generated (see save-load-graphs) (view in online documentation for working links), it always takes priority, and the graphs will be loaded from the cache instead of scanned. + /// + /// This can be useful to disable if you want to scan your graphs asynchronously, or if you have a procedural world which has not been created yet + /// at the start of the game. + /// + /// See: + /// See: + /// + public bool scanOnStartup = true; + + /// + /// Do a full GetNearest search for all graphs. + /// Additional searches will normally only be done on the graph which in the first fast search seemed to have the closest node. + /// With this setting on, additional searches will be done on all graphs since the first check is not always completely accurate. + /// More technically: GetNearestForce on all graphs will be called if true, otherwise only on the one graph which's GetNearest search returned the best node. + /// Usually faster when disabled, but higher quality searches when enabled. + /// Note: For the PointGraph this setting doesn't matter much as it has only one search mode. + /// + [System.Obsolete("This setting has been removed. It is now always true", true)] + public bool fullGetNearestSearch = false; + + /// + /// Prioritize graphs. + /// Graphs will be prioritized based on their order in the inspector. + /// The first graph which has a node closer than will be chosen instead of searching all graphs. + /// + /// Deprecated: This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched. + /// + [System.Obsolete("This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.", true)] + public bool prioritizeGraphs = false; + + /// + /// Distance limit for . + /// See: + /// + /// Deprecated: This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched. + /// + [System.Obsolete("This setting has been removed. It was always a bit of a hack. Use TraversalConstraint.graphMask if you want to choose which graphs are searched.", true)] + public float prioritizeGraphsLimit = 1F; + + /// + /// Reference to the color settings for this AstarPath object. + /// Color settings include for example which color the nodes should be in, in the sceneview. + /// + public AstarColor colorSettings; + + /// + /// Stored tag names. + /// See: AstarPath.FindTagNames + /// See: AstarPath.GetTagNames + /// + [SerializeField] + protected string[] tagNames = null; + + /// + /// The distance function to use as a heuristic. + /// The heuristic, often referred to as just 'H' is the estimated cost from a node to the target. + /// Different heuristics affect how the path picks which one to follow from multiple possible with the same length + /// See: for more details and descriptions of the different modes. + /// See: Wikipedia: Admissible heuristic + /// See: Wikipedia: A* search algorithm + /// See: Wikipedia: Dijkstra's Algorithm + /// + /// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs. + /// + public Heuristic heuristic = Heuristic.Euclidean; + + /// + /// The scale of the heuristic. + /// If a value lower than 1 is used, the pathfinder will search more nodes (slower). + /// If 0 is used, the pathfinding algorithm will be reduced to dijkstra's algorithm. This is equivalent to setting to None. + /// If a value larger than 1 is used the pathfinding will (usually) be faster because it expands fewer nodes, but the paths may no longer be the optimal (i.e the shortest possible paths). + /// + /// Usually you should leave this to the default value of 1. + /// + /// Warning: Reducing the heuristic scale below 1, or disabling the heuristic, can significantly increase the cpu cost for pathfinding, especially for large graphs. + /// + /// See: Wikipedia: Admissible heuristic + /// See: Wikipedia: A* search algorithm + /// See: Wikipedia: Dijkstra's Algorithm + /// + public float heuristicScale = 1F; + + /// + /// The effective heuristic scale. + /// This is the heuristic scale used for pathfinding. Zero is returned if is set to None, otherwise is returned. + /// See: + /// + public float effectiveHeuristicScale => heuristic == Heuristic.None ? 0 : heuristicScale; + + /// + /// Number of pathfinding threads to use. + /// Multithreading puts pathfinding in another thread, this is great for performance on 2+ core computers since the framerate will barely be affected by the pathfinding at all. + /// - None indicates that the pathfinding is run in the Unity thread as a coroutine + /// - Automatic will try to adjust the number of threads to the number of cores and memory on the computer. + /// Less than 512mb of memory or a single core computer will make it revert to using no multithreading. + /// + /// It is recommended that you use one of the "Auto" settings that are available. + /// The reason is that even if your computer might be beefy and have 8 cores. + /// Other computers might only be quad core or dual core in which case they will not benefit from more than + /// 1 or 3 threads respectively (you usually want to leave one core for the unity thread). + /// If you use more threads than the number of cores on the computer it is mostly just wasting memory, it will not run any faster. + /// The extra memory usage is not trivially small. Each thread needs to keep a small amount of data for each node in all the graphs. + /// It is not the full graph data but it is proportional to the number of nodes. + /// The automatic settings will inspect the machine it is running on and use that to determine the number of threads so that no memory is wasted. + /// + /// The exception is if you only have one (or maybe two characters) active at time. Then you should probably just go with one thread always since it is very unlikely + /// that you will need the extra throughput given by more threads. Keep in mind that more threads primarily increases throughput by calculating different paths on different + /// threads, it will not calculate individual paths any faster. + /// + /// Warning: If you are modifying the pathfinding core scripts or if you are directly modifying graph data without using any of the + /// safe wrappers (like , multithreading can cause strange errors and cause pathfinding to stop working if you are not careful. + /// + /// Note: WebGL does not support threads at all (since javascript is single-threaded) so no threads will be used on that platform. + /// + /// Note: This setting only applies to pathfinding. Graph updates use the Unity Job System, which uses a different thread pool. + /// + /// See: CalculateThreadCount + /// + public ThreadCount threadCount = ThreadCount.One; + + /// + /// Max number of milliseconds to spend on pathfinding during each frame. + /// At least 500 nodes will be searched each frame (if there are that many to search). + /// When using multithreading this value is irrelevant. + /// + public float maxFrameTime = 1F; + + /// + /// Throttle graph updates and batch them to improve performance. + /// If toggled, graph updates will batched and executed less often (specified by . + /// + /// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need + /// to be stopped as often, and it reduces the overhead per graph update. + /// All graph updates are still applied, they are just batched together so that more of them are + /// applied at the same time. + /// + /// Do not use this if you want minimal latency between a graph update being requested + /// and it being applied. + /// + /// This only applies to graph updates requested using the method. Not those requested + /// using . + /// + /// If you want to apply graph updates immediately at some point, you can call . + /// + /// See: graph-updates (view in online documentation for working links) + /// + public bool batchGraphUpdates = false; + + /// + /// Minimum number of seconds between each batch of graph updates. + /// If is true, this defines the minimum number of seconds between each batch of graph updates. + /// + /// This can have a positive impact on pathfinding throughput since the pathfinding threads do not need + /// to be stopped as often, and it reduces the overhead per graph update. + /// All graph updates are still applied however, they are just batched together so that more of them are + /// applied at the same time. + /// + /// Do not use this if you want minimal latency between a graph update being requested + /// and it being applied. + /// + /// This only applies to graph updates requested using the method. Not those requested + /// using . + /// + /// See: graph-updates (view in online documentation for working links) + /// + public float graphUpdateBatchingInterval = 0.2F; + + #endregion + + #region DebugVariables +#if ProfileAstar + /// + /// How many paths has been computed this run. From application start. + /// Debugging variable + /// + public static int PathsCompleted = 0; + + public static System.Int64 TotalSearchedNodes = 0; + public static System.Int64 TotalSearchTime = 0; +#endif + + /// The time it took for the last call to to complete + public float lastScanTime { get; private set; } + + /// + /// The path to debug using gizmos. + /// This is the path handler used to calculate the last path. + /// It is used in the editor to draw debug information using gizmos. + /// + [System.NonSerialized] + internal PathHandler debugPathData; + + /// The path ID to debug using gizmos + [System.NonSerialized] + internal ushort debugPathID; + + /// + /// Debug string from the last completed path. + /// Will be updated if == PathLog.InGame + /// + string inGameDebugPath; + + #endregion + + #region StatusVariables + + /// + /// True while any graphs are being scanned. + /// + /// This is primarily relevant when scanning graph asynchronously. + /// + /// Note: Not to be confused with graph updates. + /// + /// Note: This will be false during and during the .LatePostScan event. + /// + /// See: IsAnyGraphUpdateQueued + /// See: IsAnyGraphUpdateInProgress + /// + [field: System.NonSerialized] + public bool isScanning { get; private set; } + + /// + /// Number of parallel pathfinders. + /// Returns the number of concurrent processes which can calculate paths at once. + /// When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used). + /// See: IsUsingMultithreading + /// + public int NumParallelThreads => pathProcessor.NumThreads; + + /// + /// Returns whether or not multithreading is used. + /// \exception System.Exception Is thrown when it could not be decided if multithreading was used or not. + /// This should not happen if pathfinding is set up correctly. + /// Note: This uses info about if threads are running right now, it does not use info from the settings on the A* object. + /// + public bool IsUsingMultithreading => pathProcessor.IsUsingMultithreading; + + /// + /// Returns if any graph updates are waiting to be applied. + /// Note: This is false while the updates are being performed. + /// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by . + /// + public bool IsAnyGraphUpdateQueued => graphUpdates.IsAnyGraphUpdateQueued; + + /// + /// Returns if any graph updates are being calculated right now. + /// Note: This does *not* includes other types of work items such as navmesh cutting or anything added by . + /// + /// See: IsAnyWorkItemInProgress + /// + public bool IsAnyGraphUpdateInProgress => graphUpdates.IsAnyGraphUpdateInProgress; + + /// + /// Returns if any work items are in progress right now. + /// Note: This includes pretty much all types of graph updates. + /// Such as normal graph updates, navmesh cutting and anything added by . + /// + public bool IsAnyWorkItemInProgress => workItems.workItemsInProgress; + + /// + /// Returns if this code is currently being exectuted inside a work item. + /// Note: This includes pretty much all types of graph updates. + /// Such as normal graph updates, navmesh cutting and anything added by . + /// + /// In contrast to this is only true when work item code is being executed, it is not + /// true in-between the updates to a work item that takes several frames to complete. + /// + internal bool IsInsideWorkItem => workItems.workItemsInProgressRightNow; + + #endregion + + #region Callbacks + /// + /// Called on Awake before anything else is done. + /// This is called at the start of the Awake call, right after has been set, but this is the only thing that has been done. + /// Use this when you want to set up default settings for an AstarPath component created during runtime since some settings can only be changed in Awake + /// (such as multithreading related stuff) + /// + /// // Create a new AstarPath object on Start and apply some default settings + /// public void Start () { + /// AstarPath.OnAwakeSettings += ApplySettings; + /// AstarPath astar = gameObject.AddComponent(); + /// } + /// + /// public void ApplySettings () { + /// // Unregister from the delegate + /// AstarPath.OnAwakeSettings -= ApplySettings; + /// // For example threadCount should not be changed after the Awake call + /// // so here's the only place to set it if you create the component during runtime + /// AstarPath.active.threadCount = ThreadCount.One; + /// } + /// + /// + public static System.Action OnAwakeSettings; + + /// Called for each graph before they are scanned. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnGraphDelegate OnGraphPreScan; + + /// Called for each graph after they have been scanned. All other graphs might not have been scanned yet. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnGraphDelegate OnGraphPostScan; + + /// Called for each path before searching. Be careful when using multithreading since this will be called from a different thread. + public static OnPathDelegate OnPathPreSearch; + + /// Called for each path after searching. Be careful when using multithreading since this will be called from a different thread. + public static OnPathDelegate OnPathPostSearch; + + /// Called before starting the scanning. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnScanDelegate OnPreScan; + + /// Called after scanning. This is called before applying links, flood-filling the graphs and other post processing. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnScanDelegate OnPostScan; + + /// Called after scanning has completed fully. This is called as the last thing in the Scan function. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnScanDelegate OnLatePostScan; + + /// Called when any graphs are updated. Register to for example recalculate the path whenever a graph changes. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. + public static OnScanDelegate OnGraphsUpdated; + + /// + /// Called when pathID overflows 65536 and resets back to zero. + /// Note: This callback will be cleared every time it is called, so if you want to register to it repeatedly, register to it directly on receiving the callback as well. + /// + public static System.Action On65KOverflow; + + /// + /// Called right after callbacks on paths have been called. + /// + /// A path's callback function runs on the main thread when the path has been calculated. + /// This is done in batches for all paths that have finished their calculation since the last frame. + /// This event will trigger right after a batch of callbacks have been called. + /// + /// If you do not want to use individual path callbacks, you can use this instead to poll all pending paths + /// and see which ones have completed. This is better than doing it in e.g. the Update loop, because + /// here you will have a guarantee that all calculated paths are still valid. + /// Immediately after this callback has finished, other things may invalidate calculated paths, like for example + /// graph updates. + /// + /// This is used by the ECS integration to update all entities' pending paths, without having to store + /// a callback for each agent, and also to avoid the ECS synchronization overhead that having individual + /// callbacks would entail. + /// + public static System.Action OnPathsCalculated; + + #endregion + + #region MemoryStructures + + /// Processes graph updates + readonly GraphUpdateProcessor graphUpdates; + + /// Holds a hierarchical graph to speed up some queries like if there is a path between two nodes + internal readonly HierarchicalGraph hierarchicalGraph; + + /// Holds all active off-mesh links + public readonly OffMeshLinks offMeshLinks; + + /// + /// Handles navmesh cuts. + /// See: + /// + public NavmeshUpdates navmeshUpdates = new NavmeshUpdates(); + + /// Processes work items + readonly WorkItemProcessor workItems; + + /// Holds all paths waiting to be calculated and calculates them + internal readonly PathProcessor pathProcessor; + + /// Holds global node data that cannot be stored in individual graphs + internal GlobalNodeStorage nodeStorage; + + /// + /// Global read-write lock for graph data. + /// + /// Graph data is always consistent from the main-thread's perspective, but if you are using jobs to read from graph data, you may need this. + /// + /// A write lock is held automatically... + /// + /// - During graph updates. During async graph updates, the lock is only held once per frame while the graph update is actually running, not for the whole duration. + /// - During work items. Async work items work similarly to graph updates, the lock is only held once per frame while the work item is actually running. + /// - When events run. + /// - When graph related callbacks, such as , run. + /// - During the last step of a graph's scanning process, when the new graph data is being made visible to the rest of the game. See . + /// + /// To use e.g. AstarPath.active.GetNearest from an ECS job, you'll need to acquire a read lock first, and make sure the lock is only released when the job is finished. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + /// See: + /// + RWLock graphDataLock = new RWLock(); + + /// Makes sure QueueGraphUpdates will not queue multiple graph update orders + bool graphUpdatesWorkItemAdded = false; + + /// + /// Time the last graph update was done. + /// Used to group together frequent graph updates to batches + /// + float lastGraphUpdate = float.NegativeInfinity; + + /// Held if any work items are currently queued + PathProcessor.GraphUpdateLock workItemLock; + + /// Holds all completed paths waiting to be returned to where they were requested + internal readonly PathReturnQueue pathReturnQueue; + + /// + /// Holds settings for heuristic optimization. + /// See: heuristic-opt (view in online documentation for working links) + /// + public EuclideanEmbedding euclideanEmbedding = new EuclideanEmbedding(); + + /// + /// If an async scan is running, this will be set to the coroutine. + /// + /// This primarily used to be able to force the async scan to complete immediately, + /// if the AstarPath component should happen to be destroyed while an async scan is running. + /// + IEnumerator asyncScanTask; + + #endregion + + /// + /// Shows or hides graph inspectors. + /// Used internally by the editor. + /// + /// See: + /// See: + /// + public bool showGraphs = false; + + /// + /// Shows or hides the graph visualizations in standalone games. + /// Normally, graph visualizations are only shown in the editor. + /// If this is set to true, the graph visualizations will also be shown in standalone games. + /// + /// Note: In the editor, this setting has no effect. + /// + /// Note: will be ignored in standalone builds, and this value will be used instead. + /// + /// See: + /// + public bool showGraphsInStandalonePlayer = false; + + /// + /// The next unused Path ID. + /// Incremented for every call to GetNextPathID + /// + private ushort nextFreePathID = 1; + + private AstarPath () { + pathReturnQueue = new PathReturnQueue(this, () => { + if (OnPathsCalculated != null) OnPathsCalculated(); + }); + + // Make sure that the pathProcessor and node storage is never null + nodeStorage = new GlobalNodeStorage(this); + hierarchicalGraph = new HierarchicalGraph(nodeStorage); + pathProcessor = new PathProcessor(this, pathReturnQueue, 1, false); + offMeshLinks = new OffMeshLinks(this); + + workItems = new WorkItemProcessor(this); + graphUpdates = new GraphUpdateProcessor(this); + navmeshUpdates.astar = this; + data = new AstarData(this); + + // Forward graphUpdates.OnGraphsUpdated to AstarPath.OnGraphsUpdated + workItems.OnGraphsUpdated += () => { + if (OnGraphsUpdated != null) { + try { + OnGraphsUpdated(this); + } catch (System.Exception e) { + Debug.LogException(e); + } + } + }; + + pathProcessor.OnPathPreSearch += path => { + var tmp = OnPathPreSearch; + if (tmp != null) tmp(path); + }; + + pathProcessor.OnPathPostSearch += (pathHandler, path) => { + LogPathResults(pathHandler.DebugStringBuilder, path); + var tmp = OnPathPostSearch; + if (tmp != null) tmp(path); + }; + + // Sent every time the path queue is unblocked + pathProcessor.OnQueueUnblocked += () => { + if (euclideanEmbedding.dirty) { + euclideanEmbedding.RecalculateCosts(); + } + }; + } + + /// + /// Returns tag names. + /// Makes sure that the tag names array is not null and of length 32. + /// If it is null or not of length 32, it creates a new array and fills it with 0,1,2,3,4 etc... + /// See: AstarPath.FindTagNames + /// + public string[] GetTagNames () { + if (tagNames == null || tagNames.Length != 32) { + tagNames = new string[32]; + for (int i = 0; i < tagNames.Length; i++) { + tagNames[i] = ""+i; + } + tagNames[0] = "Basic Ground"; + } + return tagNames; + } + + /// + /// Used outside of play mode to initialize the AstarPath object even if it has not been selected in the inspector yet. + /// This will set the property and deserialize all graphs. + /// + /// This is useful if you want to do changes to the graphs in the editor outside of play mode, but cannot be sure that the graphs have been deserialized yet. + /// In play mode this method does nothing. + /// + public static void FindAstarPath () { + if (Application.isPlaying) return; + if (active == null) active = UnityCompatibility.FindAnyObjectByType(); + if (active != null && (active.data.graphs == null || active.data.graphs.Length == 0)) active.data.DeserializeGraphs(); + } + + /// + /// Tries to find an AstarPath object and return tag names. + /// If an AstarPath object cannot be found, it returns an array of length 1 with an error message. + /// See: AstarPath.GetTagNames + /// + public static string[] FindTagNames () { + FindAstarPath(); + return active != null? active.GetTagNames () : new string[1] { "There is no AstarPath component in the scene" }; + } + + /// Returns the next free path ID + internal ushort GetNextPathID () { + if (nextFreePathID == 0) { + nextFreePathID++; + + if (On65KOverflow != null) { + System.Action tmp = On65KOverflow; + On65KOverflow = null; + tmp(); + } + } + return nextFreePathID++; + } + +#if UNITY_EDITOR + struct DebugLimitsCtx { + public UnsafeSpan debugPathNodes; + public ushort debugPathID; + public float debugFloor; + public float debugRoof; + public GraphDebugMode debugMode; + } +#endif + + void RecalculateDebugLimits () { +#if UNITY_EDITOR + var ctx = new DebugLimitsCtx { + debugPathNodes = showSearchTree && debugPathData != null && debugPathData.threadID < active.nodeStorage.pathfindingThreadData.Length ? active.nodeStorage.pathfindingThreadData[debugPathData.threadID].debugPathNodes : default, + debugPathID = debugPathID, + debugFloor = float.PositiveInfinity, + debugRoof = float.NegativeInfinity, + debugMode = debugMode, + }; + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null && graphs[i].drawGizmos) { + graphs[i].GetNodes(static (GraphNode node, ref DebugLimitsCtx ctx) => { + if (node.Walkable && (ctx.debugPathNodes.length == 0 || GraphGizmoHelper.InSearchTree(node, ctx.debugPathNodes, ctx.debugPathID))) { + float value; + if (ctx.debugMode == GraphDebugMode.Penalty) { + value = node.Penalty; + } else if (ctx.debugPathNodes.Length > 0) { + var rnode = ctx.debugPathNodes[node.NodeIndex]; + switch (ctx.debugMode) { + case GraphDebugMode.F: + value = rnode.g + rnode.h; + break; + case GraphDebugMode.G: + value = rnode.g; + break; + default: + case GraphDebugMode.H: + value = rnode.h; + break; + } + } else { + value = 0; + } + ctx.debugFloor = Mathf.Min(ctx.debugFloor, value); + ctx.debugRoof = Mathf.Max(ctx.debugRoof, value); + } + }, ref ctx); + } + } + + if (float.IsInfinity(ctx.debugFloor)) { + ctx.debugFloor = 0; + ctx.debugRoof = 1; + } + + // Make sure they are not identical, that will cause the color interpolation to fail + if (ctx.debugRoof-ctx.debugFloor < 1) ctx.debugRoof += 1; + debugFloor = ctx.debugFloor; + debugRoof = ctx.debugRoof; +#else + debugFloor = 0; + debugRoof = 1; +#endif + } + + /// Calls OnDrawGizmos on all graphs + public override void DrawGizmos () { + if (active != this || graphs == null) { + return; + } + + InitializeColors(); + + var render = Application.isEditor ? showNavGraphs : showGraphsInStandalonePlayer; + var renderInGame = showGraphsInStandalonePlayer && !Application.isEditor; + + // Note: even when graph updates are in-progress, the graph data will always be consistent and valid on the main thread. + // So we can draw gizmos at any time. + + if (render && !manualDebugFloorRoof) { + RecalculateDebugLimits(); + } + + Profiler.BeginSample("Graph.OnDrawGizmos"); + // Loop through all graphs and draw their gizmos + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null && graphs[i].drawGizmos) + graphs[i].OnDrawGizmos(DrawingManager.instance.gizmos, render, default, renderInGame); + } + Profiler.EndSample(); + + if (render) { + euclideanEmbedding.OnDrawGizmos(); + if (debugMode == GraphDebugMode.HierarchicalNode) hierarchicalGraph.OnDrawGizmos(DrawingManager.instance.gizmos, renderInGame); + if (debugMode == GraphDebugMode.NavmeshBorderObstacles) hierarchicalGraph.navmeshEdges.OnDrawGizmos(DrawingManager.instance.gizmos, renderInGame); + } + + workItems.DrawGizmos(); + } + +#if !ASTAR_NO_GUI + /// + /// Draws the InGame debugging (if enabled) + /// See: PathLog + /// + private void OnGUI () { + if (logPathResults == PathLog.InGame && inGameDebugPath != "") { + GUI.Label(new Rect(5, 5, 400, 600), inGameDebugPath); + } + } +#endif + + /// + /// Prints path results to the log. What it prints can be controled using . + /// See: + /// See: PathLog + /// See: Pathfinding.Path.DebugString + /// + private void LogPathResults (System.Text.StringBuilder debugStringBuilder, Path path) { + if (logPathResults != PathLog.None && (path.error || logPathResults != PathLog.OnlyErrors)) { + debugStringBuilder.Clear(); + (path as IPathInternals).DebugString(debugStringBuilder, logPathResults); + var output = debugStringBuilder.ToString(); + + if (logPathResults == PathLog.InGame) { + inGameDebugPath = output; + } else if (path.error) { + Debug.LogWarning(output); + } else { + Debug.Log(output); + } + } + } + + /// + /// Checks if any work items need to be executed + /// then runs pathfinding for a while (if not using multithreading because + /// then the calculation happens in other threads) + /// and then returns any calculated paths to the + /// scripts that requested them. + /// + /// See: PerformBlockingActions + /// See: PathProcessor.TickNonMultithreaded + /// See: PathReturnQueue.ReturnPaths + /// + private void Update () { + navmeshUpdates.Update(); + + // This class uses the [ExecuteInEditMode] attribute + // So Update is called even when not playing + // Don't do anything when not in play mode + if (!Application.isPlaying) return; + + TryQueueGraphUpdates(); + + // Execute blocking actions such as graph updates + // when not scanning + if (!isScanning) { + PerformBlockingActions(); + } + + // Calculates paths when not using multithreading + if (!pathProcessor.IsUsingMultithreading) pathProcessor.TickNonMultithreaded(); + + // Return calculated paths + pathReturnQueue.ReturnPaths(true); + + if (!Application.isEditor && showGraphsInStandalonePlayer) { + showGraphs = showGraphsInStandalonePlayer; + DrawGizmos(); + } + } + + private void PerformBlockingActions (bool force = false) { + if (workItemLock.Held && pathProcessor.queue.allReceiversBlocked) { + // Return all paths before starting blocking actions + // since these might change the graph and make returned paths invalid (at least the nodes) + pathReturnQueue.ReturnPaths(false); + + Profiler.BeginSample("Work Items"); + if (workItems.ProcessWorkItemsForUpdate(force)) { + // At this stage there are no more work items, resume pathfinding threads + workItemLock.Release(); + } + Profiler.EndSample(); + } + } + + /// + /// Add a work item to be processed when pathfinding is paused. + /// + /// The callback will be called once when it is safe to update graphs. + /// + /// This is a convenience method that is equivalent to + /// + /// AddWorkItem(new AstarWorkItem(callback)); + /// + /// + /// See: + /// + public void AddWorkItem (System.Action callback) { + AddWorkItem(new AstarWorkItem(callback)); + } + + /// + /// Add a work item to be processed when pathfinding is paused. + /// + /// THe callback will be called once when it is safe to update graphs. + /// + /// This is a convenience method that is equivalent to + /// + /// AddWorkItem(new AstarWorkItem(callback)); + /// + /// + /// See: + /// + public void AddWorkItem (System.Action callback) { + AddWorkItem(new AstarWorkItem(callback)); + } + + /// + /// Add a work item to be processed when pathfinding is paused. + /// + /// The work item will be executed when it is safe to update nodes. This is defined as between the path searches. + /// When using more threads than one, calling this often might decrease pathfinding performance due to a lot of idling in the threads. + /// Not performance as in it will use much CPU power, but performance as in the number of paths per second will probably go down + /// (though your framerate might actually increase a tiny bit). + /// + /// You should only call this function from the main unity thread (i.e normal game code). + /// + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem(() => { + /// // Safe to update graphs here + /// var node = AstarPath.active.GetNearest(transform.position).node; + /// node.Walkable = false; + /// })); + /// + /// + /// + /// AstarPath.active.AddWorkItem(() => { + /// // Safe to update graphs here + /// var node = AstarPath.active.GetNearest(transform.position).node; + /// node.position = (Int3)transform.position; + /// }); + /// + /// + /// You can run work items over multiple frames: + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem(() => { + /// // Called once, right before the + /// // first call to the method below + /// }, + /// force => { + /// // Called every frame until complete. + /// // Signal that the work item is + /// // complete by returning true. + /// // The "force" parameter will + /// // be true if the work item is + /// // required to complete immediately. + /// // In that case this method should + /// // block and return true when done. + /// return true; + /// })); + /// + /// + /// See: + /// + public void AddWorkItem (AstarWorkItem item) { + workItems.AddWorkItem(item); + + // Make sure pathfinding is stopped and work items are processed + if (!workItemLock.Held) { + workItemLock = PausePathfindingSoon(); + } + +#if UNITY_EDITOR + // If not playing, execute instantly + if (!Application.isPlaying) { + FlushWorkItems(); + } +#endif + } + + #region GraphUpdateMethods + + /// + /// Will apply queued graph updates as soon as possible, regardless of . + /// Calling this multiple times, before graph updates are applied, is equivalent to calling it once. + /// This function is useful if you are limiting graph updates, but you want a specific graph update to be applied as soon as possible regardless of the time limit. + /// Note that this does not block until the updates are done, it merely bypasses the time limit. + /// + /// See: + /// + public void QueueGraphUpdates () { + if (!graphUpdatesWorkItemAdded) { + graphUpdatesWorkItemAdded = true; + var workItem = graphUpdates.GetWorkItem(); + + // Add a new work item which first + // sets the graphUpdatesWorkItemAdded flag to false + // and then processes the graph updates + AddWorkItem(new AstarWorkItem(context => { + graphUpdatesWorkItemAdded = false; + lastGraphUpdate = Time.realtimeSinceStartup; + + workItem.initWithContext(context); + }, workItem.updateWithContext)); + } + } + + /// Calls , unless we should wait for a larger batch + void TryQueueGraphUpdates () { + if (IsAnyGraphUpdateQueued && (!batchGraphUpdates || Time.realtimeSinceStartup >= lastGraphUpdate + graphUpdateBatchingInterval)) { + QueueGraphUpdates(); + } + } + + /// + /// Update all graphs within bounds after delay seconds. + /// The graphs will be updated as soon as possible. + /// + /// + /// // Create an obstacle + /// var obstacle = GameObject.Instantiate(someObstacle, somePosition, Quaternion.identity); + /// + /// // Use the bounding box from the attached collider + /// var bounds = obstacle.GetComponent().bounds; + /// + /// // Update the graph to account for the new obstacle + /// AstarPath.active.UpdateGraphs(bounds); + /// + /// + /// See: FlushGraphUpdates + /// See: batchGraphUpdates + /// See: graph-updates (view in online documentation for working links) + /// + public void UpdateGraphs (Bounds bounds, float delay) { + UpdateGraphs(new GraphUpdateObject(bounds), delay); + } + + /// + /// Update all graphs using the GraphUpdateObject after delay seconds. + /// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty. + /// + /// + /// // using Pathfinding; //At top of script + /// + /// // As an example, use the bounding box from the attached collider + /// Bounds bounds = GetComponent().bounds; + /// var guo = new GraphUpdateObject(bounds); + /// + /// // Set some settings + /// guo.updatePhysics = true; + /// AstarPath.active.UpdateGraphs(guo); + /// + /// + /// See: FlushGraphUpdates + /// See: batchGraphUpdates + /// See: graph-updates (view in online documentation for working links) + /// + public void UpdateGraphs (GraphUpdateObject ob, float delay) { + StartCoroutine(UpdateGraphsInternal(ob, delay)); + } + + /// Update all graphs using the GraphUpdateObject after delay seconds + IEnumerator UpdateGraphsInternal (GraphUpdateObject ob, float delay) { + yield return new WaitForSeconds(delay); + UpdateGraphs(ob); + } + + /// + /// Update all graphs within bounds. + /// The graphs will be updated as soon as possible. + /// + /// This is equivalent to + /// + /// UpdateGraphs(new GraphUpdateObject(bounds)); + /// + /// + /// + /// // Create an obstacle + /// var obstacle = GameObject.Instantiate(someObstacle, somePosition, Quaternion.identity); + /// + /// // Use the bounding box from the attached collider + /// var bounds = obstacle.GetComponent().bounds; + /// + /// // Update the graph to account for the new obstacle + /// AstarPath.active.UpdateGraphs(bounds); + /// + /// + /// See: FlushGraphUpdates + /// See: batchGraphUpdates + /// See: graph-updates (view in online documentation for working links) + /// + public void UpdateGraphs (Bounds bounds) { + UpdateGraphs(new GraphUpdateObject(bounds)); + } + + /// + /// Update all graphs using the GraphUpdateObject. + /// This can be used to, e.g make all nodes in a region unwalkable, or set them to a higher penalty. + /// The graphs will be updated as soon as possible (with respect to + /// + /// + /// // using Pathfinding; //At top of script + /// + /// // As an example, use the bounding box from the attached collider + /// Bounds bounds = GetComponent().bounds; + /// var guo = new GraphUpdateObject(bounds); + /// + /// // Set some settings + /// guo.updatePhysics = true; + /// AstarPath.active.UpdateGraphs(guo); + /// + /// + /// See: FlushGraphUpdates + /// See: batchGraphUpdates + /// See: graph-updates (view in online documentation for working links) + /// + public void UpdateGraphs (GraphUpdateObject ob) { + if (ob.internalStage != GraphUpdateObject.STAGE_CREATED) { + throw new System.Exception("You are trying to update graphs using the same graph update object twice. Please create a new GraphUpdateObject instead."); + } + ob.internalStage = GraphUpdateObject.STAGE_PENDING; + graphUpdates.AddToQueue(ob); + + TryQueueGraphUpdates(); + } + + /// + /// Forces graph updates to complete in a single frame. + /// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause. + /// When all threads have paused, graph updates will be performed. + /// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another. + /// But you probably wont have to worry about that. + /// + /// Note: This is almost identical to , but added for more descriptive name. + /// This function will also override any time limit delays for graph updates. + /// This is because graph updates are implemented using work items. + /// So calling this function will also execute any other work items (if any are queued). + /// + /// Will not do anything if there are no graph updates queued (not even execute other work items). + /// + public void FlushGraphUpdates () { + if (IsAnyGraphUpdateQueued || IsAnyGraphUpdateInProgress) { + QueueGraphUpdates(); + FlushWorkItems(); + } + } + + #endregion + + /// + /// Forces work items to complete in a single frame. + /// This will force all work items to run immidiately. + /// This will force the pathfinding threads to finish calculating the path they are currently calculating (if any) and then pause. + /// When all threads have paused, work items will be executed (which can be e.g graph updates). + /// + /// Warning: Using this very often (many times per second) can reduce your fps due to a lot of threads waiting for one another. + /// But you probably wont have to worry about that + /// + /// Note: This is almost (note almost) identical to , but added for more descriptive name. + /// + /// Will not do anything if there are no queued work items waiting to run. + /// + public void FlushWorkItems () { + if (workItems.anyQueued || workItems.workItemsInProgress) { + if (active != this) throw new System.Exception("This AstarPath component is not initialized in a scene. Are you trying to add work items to a prefab or a disabled AstarPath component?"); + using (PausePathfinding()) { + PerformBlockingActions(true); + } + } + } + + /// Initializes the field + void InitializePathProcessor () { + int numThreads = threadCount.ToConcreteThreadCount(); + + // Outside of play mode everything is synchronous, so no threads are used. + if (!Application.isPlaying) numThreads = 0; + + + int numProcessors = Mathf.Max(numThreads, 1); + bool multithreaded = numThreads > 0; + pathProcessor.StopThreads(); + pathProcessor.SetThreadCount(numProcessors, multithreaded); + } + + void InitializeColors () { + colorSettings = colorSettings ?? new AstarColor(); + colorSettings.PushToStatic(); + } + + void ShutdownPathfindingThreads () { + // Block until the pathfinding threads have + // completed their current path calculation + var graphLock = PausePathfinding(); + + navmeshUpdates.OnDisable(); + + euclideanEmbedding.dirty = false; + + // Discard all queued graph updates. Graph updates that are already in progress will still be allowed to finish, + // as they may be allocating unmanaged data which we don't know how to safely deallocate. + graphUpdates.DiscardQueued(); + + // TODO: Add unit test that verifies that work items that are added will always complete + // Ensure work items complete before disabling this component. + // This is important because work items may allocate temporary unmanaged memory, so we cannot just forget about them. + FlushWorkItems(); + + if (logPathResults == PathLog.Heavy) + Debug.Log("Processing Possible Work Items"); + + // Try to join pathfinding threads + pathProcessor.StopThreads(); + + if (logPathResults == PathLog.Heavy) + Debug.Log("Returning Paths"); + + + // Return all paths + pathReturnQueue.ReturnPaths(false); + graphLock.Release(); + euclideanEmbedding.OnDisable(); + } + + /// + /// Called after this component is enabled. + /// + /// Unless the component has already been activated in Awake, this method should: + /// - Ensure the singleton holds (setting to this). + /// - Make sure all subsystems that were disabled in OnDisable are again enabled. + /// - This includes starting pathfinding threads. + /// + void OnEnable () { + // If the component gets re-enabled during runtime. + // Note that the first time the component loads, then Awake will run first + // and will already have set the #active field. + // In the editor, OnDisable -> OnEnable will be called when an undo or redo event happens (both in and outside of play mode). + if (active != null) { + if (active != this && Application.isPlaying) { + if (this.enabled) { + Debug.LogWarning("Another A* component is already in the scene. More than one A* component cannot be active at the same time. Disabling this one.", this); + } + enabled = false; + } + return; + } + + // Very important to set this. Ensures the singleton pattern holds + active = this; + + // Disable GUILayout to gain some performance, it is not used in the OnGUI call + useGUILayout = false; + + if (OnAwakeSettings != null) { + OnAwakeSettings(); + } + + hierarchicalGraph.OnEnable(); + + // To make sure all graph modifiers have been enabled before scan (to avoid script execution order issues) + GraphModifier.FindAllModifiers(); + RelevantGraphSurface.FindAllGraphSurfaces(); + + InitializeColors(); + + navmeshUpdates.OnEnable(); + + // This will load the graph settings, or whole initialized graphs from the cache, if one has been supplied. + data.OnEnable(); + + // Flush work items, possibly added when loading the graph data + FlushWorkItems(); + + euclideanEmbedding.dirty = true; + + InitializePathProcessor(); + + // This class uses the [ExecuteInEditMode] attribute + // So OnEnable is called even when not playing + // Don't scan the graphs unless we are in play mode + if (Application.isPlaying) { + // Scan the graphs if #scanOnStartup is enabled, and we have not loaded a graph cache already. + // We only do this the first time the AstarPath component is enabled. + if (scanOnStartup && !hasScannedGraphAtStartup && (!data.cacheStartup || data.file_cachedStartup == null)) { + hasScannedGraphAtStartup = true; + Scan(); + } + } + } + + + /// + /// Cleans up graphs to avoid memory leaks. + /// + /// This is called by Unity when: + /// - The component is explicitly disabled in play mode or editor mode. + /// - When the component is about to be destroyed + /// - Including when the game stops + /// - When an undo/redo event takes place (Unity will first disable the component and then enable it again). + /// + /// During edit and play mode this method should: + /// - Destroy all node data (but not the graphs themselves) + /// - Dispose all unmanaged data + /// - Shutdown pathfinding threads if they are running (any pending path requests are left in the queue) + /// + void OnDisable () { + if (active == this) { + if (asyncScanTask != null) { + Debug.LogWarning("An async scan was running when the AstarPath component was disabled. Blocking until the async scan is complete.", this); + BlockUntilAsyncScanComplete(); + } + + // Ensure there are no jobs running that might read or write graph data + graphDataLock.WriteSync().Unlock(); + + ShutdownPathfindingThreads(); + + // We need to call dispose data here because in the editor the OnDestroy + // method is not called but OnDisable is. It is vital that graph data + // is destroyed even in the editor (e.g. when going from edit mode to play mode) + // because a lot of data is stored as NativeArrays which need to be disposed. + + // There is also another case where this is important. When the unity + // editor is configured to stop play mode after recompiling scripts + // it seems to not call OnDestroy (or at least not reliably across all versions of Unity). + // So we need to ensure we dispose of all the data during OnDisable. + data.DestroyAllNodes(); + data.DisposeUnmanagedData(); + hierarchicalGraph.OnDisable(); + nodeStorage.OnDisable(); + offMeshLinks.OnDisable(); + active = null; + } + } + + /// + /// Clears up variables and other stuff, destroys graphs. + /// Note that when destroying an AstarPath object, all static variables such as callbacks will be cleared. + /// + void OnDestroy () { + if (logPathResults == PathLog.Heavy) + Debug.Log("AstarPath Component Destroyed - Cleaning Up Pathfinding Data"); + + // active has already been set to null during OnDisable. + // We temporarily make this object the active one just during the destruction. + var prevActive = active; + active = this; + + ShutdownPathfindingThreads(); + + pathProcessor.Dispose(); + + if (logPathResults == PathLog.Heavy) + Debug.Log("Destroying Graphs"); + + // Clean up graph data + // Data may be null if this object was never enabled because another A* instance existed. + if (data != null) data.OnDestroy(); + + active = prevActive; + + if (logPathResults == PathLog.Heavy) + Debug.Log("Cleaning up variables"); + + // Clear all static variables, otherwise the next scene might get weird data + if (active == this) { + // Clear all callbacks + OnAwakeSettings = null; + OnGraphPreScan = null; + OnGraphPostScan = null; + OnPathPreSearch = null; + OnPathPostSearch = null; + OnPreScan = null; + OnPostScan = null; + OnLatePostScan = null; + On65KOverflow = null; + OnGraphsUpdated = null; + + active = null; + } + } + + #region ScanMethods + + /// + /// Allocate a bunch of nodes at once. + /// This is faster than allocating each individual node separately and it can be done in a separate thread by using jobs. + /// + /// + /// var nodes = new PointNode[128]; + /// var job = AstarPath.active.AllocateNodes(nodes, 128, () => new PointNode(), 1); + /// + /// job.Complete(); + /// + /// + /// See: + /// + /// Node array to fill + /// How many nodes to allocate + /// Delegate which creates a node. () => new T(). Note that new T(AstarPath.active) should *not* be used as that will cause the node to be initialized twice. + /// How many variants of the node to allocate. Should be the same as \reflink{GraphNode.PathNodeVariants} for this node type. + public Unity.Jobs.JobHandle AllocateNodes(T[] result, int count, System.Func createNode, uint variantsPerNode) where T : GraphNode { + if (!pathProcessor.queue.allReceiversBlocked) { + throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct"); + } + return nodeStorage.AllocateNodesJob(result, count, createNode, variantsPerNode); + } + + /// + /// Initializes temporary path data for a node. + /// + /// Use like: InitializeNode(new PointNode()) + /// + /// See: + /// + internal void InitializeNode (GraphNode node) { + if (!pathProcessor.queue.allReceiversBlocked) { + throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct"); + } + nodeStorage.InitializeNode(node); + } + + internal void InitializeNodes (GraphNode[] nodes) { + if (!pathProcessor.queue.allReceiversBlocked) { + throw new System.Exception("Trying to initialize a node when it is not safe to initialize any nodes. Must be done during a graph update. See http://arongranberg.com/astar/docs/graph-updates.html#direct"); + } + + for (int i = 0; i < nodes.Length; i++) nodeStorage.InitializeNode(nodes[i]); + } + + /// + /// Internal method to destroy a given node. + /// This is to be called after the node has been disconnected from the graph so that it cannot be reached from any other nodes. + /// It should only be called during graph updates, that is when the pathfinding threads are either not running or paused. + /// + /// Warning: This method should not be called by user code. It is used internally by the system. + /// + internal void DestroyNode (GraphNode node) { + nodeStorage.DestroyNode(node); + } + + /// + /// Blocks until all pathfinding threads are paused and blocked. + /// + /// + /// var graphLock = AstarPath.active.PausePathfinding(); + /// // Here we can modify the graphs safely. For example by increasing the penalty of a node + /// AstarPath.active.data.gridGraph.GetNode(0, 0).Penalty += 1000; + /// + /// // Allow pathfinding to resume + /// graphLock.Release(); + /// + /// + /// Returns: A lock object. You need to call on that object to allow pathfinding to resume. + /// Note: In most cases this should not be called from user code. Use the method instead. + /// + /// See: + /// + public PathProcessor.GraphUpdateLock PausePathfinding () { + // Ensure there are no jobs running that might read or write graph data, + // as this method is typically used right before one modifies graph data. + graphDataLock.WriteSync().Unlock(); + return pathProcessor.PausePathfinding(true); + } + + /// + /// Blocks the path queue so that e.g work items can be performed. + /// + /// Pathfinding threads will stop accepting new path requests and will finish the ones they are currently calculating asynchronously. + /// When the lock is released, the pathfinding threads will resume as normal. + /// + /// Note: You are unlikely to need to use this method. It is primarily for internal use. + /// + public PathProcessor.GraphUpdateLock PausePathfindingSoon () { + return pathProcessor.PausePathfinding(false); + } + + /// Blocks until the currently running async scan (if any) has completed + void BlockUntilAsyncScanComplete () { + // We can't block and wait for the async scan, so we have to spin. + // Not great, but this is not something that should happen during normal gameplay. + // It's more a fallback if the user doesn't wait for the async scan to complete before starting a new one. + // Note: The ProgressScanningIteratorsConcurrently method used internally by the scan will ensure + // that the thread yields its time slice in case it's just waiting for other threads. + while (asyncScanTask != null && asyncScanTask.MoveNext()) {} + asyncScanTask = null; + } + + /// + /// Scans a particular graph. + /// Calling this method will recalculate the specified graph from scratch. + /// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use + /// smaller graph updates whenever possible. + /// + /// + /// // Recalculate all graphs + /// AstarPath.active.Scan(); + /// + /// // Recalculate only the first grid graph + /// var graphToScan = AstarPath.active.data.gridGraph; + /// AstarPath.active.Scan(graphToScan); + /// + /// // Recalculate only the first and third graphs + /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] }; + /// AstarPath.active.Scan(graphsToScan); + /// + /// + /// See: graph-updates (view in online documentation for working links) + /// See: ScanAsync + /// + public void Scan (NavGraph graphToScan) { + if (graphToScan == null) throw new System.ArgumentNullException(); + Scan(new NavGraph[] { graphToScan }); + } + + /// + /// Scans all specified graphs. + /// + /// Calling this method will recalculate all specified graphs (or all graphs if the graphsToScan parameter is null) from scratch. + /// This method is pretty slow (depending on graph type and graph complexity of course), so it is advisable to use + /// smaller graph updates whenever possible. + /// + /// + /// // Recalculate all graphs + /// AstarPath.active.Scan(); + /// + /// // Recalculate only the first grid graph + /// var graphToScan = AstarPath.active.data.gridGraph; + /// AstarPath.active.Scan(graphToScan); + /// + /// // Recalculate only the first and third graphs + /// var graphsToScan = new [] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] }; + /// AstarPath.active.Scan(graphsToScan); + /// + /// + /// See: graph-updates (view in online documentation for working links) + /// See: ScanAsync + /// + /// The graphs to scan. If this parameter is null then all graphs will be scanned + public void Scan (NavGraph[] graphsToScan = null) { + var prevStage = (ScanningStage)(-1); + + if (asyncScanTask != null) { + Debug.LogWarning("An async scan was already running when a new scan was requested. Blocking until it is complete. You can check if a scan is currently in progress using the AstarPath.active.isScanning property.", this); + BlockUntilAsyncScanComplete(); + } + + Profiler.BeginSample("Scan"); + Profiler.BeginSample("Init"); + foreach (var p in ScanInternal(graphsToScan, false)) { + if (prevStage != p.stage) { + Profiler.EndSample(); + Profiler.BeginSample(p.stage.ToString()); +#if !NETFX_CORE && UNITY_EDITOR + // Log progress to the console + System.Console.WriteLine(p.stage); +#endif + prevStage = p.stage; + } + } + Profiler.EndSample(); + Profiler.EndSample(); + } + + /// + /// Scans a particular graph asynchronously. This is a IEnumerable, you can loop through it to get the progress + /// + /// You can scan graphs asyncronously by yielding when you iterate through the returned IEnumerable. + /// Note that this does not guarantee a good framerate, but it will allow you + /// to at least show a progress bar while scanning. + /// + /// + /// IEnumerator Start () { + /// foreach (Progress progress in AstarPath.active.ScanAsync()) { + /// Debug.Log("Scanning... " + progress.ToString()); + /// yield return null; + /// } + /// } + /// + /// + /// See: Scan + /// + public IEnumerable ScanAsync (NavGraph graphToScan) { + if (graphToScan == null) throw new System.ArgumentNullException(); + return ScanAsync(new NavGraph[] { graphToScan }); + } + + /// + /// Scans all specified graphs asynchronously. This is a IEnumerable, you can loop through it to get the progress + /// + /// You can scan graphs asyncronously by yielding when you loop through the progress. + /// Note that this does not guarantee a good framerate, but it will allow you + /// to at least show a progress bar during scanning. + /// + /// + /// IEnumerator Start () { + /// foreach (Progress progress in AstarPath.active.ScanAsync()) { + /// Debug.Log("Scanning... " + progress.ToString()); + /// yield return null; + /// } + /// } + /// + /// + /// Note: If the graphs are already scanned, doing an async scan will temporarily cause increased memory usage, since two copies of the graphs will be kept in memory during the async scan. + /// This may not be desirable on some platforms. A non-async scan will not cause this temporary increased memory usage. + /// + /// See: Scan + /// + /// The graphs to scan. If this parameter is null then all graphs will be scanned + public IEnumerable ScanAsync (NavGraph[] graphsToScan = null) { + if (asyncScanTask != null) { + Debug.LogWarning("An async scan was already running when a new async scan was requested. Blocking until the previous one is complete. You can check if a scan is currently in progress using the AstarPath.active.isScanning property.", this); + BlockUntilAsyncScanComplete(); + } + asyncScanTask = ScanInternal(graphsToScan, true).GetEnumerator(); + // We cannot inline the TickAsyncScanUntilCompletion function, because we want *this* function to + // not be a coroutine, so that the setup runs immediately when calling ScanAsync, + // instead of defering until the coroutine is ticked for the first time. + + // We tick the coroutine once here to do some inital setup. + // This includes setting isScanning to true. + try { + asyncScanTask.MoveNext(); + } catch { + asyncScanTask = null; + throw; + } + return TickAsyncScanUntilCompletion(asyncScanTask); + } + + IEnumerable TickAsyncScanUntilCompletion (IEnumerator task) { + while (true) { + try { + if (!task.MoveNext()) break; + } catch { + if (asyncScanTask == task) asyncScanTask = null; + throw; + } + yield return task.Current; + } + if (asyncScanTask == task) asyncScanTask = null; + } + + class DummyGraphUpdateContext : IGraphUpdateContext { + public void DirtyBounds (Bounds bounds) {} + } + + class DestroyGraphPromise : IGraphUpdatePromise { + public IGraphInternals graph; + public IEnumerator Prepare () { + return null; + } + public void Apply (IGraphUpdateContext context) { + graph.DestroyAllNodes(); + } + } + + IEnumerable ScanInternal (NavGraph[] graphsToScan, bool async) { + if (graphsToScan == null) graphsToScan = graphs; + + if (graphsToScan == null || graphsToScan.Length == 0) { + yield break; + } + + // Guard to ensure the A* object is always enabled if the graphs have any valid data. + // This is because otherwise the OnDisable method will not be called and some unmanaged data + // in NativeArrays may end up leaking. + if (!enabled) throw new System.InvalidOperationException("The AstarPath object must be enabled to scan graphs"); + if (active != this) throw new System.InvalidOperationException("The AstarPath object is not enabled in a scene"); + + isScanning = true; + + var graphUpdateLock = PausePathfinding(); + + // Make sure all paths that are in the queue to be returned + // are returned immediately + // Some modifiers (e.g the funnel modifier) rely on + // the nodes being valid when the path is returned + pathReturnQueue.ReturnPaths(false); + + // Ensure all graph updates that are in progress get completed immediately. + // Graph updates that are in progress may use graph data, and we don't want to re-scan the graphs under their feet. + workItems.ProcessWorkItemsForScan(true); + + if (!Application.isPlaying) { + data.FindGraphTypes(); + GraphModifier.FindAllModifiers(); + } + + + yield return new Progress(0.05F, ScanningStage.PreProcessingGraphs); + + + using (var writeLock2 = graphDataLock.WriteSync()) { + try { + if (OnPreScan != null) { + OnPreScan(this); + } + + GraphModifier.TriggerEvent(GraphModifier.EventType.PreScan); + GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); + } catch { + isScanning = false; + graphUpdateLock.Release(); + throw; + } + } + + data.LockGraphStructure(); + + // Make sure the physics engine data is up to date. + // Scanning graphs may use physics methods and it is very confusing if they + // do not always pick up the latest changes made to the scene. + Physics.SyncTransforms(); + Physics2D.SyncTransforms(); + + var watch = System.Diagnostics.Stopwatch.StartNew(); + + // Destroy previous nodes, unless we are doing an async scan. + // We do not want the graphs to be in an invalid state during the async scan, + // so we cannot eagerly destroy them here. + // This means that during an async scan we may have two copies of the graphs in memory. + // Most of the data will be destroyed at the end of the async scan, but some memory will + // still be reserved. So a non-async scan is more memory efficient. + if (!async) { + using (var writeLock2 = graphDataLock.WriteSync()) { + Profiler.BeginSample("Destroy previous nodes"); + for (int i = 0; i < graphsToScan.Length; i++) { + if (graphsToScan[i] != null) { + ((IGraphInternals)graphsToScan[i]).DestroyAllNodes(); + } + } + Profiler.EndSample(); + } + } + + if (OnGraphPreScan != null) { + using (var writeLock2 = graphDataLock.WriteSync()) { + try { + for (int i = 0; i < graphsToScan.Length; i++) { + if (graphsToScan[i] != null) OnGraphPreScan(graphsToScan[i]); + } + } catch { + isScanning = false; + data.UnlockGraphStructure(); + graphUpdateLock.Release(); + throw; + } + } + } + + // Loop through all graphs and start scanning them + var promises = new List<(IGraphUpdatePromise, IEnumerator)>(graphsToScan.Length); + for (int i = 0; i < graphsToScan.Length; i++) { + if (graphsToScan[i] != null) { + var promise = ((IGraphInternals)graphsToScan[i]).ScanInternal(async) ?? new DestroyGraphPromise { graph = (IGraphInternals)graphsToScan[i] }; + var iterator = promise.Prepare(); + promises.Add((promise, iterator)); + } + } + + // Scan all graphs concurrently by progressing all scanning iterators. + // If the graphs use the job system internally (like the grid, recast and navmesh graphs), + // then multiple graphs will even be scanned in parallel. + while (true) { + int firstNonFinished; + try { + firstNonFinished = GraphUpdateProcessor.PrepareGraphUpdatePromises(promises, async ? TimeSlice.MillisFromNow(2) : TimeSlice.Infinite); + } catch { + isScanning = false; + data.UnlockGraphStructure(); + graphUpdateLock.Release(); + throw; + } + if (firstNonFinished == -1) { + break; + } else { + // Just used for progress information + // This graph will advance the progress bar from minp to maxp + float meanProgress = 0; + for (int i = 0; i < promises.Count; i++) meanProgress += promises[i].Item1.Progress; + meanProgress /= promises.Count; + yield return new Progress(Mathf.Lerp(0.1f, 0.8f, meanProgress), ScanningStage.ScanningGraph, firstNonFinished, promises.Count); + } + } + + yield return new Progress(0.95f, ScanningStage.FinishingScans); + + // Now we proceed with the last step of each graph's scanning process + // This part will make the results of the scan visible to the rest of the game. + // As a consequence, we must make sure to *not* yield anymore after this point, + // since that would make the rest of the game run while the graphs may be in an invalid state. + var writeLock = graphDataLock.WriteSync(); + + var ctx = new DummyGraphUpdateContext(); + try { + GraphUpdateProcessor.ApplyGraphUpdatePromises(promises, ctx); + } catch { + isScanning = false; + data.UnlockGraphStructure(); + graphUpdateLock.Release(); + writeLock.Unlock(); + throw; + } + + + for (int i = 0; i < graphsToScan.Length; i++) { + if (graphsToScan[i] != null) { + if (OnGraphPostScan != null) { + try { + OnGraphPostScan(graphsToScan[i]); + } catch { + isScanning = false; + data.UnlockGraphStructure(); + graphUpdateLock.Release(); + writeLock.Unlock(); + throw; + } + } + // Notify the off mesh links subsystem that graphs have been recalculated, and we may need to recalculate off mesh links. + // But skip this for the link graph, since that's the graph that holds the off mesh link nodes themselves. + if (!(graphsToScan[i] is LinkGraph)) offMeshLinks.DirtyBounds(graphsToScan[i].bounds); + } + } + + // Unlock the graph structure here so that e.g. off-mesh-links can add the point graph required for them to work + data.UnlockGraphStructure(); + + try { + // Graph Modifiers and the OnGraphsUpdated callback may modify graphs arbitrarily, so this also needs to be inside the write lock + if (OnPostScan != null) OnPostScan(this); + GraphModifier.TriggerEvent(GraphModifier.EventType.PostScan); + } catch { + isScanning = false; + graphUpdateLock.Release(); + writeLock.Unlock(); + throw; + } + + // This lock may not be held if there are no work items pending + if (workItemLock.Held) { + Profiler.BeginSample("Work Items"); + // Note that this never sends PostUpdate (or similar) events. Those are sent below instead. + workItems.ProcessWorkItemsForScan(true); + Profiler.EndSample(); + workItemLock.Release(); + } + + offMeshLinks.Refresh(); + + GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdateBeforeAreaRecalculation); + + // Recalculate connected components synchronously + hierarchicalGraph.RecalculateIfNecessary(); + + // Scanning a graph *is* a type of update + GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); + if (OnGraphsUpdated != null) { + try { + OnGraphsUpdated(this); + } catch { + isScanning = false; + graphUpdateLock.Release(); + writeLock.Unlock(); + throw; + } + } + + // Signal that we have stopped scanning here + isScanning = false; + + try { + if (OnLatePostScan != null) OnLatePostScan(this); + GraphModifier.TriggerEvent(GraphModifier.EventType.LatePostScan); + } catch { + graphUpdateLock.Release(); + writeLock.Unlock(); + throw; + } + + writeLock.Unlock(); + + euclideanEmbedding.dirty = true; + euclideanEmbedding.RecalculatePivots(); + + // Perform any blocking actions + FlushWorkItems(); + // Resume pathfinding threads + graphUpdateLock.Release(); + + watch.Stop(); + lastScanTime = (float)watch.Elapsed.TotalSeconds; + + if (logPathResults != PathLog.None && logPathResults != PathLog.OnlyErrors) { + Debug.Log("Scanned graphs in " + (lastScanTime*1000).ToString("0") + " ms"); + } + } + + #endregion + + internal void DirtyBounds (Bounds bounds) { + offMeshLinks.DirtyBounds(bounds); + workItems.DirtyBounds(bounds); + } + + internal void VisualizeOriginalGraphUpdateBounds (Bounds bounds) { + workItems.VisualizeOriginalGraphUpdateBounds(bounds); + } + + private static int waitForPathDepth = 0; + + /// + /// Blocks until the path has been calculated. + /// + /// Normally it takes a few frames for a path to be calculated and returned. + /// This function will ensure that the path will be calculated when this function returns + /// and that the callback for that path has been called. + /// + /// If requesting a lot of paths in one go and waiting for the last one to complete, + /// it will calculate most of the paths in the queue (only most if using multithreading, all if not using multithreading). + /// + /// Use this function only if you really need to. + /// There is a point to spreading path calculations out over several frames. + /// It smoothes out the framerate and makes sure requesting a large + /// number of paths at the same time does not cause lag. + /// + /// Note: Graph updates and other callbacks might get called during the execution of this function. + /// + /// When the pathfinder is shutting down. I.e in OnDestroy, this function will not do anything. + /// + /// Throws: Exception if pathfinding is not initialized properly for this scene (most likely no AstarPath object exists) + /// or if the path has not been started yet. + /// Also throws an exception if critical errors occur such as when the pathfinding threads have crashed (which should not happen in normal cases). + /// This prevents an infinite loop while waiting for the path. + /// + /// See: Pathfinding.Path.WaitForPath + /// See: Pathfinding.Path.BlockUntilCalculated + /// + /// The path to wait for. The path must be started, otherwise an exception will be thrown. + public static void BlockUntilCalculated (Path path) { + if (active == null) + throw new System.Exception("Pathfinding is not correctly initialized in this scene (yet?). " + + "AstarPath.active is null.\nDo not call this function in Awake"); + + if (path == null) throw new System.ArgumentNullException(nameof(path)); + + if (active.pathProcessor.queue.isClosed) return; + + if (path.PipelineState == PathState.Created) { + throw new System.Exception("The specified path has not been started yet."); + } + + waitForPathDepth++; + + if (waitForPathDepth == 5) { + Debug.LogError("You are calling the BlockUntilCalculated function recursively (maybe from a path callback). Please don't do this."); + } + + if (path.PipelineState < PathState.ReturnQueue) { + if (active.IsUsingMultithreading) { + while (path.PipelineState < PathState.ReturnQueue) { + if (active.pathProcessor.queue.isClosed) { + waitForPathDepth--; + throw new System.Exception("Pathfinding Threads seem to have crashed."); + } + + // Wait for threads to calculate paths + Thread.Sleep(1); + active.PerformBlockingActions(true); + } + } else { + while (path.PipelineState < PathState.ReturnQueue) { + if (active.pathProcessor.queue.isEmpty && path.PipelineState != PathState.Processing) { + waitForPathDepth--; + throw new System.Exception("Critical error. Path Queue is empty but the path state is '" + path.PipelineState + "'"); + } + + // Calculate some paths + active.pathProcessor.TickNonMultithreaded(); + active.PerformBlockingActions(true); + } + } + } + + active.pathReturnQueue.ReturnPaths(false); + waitForPathDepth--; + } + + /// + /// Adds the path to a queue so that it will be calculated as soon as possible. + /// The callback specified when constructing the path will be called when the path has been calculated. + /// Usually you should use the Seeker component instead of calling this function directly. + /// + /// + /// // There must be an AstarPath instance in the scene + /// if (AstarPath.active == null) return; + /// + /// // We can calculate multiple paths asynchronously + /// for (int i = 0; i < 10; i++) { + /// var path = ABPath.Construct(transform.position, transform.position+transform.forward*i*10, OnPathComplete); + /// + /// // Calculate the path by using the AstarPath component directly + /// AstarPath.StartPath(path); + /// } + /// + /// + /// The path that should be enqueued. + /// If true, the path will be pushed to the front of the queue, bypassing all waiting paths and making it the next path to be calculated. + /// This can be useful if you have a path which you want to prioritize over all others. Be careful to not overuse it though. + /// If too many paths are put in the front of the queue often, this can lead to normal paths having to wait a very long time before being calculated. + /// Typically path.BlockUntilCalculated will be called when not in play mode. However, the play mode check will not work if + /// you call this from a separate thread, or a job. In that case you can set this to true to skip the check. + public static void StartPath (Path path, bool pushToFront = false, bool assumeInPlayMode = false) { + // Copy to local variable to avoid multithreading issues + var astar = active; + + if (System.Object.ReferenceEquals(astar, null)) { + Debug.LogError("There is no AstarPath object in the scene or it has not been initialized yet"); + return; + } + + if (path.PipelineState != PathState.Created) { + throw new System.Exception("The path has an invalid state. Expected " + PathState.Created + " found " + path.PipelineState + "\n" + + "Make sure you are not requesting the same path twice"); + } + + if (astar.pathProcessor.queue.isClosed) { + path.FailWithError("No new paths are accepted"); + return; + } + + if (astar.graphs == null || astar.graphs.Length == 0) { + Debug.LogError("There are no graphs in the scene"); + path.FailWithError("There are no graphs in the scene"); + Debug.LogError(path.errorLog); + return; + } + + path.Claim(astar); + + // Will increment p.state to PathState.PathQueue + ((IPathInternals)path).AdvanceState(PathState.PathQueue); + if (pushToFront) { + astar.pathProcessor.queue.PushFront(path); + } else { + astar.pathProcessor.queue.Push(path); + } + + // Outside of play mode, all path requests are synchronous. + // However, inside a job we cannot check this, because Unity will throw an exception. + // But luckily pretty much all jobs will run in game mode anyway. So we assume that if we are in a job, we are in game mode. + if (!assumeInPlayMode && !Unity.Jobs.LowLevel.Unsafe.JobsUtility.IsExecutingJob && !Application.isPlaying) { + BlockUntilCalculated(path); + } + } + + /// + /// True if the point is on a walkable part of the navmesh, as seen from above. + /// + /// A point is considered on the navmesh if it is above or below a walkable navmesh surface, at any distance, + /// and if it is not above/below a closer unwalkable node. + /// + /// Note: This means that, for example, in multi-story building a point will be considered on the navmesh if any walkable floor is below or above the point. + /// If you want more complex behavior then you can use the GetNearest method together with the appropriate settings for your use case. + /// + /// This uses the graph's natural up direction to determine which way is up. + /// Therefore, it will also work on rotated graphs, as well as graphs in 2D mode. + /// + /// This method works for all graph types. + /// However, for s, this will never return true unless you pass in the exact coordinate of a node, since point nodes do not have a surface. + /// + /// Note: For spherical navmeshes (or other weird shapes), this method will not work as expected, as there's no well defined "up" direction. + /// + /// [Open online documentation to see images] + /// + /// See: to check if a point is on the navmesh of a specific graph. + /// + /// The point to check + public bool IsPointOnNavmesh (Vector3 position) { + const float MaxHorizontalDistance = 0.01f; + const float MaxCostSqr = MaxHorizontalDistance * MaxHorizontalDistance; + // We use the None constraint, instead of Walkable, to avoid ignoring unwalkable nodes that are closer to the point. + var constraint = NearestNodeConstraint.None; + constraint.distanceMetric = DistanceMetric.ClosestAsSeenFromAbove(); + constraint.maxDistanceSqr = MaxCostSqr; + var nearest = GetNearest(position, constraint); + UnityEngine.Assertions.Assert.IsTrue(nearest.node == null || nearest.distanceCostSqr <= MaxCostSqr); + return nearest.node != null && nearest.node.Walkable; + } + + /// + /// Returns the nearest node to a point. + /// This method will search through all graphs and query them for the closest node (walkable or unwalkable) to this position. + /// + /// Equivalent to GetNearest(position, NearestNodeConstraint.None). + /// + /// + /// // Find the closest node to this GameObject's position + /// GraphNode node = AstarPath.active.GetNearest(transform.position).node; + /// + /// if (node.Walkable) { + /// // Yay, the node is walkable, we can place a tower here or something + /// } + /// + /// + /// See: + /// + public NNInfo GetNearest (Vector3 position) { + return GetNearest(position, NearestNodeConstraint.None); + } + + /// + /// Returns the nearest node to a point using the specified NNConstraint. + /// + /// Searches through all graphs for their nearest nodes to the specified position and picks the closest one. + /// The NNConstraint can be used to specify constraints on which nodes can be chosen, such as only picking walkable nodes. + /// + /// + /// GraphNode node = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.Walkable).node; + /// + /// + /// + /// var constraint = NearestNodeConstraint.None; + /// + /// // Constrain the search to walkable nodes only + /// constraint.walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable; + /// + /// // Constrain the search to only nodes with tag 3 or tag 5 + /// // The 'tags' field is a bitmask + /// constraint.tags = (1 << 3) | (1 << 5); + /// + /// var info = AstarPath.active.GetNearest(transform.position, constraint); + /// var node = info.node; + /// var closestPoint = info.position; + /// + /// + /// See: + /// Deprecated: This method is obsolete. Use the overload that takes a NearestNodeConstraint instead. + /// + /// The point to find nodes close to + /// The constraint which determines which graphs and nodes are acceptable to search on. May be null, in which case all nodes will be considered acceptable. + [System.Obsolete("NNConstraint is obsolete. Use the overload that takes a NearestNodeConstraint instead")] + public NNInfo GetNearest (Vector3 position, NNConstraint constraint) { + return GetNearest(position, constraint != null ? constraint.ToNearestNodeConstraint() : NearestNodeConstraint.None); + } + + /// + /// Returns the nearest node to a point using the specified NearestNodeConstraint. + /// + /// Searches through all graphs for their nearest nodes to the specified position and picks the closest one. + /// The can be used to specify constraints on which nodes can be chosen, such as only picking walkable nodes. + /// + /// + /// GraphNode node = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.Walkable).node; + /// + /// + /// + /// var constraint = NearestNodeConstraint.None; + /// + /// // Constrain the search to walkable nodes only + /// constraint.walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable; + /// + /// // Constrain the search to only nodes with tag 3 or tag 5 + /// // The 'tags' field is a bitmask + /// constraint.tags = (1 << 3) | (1 << 5); + /// + /// var info = AstarPath.active.GetNearest(transform.position, constraint); + /// var node = info.node; + /// var closestPoint = info.position; + /// + /// + /// See: + /// + /// The point to find nodes close to + /// The constraint which determines which graphs and nodes are acceptable to search on. + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public NNInfo GetNearest (Vector3 position, NearestNodeConstraint constraint) { + return GetNearest(position, ref constraint); + } + + internal NNInfo GetNearest (Vector3 position, ref NearestNodeConstraint constraint) { + // Cache property lookups + var graphs = this.graphs; + constraint.maxDistanceSqr = constraint.maxDistanceSqrOrDefault(active); + UnityEngine.Assertions.Assert.IsTrue(constraint.maxDistanceSqr >= 0); + NNInfo nearestNode = NNInfo.Empty; + + if (graphs == null || graphs.Length == 0) return nearestNode; + + // Use a fast path in case there is only one graph. + // This improves performance by about 10% when there is only one graph. + if (graphs.Length == 1) { + var graph = graphs[0]; + if (graph == null || !constraint.graphMask.Contains(graph)) { + return nearestNode; + } + + nearestNode = graph.GetNearest(position, ref constraint); + UnityEngine.Assertions.Assert.IsTrue(nearestNode.node == null || nearestNode.distanceCostSqr <= constraint.maxDistanceSqr); + } else { + UnsafeSpan<(float, uint)> distances; + unsafe { + // The number of graphs is limited to GraphNode.MaxGraphIndex (256), + // and typically there are only a few graphs, so allocating this on the stack is fine. + var distancesPtr = stackalloc (float, int)[graphs.Length]; + distances = new UnsafeSpan<(float, uint)>(distancesPtr, graphs.Length); + } + + // Iterate through all graphs and find a lower bound on the distance to the nearest node. + // We then sort these distances and run the full get nearest search on the graphs in order of increasing distance. + // This is an optimization to avoid running the full get nearest search on graphs which are far away. + int numCandidateGraphs = 0; + for (uint i = 0; i < (uint)graphs.Length; i++) { + NavGraph graph = graphs[i]; + + // Check if this graph should be searched + if (graph == null || !constraint.graphMask.Contains(i)) { + continue; + } + var lowerBound = graph.NearestNodeDistanceSqrLowerBound(position, ref constraint); + if (lowerBound > constraint.maxDistanceSqr) continue; + + distances[numCandidateGraphs++] = (lowerBound, i); + } + distances = distances.Slice(0, numCandidateGraphs); + distances.Sort(); + for (int i = 0; i < distances.Length; i++) { + if (distances[i].Item1 > constraint.maxDistanceSqr) break; + var graph = graphs[distances[i].Item2]; + NNInfo nnInfo = graph.GetNearest(position, ref constraint); + if (nnInfo.distanceCostSqr < constraint.maxDistanceSqr) { + constraint.maxDistanceSqr = nnInfo.distanceCostSqr; + nearestNode = nnInfo; + } + } + } + return nearestNode; + } + + /// + /// Finds the closest navmesh border to the given position. + /// + /// Returns: True if a border was found, false otherwise. + /// + /// [Open online documentation to see images] + /// [Open online documentation to see videos] + /// + /// + /// var origin = target.position; + /// using (Draw.WithLineWidth(2)) { + /// if (AstarPath.active.GetNearestBorder(origin, NearestNodeConstraint.Walkable, out var hit)) { + /// Draw.Arrow(origin, hit.point, Palette.Yellow); + /// Draw.SphereOutline(origin, 0.1f, Palette.Yellow); + /// Draw.SphereOutline(hit.point, 0.1f, Palette.Yellow); + /// Draw.Ray(hit.tangentOrigin, hit.tangent, Palette.Purple); + /// } + /// } + /// + /// + /// This method will first find the nearest node to the specified position using the specified constraint. + /// You can think of this as the node the agent is currently standing on. + /// Then, it will search for the closest border to that node, which is connected to it. + /// + /// The constraint is only used to find the nearest node, it does not inform what borders are found afterwards. + /// Notably, if an agent cannot traverse a given tag, it will still not consider a border between that tag and a node with a walkable tag as a valid border. + /// + /// On a point graph, this method will always return false, since point nodes do not have any borders as such. + /// + /// Note: hit.node will always be null, as it is currently not possible for the code to figure out which border belongs to what node. You can call with hit.point if you need this information. + /// + /// See: + /// See: + /// See: + /// + /// The position to search from. + /// Constraint for which nodes are acceptable for the agent to stand on. + /// Information about the closest border, if any was found. + public bool GetNearestBorder (Vector3 position, NearestNodeConstraint constraint, out GraphHitInfo hit) { + var nn = GetNearest(position, constraint); + return GetNearestBorder(position, nn.node, out hit); + } + + /// + /// Finds the closest navmesh border to the given position. + /// + /// [Open online documentation to see images] + /// [Open online documentation to see videos] + /// + /// + /// var origin = target.position; + /// using (Draw.WithLineWidth(2)) { + /// if (AstarPath.active.GetNearestBorder(origin, NearestNodeConstraint.Walkable, out var hit)) { + /// Draw.Arrow(origin, hit.point, Palette.Yellow); + /// Draw.SphereOutline(origin, 0.1f, Palette.Yellow); + /// Draw.SphereOutline(hit.point, 0.1f, Palette.Yellow); + /// Draw.Ray(hit.tangentOrigin, hit.tangent, Palette.Purple); + /// } + /// } + /// + /// + /// Returns: True if a border was found, false otherwise. + /// + /// Note: hit.node will always be null, as it is currently not possible for the code to figure out which border belongs to what node. You can call with hit.point if you need this information. + /// + /// See: + /// See: + /// See: + /// + /// The position to search from. + /// The node to search from. Only borders connected (directly or indirectly) to this node will be considered. This could for example be the node that an agent is currently standing on. + /// Information about the closest border, if any was found. + public bool GetNearestBorder (Vector3 position, GraphNode sourceNode, out GraphHitInfo hit) { + if (sourceNode is PointNode) { + // Early out on point graphs, as it would otherwise try to search through the whole graph, looking for a border that won't exist. + hit = new GraphHitInfo { + origin = position, + point = Vector3.positiveInfinity, + }; + return false; + } + + var scratchBuffer = new Unity.Collections.NativeList(Unity.Collections.Allocator.Temp); + scratchBuffer.Clear(); + var borderData = GetNavmeshBorderData(out var readLock); + readLock.dependency.Complete(); + hit = borderData.GetClosestEdge(sourceNode != null && sourceNode.Walkable ? sourceNode.HierarchicalNodeIndex : -1, position, scratchBuffer); + readLock.Unlock(); + return hit.point.x != float.PositiveInfinity; + } + + /// + /// True if there is an obstacle between start and end on the navmesh. + /// + /// This is a simple api to check if there is an obstacle between two points. + /// If you need more detailed information, you can use or (for navmesh/recast graphs). + /// Those overloads can also return which nodes the line passed through, and allow you use custom node filtering. + /// + /// + /// var start = transform.position; + /// var end = start + Vector3.forward * 10; + /// if (AstarPath.active.Linecast(start, end)) { + /// Debug.DrawLine(start, end, Color.red); + /// } else { + /// Debug.DrawLine(start, end, Color.green); + /// } + /// + /// + /// Note: Only grid, recast and navmesh graphs support linecasts. The closest raycastable graph to the start point will be used for the linecast. + /// Note: Linecasts cannot pass through off-mesh links. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// See: + /// See: + /// See: linecasting (view in online documentation for working links), for more details about linecasting + /// + public bool Linecast (Vector3 start, Vector3 end) { + var startGraph = ClosestRaycastableGraph(start); + return startGraph == null || startGraph.Linecast(start, end); + } + + /// + /// True if there is an obstacle between start and end on the navmesh. + /// + /// This is a simple api to check if there is an obstacle between two points. + /// If you need more detailed information, you can use or (for navmesh/recast graphs). + /// Those overloads can also return which nodes the line passed through, and allow you use custom node filtering. + /// + /// + /// var start = transform.position; + /// var end = start + Vector3.forward * 10; + /// if (AstarPath.active.Linecast(start, end, out var hit)) { + /// Debug.DrawLine(start, end, Color.red); + /// Debug.DrawRay(hit.point, Vector3.up, Color.red); + /// } else { + /// Debug.DrawLine(start, end, Color.green); + /// } + /// + /// + /// Note: Only grid, recast and navmesh graphs support linecasts. The closest raycastable graph to the start point will be used for the linecast. + /// Note: Linecasts cannot pass through off-mesh links. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// See: + /// See: + /// See: linecasting (view in online documentation for working links), for more details about linecasting + /// + public bool Linecast (Vector3 start, Vector3 end, out GraphHitInfo hit) { + var startGraph = ClosestRaycastableGraph(start); + if (startGraph == null) { + hit = new GraphHitInfo { + origin = start, + point = start, + }; + return true; + } + return startGraph.Linecast(start, end, out hit); + } + + IRaycastableGraph ClosestRaycastableGraph (Vector3 point) { + if (data.graphs == null) return null; + + // Most games have just a single raycastable graph. + IRaycastableGraph graph = null; + int found = 0; + for (int i = 0; i < data.graphs.Length; i++) { + if (data.graphs[i] is IRaycastableGraph g) { + graph = g; + found++; + } + } + + // If there's more than one graph that can perform linecasts, + // then find the nearest graph to the point. + if (found > 1) { + var startNode = GetNearest(point); + graph = startNode.node?.Graph as IRaycastableGraph; + } + return graph; + } + + /// + /// Returns the nearest node to a ray (slow). + /// Warning: This function is brute-force and very slow, use with caution + /// + public GraphNode GetNearest (Ray ray) { + if (graphs == null) return null; + + float minDist = Mathf.Infinity; + GraphNode nearestNode = null; + + Vector3 lineDirection = ray.direction; + Vector3 lineOrigin = ray.origin; + + for (int i = 0; i < graphs.Length; i++) { + NavGraph graph = graphs[i]; + + graph.GetNodes(node => { + Vector3 pos = (Vector3)node.position; + Vector3 p = lineOrigin+(Vector3.Dot(pos-lineOrigin, lineDirection)*lineDirection); + + float tmp = Mathf.Abs(p.x-pos.x); + tmp *= tmp; + if (tmp > minDist) return; + + tmp = Mathf.Abs(p.z-pos.z); + tmp *= tmp; + if (tmp > minDist) return; + + float dist = (p-pos).sqrMagnitude; + + if (dist < minDist) { + minDist = dist; + nearestNode = node; + } + }); + } + + return nearestNode; + } + + /// + /// Captures a snapshot of a part of the graphs, to allow restoring it later. + /// + /// This is useful if you want to do a graph update, but you want to be able to restore the graph to the previous state. + /// + /// The snapshot will capture enough information to restore the graphs, assuming the world only changed within the given bounding box. + /// This means the captured region may be larger than the bounding box. + /// + /// Limitations: + /// - Currently, the and supports snapshots. Other graph types do not support it. + /// - The graph must not change its dimensions or other core parameters between the time the snapshot is taken and the time it is restored. + /// - Custom node connections may not be preserved. Unless they are added as off-mesh links using e.g. a component. + /// - The snapshot must not be captured during a work item, graph update or when the graphs are being scanned, as the graphs may not be in a consistent state during those times. + /// + /// See: , which uses this method internally. + /// See: + /// + /// Note: You must dispose the returned snapshot when you are done with it, to avoid leaking memory. + /// + public GraphSnapshot Snapshot (Bounds bounds, GraphMask graphMask) { + Profiler.BeginSample("Capturing Graph Snapshot"); + var inner = new List(); + for (uint i = 0; i < (uint)graphs.Length; i++) { + if (graphs[i] != null && graphMask.Contains(i)) { + var s = graphs[i].Snapshot(bounds); + if (s != null) inner.Add(s); + } + } + Profiler.EndSample(); + return new GraphSnapshot(inner); + } + + /// + /// Allows you to access read-only graph data in jobs safely. + /// + /// You can for example use AstarPath.active.GetNearest(...) in a job. + /// + /// Using is always safe to use in jobs even without calling this method. + /// + /// When a graph update, work item, or graph scan would start, it will first block on the given dependency + /// to ensure no race conditions occur. + /// + /// If you do not call this method, then a graph update might start in the middle of your job, causing race conditions + /// and all manner of other hard-to-diagnose bugs. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + /// See: + /// See: + /// + public RWLock.ReadLockAsync LockGraphDataForReading() => graphDataLock.Read(); + + /// + /// Aquires an exclusive lock on the graph data asynchronously. + /// This is used when graphs want to modify graph data. + /// + /// This is a low-level primitive, usually you do not need to use this method. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + /// See: + /// See: + /// + public RWLock.WriteLockAsync LockGraphDataForWriting() => graphDataLock.Write(); + + /// + /// Aquires an exclusive lock on the graph data. + /// This is used when graphs want to modify graph data. + /// + /// This is a low-level primitive, usually you do not need to use this method. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + /// See: + /// See: + /// See: + /// + public RWLock.LockSync LockGraphDataForWritingSync() => graphDataLock.WriteSync(); + + /// + /// Obstacle data for navmesh edges. + /// + /// This can be used to get information about the edge/borders of the navmesh. + /// It can also be queried in burst jobs. Just make sure you release the read lock after you are done with it. + /// + /// Note: This is not a method that you are likely to need to use. + /// It is used internally for things like local avoidance. + /// + public NavmeshEdges.NavmeshBorderData GetNavmeshBorderData(out RWLock.CombinedReadLockAsync readLock) => hierarchicalGraph.navmeshEdges.GetNavmeshEdgeData(out readLock); +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs.meta new file mode 100644 index 0000000..f5172e5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/AstarPath.cs.meta @@ -0,0 +1,16 @@ +fileFormatVersion: 2 +guid: 78396926cbbfc4ac3b48fc5fc34a87d1 +labels: +- Pathfinder +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - gizmoSurfaceMaterial: {fileID: 2100000, guid: 5ce51318bbfb1466188b929a68a6bd3a, + type: 2} + - gizmoLineMaterial: {fileID: 2100000, guid: 91035448860ba4e708919485c73f7edc, type: 2} + executionOrder: -10000 + icon: {fileID: 2800000, guid: 1620f833be5302149a071c06944d3e9b, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections.meta new file mode 100644 index 0000000..7bf7a0b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b2120e89e2185f4bbaa86f8099157df +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs new file mode 100644 index 0000000..093ea25 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs @@ -0,0 +1,109 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using Pathfinding.Pooling; + +namespace Pathfinding.Util { + /// Various utilities for handling arrays and memory + public static class Memory { + /// + /// Returns a new array with at most length newLength. + /// The array will contain a copy of all elements of arr up to but excluding the index newLength. + /// + public static T[] ShrinkArray(T[] arr, int newLength) { + newLength = Math.Min(newLength, arr.Length); + var shrunkArr = new T[newLength]; + Array.Copy(arr, shrunkArr, newLength); + return shrunkArr; + } + + /// Swaps the variables a and b + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static void Swap(ref T a, ref T b) { + T tmp = a; + + a = b; + b = tmp; + } + + public static void Realloc(ref NativeArray arr, int newSize, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : struct { + if (arr.IsCreated && arr.Length >= newSize) return; + + var newArr = new NativeArray(newSize, allocator, options); + if (arr.IsCreated) { + // Copy over old data + NativeArray.Copy(arr, newArr, arr.Length); + arr.Dispose(); + } + arr = newArr; + } + + public static void Realloc(ref T[] arr, int newSize) { + if (arr == null) { + arr = new T[newSize]; + } else if (newSize > arr.Length) { + var newArr = new T[newSize]; + arr.CopyTo(newArr, 0); + arr = newArr; + } + } + + public static T[] UnsafeAppendBufferToArray(UnsafeAppendBuffer src) where T : unmanaged { + var elementCount = src.Length / UnsafeUtility.SizeOf(); + var dst = new T[elementCount]; + + unsafe { + var gCHandle = System.Runtime.InteropServices.GCHandle.Alloc(dst, System.Runtime.InteropServices.GCHandleType.Pinned); + System.IntPtr value = gCHandle.AddrOfPinnedObject(); + UnsafeUtility.MemCpy((byte*)(void*)value, src.Ptr, (long)elementCount * (long)UnsafeUtility.SizeOf()); + gCHandle.Free(); + } + return dst; + } + + public static bool SequenceEqual(T[] a, T[] b) where T : IEquatable { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + if (a.Length != b.Length) return false; + + for (int i = 0; i < a.Length; i++) { + if (!a[i].Equals(b[i])) return false; + } + return true; + } + + public static void Rotate3DArray(T[] arr, int3 size, int dx, int dz) { + int width = size.x; + int height = size.y; + int depth = size.z; + dx = dx % width; + dz = dz % depth; + if (dx != 0) { + if (dx < 0) dx = width + dx; + var tmp = ArrayPool.Claim(dx); + for (int y = 0; y < height; y++) { + var offset = y * width * depth; + for (int z = 0; z < depth; z++) { + Array.Copy(arr, offset + z * width + width - dx, tmp, 0, dx); + Array.Copy(arr, offset + z * width, arr, offset + z * width + dx, width - dx); + Array.Copy(tmp, 0, arr, offset + z * width, dx); + } + } + ArrayPool.Release(ref tmp); + } + + if (dz != 0) { + if (dz < 0) dz = depth + dz; + var tmp = ArrayPool.Claim(dz * width); + for (int y = 0; y < height; y++) { + var offset = y * width * depth; + Array.Copy(arr, offset + (depth - dz) * width, tmp, 0, dz * width); + Array.Copy(arr, offset, arr, offset + dz * width, (depth - dz) * width); + Array.Copy(tmp, 0, arr, offset, dz * width); + } + ArrayPool.Release(ref tmp); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs.meta new file mode 100644 index 0000000..2b61ad2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/AstarMemory.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9bdecddfdfec947eb8ed96282e4b1fe1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs new file mode 100644 index 0000000..3c5700a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs @@ -0,0 +1,387 @@ +// #define VALIDATE_BINARY_HEAP +#pragma warning disable 162 +#pragma warning disable 429 +using Unity.Mathematics; +using Unity.Collections; +using Unity.Burst; +using Unity.Burst.CompilerServices; + +namespace Pathfinding { + using Pathfinding.Collections; + + /// + /// Binary heap implementation. + /// Binary heaps are really fast for ordering nodes in a way that + /// makes it possible to get the node with the lowest F score. + /// Also known as a priority queue. + /// + /// This has actually been rewritten as a 4-ary heap + /// for performance, but it's the same principle. + /// + /// See: http://en.wikipedia.org/wiki/Binary_heap + /// See: https://en.wikipedia.org/wiki/D-ary_heap + /// + [BurstCompile] + public struct BinaryHeap { + /// Internal backing array for the heap + private UnsafeSpan heap; + + /// Number of items in the tree + public int numberOfItems; + uint insertionOrder; + + /// Ties between elements that have the same F score can be broken by the H score or by insertion order + public TieBreaking tieBreaking; + + public enum TieBreaking : byte { + HScore, + InsertionOrder, + } + + /// The tree will grow by at least this factor every time it is expanded + public const float GrowthFactor = 2; + + /// + /// Number of children of each node in the tree. + /// Different values have been tested and 4 has been empirically found to perform the best. + /// See: https://en.wikipedia.org/wiki/D-ary_heap + /// + const int D = 4; + + public const ushort NotInHeap = 0xFFFF; + + /// True if the heap does not contain any elements + public bool isEmpty => numberOfItems <= 0; + + /// Item in the heap + private struct HeapNode { + public uint pathNodeIndex; + /// Bitpacked F and G scores + public ulong sortKey; + + public HeapNode (uint pathNodeIndex, uint tieBreaker, uint f) { + this.pathNodeIndex = pathNodeIndex; + this.sortKey = ((ulong)f << 32) | (ulong)tieBreaker; + } + + public uint F { + get => (uint)(sortKey >> 32); + set => sortKey = (sortKey & 0xFFFFFFFFUL) | ((ulong)value << 32); + } + + public uint TieBreaker { + get => (uint)sortKey; + set => sortKey = (sortKey & 0xFFFFFFFF00000000UL) | (ulong)value; + } + } + + /// + /// Rounds up v so that it has remainder 1 when divided by D. + /// I.e it is of the form n*D + 1 where n is any non-negative integer. + /// + static int RoundUpToNextMultipleMod1 (int v) { + // I have a feeling there is a nicer way to do this + return v + (4 - ((v-1) % D)) % D; + } + + /// Create a new heap with the specified initial capacity + public BinaryHeap (int capacity) { + // Make sure the size has remainder 1 when divided by D + // This allows us to always guarantee that indices used in the Remove method + // will never throw out of bounds exceptions + capacity = RoundUpToNextMultipleMod1(capacity); + + heap = new UnsafeSpan(Unity.Collections.Allocator.Persistent, capacity); + numberOfItems = 0; + insertionOrder = 0; + tieBreaking = TieBreaking.HScore; + } + + public void Dispose () { + unsafe { + AllocatorManager.Free(Allocator.Persistent, heap.ptr, heap.Length); + } + } + + /// Removes all elements from the heap + public void Clear (UnsafeSpan pathNodes) { + // Clear all heap indices, and references to the heap nodes + for (int i = 0; i < numberOfItems; i++) { + pathNodes[heap[i].pathNodeIndex].heapIndex = NotInHeap; + } + + numberOfItems = 0; + insertionOrder = 0; + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public uint GetPathNodeIndex(int heapIndex) => heap[heapIndex].pathNodeIndex; + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public uint GetH(int heapIndex) => tieBreaking == TieBreaking.HScore ? heap[heapIndex].TieBreaker : 0; + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public uint GetF(int heapIndex) => heap[heapIndex].F; + + /// + /// Replaces the H score of a node in the heap. + /// + /// Warning: Assumes that ties are broken by H scores. + /// + public void SetH (int heapIndex, uint h) { + UnityEngine.Assertions.Assert.IsTrue(tieBreaking == TieBreaking.HScore); + ref var node = ref heap[heapIndex]; + var g = node.F - node.TieBreaker; + node.TieBreaker = h; + node.F = g + h; + } + + /// Expands to a larger backing array when the current one is too small + static void Expand (ref UnsafeSpan heap) { + // 65533 == 1 mod 4 and slightly smaller than 1<<16 = 65536 + int newSize = math.max(heap.Length+4, math.min(65533, (int)math.round(heap.Length*GrowthFactor))); + + // Make sure the size has remainder 1 when divided by D + // This allows us to always guarantee that indices used in the Remove method + // will never throw out of bounds exceptions + newSize = RoundUpToNextMultipleMod1(newSize); + + // Check if the heap is really large + // Also note that heaps larger than this are not supported + // since PathNode.heapIndex is a ushort and can only store + // values up to 65535 (NotInHeap = 65535 is reserved however) + if (newSize > (1<<16) - 2) { + throw new System.Exception("Binary Heap Size really large (>65534). A heap size this large is probably the cause of pathfinding running in an infinite loop. "); + } + + var newHeap = new UnsafeSpan(Unity.Collections.Allocator.Persistent, newSize); + newHeap.CopyFrom(heap); + unsafe { + AllocatorManager.Free(Allocator.Persistent, heap.ptr, heap.Length); + } +#if ASTARDEBUG + UnityEngine.Debug.Log("Resizing binary heap to "+newSize); +#endif + heap = newHeap; + } + + /// Adds a node to the heap + public void Add (UnsafeSpan nodes, uint pathNodeIndex, uint g, uint h) { + Add(ref this, ref nodes, pathNodeIndex, g, h, insertionOrder++, tieBreaking); + } + + [BurstCompile] + static void Add (ref BinaryHeap binaryHeap, ref UnsafeSpan nodes, uint pathNodeIndex, uint g, uint h, uint insertionOrder, TieBreaking tieBreaking) { + ref var numberOfItems = ref binaryHeap.numberOfItems; + ref var heap = ref binaryHeap.heap; + var f = g + h; + + // We need to shift the insertion order by 2 bits, since there are some bit-stealing shenanigans going on in the Remove method + var tieBreaker = tieBreaking == TieBreaking.HScore ? h : (insertionOrder << 2); + + // Check if node is already in the heap + ref var node = ref nodes[pathNodeIndex]; + if (node.heapIndex != NotInHeap) { + var activeNode = new HeapNode(pathNodeIndex, tieBreaker, f); + UnityEngine.Assertions.Assert.AreEqual(activeNode.pathNodeIndex, pathNodeIndex); + DecreaseKey(heap, nodes, activeNode, node.heapIndex); + Validate(ref nodes, ref binaryHeap); + } else { + if (numberOfItems == heap.Length) { + Expand(ref heap); + } + Validate(ref nodes, ref binaryHeap); + + DecreaseKey(heap, nodes, new HeapNode(pathNodeIndex, tieBreaker, f), (ushort)numberOfItems); + numberOfItems++; + Validate(ref nodes, ref binaryHeap); + } + } + + static void DecreaseKey (UnsafeSpan heap, UnsafeSpan nodes, HeapNode node, ushort index) { + // This is where 'obj' is in the binary heap logically speaking + // (for performance reasons we don't actually store it there until + // we know the final index, that's just a waste of CPU cycles) + uint bubbleIndex = index; + + while (bubbleIndex != 0) { + // Parent node of the bubble node + uint parentIndex = (bubbleIndex-1) / D; + + Hint.Assume(parentIndex < heap.length); + Hint.Assume(bubbleIndex < heap.length); + if (node.sortKey < heap[parentIndex].sortKey) { + // Swap the bubble node and parent node + // (we don't really need to store the bubble node until we know the final index though + // so we do that after the loop instead) + heap[bubbleIndex] = heap[parentIndex]; + nodes[heap[bubbleIndex].pathNodeIndex].heapIndex = (ushort)bubbleIndex; + bubbleIndex = parentIndex; + } else { + break; + } + } + + Hint.Assume(bubbleIndex < heap.length); + heap[bubbleIndex] = node; + nodes[node.pathNodeIndex].heapIndex = (ushort)bubbleIndex; + } + + /// + /// Returns the node with the lowest F score from the heap. + /// + /// Note: If is set not set to HScore, the returned h score will be 0. + /// + public uint Remove (UnsafeSpan nodes, out uint g, out uint h) { + var index = Remove(ref nodes, ref this, out var removedTieBreaker, out var removedF); + h = tieBreaking == TieBreaking.HScore ? removedTieBreaker : 0; + g = removedF - h; + return index; + } + + [BurstCompile] + static uint Remove (ref UnsafeSpan nodes, ref BinaryHeap binaryHeap, [NoAlias] out uint removedTieBreaker, [NoAlias] out uint removedF) { + ref var numberOfItems = ref binaryHeap.numberOfItems; + var heap = binaryHeap.heap; + + if (numberOfItems == 0) { + throw new System.InvalidOperationException("Removing item from empty heap"); + } + + // This is the smallest item in the heap. + // Mark it as removed from the heap. + Hint.Assume(0UL < heap.length); + uint returnIndex = heap[0].pathNodeIndex; + nodes[returnIndex].heapIndex = NotInHeap; + removedTieBreaker = heap[0].TieBreaker; + removedF = heap[0].F; + + numberOfItems--; + if (numberOfItems == 0) { + return returnIndex; + } + + // Last item in the heap array + Hint.Assume((uint)numberOfItems < heap.length); + var swapItem = heap[numberOfItems]; + uint swapIndex = 0; + ulong comparisonKey = swapItem.sortKey & ~0x3UL; + + // Trickle upwards + while (true) { + var parent = swapIndex; + uint pd = parent * D + 1; + + // If this holds, then the indices used + // below are guaranteed to not throw an index out of bounds + // exception since we choose the size of the array in that way + if (pd < numberOfItems) { + // Find the child node with the smallest F score, or if equal, the smallest G score. + // The sorting key is the tuple (F,G). + // However, to be able to easily get the smallest index, we steal the lowest 2 bits of G + // and use it for the child index (0..3) instead. + // This means that tie-breaking will not be perfect, but in all practical cases it will + // yield exactly the same result since G scores typically differ by more than 4 anyway. + Hint.Assume(pd+0 < heap.length); + ulong l0 = (heap[pd+0].sortKey & ~0x3UL) | 0; + Hint.Assume(pd+1 < heap.length); + ulong l1 = (heap[pd+1].sortKey & ~0x3UL) | 1; + Hint.Assume(pd+2 < heap.length); + ulong l2 = (heap[pd+2].sortKey & ~0x3UL) | 2; + Hint.Assume(pd+3 < heap.length); + ulong l3 = (heap[pd+3].sortKey & ~0x3UL) | 3; + + ulong smallest = l0; + // Not all children may exist, so we need to check that the index is valid + // TODO: Could optimize by ensuring that the sort keys for invalid children are always set to ulong.MaxValue + if (pd+1 < numberOfItems) smallest = math.min(smallest, l1); + if (pd+2 < numberOfItems) smallest = math.min(smallest, l2); + if (pd+3 < numberOfItems) smallest = math.min(smallest, l3); + + if ((smallest & ~0x3UL) < comparisonKey) { + swapIndex = pd + (uint)(smallest & 0x3UL); + + // One of the parent's children are smaller or equal, swap them + // (actually we are just pretenting we swapped them, we hold the swapItem + // in local variable and only assign it once we know the final index) + Hint.Assume(parent < heap.length); + Hint.Assume(swapIndex < heap.length); + heap[parent] = heap[swapIndex]; + Hint.Assume(swapIndex < heap.length); + nodes[heap[swapIndex].pathNodeIndex].heapIndex = (ushort)parent; + } else { + break; + } + } else { + break; + } + } + + // Assign element to the final position + Hint.Assume(swapIndex < heap.length); + heap[swapIndex] = swapItem; + nodes[swapItem.pathNodeIndex].heapIndex = (ushort)swapIndex; + + // For debugging + Validate(ref nodes, ref binaryHeap); + + return returnIndex; + } + + [System.Diagnostics.Conditional("VALIDATE_BINARY_HEAP")] + static void Validate (ref UnsafeSpan nodes, ref BinaryHeap binaryHeap) { + for (int i = 1; i < binaryHeap.numberOfItems; i++) { + int parentIndex = (i-1)/D; + if (binaryHeap.heap[parentIndex].F > binaryHeap.heap[i].F) { + throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + binaryHeap.heap[parentIndex].F + " > " + binaryHeap.heap[i].F + " ) "); + } + + if (binaryHeap.heap[parentIndex].sortKey > binaryHeap.heap[i].sortKey) { + throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + binaryHeap.heap[parentIndex].F + " > " + binaryHeap.heap[i].F + " ) "); + } + + if (nodes[binaryHeap.heap[i].pathNodeIndex].heapIndex != i) { + throw new System.Exception("Invalid heap index"); + } + } + } + + /// + /// Rebuilds the heap by trickeling down all items. + /// Usually called after the hTarget on a path has been changed + /// + public void Rebuild (UnsafeSpan nodes) { +#if ASTARDEBUG + int changes = 0; +#endif + + for (int i = 2; i < numberOfItems; i++) { + int bubbleIndex = i; + var node = heap[i]; + uint nodeF = node.F; + while (bubbleIndex != 1) { + int parentIndex = bubbleIndex / D; + + if (nodeF < heap[parentIndex].F) { + heap[bubbleIndex] = heap[parentIndex]; + nodes[heap[bubbleIndex].pathNodeIndex].heapIndex = (ushort)bubbleIndex; + + heap[parentIndex] = node; + nodes[heap[parentIndex].pathNodeIndex].heapIndex = (ushort)parentIndex; + + bubbleIndex = parentIndex; +#if ASTARDEBUG + changes++; +#endif + } else { + break; + } + } + } + +#if ASTARDEBUG + UnityEngine.Debug.Log("+++ Rebuilt Heap - "+changes+" changes +++"); +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs.meta new file mode 100644 index 0000000..0ba2009 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/BinaryHeap.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: eb4299e8747f44ad2b4e086752108ea3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs new file mode 100644 index 0000000..3bd4f80 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs @@ -0,0 +1,268 @@ +using System.Collections; +using System.Collections.Generic; +using Pathfinding.Pooling; +using Unity.Profiling; +using System.Runtime.CompilerServices; + +namespace Pathfinding.Collections { + /// + /// Implements an efficient circular buffer that can be appended to in both directions. + /// + /// See: + /// + public struct CircularBuffer : IReadOnlyList, IReadOnlyCollection { + internal T[] data; + internal int head; + int length; + + /// Number of items in the buffer + public readonly int Length { + [IgnoredByDeepProfiler] + get { + return length; + } + } + /// Absolute index of the first item in the buffer, may be negative or greater than + public readonly int AbsoluteStartIndex => head; + /// Absolute index of the last item in the buffer, may be negative or greater than + public readonly int AbsoluteEndIndex => head + length - 1; + + /// First item in the buffer, throws if the buffer is empty + public readonly ref T First { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { + return ref data[head & (data.Length-1)]; + } + } + + /// Last item in the buffer, throws if the buffer is empty + public readonly ref T Last { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { + return ref data[(head+length-1) & (data.Length-1)]; + } + } + + readonly int IReadOnlyCollection.Count { + [IgnoredByDeepProfiler] + get { + return length; + } + } + + /// Create a new buffer with the given capacity + public CircularBuffer(int initialCapacity) { + data = ArrayPool.Claim(initialCapacity); + head = 0; + length = 0; + } + + /// + /// Create a new buffer using the given array as an internal store. + /// This will take ownership of the given array. + /// + public CircularBuffer(T[] backingArray) { + data = backingArray; + head = 0; + length = 0; + } + + /// Resets the buffer's length to zero. Does not clear the current allocation + public void Clear () { + length = 0; + head = 0; + } + + /// Appends a list of items to the end of the buffer + public void AddRange (List items) { + // TODO: Can be optimized + for (int i = 0; i < items.Count; i++) PushEnd(items[i]); + } + + /// Pushes a new item to the start of the buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public void PushStart (T item) { + if (data == null || length >= data.Length) Grow(); + length += 1; + head -= 1; + this[0] = item; + } + + /// Pushes a new item to the end of the buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public void PushEnd (T item) { + if (data == null || length >= data.Length) Grow(); + length += 1; + this[length-1] = item; + } + + /// Pushes a new item to the start or the end of the buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public void Push (bool toStart, T item) { + if (toStart) PushStart(item); + else PushEnd(item); + } + + /// Removes and returns the first element + [IgnoredByDeepProfiler] + public T PopStart () { + if (length == 0) throw new System.InvalidOperationException(); + var r = this[0]; + head++; + length--; + return r; + } + + /// Removes and returns the last element + [IgnoredByDeepProfiler] + public T PopEnd () { + if (length == 0) throw new System.InvalidOperationException(); + var r = this[length-1]; + length--; + return r; + } + + /// Pops either from the start or from the end of the buffer + [IgnoredByDeepProfiler] + public T Pop (bool fromStart) { + if (fromStart) return PopStart(); + else return PopEnd(); + } + + /// Return either the first element or the last element + public readonly T GetBoundaryValue (bool start) { + return GetAbsolute(start ? AbsoluteStartIndex : AbsoluteEndIndex); + } + + /// Inserts an item at the given absolute index + public void InsertAbsolute (int index, T item) { + SpliceUninitializedAbsolute(index, 0, 1); + data[index & (data.Length - 1)] = item; + } + + /// Removes toRemove items from the buffer, starting at startIndex, and then inserts the toInsert items at startIndex + public void Splice (int startIndex, int toRemove, List toInsert) { + SpliceAbsolute(startIndex + head, toRemove, toInsert); + } + + /// Like , but startIndex is an absolute index + public void SpliceAbsolute (int startIndex, int toRemove, List toInsert) { + if (toInsert == null) { + SpliceUninitializedAbsolute(startIndex, toRemove, 0); + } else { + SpliceUninitializedAbsolute(startIndex, toRemove, toInsert.Count); + for (int i = 0; i < toInsert.Count; i++) data[(startIndex + i) & (data.Length - 1)] = toInsert[i]; + } + } + + /// Like , but the newly inserted items are left in an uninitialized state + public void SpliceUninitialized (int startIndex, int toRemove, int toInsert) { + SpliceUninitializedAbsolute(startIndex + head, toRemove, toInsert); + } + + /// Like , but startIndex is an absolute index + public void SpliceUninitializedAbsolute (int startIndex, int toRemove, int toInsert) { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (startIndex - head < 0 || startIndex + toRemove - head > length) throw new System.ArgumentOutOfRangeException(); +#endif + var itemsToAdd = toInsert - toRemove; + while (this.length + itemsToAdd > this.data.Length) Grow(); + + // move items [startIndex+length .. end] itemsToAdd steps forward in the array + MoveAbsolute(startIndex + toRemove, AbsoluteEndIndex, itemsToAdd); + this.length += itemsToAdd; + } + + void MoveAbsolute (int startIndex, int endIndex, int deltaIndex) { + if (deltaIndex > 0) { + for (int i = endIndex; i >= startIndex; i--) data[(i+deltaIndex) & (data.Length-1)] = data[i & (data.Length-1)]; + } else if (deltaIndex < 0) { + for (int i = startIndex; i <= endIndex; i++) data[(i+deltaIndex) & (data.Length-1)] = data[i & (data.Length-1)]; + } + } + + /// Indexes the buffer, with index 0 being the first element + public T this[int index] { + [MethodImpl(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + readonly get { +#if UNITY_EDITOR + if ((uint)index >= length) throw new System.ArgumentOutOfRangeException(); +#endif + return data[(index+head) & (data.Length-1)]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + set { +#if UNITY_EDITOR + if ((uint)index >= length) throw new System.ArgumentOutOfRangeException(); +#endif + data[(index+head) & (data.Length-1)] = value; + } + } + + /// + /// Indexes the buffer using absolute indices. + /// When pushing to and popping from the buffer, the absolute indices do not change. + /// So e.g. after doing PushStart(x) on an empty buffer, GetAbsolute(-1) will get the newly pushed element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public readonly T GetAbsolute (int index) { +#if UNITY_EDITOR + if ((uint)(index - head) >= length) throw new System.ArgumentOutOfRangeException(); +#endif + return data[index & (data.Length-1)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public readonly void SetAbsolute (int index, T value) { +#if UNITY_EDITOR + if ((uint)(index - head) >= length) throw new System.ArgumentOutOfRangeException(); +#endif + data[index & (data.Length-1)] = value; + } + + void Grow () { + var newData = ArrayPool.Claim(System.Math.Max(4, data != null ? data.Length*2 : 0)); + if (data != null) { + var inOrderItems = data.Length - (head & (data.Length-1)); + System.Array.Copy(data, head & (data.Length-1), newData, head & (newData.Length - 1), inOrderItems); + var wraparoundItems = length - inOrderItems; + if (wraparoundItems > 0) System.Array.Copy(data, 0, newData, (head + inOrderItems) & (newData.Length - 1), wraparoundItems); + + ArrayPool.Release(ref data); + } + data = newData; + } + + /// Release the backing array of this buffer back into an array pool + public void Pool () { + ArrayPool.Release(ref data); + length = 0; + head = 0; + } + + public IEnumerator GetEnumerator () { + for (int i = 0; i < length; i++) { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator () { + for (int i = 0; i < length; i++) { + yield return this[i]; + } + } + + public CircularBuffer Clone () { + return new CircularBuffer { + data = data != null ? (T[])data.Clone() : null, + length = length, + head = head + }; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs.meta new file mode 100644 index 0000000..ea6fc4e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/CircularBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c76961280d816f42a709dc7150208c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs new file mode 100644 index 0000000..7ec6216 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs @@ -0,0 +1,288 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using UnityEngine.Assertions; + +namespace Pathfinding.Collections { + /// + /// Thread-safe hierarchical bitset. + /// + /// Stores an array of bits. Each bit can be set or cleared individually from any thread. + /// + /// Note: Setting the capacity is not thread-safe, nor is iterating over the bitset while it is being modified. + /// + [BurstCompile] + public struct HierarchicalBitset { + UnsafeSpan l1; + UnsafeSpan l2; + UnsafeSpan l3; + Allocator allocator; + + const int Log64 = 6; + + public HierarchicalBitset (int size, Allocator allocator) { + this.allocator = allocator; + l1 = new UnsafeSpan(allocator, (size + 64 - 1) >> Log64); + l2 = new UnsafeSpan(allocator, (size + (64*64 - 1)) >> Log64 >> Log64); + l3 = new UnsafeSpan(allocator, (size + (64*64*64 - 1)) >> Log64 >> Log64 >> Log64); + l1.FillZeros(); + l2.FillZeros(); + l3.FillZeros(); + } + + public bool IsCreated => Capacity > 0; + + public void Dispose () { + l1.Free(allocator); + l2.Free(allocator); + l3.Free(allocator); + this = default; + } + + public int Capacity { + get { + return l1.Length << Log64; + } + set { + if (value < Capacity) throw new System.ArgumentException("Shrinking the bitset is not supported"); + if (value == Capacity) return; + var b = new HierarchicalBitset(value, allocator); + + // Copy the old data + l1.CopyTo(b.l1); + l2.CopyTo(b.l2); + l3.CopyTo(b.l3); + + Dispose(); + this = b; + } + } + + /// Number of set bits in the bitset + public int Count () { + int count = 0; + for (int i = 0; i < l1.Length; i++) { + count += math.countbits(l1[i]); + } + return count; + } + + /// True if the bitset is empty + public bool IsEmpty { + get { + for (int i = 0; i < l3.Length; i++) { + if (l3[i] != 0) return false; + } + return true; + } + } + + /// Clear all bits + public void Clear () { + // TODO: Optimize? + l1.FillZeros(); + l2.FillZeros(); + l3.FillZeros(); + } + + public void GetIndices (NativeList result) { + var buffer = new NativeArray(256, Allocator.Temp); + var iter = GetIterator(buffer.AsUnsafeSpan()); + while (iter.MoveNext()) { + var span = iter.Current; + for (int i = 0; i < span.Length; i++) { + result.Add(span[i]); + } + } + } + + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + static bool SetAtomic (ref UnsafeSpan span, int index) { + var cellIndex = index >> Log64; + var currentValue = span[cellIndex]; + // Note: 1 << index will only use the lower 6 bits of index + if ((currentValue & (1UL << index)) != 0) { + // Bit already set + return true; + } + + // TODO: Use Interlocked.Or in newer .net versions + while (true) { + var actualValue = (ulong)System.Threading.Interlocked.CompareExchange(ref UnsafeUtility.As(ref span[cellIndex]), (long)(currentValue | (1UL << index)), (long)currentValue); + if (actualValue != currentValue) currentValue = actualValue; + else break; + } + return false; + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + static bool ResetAtomic (ref UnsafeSpan span, int index) { + var cellIndex = index >> Log64; + var currentValue = span[cellIndex]; + // Note: 1 << index will only use the lower 6 bits of index + if ((currentValue & (1UL << index)) == 0) { + // Bit already cleared + return true; + } + + // TODO: Use Interlocked.Or in newer .net versions + while (true) { + var actualValue = (ulong)System.Threading.Interlocked.CompareExchange(ref UnsafeUtility.As(ref span[cellIndex]), (long)(currentValue & ~(1UL << index)), (long)currentValue); + if (actualValue != currentValue) currentValue = actualValue; + else break; + } + return false; + } + + /// Get the value of a bit + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool Get (int index) { + // Note: 1 << index will only use the lower 6 bits of index + return (l1[index >> Log64] & (1UL << index)) != 0; + } + + /// Set a given bit to 1 + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Set (int index) { + if (SetAtomic(ref l1, index)) return; + SetAtomic(ref l2, index >> Log64); + SetAtomic(ref l3, index >> (2*Log64)); + } + + /// Set a given bit to 0 + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Reset (int index) { + if (ResetAtomic(ref l1, index)) return; + if (l1[index >> Log64] == 0) ResetAtomic(ref l2, index >> Log64); + if (l2[index >> (2*Log64)] == 0) ResetAtomic(ref l3, index >> (2*Log64)); + } + + /// Get an iterator over all set bits. + /// A buffer to use for temporary storage. A slice of this buffer will be returned on each iteration, filled with the indices of the set bits. + public Iterator GetIterator (UnsafeSpan scratchBuffer) { + return new Iterator(this, scratchBuffer); + } + + [BurstCompile] + public struct Iterator : IEnumerator >, IEnumerable > { + HierarchicalBitset bitSet; + UnsafeSpan result; + int resultCount; + int l3index; + int l3bitIndex; + int l2bitIndex; + + public UnsafeSpan Current => result.Slice(0, resultCount); + + object IEnumerator.Current => throw new System.NotImplementedException(); + + public void Reset() => throw new System.NotImplementedException(); + + public void Dispose () {} + + public IEnumerator > GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => throw new System.NotImplementedException(); + + static int l2index(int l3index, int l3bitIndex) => (l3index << Log64) + l3bitIndex; + static int l1index(int l2index, int l2bitIndex) => (l2index << Log64) + l2bitIndex; + + public Iterator (HierarchicalBitset bitSet, UnsafeSpan result) { + this.bitSet = bitSet; + this.result = result; + resultCount = 0; + l3index = 0; + l3bitIndex = 0; + l2bitIndex = 0; + if (result.Length < 128) { + // Minimum is actually 64, but that can be very inefficient + throw new System.ArgumentException("Result array must be at least 128 elements long"); + } + } + + public bool MoveNext () { + return MoveNextBurst(ref this); + } + + [BurstCompile] + public static bool MoveNextBurst (ref Iterator iter) { + return iter.MoveNextInternal(); + } + + // Inline + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + bool MoveNextInternal () { + // Store various data in local variables to avoid writing them to memory every time they are updated + uint resultCount = 0; + int l3index = this.l3index; + int l3bitIndex = this.l3bitIndex; + int l2bitIndex = this.l2bitIndex; + Assert.IsTrue(l2bitIndex < 64 && l3bitIndex < 64); + + for (; l3index < bitSet.l3.length; l3index++) { + // Get the L3 cell, and mask out all bits we have already visited + var l3cell = bitSet.l3[l3index] & (~0UL << l3bitIndex); + if (l3cell == 0) continue; + + while (l3cell != 0) { + // Find the next set bit in the L3 cell + l3bitIndex = math.tzcnt(l3cell); + + // Nest check for level 2 + int l2index = Iterator.l2index(l3index, l3bitIndex); + // The l2 cell is guaranteed to be non-zero, even after masking out the bits we have already visited + var l2cell = bitSet.l2[l2index] & (~0UL << l2bitIndex); + Assert.AreNotEqual(0, l2cell); + + while (l2cell != 0) { + l2bitIndex = math.tzcnt(l2cell); + // Stop the loop if we have almost filled the result array + // Each L1 cell may contain up to 64 set bits + if (resultCount + 64 > result.Length) { + this.resultCount = (int)resultCount; + this.l3index = l3index; + this.l3bitIndex = l3bitIndex; + this.l2bitIndex = l2bitIndex; + return true; + } + + int l1index = Iterator.l1index(l2index, l2bitIndex); + var l1cell = bitSet.l1[l1index]; + int l1indexStart = l1index << Log64; + Assert.AreNotEqual(0, l1cell); + + while (l1cell != 0) { + var l1bitIndex = math.tzcnt(l1cell); + l1cell &= l1cell - 1UL; // clear lowest bit + int index = l1indexStart + l1bitIndex; + Unity.Burst.CompilerServices.Hint.Assume(resultCount < (uint)result.Length); + result[resultCount++] = index; + } + + l2cell &= l2cell - 1UL; + } + + // Skip a bit at the L3 level + l3cell &= l3cell - 1UL; // clear lowest bit + // Enter new L2 level + l2bitIndex = 0; + } + + l2bitIndex = 0; + l3bitIndex = 0; + } + + this.resultCount = (int)resultCount; + this.l3index = l3index; + this.l3bitIndex = l3bitIndex; + this.l2bitIndex = l2bitIndex; + return resultCount > 0; + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs.meta new file mode 100644 index 0000000..e49aade --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/HierarchicalBitset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c9a4ed21e5f87d40aba2fcc4e01146d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs new file mode 100644 index 0000000..4aaa935 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs @@ -0,0 +1,336 @@ +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using UnityEngine.Assertions; +using Unity.Profiling; + +namespace Pathfinding.Collections { + /// + /// Implements an efficient circular buffer that can be appended to in both directions. + /// + /// See: + /// + public struct NativeCircularBuffer : IReadOnlyList, IReadOnlyCollection where T : unmanaged { + [NativeDisableUnsafePtrRestriction] + internal unsafe T* data; + internal int head; + int length; + /// Capacity of the allocation minus 1. Invariant: (a power of two) minus 1 + int capacityMask; + + /// The allocator used to create the internal buffer. + public AllocatorManager.AllocatorHandle Allocator; + /// Number of items in the buffer + public readonly int Length { + [IgnoredByDeepProfiler] + get { + return length; + } + } + + /// Absolute index of the first item in the buffer, may be negative or greater than + public readonly int AbsoluteStartIndex => head; + /// Absolute index of the last item in the buffer, may be negative or greater than + public readonly int AbsoluteEndIndex => head + length - 1; + + /// First item in the buffer throws if the buffer is empty + public readonly ref T First { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { + unsafe { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length == 0) throw new System.InvalidOperationException(); +#endif + return ref data[head & capacityMask]; + } + } + } + + /// Last item in the buffer, throws if the buffer is empty + public readonly ref T Last { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length == 0) throw new System.InvalidOperationException(); +#endif + unsafe { return ref data[(head+length-1) & capacityMask]; } + } + } + + readonly int IReadOnlyCollection.Count => Length; + + public readonly bool IsCreated { + get { + unsafe { + return data != null; + } + } + } + + /// Create a new empty buffer + + public NativeCircularBuffer(AllocatorManager.AllocatorHandle allocator) { + unsafe { + data = null; + } + Allocator = allocator; + capacityMask = -1; + head = 0; + length = 0; + } + + /// Create a new buffer with the given capacity + public NativeCircularBuffer(int initialCapacity, AllocatorManager.AllocatorHandle allocator) { + initialCapacity = math.ceilpow2(initialCapacity); + unsafe { + data = AllocatorManager.Allocate(allocator, initialCapacity); + capacityMask = initialCapacity - 1; + } + Allocator = allocator; + head = 0; + length = 0; + } + + unsafe public NativeCircularBuffer(CircularBuffer buffer, out ulong gcHandle) : this(buffer.data, buffer.head, buffer.Length, out gcHandle) {} + + unsafe public NativeCircularBuffer(T[] data, int head, int length, out ulong gcHandle) { + Assert.IsTrue((data.Length & (data.Length - 1)) == 0); + Assert.IsTrue(length <= data.Length); + unsafe { + this.data = (T*)UnsafeUtility.PinGCArrayAndGetDataAddress(data, out gcHandle); + } + this.capacityMask = data.Length - 1; + this.head = head; + this.length = length; + Allocator = Unity.Collections.Allocator.None; + } + + /// Resets the buffer's length to zero. Does not clear the current allocation + public void Clear () { + length = 0; + head = 0; + } + + /// Appends a list of items to the end of the buffer + public void AddRange (List items) { + // TODO: Can be optimized + for (int i = 0; i < items.Count; i++) PushEnd(items[i]); + } + + /// Pushes a new item to the start of the buffer + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public void PushStart (T item) { + if (length > capacityMask) Grow(); + length += 1; + head -= 1; + this[0] = item; + } + + /// Pushes a new item to the end of the buffer + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public void PushEnd (T item) { + if (length > capacityMask) Grow(); + length += 1; + this[length-1] = item; + } + + /// Pushes a new item to the start or the end of the buffer + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void Push (bool toStart, T item) { + if (toStart) PushStart(item); + else PushEnd(item); + } + + /// Removes and returns the first element + [IgnoredByDeepProfiler] + public T PopStart () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length == 0) throw new System.InvalidOperationException(); +#endif + var r = this[0]; + head++; + length--; + return r; + } + + /// Removes and returns the last element + [IgnoredByDeepProfiler] + public T PopEnd () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length == 0) throw new System.InvalidOperationException(); +#endif + var r = this[length-1]; + length--; + return r; + } + + /// Pops either from the start or from the end of the buffer + public T Pop (bool fromStart) { + if (fromStart) return PopStart(); + else return PopEnd(); + } + + /// Return either the first element or the last element + public readonly T GetBoundaryValue (bool start) { + return start ? GetAbsolute(AbsoluteStartIndex) : GetAbsolute(AbsoluteEndIndex); + } + + /// Lowers the length of the buffer to the given value, and does nothing if the given value is greater or equal to the current length + + public void TrimTo (int length) { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length < 0) throw new System.ArgumentOutOfRangeException(); +#endif + this.length = math.min(this.length, length); + } + + /// Removes toRemove items from the buffer, starting at startIndex, and then inserts the toInsert items at startIndex + + public void Splice (int startIndex, int toRemove, List toInsert) { + SpliceAbsolute(startIndex + head, toRemove, toInsert); + } + + /// Like , but startIndex is an absolute index + + public void SpliceAbsolute (int startIndex, int toRemove, List toInsert) { + SpliceUninitializedAbsolute(startIndex, toRemove, toInsert.Count); + unsafe { + for (int i = 0; i < toInsert.Count; i++) data[(startIndex + i) & capacityMask] = toInsert[i]; + } + } + + /// Like , but the newly inserted items are left in an uninitialized state + public void SpliceUninitialized (int startIndex, int toRemove, int toInsert) { + SpliceUninitializedAbsolute(startIndex + head, toRemove, toInsert); + } + + /// Like , but startIndex is an absolute index + public void SpliceUninitializedAbsolute (int startIndex, int toRemove, int toInsert) { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (startIndex - head < 0 || startIndex + toRemove - head > length) throw new System.ArgumentOutOfRangeException(); +#endif + var itemsToAdd = toInsert - toRemove; + while (this.length + itemsToAdd > capacityMask + 1) Grow(); + + // move items [startIndex+length .. end] itemsToAdd steps forward in the array + MoveAbsolute(startIndex + toRemove, AbsoluteEndIndex, itemsToAdd); + this.length += itemsToAdd; + } + + void MoveAbsolute (int startIndex, int endIndex, int deltaIndex) { + unsafe { + if (deltaIndex > 0) { + for (int i = endIndex; i >= startIndex; i--) data[(i+deltaIndex) & capacityMask] = data[i & capacityMask]; + } else if (deltaIndex < 0) { + for (int i = startIndex; i <= endIndex; i++) data[(i+deltaIndex) & capacityMask] = data[i & capacityMask]; + } + } + } + + /// Indexes the buffer, with index 0 being the first element + public T this[int index] { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + readonly get { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if ((uint)index >= length) throw new System.ArgumentOutOfRangeException(); +#endif + unsafe { + return data[(index+head) & capacityMask]; + } + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + set { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if ((uint)index >= length) throw new System.ArgumentOutOfRangeException(); +#endif + unsafe { + data[(index+head) & capacityMask] = value; + } + } + } + + /// + /// Indexes the buffer using absolute indices. + /// When pushing to and popping from the buffer, the absolute indices do not change. + /// So e.g. after doing PushStart(x) on an empty buffer, GetAbsolute(-1) will get the newly pushed element. + /// + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + public readonly T GetAbsolute (int index) { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if ((uint)(index - head) >= length) throw new System.ArgumentOutOfRangeException(); +#endif + unsafe { + return data[index & capacityMask]; + } + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + void Grow () { + unsafe { + // Note: Will always be a power of 2 since capacity is a power of 2 + var capacity = capacityMask + 1; + var newCapacity = math.max(4, capacity*2); + var newData = AllocatorManager.Allocate(this.Allocator, newCapacity); + if (data != null) { + var inOrderItems = capacity - (head & capacityMask); + UnsafeUtility.MemCpy(newData + (head & (newCapacity - 1)), data + (head & capacityMask), inOrderItems * sizeof(T)); + var wraparoundItems = length - inOrderItems; + if (wraparoundItems > 0) { + UnsafeUtility.MemCpy(newData + ((head + inOrderItems) & (newCapacity - 1)), data, wraparoundItems * sizeof(T)); + } + AllocatorManager.Free(Allocator, data); + } + capacityMask = newCapacity - 1; + data = newData; + } + } + + /// Releases the unmanaged memory held by this container + public void Dispose () { + capacityMask = -1; + length = 0; + head = 0; + unsafe { + AllocatorManager.Free(Allocator, data); + data = null; + } + } + + public IEnumerator GetEnumerator () { + for (int i = 0; i < length; i++) { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator () { + for (int i = 0; i < length; i++) { + yield return this[i]; + } + } + + public NativeCircularBuffer Clone () { + unsafe { + if (!IsCreated) return default; + + var newData = AllocatorManager.Allocate(this.Allocator, capacityMask + 1); + UnsafeUtility.MemCpy(newData, data, length * sizeof(T)); + return new NativeCircularBuffer { + data = newData, + head = head, + length = length, + capacityMask = capacityMask, + Allocator = this.Allocator + }; + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs.meta new file mode 100644 index 0000000..0e52bc5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/NativeCircularBuffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c126a7f5fd26b684984ddef8030409f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs new file mode 100644 index 0000000..550565c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs @@ -0,0 +1,323 @@ +// #define DEBUG_ALLOCATOR +namespace Pathfinding.Collections { + using Unity.Mathematics; + using Unity.Collections; + using Unity.Collections.LowLevel.Unsafe; + + /// + /// A tiny slab allocator. + /// Allocates spans of type T in power-of-two sized blocks. + /// + /// Note: This allocator has no support for merging adjacent freed blocks. + /// Therefore it is best suited for similarly sized allocations which are relatively small. + /// + /// Can be used in burst jobs. + /// + /// This is faster than allocating NativeArrays using the Temp allocator, and significantly faster + /// than allocating them using the Persistent allocator. + /// + public struct SlabAllocator where T : unmanaged { + /// Allocation which is always invalid + public const int InvalidAllocation = -2; + /// Allocation representing a zero-length array + public const int ZeroLengthArray = -1; + + // The max number of items we are likely to need to allocate comes from the connections array of each hierarchical node. + // If you have a ton (thousands) of off-mesh links next to each other, then that array can get large. + public const int MaxAllocationSizeIndex = 12; + public const int MaxAllocationSize = 1 << MaxAllocationSizeIndex; + + internal static int SizeIndexToElements (int sizeIndex) { + return 1 << sizeIndex; + } + + internal static int ElementsToSizeIndex (int nElements) { + if (nElements < 0) throw new System.Exception("SlabAllocator cannot allocate less than 1 element"); + if (nElements == 0) return 0; + int sizeIndex = CollectionHelper.Log2Ceil(nElements); + if (sizeIndex > MaxAllocationSizeIndex) throw new System.Exception("SlabAllocator cannot allocate more than MaxAllocationSize elements."); + return sizeIndex; + } + + const uint UsedBit = 1u << 31; + const uint AllocatedBit = 1u << 30; + const uint LengthMask = AllocatedBit - 1; + public bool IsDebugAllocator => false; + + [NativeDisableUnsafePtrRestriction] + unsafe AllocatorData* data; + + struct AllocatorData { + public UnsafeList mem; + public unsafe fixed int freeHeads[MaxAllocationSizeIndex+1]; + } + + struct Header { + public uint length; + } + + struct NextBlock { + public int next; + } + + public bool IsCreated { + get { + unsafe { + return data != null; + } + } + } + + public int ByteSize { + get { + unsafe { + return data->mem.Length; + } + } + } + + public SlabAllocator(int initialCapacityBytes, AllocatorManager.AllocatorHandle allocator) { + unsafe { + data = AllocatorManager.Allocate(allocator); + data->mem = new UnsafeList(initialCapacityBytes, allocator); + Clear(); + } + } + + /// + /// Frees all existing allocations. + /// Does not free the underlaying unmanaged memory. Use for that. + /// + public void Clear () { + CheckDisposed(); + unsafe { + data->mem.Clear(); + for (int i = 0; i < MaxAllocationSizeIndex + 1; i++) { + data->freeHeads[i] = -1; + } + } + } + + + /// + /// Get the span representing the given allocation. + /// The returned array does not need to be disposed. + /// It is only valid until the next call to , or . + /// + public UnsafeSpan GetSpan (int allocatedIndex) { + CheckDisposed(); + unsafe { + if (allocatedIndex == ZeroLengthArray) return new UnsafeSpan(null, 0); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (allocatedIndex < sizeof(Header) || allocatedIndex >= data->mem.Length) throw new System.IndexOutOfRangeException($"Invalid allocation {allocatedIndex}"); +#endif + var ptr = data->mem.Ptr + allocatedIndex; + var header = (Header*)(ptr - sizeof(Header)); + var length = header->length & LengthMask; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length > SizeIndexToElements(MaxAllocationSizeIndex)) throw new System.Exception($"Invalid allocation {allocatedIndex}"); + if ((header->length & AllocatedBit) == 0) throw new System.Exception("Trying to get a span for an unallocated index"); +#endif + return new UnsafeSpan(ptr, (int)length); + } + } + + public void Realloc (ref int allocatedIndex, int nElements) { + CheckDisposed(); + if (allocatedIndex == ZeroLengthArray) { + allocatedIndex = Allocate(nElements); + return; + } + + unsafe { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (allocatedIndex < sizeof(Header) || allocatedIndex >= data->mem.Length) throw new System.IndexOutOfRangeException(); +#endif + var ptr = data->mem.Ptr + allocatedIndex; + var header = (Header*)(ptr - sizeof(Header)); + var length = header->length & LengthMask; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length > SizeIndexToElements(MaxAllocationSizeIndex)) throw new System.Exception("Invalid index"); + if ((header->length & AllocatedBit) == 0) throw new System.Exception("Trying to get a span for an unallocated index"); +#endif + var capacityIndex = ElementsToSizeIndex((int)length); + var newCapacityIndex = ElementsToSizeIndex((int)nElements); + if (capacityIndex == newCapacityIndex) { + header->length = (uint)nElements | AllocatedBit | UsedBit; + } else { + int newAllocation = Allocate(nElements); + var oldSpan = GetSpan(allocatedIndex); + var newSpan = GetSpan(newAllocation); + oldSpan.Slice(0, math.min((int)length, nElements)).CopyTo(newSpan); + Free(allocatedIndex); + allocatedIndex = newAllocation; + } + } + } + + /// + /// Allocates an array big enough to fit the given values and copies them to the new allocation. + /// Returns: An ID for the new allocation. + /// + public int Allocate (System.Collections.Generic.List values) { + var index = Allocate(values.Count); + var span = GetSpan(index); + for (int i = 0; i < span.Length; i++) span[i] = values[i]; + return index; + } + + /// + /// Allocates an array big enough to fit the given values and copies them to the new allocation. + /// Returns: An ID for the new allocation. + /// + public int Allocate (NativeList values) { + var index = Allocate(values.Length); + GetSpan(index).CopyFrom(values.AsArray()); + return index; + } + + /// + /// Allocates an array of type T with length nElements. + /// Must later be freed using (or . + /// + /// Returns: An ID for the new allocation. + /// + public int Allocate (int nElements) { + CheckDisposed(); + if (nElements == 0) return ZeroLengthArray; + var sizeIndex = ElementsToSizeIndex(nElements); + unsafe { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (sizeIndex < 0 || sizeIndex > MaxAllocationSizeIndex) throw new System.Exception("Invalid size index " + sizeIndex); +#endif + int head = data->freeHeads[sizeIndex]; + if (head != -1) { + var ptr = data->mem.Ptr; + data->freeHeads[sizeIndex] = ((NextBlock*)(ptr + head))->next; + *(Header*)(ptr + head - sizeof(Header)) = new Header { length = (uint)nElements | UsedBit | AllocatedBit }; + return head; + } + + int headerStart = data->mem.Length; + int requiredSize = headerStart + sizeof(Header) + SizeIndexToElements(sizeIndex)*sizeof(T); + if (Unity.Burst.CompilerServices.Hint.Unlikely(requiredSize > data->mem.Capacity)) { + data->mem.SetCapacity(math.max(data->mem.Capacity*2, requiredSize)); + } + + // Set the length field directly because we know we don't have to resize the list, + // and we do not care about zeroing the memory. + data->mem.m_length = requiredSize; + *(Header*)(data->mem.Ptr + headerStart) = new Header { length = (uint)nElements | UsedBit | AllocatedBit }; + return headerStart + sizeof(Header); + } + } + + /// Frees a single allocation + public void Free (int allocatedIndex) { + CheckDisposed(); + if (allocatedIndex == ZeroLengthArray) return; + unsafe { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (allocatedIndex < sizeof(Header) || allocatedIndex >= data->mem.Length) throw new System.IndexOutOfRangeException(); +#endif + var ptr = data->mem.Ptr; + var header = (Header*)(ptr + allocatedIndex - sizeof(Header)); + var length = (int)(header->length & LengthMask); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (length < 0 || length > SizeIndexToElements(MaxAllocationSizeIndex)) throw new System.Exception("Invalid index"); + if ((header->length & AllocatedBit) == 0) throw new System.Exception("Trying to free an already freed index"); +#endif + + var sizeIndex = ElementsToSizeIndex(length); + + *(NextBlock*)(ptr + allocatedIndex) = new NextBlock { + next = data->freeHeads[sizeIndex] + }; + data->freeHeads[sizeIndex] = allocatedIndex; + // Mark as not allocated + header->length &= ~(AllocatedBit | UsedBit); + } + } + + public void CopyTo (SlabAllocator other) { + CheckDisposed(); + other.CheckDisposed(); + unsafe { + other.data->mem.CopyFrom(data->mem); + for (int i = 0; i < MaxAllocationSizeIndex + 1; i++) { + other.data->freeHeads[i] = data->freeHeads[i]; + } + } + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + void CheckDisposed () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + unsafe { + if (data == null) throw new System.InvalidOperationException("SlabAllocator is already disposed or not initialized"); + } +#endif + } + + /// Frees all unmanaged memory associated with this container + public void Dispose () { + unsafe { + if (data == null) return; + var allocator = data->mem.Allocator; + data->mem.Dispose(); + AllocatorManager.Free(allocator, data); + data = null; + } + } + + public List GetList (int allocatedIndex) { + return new List(this, allocatedIndex); + } + + public ref struct List { + public UnsafeSpan span; + SlabAllocator allocator; + // TODO: Can be derived from span + public int allocationIndex; + + public List(SlabAllocator allocator, int allocationIndex) { + this.span = allocator.GetSpan(allocationIndex); + this.allocator = allocator; + this.allocationIndex = allocationIndex; + } + + public void Add (T value) { + allocator.Realloc(ref allocationIndex, span.Length + 1); + span = allocator.GetSpan(allocationIndex); + span[span.Length - 1] = value; + } + + public void RemoveAt (int index) { + span.Slice(index + 1).CopyTo(span.Slice(index, span.Length - index - 1)); + allocator.Realloc(ref allocationIndex, span.Length - 1); + span = allocator.GetSpan(allocationIndex); + } + + public void Clear () { + allocator.Realloc(ref allocationIndex, 0); + span = allocator.GetSpan(allocationIndex); + } + + public int Length => span.Length; + + public ref T this[int index] { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get { + return ref span[index]; + } + } + } + } + + public static class SlabListExtensions { + public static void Remove(ref this SlabAllocator.List list, T value) where T : unmanaged, System.IEquatable { + int idx = list.span.IndexOf(value); + if (idx != -1) list.RemoveAt(idx); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs.meta new file mode 100644 index 0000000..b317b73 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/SlabAllocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 020cb204e780440f8987b23cd21c02d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs new file mode 100644 index 0000000..dcc305e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs @@ -0,0 +1,511 @@ +using Unity.Mathematics; +using Unity.Profiling; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using System.Runtime.CompilerServices; +using System.Collections.Generic; + +namespace Pathfinding.Collections { + /// + /// Replacement for System.Span which is compatible with earlier versions of C#. + /// + /// Warning: These spans do not in any way guarantee that the memory they refer to is valid. It is up to the user to make sure + /// the memory is not deallocated before usage. It should never be used to refer to managed heap memory without pinning it, since unpinned managed memory can be moved by some runtimes. + /// + /// This has several benefits over e.g. UnsafeList: + /// - It is faster to index into a span than into an UnsafeList, especially from C#. In fact, indexing into an UnsafeSpan is as fast as indexing into a native C# array. + /// - As a comparison, indexing into a NativeArray can easily be 10x slower, and indexing into an UnsafeList is at least a few times slower. + /// - You can create a UnsafeSpan from a C# array by pinning it. + /// - It can be sliced efficiently. + /// - It supports ref returns for the indexing operations. + /// + public readonly struct UnsafeSpan where T : unmanaged { + [NativeDisableUnsafePtrRestriction] + internal readonly unsafe T* ptr; + internal readonly uint length; + public readonly Allocator Allocator; + + /// Number of elements in this span + public int Length => (int)length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe UnsafeSpan(void* ptr, int length, Allocator allocator = Unity.Collections.Allocator.None) { + if (length < 0) throw new System.ArgumentOutOfRangeException(); + if (length > 0 && ptr == null) throw new System.ArgumentNullException(); + this.ptr = (T*)ptr; + this.length = (uint)length; + this.Allocator = allocator; + } + + /// Creates a new UnsafeSpan from a C# array + public unsafe UnsafeSpan(T[] data, Allocator allocator) : this(allocator, data.Length) { + this.CopyFrom(data); + } + + /// + /// Creates a new UnsafeSpan from a C# array. + /// The array is pinned to ensure it does not move while the span is in use. + /// + /// You must unpin the pinned memory using UnsafeUtility.ReleaseGCObject when you are done with the span. + /// + public unsafe UnsafeSpan(T[] data, out ulong gcHandle) { + unsafe { + this.ptr = (T*)UnsafeUtility.PinGCArrayAndGetDataAddress(data, out gcHandle); + } + this.length = (uint)data.Length; + this.Allocator = Unity.Collections.Allocator.None; + } + + /// + /// Creates a new UnsafeSpan from a 2D C# array. + /// The array is pinned to ensure it does not move while the span is in use. + /// + /// You must unpin the pinned memory using UnsafeUtility.ReleaseGCObject when you are done with the span. + /// + public unsafe UnsafeSpan(T[,] data, out ulong gcHandle) { + unsafe { + this.ptr = (T*)UnsafeUtility.PinGCArrayAndGetDataAddress(data, out gcHandle); + } + this.length = (uint)data.Length; + this.Allocator = Unity.Collections.Allocator.None; + } + + /// + /// Allocates a new UnsafeSpan with the specified length. + /// The memory is not initialized. + /// + /// You are responsible for freeing the memory using the same allocator when you are done with it. + /// + public UnsafeSpan(Allocator allocator, int length) { + unsafe { + if (length < 0) throw new System.ArgumentOutOfRangeException(); + if (length > 0) this.ptr = (T*)UnsafeUtility.MallocTracked(length * (long)UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), allocator, 1); + else this.ptr = null; + this.length = (uint)length; + this.Allocator = allocator; + } + } + + public ref T this[int index] { + // With aggressive inlining the performance of indexing is essentially the same as indexing into a native C# array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { + unsafe { + if ((uint)index >= length) throw new System.IndexOutOfRangeException(); + Unity.Burst.CompilerServices.Hint.Assume(ptr != null); + return ref *(ptr + index); + } + } + } + + public ref T this[uint index] { + // With aggressive inlining the performance of indexing is essentially the same as indexing into a native C# array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [IgnoredByDeepProfiler] + get { + unsafe { + if (index >= length) throw new System.IndexOutOfRangeException(); + Unity.Burst.CompilerServices.Hint.Assume(ptr != null); + Unity.Burst.CompilerServices.Hint.Assume(ptr + index != null); + return ref *(ptr + index); + } + } + } + + /// + /// Returns a copy of this span, but with a different data-type. + /// The new data-type must have the same size as the old one. + /// + /// In burst, this should effectively be a no-op, except possibly a branch. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UnsafeSpan Reinterpret () where U : unmanaged { + unsafe { + if (sizeof(T) != sizeof(U)) throw new System.InvalidOperationException("Cannot reinterpret span because the size of the types do not match"); + return new UnsafeSpan(ptr, (int)length, Allocator); + } + } + + /// + /// Returns a copy of this span, but with a different data-type. + /// The new data-type does not need to have the same size as the old one. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UnsafeSpan Reinterpret(int expectedOriginalTypeSize) where U : unmanaged { + unsafe { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (sizeof(T) != expectedOriginalTypeSize) throw new System.InvalidOperationException("Cannot reinterpret span because sizeof(T) != expectedOriginalTypeSize"); +#endif + return new UnsafeSpan(ptr, (int)length * sizeof(T) / sizeof(U), Allocator); + } + } + + /// + /// Creates a new span which is a slice of this span. + /// The new span will start at the specified index and have the specified length. + /// + public UnsafeSpan Slice (int start, int length) { + if (start < 0 || length < 0 || start + length > this.length) throw new System.ArgumentOutOfRangeException(); + unsafe { + return new UnsafeSpan(ptr + start, length, Allocator); + } + } + + /// + /// Creates a new span which is a slice of this span. + /// The new span will start at the specified index and continue to the end of this span. + /// + public UnsafeSpan Slice (int start) { + return Slice(start, (int)this.length - start); + } + + /// Copy the range [startIndex,startIndex+count) to [toIndex,toIndex+count) + public void Move (int startIndex, int toIndex, int count) { + unsafe { + if (count < 0) throw new System.ArgumentOutOfRangeException(); + if (startIndex < 0 || startIndex + count > length) throw new System.ArgumentOutOfRangeException(); + if (toIndex < 0 || toIndex + count > length) throw new System.ArgumentOutOfRangeException(); + // If length is zero, the pointers may be null, which is technically undefined behavior (but in practice usually fine) + if (count == 0) return; + UnsafeUtility.MemMove(ptr + toIndex, ptr + startIndex, (long)sizeof(T) * (long)count); + } + } + + /// + /// Removes an element from a span, reducing its length by one. + /// This is done by moving all elements after the index one step back. + /// + /// The underlaying allocation is not resized, only the length field is changed. + /// + public static void RemoveAt (ref UnsafeSpan span, int index) { + if (index < 0 || index >= span.length) throw new System.ArgumentOutOfRangeException(); + span.Move(index + 1, index, (int)span.length - index - 1); + span = span.Slice(0, (int)span.length - 1); + } + + /// + /// Copies the memory of this span to another span. + /// The other span must be large enough to hold the contents of this span. + /// + /// Note: Assumes the other span does not alias this one. + /// + public void CopyTo (UnsafeSpan other) { + if (other.length < length) throw new System.ArgumentException(); + unsafe { + // If length is zero, the pointers may be null, which is technically undefined behavior (but in practice usually fine) + if (length > 0) UnsafeUtility.MemCpy(other.ptr, ptr, (long)sizeof(T) * (long)length); + } + } + + /// Appends all elements in this span to the given list + public void CopyTo (List buffer) { + if (buffer.Capacity < buffer.Count + Length) buffer.Capacity = buffer.Count + Length; + for (int i = 0; i < Length; i++) buffer.Add(this[i]); + } + + /// + /// Creates a new copy of the span allocated using the given allocator. + /// + /// You are responsible for freeing this memory using the same allocator when you are done with it. + /// + public UnsafeSpan Clone (Allocator allocator) { + unsafe { + var clone = new UnsafeSpan(allocator, (int)length); + CopyTo(clone); + return clone; + } + } + + /// Converts the span to a managed array + public T[] ToArray () { + var arr = new T[length]; + if (length > 0) { + unsafe { + fixed (T* ptr = arr) { + UnsafeUtility.MemCpy(ptr, this.ptr, (long)sizeof(T) * (long)length); + } + } + } + return arr; + } + + /// + /// Moves this data to a new NativeArray. + /// + /// This transfers ownership of the memory to the NativeArray, without any copying. + /// The NativeArray must be disposed when you are done with it. + /// + /// Warning: This span must have been allocated using the specified allocator. + /// + public unsafe NativeArray MoveToNativeArray (Allocator allocator) { + UnityEngine.Assertions.Assert.AreEqual(allocator, Allocator, "Allocator mismatch. The span must have been allocated using the same allocator as the one used to create the NativeArray."); + var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, Length, allocator); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, AtomicSafetyHandle.Create()); +#endif + return arr; + } + + /// + /// Frees the underlaying memory. + /// + /// Warning: The span must have been allocated using the specified allocator. + /// + /// Warning: You must never use this span (or any other span referencing the same memory) again after calling this method. + /// + public unsafe void Free (Allocator expectedAllocator) { + if (length > 0) { + UnityEngine.Assertions.Assert.AreEqual(expectedAllocator, Allocator, "Allocator mismatch. The span must have been allocated using the same allocator as the one used to free it."); + UnsafeUtility.FreeTracked(ptr, expectedAllocator); + } + } + + public void Free () { + Free(Allocator); + } + + + /// + /// Returns a new span with a different size, copies the current data over to it, and frees this span. + /// + /// The new span may be larger or smaller than the current span. If it is larger, the new elements will be uninitialized. + /// + /// Warning: The span must have been allocated using the specified allocator. + /// + /// Warning: You must never use the old span (or any other span referencing the same memory) again after calling this method. + /// + /// Returns: The new span. + /// + public unsafe UnsafeSpan Reallocate (Allocator allocator, int newSize) { + if (length > 0) UnityEngine.Assertions.Assert.AreEqual(allocator, Allocator, "Allocator mismatch. The span must have been allocated using the same allocator as the one used to reallocate it."); + if (newSize < 0) throw new System.ArgumentOutOfRangeException(nameof(newSize), "New size must be non-negative"); + if (newSize == Length) return this; // No need to reallocate if the size is the same + var newSpan = new UnsafeSpan(allocator, newSize); + Slice(0, System.Math.Min(newSize, Length)).CopyTo(newSpan); + Free(allocator); + return newSpan; + } + } + + public static class SpanExtensions { + public static void FillZeros(this UnsafeSpan span) where T : unmanaged { + unsafe { + if (span.length > 0) UnsafeUtility.MemSet(span.ptr, 0, (long)sizeof(T) * (long)span.length); + } + } + + public static void Fill(this UnsafeSpan span, T value) where T : unmanaged { + unsafe { + // This is wayy faster than a C# for loop (easily 10x faster). + // It is also faster than a burst loop (at least as long as the span is reasonably large). + // It also generates a lot less code than a burst for loop. + if (span.length > 0) { + // If this is too big, unity seems to overflow and crash internally + if ((long)sizeof(T) * (long)span.length > (long)int.MaxValue) throw new System.ArgumentException("Span is too large to fill"); + UnsafeUtility.MemCpyReplicate(span.ptr, &value, sizeof(T), (int)span.length); + } + } + } + + /// + /// Copies the contents of a NativeArray to this span. + /// The span must be large enough to hold the contents of the array. + /// + public static void CopyFrom(this UnsafeSpan span, NativeArray array) where T : unmanaged { + array.AsUnsafeReadOnlySpan().CopyTo(span); + } + + /// + /// Copies the contents of another span to this span. + /// The span must be large enough to hold the contents of the array. + /// + public static void CopyFrom(this UnsafeSpan span, UnsafeSpan other) where T : unmanaged { + other.CopyTo(span); + } + + /// + /// Copies the contents of an array to this span. + /// The span must be large enough to hold the contents of the array. + /// + public static void CopyFrom(this UnsafeSpan span, T[] array) where T : unmanaged { + if (array.Length > span.Length) throw new System.InvalidOperationException(); + if (array.Length == 0) return; + unsafe { + var ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out var gcHandle); + UnsafeUtility.MemCpy(span.ptr, ptr, (long)sizeof(T) * (long)array.Length); + UnsafeUtility.ReleaseGCObject(gcHandle); + } + } + + /// + /// Converts an UnsafeAppendBuffer to a span. + /// The buffer must be a multiple of the element size. + /// + /// The span is a view of the buffer memory, so do not dispose the buffer while the span is in use. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpan(this UnsafeAppendBuffer buffer) where T : unmanaged { + unsafe { + var items = buffer.Length / UnsafeUtility.SizeOf(); + if (items * UnsafeUtility.SizeOf() != buffer.Length) throw new System.ArgumentException("Buffer length is not a multiple of the element size"); + return new UnsafeSpan(buffer.Ptr, items); + } + } + + /// + /// Converts a NativeList to a span. + /// + /// The span is a view of the list memory, so do not dispose the list while the span is in use. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpan(this NativeList list) where T : unmanaged { + unsafe { + return new UnsafeSpan(list.GetUnsafePtr(), list.Length); + } + } + + /// + /// Converts a NativeArray to a span. + /// + /// The span is a view of the array memory, so do not dispose the array while the span is in use. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpan(this NativeArray arr) where T : unmanaged { + unsafe { + return new UnsafeSpan(arr.GetUnsafePtr(), arr.Length); + } + } + + /// + /// Moves a NativeArray to a span. + /// + /// This transfers ownership of the memory to the UnsafeSpan, without any copying. + /// The span must be disposed using when you are done with it. + /// The native array cannot be used after this operation, and must not be disposed. + /// + public static UnsafeSpan MoveToUnsafeSpan(this NativeArray arr) where T : unmanaged { + unsafe { + var span = new UnsafeSpan(arr.GetUnsafePtr(), arr.Length, arr.GetAllocator()); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var safetyHandle = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(arr); + AtomicSafetyHandle.Release(safetyHandle); +#endif + return span; + } + } + + /// + /// Converts a NativeArray to a span without performing any checks. + /// + /// The span is a view of the array memory, so do not dispose the array while the span is in use. + /// This method does not perform any checks to ensure that the array is safe to write to or read from. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpanNoChecks(this NativeArray arr) where T : unmanaged { + unsafe { + return new UnsafeSpan(NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(arr), arr.Length); + } + } + + /// + /// Converts a NativeArray to a span, assuming it will only be read. + /// + /// The span is a view of the array memory, so do not dispose the array while the span is in use. + /// + /// Warning: No checks are done to ensure that you only read from the array. You are responsible for ensuring that you do not write to the span. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeReadOnlySpan(this NativeArray arr) where T : unmanaged { + unsafe { + return new UnsafeSpan(arr.GetUnsafeReadOnlyPtr(), arr.Length); + } + } + + static readonly int AllocatorOffset = UnsafeUtility.GetFieldOffset(typeof(NativeArray).GetField("m_AllocatorLabel", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)); + + public static Allocator GetAllocator(this NativeArray arr) where T : unmanaged { + unsafe { + var ptr = (byte*)UnsafeUtility.AddressOf(ref arr); + return *(Allocator*)(ptr + AllocatorOffset); + } + } + + /// + /// Converts an UnsafeList to a span. + /// + /// The span is a view of the list memory, so do not dispose the list while the span is in use. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpan(this UnsafeList arr) where T : unmanaged { + unsafe { + return new UnsafeSpan(arr.Ptr, arr.Length); + } + } + + /// + /// Converts a NativeSlice to a span. + /// + /// The span is a view of the slice memory, so do not dispose the underlaying memory allocation while the span is in use. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeSpan AsUnsafeSpan(this NativeSlice slice) where T : unmanaged { + unsafe { + return new UnsafeSpan(slice.GetUnsafePtr(), slice.Length); + } + } + + /// Returns true if the value exists in the span + public static bool Contains(this UnsafeSpan span, T value) where T : unmanaged, System.IEquatable { + return IndexOf(span, value) != -1; + } + + /// + /// Returns the index of the first occurrence of a value in the span. + /// If the value is not found, -1 is returned. + /// + public static int IndexOf(this UnsafeSpan span, T value) where T : unmanaged, System.IEquatable { + unsafe { + return System.MemoryExtensions.IndexOf(new System.ReadOnlySpan(span.ptr, (int)span.length), value); + } + } + + /// Sorts the span in ascending order + public static void Sort(this UnsafeSpan span) where T : unmanaged, System.IComparable { + unsafe { + NativeSortExtension.Sort(span.ptr, span.Length); + } + } + + /// Sorts the span in ascending order + public static void Sort(this UnsafeSpan span, U comp) where T : unmanaged where U : System.Collections.Generic.IComparer { + unsafe { + NativeSortExtension.Sort(span.ptr, span.Length, comp); + } + } + +#if !MODULE_COLLECTIONS_2_4_0_OR_NEWER + /// Shifts elements toward the end of this list, increasing its length + public static void InsertRange(this NativeList list, int index, int count) where T : unmanaged { + list.ResizeUninitialized(list.Length + count); + list.AsUnsafeSpan().Move(index, index + count, list.Length - (index + count)); + } +#endif + +#if !MODULE_COLLECTIONS_2_1_0_OR_NEWER + /// Appends value count times to the end of this list + public static void AddReplicate(this NativeList list, T value, int count) where T : unmanaged { + var origLength = list.Length; + list.ResizeUninitialized(origLength + count); + list.AsUnsafeSpan().Slice(origLength).Fill(value); + } + + /// Appends value count times to the end of this list + public static void AddReplicate(this UnsafeList list, T value, int count) where T : unmanaged { + var origLength = list.Length; + list.Resize(origLength + count, NativeArrayOptions.UninitializedMemory); + list.AsUnsafeSpan().Slice(origLength).Fill(value); + } +#endif + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs.meta new file mode 100644 index 0000000..de2fb18 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Collections/Span.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa5a1e493f0bf8946bc5fe477ef4710b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Control.meta new file mode 100644 index 0000000..14d82e8 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 756b69de94c66c508a7195b291178ac7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs new file mode 100644 index 0000000..cf08a20 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs @@ -0,0 +1,208 @@ +using UnityEngine; + +namespace Pathfinding.Util { + public static class MovementUtilities { + public static float FilterRotationDirection (ref Vector2 state, ref Vector2 state2, Vector2 deltaPosition, float threshold, float deltaTime, bool avoidingOtherAgents) { + const float Decay = 0.5f; + + var lastState = state; + + if (!avoidingOtherAgents) { + // When not avoiding other agents, we can be a bit more aggressive with rotating towards the target. + // This is because in that case, the velocity is much less noisy. + state += deltaPosition * 10; + } else { + state += deltaPosition; + } + + // Decay the state slowly (has the most effect if the agent is standing still) + state *= Mathf.Clamp01(1.0f - deltaTime*Decay); + float stateLength = state.magnitude; + + if (stateLength > threshold*2f) { + state = state * (threshold*2f/stateLength); + stateLength = threshold*2f; + } + + // TODO: Figure out what to do with + state2 += (state - lastState) * Decay; + state2 *= Mathf.Clamp01(1.0f - deltaTime*Decay); + + // Prevent rotation if the agent doesn't move much + float speed = stateLength > threshold ? 1.0f : 0.0f; + return speed; + } + + /// + /// Clamps the velocity to the max speed and optionally the forwards direction. + /// + /// Note that all vectors are 2D vectors, not 3D vectors. + /// + /// Returns: The clamped velocity in world units per second. + /// + /// Desired velocity of the character. In world units per second. + /// Max speed of the character. In world units per second. + /// Value between 0 and 1 which determines how much slower the character should move than normal. + /// Normally 1 but should go to 0 when the character approaches the end of the path. + /// Slow the character down if the desired velocity is not in the same direction as the forward vector. + /// Prevent the velocity from being too far away from the forward direction of the character. + /// Forward direction of the character. Used together with the slowWhenNotFacingTarget parameter. + public static Vector2 ClampVelocity (Vector2 velocity, float maxSpeed, float speedLimitFactor, bool slowWhenNotFacingTarget, bool preventMovingBackwards, Vector2 forward) { + // Max speed to use for this frame + var currentMaxSpeed = maxSpeed * speedLimitFactor; + + // Check if the agent should slow down in case it is not facing the direction it wants to move in + if (slowWhenNotFacingTarget && (forward.x != 0 || forward.y != 0)) { + float currentSpeed; + var normalizedVelocity = VectorMath.Normalize(velocity, out currentSpeed); + float dot = Vector2.Dot(normalizedVelocity, forward); + + // Lower the speed when the character's forward direction is not pointing towards the desired velocity + // 1 when velocity is in the same direction as forward + // 0.2 when they point in the opposite directions + float directionSpeedFactor = Mathf.Clamp(dot+0.707f, 0.2f, 1.0f); + currentMaxSpeed *= directionSpeedFactor; + currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed); + + if (preventMovingBackwards) { + // Angle between the forwards direction of the character and our desired velocity + float angle = Mathf.Acos(Mathf.Clamp(dot, -1, 1)); + + // Clamp the angle to 20 degrees + // We cannot keep the velocity exactly in the forwards direction of the character + // because we use the rotation to determine in which direction to rotate and if + // the velocity would always be in the forwards direction of the character then + // the character would never rotate. + // Allow larger angles when near the end of the path to prevent oscillations. + angle = Mathf.Min(angle, (20f + 180f*(1 - speedLimitFactor*speedLimitFactor))*Mathf.Deg2Rad); + + float sin = Mathf.Sin(angle); + float cos = Mathf.Cos(angle); + + // Determine if we should rotate clockwise or counter-clockwise to move towards the current velocity + sin *= Mathf.Sign(normalizedVelocity.x*forward.y - normalizedVelocity.y*forward.x); + // Rotate the #forward vector by #angle radians + // The rotation is done using an inlined rotation matrix. + // See https://en.wikipedia.org/wiki/Rotation_matrix + return new Vector2(forward.x*cos + forward.y*sin, forward.y*cos - forward.x*sin) * currentSpeed; + } else { + return normalizedVelocity * currentSpeed; + } + } else { + return Vector2.ClampMagnitude(velocity, currentMaxSpeed); + } + } + + /// Calculate an acceleration to move deltaPosition units and get there with approximately a velocity of targetVelocity + public static Vector2 CalculateAccelerationToReachPoint (Vector2 deltaPosition, Vector2 targetVelocity, Vector2 currentVelocity, float forwardsAcceleration, float rotationSpeed, float maxSpeed, Vector2 forwardsVector) { + // Guard against div by zero + if (forwardsAcceleration <= 0) return Vector2.zero; + + float currentSpeed = currentVelocity.magnitude; + + // Convert rotation speed to an acceleration + // See https://en.wikipedia.org/wiki/Centripetal_force + var sidewaysAcceleration = currentSpeed * rotationSpeed * Mathf.Deg2Rad; + + // To avoid weird behaviour when the rotation speed is very low we allow the agent to accelerate sideways without rotating much + // if the rotation speed is very small. Also guards against division by zero. + sidewaysAcceleration = Mathf.Max(sidewaysAcceleration, forwardsAcceleration); + + // Transform coordinates to local space where +X is the forwards direction + // This is essentially equivalent to Transform.InverseTransformDirection. + deltaPosition = VectorMath.ComplexMultiplyConjugate(deltaPosition, forwardsVector); + targetVelocity = VectorMath.ComplexMultiplyConjugate(targetVelocity, forwardsVector); + currentVelocity = VectorMath.ComplexMultiplyConjugate(currentVelocity, forwardsVector); + float ellipseSqrFactorX = 1 / (forwardsAcceleration*forwardsAcceleration); + float ellipseSqrFactorY = 1 / (sidewaysAcceleration*sidewaysAcceleration); + + // If the target velocity is zero we can use a more fancy approach + // and calculate a nicer path. + // In particular, this is the case at the end of the path. + if (targetVelocity == Vector2.zero) { + // Run a binary search over the time to get to the target point. + float mn = 0.01f; + float mx = 10; + while (mx - mn > 0.01f) { + var time = (mx + mn) * 0.5f; + + // Given that we want to move deltaPosition units from out current position, that our current velocity is given + // and that when we reach the target we want our velocity to be zero. Also assume that our acceleration will + // vary linearly during the slowdown. Then we can calculate what our acceleration should be during this frame. + + //{ t = time + //{ deltaPosition = vt + at^2/2 + qt^3/6 + //{ 0 = v + at + qt^2/2 + //{ solve for a + // a = acceleration vector + // q = derivative of the acceleration vector + var a = (6*deltaPosition - 4*time*currentVelocity)/(time*time); + var q = 6*(time*currentVelocity - 2*deltaPosition)/(time*time*time); + + // Make sure the acceleration is not greater than our maximum allowed acceleration. + // If it is we increase the time we want to use to get to the target + // and if it is not, we decrease the time to get there faster. + // Since the acceleration is described by acceleration = a + q*t + // we only need to check at t=0 and t=time. + // Note that the acceleration limit is described by an ellipse, not a circle. + var nextA = a + q*time; + if (a.x*a.x*ellipseSqrFactorX + a.y*a.y*ellipseSqrFactorY > 1.0f || nextA.x*nextA.x*ellipseSqrFactorX + nextA.y*nextA.y*ellipseSqrFactorY > 1.0f) { + mn = time; + } else { + mx = time; + } + } + + var finalAcceleration = (6*deltaPosition - 4*mx*currentVelocity)/(mx*mx); + + // Boosting + { + // The trajectory calculated above has a tendency to use very wide arcs + // and that does unfortunately not look particularly good in some cases. + // Here we amplify the component of the acceleration that is perpendicular + // to our current velocity. This will make the agent turn towards the + // target quicker. + // How much amplification to use. Value is unitless. + const float Boost = 1; + finalAcceleration.y *= 1 + Boost; + + // Clamp the velocity to the maximum acceleration. + // Note that the maximum acceleration constraint is shaped like an ellipse, not like a circle. + float ellipseMagnitude = finalAcceleration.x*finalAcceleration.x*ellipseSqrFactorX + finalAcceleration.y*finalAcceleration.y*ellipseSqrFactorY; + if (ellipseMagnitude > 1.0f) finalAcceleration /= Mathf.Sqrt(ellipseMagnitude); + } + + return VectorMath.ComplexMultiply(finalAcceleration, forwardsVector); + } else { + // Here we try to move towards the next waypoint which has been modified slightly using our + // desired velocity at that point so that the agent will more smoothly round the corner. + + // How much to strive for making sure we reach the target point with the target velocity. Unitless. + const float TargetVelocityWeight = 0.5f; + + // Limit to how much to care about the target velocity. Value is in seconds. + // This prevents the character from moving away from the path too much when the target point is far away + const float TargetVelocityWeightLimit = 1.5f; + float targetSpeed; + var normalizedTargetVelocity = VectorMath.Normalize(targetVelocity, out targetSpeed); + + var distance = deltaPosition.magnitude; + var targetPoint = deltaPosition - normalizedTargetVelocity * System.Math.Min(TargetVelocityWeight * distance * targetSpeed / (currentSpeed + targetSpeed), maxSpeed*TargetVelocityWeightLimit); + + // How quickly the agent will try to reach the velocity that we want it to have. + // We need this to prevent oscillations and jitter which is what happens if + // we let the constant go towards zero. Value is in seconds. + const float TimeToReachDesiredVelocity = 0.1f; + // TODO: Clamp to ellipse using more accurate acceleration (use rotation speed as well) + var finalAcceleration = (targetPoint.normalized*maxSpeed - currentVelocity) * (1f/TimeToReachDesiredVelocity); + + // Clamp the velocity to the maximum acceleration. + // Note that the maximum acceleration constraint is shaped like an ellipse, not like a circle. + float ellipseMagnitude = finalAcceleration.x*finalAcceleration.x*ellipseSqrFactorX + finalAcceleration.y*finalAcceleration.y*ellipseSqrFactorY; + if (ellipseMagnitude > 1.0f) finalAcceleration /= Mathf.Sqrt(ellipseMagnitude); + + return VectorMath.ComplexMultiply(finalAcceleration, forwardsVector); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs.meta new file mode 100644 index 0000000..bb4e60d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/MovementUtilities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0a6cffd9895f94907aa43f18b0904587 +timeCreated: 1490097740 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs new file mode 100644 index 0000000..7dcf5c3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs @@ -0,0 +1,1138 @@ +using UnityEngine; +using Unity.Mathematics; +using System.Collections.Generic; +using Unity.Collections; +using UnityEngine.Profiling; + +namespace Pathfinding.PID { + using Pathfinding.Drawing; + using Pathfinding.Util; + using Palette = Pathfinding.Drawing.Palette.Colorbrewer.Set1; + using Unity.Jobs; + using Unity.Profiling; + using UnityEngine.Assertions; + using Unity.Burst; + using Unity.Collections.LowLevel.Unsafe; + using Pathfinding.RVO; + + /// Core control loop for the movement script + [System.Serializable] + [BurstCompile] + public struct PIDMovement { + public struct PersistentState { + public float maxDesiredWallDistance; + } + + /// + /// Desired rotation speed in degrees per second. + /// + /// If the agent is in an open area and gets a new destination directly behind itself, it will start to rotate around with exactly this rotation speed. + /// + /// The agent will slow down its rotation speed as it approaches its desired facing direction. + /// So for example, when it is only 90 degrees away from its desired facing direction, it will only rotate with about half this speed. + /// + /// See: + /// + public float rotationSpeed; + + /// + /// Desired speed of the agent in meters per second. + /// + /// This will be multiplied by the agent's scale to get the actual speed. + /// + public float speed; + + /// + /// Maximum rotation speed in degrees per second. + /// + /// If the agent would have to rotate faster than this, it will instead slow down to get more time to rotate. + /// + /// The agent may want to rotate faster than if there's not enough space, so that it has to move in a more narrow arc. + /// It may also want to rotate faster if it is very close to its destination and it wants to make sure it ends up on the right spot without any circling. + /// + /// It is recommended to keep this at a value slightly larger than . + /// + /// See: + /// + public float maxRotationSpeed; + + /// + /// Maximum rotation speed in degrees per second while rotating on the spot. + /// + /// Only used if is enabled. + /// + public float maxOnSpotRotationSpeed; + + /// + /// Time for the agent to slow down to a complete stop when it approaches the destination point, in seconds. + /// + /// One can calculate the deceleration like: / (with units m/s^2). + /// + public float slowdownTime; + + /// + /// Time for the agent to slow down to a complete stop when rotating on the spot. + /// + /// If set to zero, the agent will instantly stop and start to turn around. + /// + /// Only used if is enabled. + /// + public float slowdownTimeWhenTurningOnSpot; + + /// + /// How big of a distance to try to keep from obstacles. + /// + /// Typically around 1 or 2 times the agent radius is a good value for this. + /// + /// Try to avoid making it so large that there might not be enough space for the agent to keep this amount of distance from obstacles. + /// It may start to move less optimally if it is not possible to keep this distance. + /// + /// This works well in open spaces, but if your game consists of a lot of tight corridors, a low, or zero value may be better. + /// + /// This will be multiplied by the agent's scale to get the actual distance. + /// + public float desiredWallDistance; + + /// + /// How wide of a turn to make when approaching a destination for which a desired facing direction has been set. + /// + /// The following video shows three agents, one with no facing direction set, and then two agents with varying values of the lead in radius. + /// [Open online documentation to see videos] + /// + /// Setting this to zero will make the agent move directly to the end of the path and rotate on the spot to face the desired facing direction, once it is there. + /// + /// When approaching a destination for which no desired facing direction has been set, this field has no effect. + /// + /// Warning: Setting this to a too small (but non-zero) value may look bad if the agent cannot rotate fast enough to stay on the arc. + /// + /// This will be multiplied by the agent's scale to get the actual radius. + /// + public float leadInRadiusWhenApproachingDestination; + + /// + /// If rotation on the spot is allowed or not. + /// + /// When the agent wants to turn significantly, enabling this will make it turn on the spot instead of moving in an arc. + /// This can make for more responsive and natural movement for humanoid characters. + /// + public bool allowRotatingOnSpot { + get => allowRotatingOnSpotBacking != 0; + set => allowRotatingOnSpotBacking = (byte)(value ? 1 : 0); + } + + /// + /// If rotation on the spot is allowed or not. + /// 1 for allowed, 0 for not allowed. + /// + /// That we have to use a byte instead of a boolean is due to a Burst limitation. + /// + [SerializeField] + byte allowRotatingOnSpotBacking; + + public const float DESTINATION_CLEARANCE_FACTOR = 4f; + + private static readonly ProfilerMarker MarkerSidewaysAvoidance = new ProfilerMarker("SidewaysAvoidance"); + private static readonly ProfilerMarker MarkerPID = new ProfilerMarker("PID"); + private static readonly ProfilerMarker MarkerOptimizeDirection = new ProfilerMarker("OptimizeDirection"); + private static readonly ProfilerMarker MarkerSmallestDistance = new ProfilerMarker("ClosestDistance"); + private static readonly ProfilerMarker MarkerConvertObstacles = new ProfilerMarker("ConvertObstacles"); + + [System.Flags] + public enum DebugFlags { + Nothing = 0, + Position = 1 << 0, + Tangent = 1 << 1, + SidewaysClearance = 1 << 2, + ForwardClearance = 1 << 3, + Obstacles = 1 << 4, + Funnel = 1 << 5, + Path = 1 << 6, + ApproachWithOrientation = 1 << 7, + Rotation = 1 << 8, + } + + public void ScaleByAgentScale (float agentScale) { + speed *= agentScale; + leadInRadiusWhenApproachingDestination *= agentScale; + desiredWallDistance *= agentScale; + } + + public float Speed (float remainingDistance) { + if (speed <= 0) return 0; + if (this.slowdownTime <= 0) return remainingDistance <= 0.0001f ? 0 : speed; + + // This is what you get if you apply a constant deceleration per unit of time of this.speed/this.slowdownTime + float slowdownFactor = Mathf.Min(1.0f, Mathf.Sqrt(2 * remainingDistance / (speed * this.slowdownTime))); + + var res = speed * slowdownFactor; + Assert.IsTrue(math.isfinite(res)); + return res; + } + + /// + /// Accelerates as quickly as possible. + /// + /// This follows the same curve as the function, as a function of the remaining distance. + /// + /// Returns: The speed the agent should have after accelerating for dt seconds. Assuming dt is small. + /// + /// The current speed of the agent. + /// The time it takes for the agent to reach the maximum speed, starting from a standstill. + /// The time to accelerate for. Can be negative to decelerate instead. + public float Accelerate (float speed, float timeToReachMaxSpeed, float dt) { + // This can be derived by assuming a constant deceleration per unit of time: + // x''(t) = A + // Integrating twice gives us + // x'(t) = A * t + // x(t) = A * t^2 / 2 + // + // Ensuring it yields the same output as the Speed function gives us the equation: + // x'(t) = S * sqrt(2 * x(t) / (S * T)) + // A * t = S * sqrt(2 * (A * t^2 / 2) / (S * T)) + // + // Which yields the acceleration when solved: + // A = S / T + if (timeToReachMaxSpeed > 0.001f) { + var a = this.speed / timeToReachMaxSpeed; + return math.clamp(speed + dt * a, 0, this.speed); + } else { + return dt > 0 ? this.speed : 0; + } + } + + public float CurveFollowingStrength (float signedDistToClearArea, float radiusToWall, float remainingDistance) { + var speed = math.max(0.00001f, this.speed); + var followingStrength = AnglePIDController.RotationSpeedToFollowingStrength(speed, math.radians(this.rotationSpeed)); + var modifiedAlpha = math.max(followingStrength, 40.0f * math.pow(math.abs(signedDistToClearArea) / math.max(0.0001f, radiusToWall), 1)); + var remainingTime = remainingDistance / speed; + + // Just before reaching the end of the path, the agent should try to follow the path very closely to avoid overshooting, + // and potentially spinning in place. + const float HIGH_EFFORT_TIME = 0.2f; + modifiedAlpha = math.max(modifiedAlpha, math.min(80.0f, math.pow(1.0f / math.max(0, remainingTime - HIGH_EFFORT_TIME), 3))); + + Assert.IsTrue(math.isfinite(modifiedAlpha) || modifiedAlpha == float.PositiveInfinity); + return modifiedAlpha; + } + + static bool ClipLineByHalfPlaneX (ref float2 a, ref float2 b, float x, float side) { + var wrongSideA = (a.x - x)*side < 0; + var wrongSideB = (b.x - x)*side < 0; + if (wrongSideA && wrongSideB) return false; + if (wrongSideA != wrongSideB) { + var t = math.unlerp(a.x, b.x, x); + var intersection = math.lerp(a, b, t); + if (wrongSideA) a = intersection; + else b = intersection; + } + return true; + } + + static void ClipLineByHalfPlaneYt (float2 a, float2 b, float y, float side, ref float mnT, ref float mxT) { + var wrongSideA = (a.y - y)*side < 0; + var wrongSideB = (b.y - y)*side < 0; + if (wrongSideA && wrongSideB) { + mnT = 1; + mxT = 0; + } else if (wrongSideA != wrongSideB) { + var t = math.unlerp(a.y, b.y, y); + if (wrongSideA) mnT = math.max(mnT, t); + else mxT = math.min(mxT, t); + } + } + + /// + /// Returns either the most clockwise, or most counter-clockwise direction of the three given directions. + /// The directions are compared pairwise, not using any global reference angle. + /// + static float2 MaxAngle (float2 a, float2 b, float2 c, bool clockwise) { + a = math.select(a, b, VectorMath.Determinant(a, b) < 0 == clockwise); + a = math.select(a, c, VectorMath.Determinant(a, c) < 0 == clockwise); + return a; + } + + /// + /// Returns either the most clockwise, or most counter-clockwise direction of the two given directions. + /// The directions are compared pairwise, not using any global reference angle. + /// + static float2 MaxAngle (float2 a, float2 b, bool clockwise) { + return math.select(a, b, VectorMath.Determinant(a, b) < 0 == clockwise); + } + + const float ALLOWED_OVERLAP_FACTOR = 0.1f; + const float STEP_MULTIPLIER = 1.0f; + const float MAX_FRACTION_OF_REMAINING_DISTANCE = 0.9f; + const int OPTIMIZATION_ITERATIONS = 8; + + static void DrawChisel (float2 start, float2 direction, float pointiness, float length, float width, CommandBuilder draw, Color col) { + draw.PushColor(col); + var cornerL = start + (direction * pointiness + new float2(-direction.y, direction.x)) * width; + var cornerR = start + (direction * pointiness - new float2(-direction.y, direction.x)) * width; + draw.xz.Line(start, cornerL, col); + draw.xz.Line(start, cornerR, col); + var remainingLength = length - pointiness * width; + if (remainingLength > 0) { + draw.xz.Ray(cornerL, direction * remainingLength, col); + draw.xz.Ray(cornerR, direction * remainingLength, col); + } + draw.PopColor(); + } + + static void SplitSegment (float2 e1, float2 e2, float desiredRadius, float length, float pointiness, ref EdgeBuffers buffers) { + // Check if it is completely outside the range we concern ourselves with. + // When the direction is rotated, we may end up caring about segments further to the side than #desiredRadius, but with + // a safety margin of 2, we should catch all potential segments that we care about. + float radiusWithMargin = desiredRadius * 2f; + if ((e1.y < -radiusWithMargin && e2.y < -radiusWithMargin) || (e1.y > radiusWithMargin && e2.y > radiusWithMargin)) return; + + // Remove the part of the segment that is behind the agent + if (!ClipLineByHalfPlaneX(ref e1, ref e2, 0, 1)) return; + + // We don't care about any segments further away than #length + if (!VectorMath.SegmentCircleIntersectionFactors(e1, e2, length*length, out var t1, out var t2)) { + // Completely outside the circle + return; + } + + // Remove the parts of segments that are really close to the agent. + // Otherwise it can try to aggressively avoid segments that are super close, but are only obstacles due to minimal floating point errors. + var thresholdRadius = desiredRadius*0.01f; + if (VectorMath.SegmentCircleIntersectionFactors(e1, e2, thresholdRadius*thresholdRadius, out var tInner1, out var tInner2) && tInner1 < t2 && tInner2 > t1) { + // Remove the intersection with the inner circle. + // This may split the segment into 0, 1 or 2 parts. + if (tInner1 > t1 && tInner1 < t2) SplitSegment2(math.lerp(e1, e2, t1), math.lerp(e1, e2, tInner1), desiredRadius, pointiness, ref buffers); + if (tInner2 > t1 && tInner2 < t2) SplitSegment2(math.lerp(e1, e2, tInner2), math.lerp(e1, e2, t2), desiredRadius, pointiness, ref buffers); + } else { + // No intersection with the inner circle. This is the common case. + SplitSegment2(math.lerp(e1, e2, t1), math.lerp(e1, e2, t2), desiredRadius, pointiness, ref buffers); + } + } + + static void SplitSegment2 (float2 e1, float2 e2, float desiredRadius, float pointiness, ref EdgeBuffers buffers) { + // The shape that we use for avoidance looks like this: + // __________ + // / + // / + // \ + // \ __________ + // + // With the agent at the pointy end of the shape. + // Here we check if the segment overlaps the triangular part of the shape, as defined by a circle with the same radius + // as the sides of the triangle. + if (VectorMath.SegmentCircleIntersectionFactors(e1, e2, (pointiness*pointiness + 1)*desiredRadius*desiredRadius, out var t1, out var t2)) { + // Split the segment at the intersection with the circle + // This may split the segment into 0, 1, 2 or 3 parts. + if (t1 > 0.0f && t2 < 1.0f) { + SplitSegment3(e1, math.lerp(e1, e2, t1), desiredRadius, false, ref buffers); + SplitSegment3(math.lerp(e1, e2, t1), math.lerp(e1, e2, t2), desiredRadius, true, ref buffers); + SplitSegment3(math.lerp(e1, e2, t2), e2, desiredRadius, false, ref buffers); + } else if (t1 > 0.0f) { + SplitSegment3(e1, math.lerp(e1, e2, t1), desiredRadius, false, ref buffers); + SplitSegment3(math.lerp(e1, e2, t1), e2, desiredRadius, true, ref buffers); + } else if (t2 < 1.0f) { + SplitSegment3(e1, math.lerp(e1, e2, t2), desiredRadius, true, ref buffers); + SplitSegment3(math.lerp(e1, e2, t2), e2, desiredRadius, false, ref buffers); + } else { + // Whole segment + SplitSegment3(e1, e2, desiredRadius, true, ref buffers); + } + } else { + // Outside the circle + SplitSegment3(e1, e2, desiredRadius, false, ref buffers); + } + } + + static void SplitSegment3 (float2 e1, float2 e2, float desiredRadius, bool inTriangularRegion, ref EdgeBuffers buffers) { + // Check the orientation of the segment, and bias it so that the agent is + // more like to try to pass on the "correct" side of the segment. + // All obstacle edges that we get from the navmesh are oriented. + // However, we only bias the segment when we calculate which side the segment is on, + // and when calculating the intersection factor. After that, we return to using + // the original segment. + // + // If we don't do this, then the agent can get stuck at the border of the navmesh. + // Even if the agent is clamped to the navmesh, it may end up a tiiny bit outside it, + // and then it would try to avoid the border of the navmesh by moving even further outside it. + var r1 = e1; + var r2 = e2; + if (r2.x < r1.x) { + r1.y -= 0.01f; + r2.y -= 0.01f; + } else { + r1.y += 0.01f; + r2.y += 0.01f; + } + + var e1Left = r1.y > 0; + + // Ensure e1 is to the left of the midpoint line + if (!e1Left) { + Memory.Swap(ref e1, ref e2); + Memory.Swap(ref r1, ref r2); + } + + // Intersection of the line e1 -> e2 with the line y=0 + var tIntersection = math.unlerp(r1.y, r2.y, 0f); + + var anyIntersection = math.isfinite(tIntersection); + if (tIntersection <= 0.0f || tIntersection >= 1.0f || !anyIntersection) { + // No intersection + SplitSegment4(e1, e2, inTriangularRegion, e1Left, ref buffers); + } else { + // Intersection. Split the segment into two parts, one for the left side, and one for the right side. + var intersection = e1 + tIntersection * (e2 - e1); + var l1 = math.lengthsq(e1 - intersection); + var l2 = math.lengthsq(e2 - intersection); + var allowedLineOverlap = desiredRadius * ALLOWED_OVERLAP_FACTOR; + float allowedLineOverlapSq = allowedLineOverlap*allowedLineOverlap; + + // Check both the left and right subsegments. Ignore them if they are really short. + if (l1 > allowedLineOverlapSq || l1 >= l2) SplitSegment4(e1, intersection, inTriangularRegion, true, ref buffers); + if (l2 > allowedLineOverlapSq || l2 >= l1) SplitSegment4(intersection, e2, inTriangularRegion, false, ref buffers); + } + } + + static void SplitSegment4 (float2 e1, float2 e2, bool inTriangularRegion, bool left, ref EdgeBuffers buffers) { + // Ignore tiiiny edges + // Not quite sure when they get generated, but they do exist. + // Including these can cause issues if end up almost, but not quite, on the midpoint line, + // near the end. + // Ideally we'd have some better code for tolerating cases when there's only a tiny obstacle on the left/right side. + if (math.all(math.abs(e1 - e2) < 0.01f)) return; + + ref var buffer = ref buffers.triangleRegionEdgesL; + if (inTriangularRegion) { + if (left) {} // NOOP + else buffer = ref buffers.triangleRegionEdgesR; + } else { + if (left) buffer = ref buffers.straightRegionEdgesL; + else buffer = ref buffers.straightRegionEdgesR; + } + + if (buffer.Length + 2 > buffer.Capacity) return; + buffer.AddNoResize(e1); + buffer.AddNoResize(e2); + } + + private struct EdgeBuffers { + public FixedList512Bytes triangleRegionEdgesL; + public FixedList512Bytes triangleRegionEdgesR; + public FixedList512Bytes straightRegionEdgesL; + public FixedList512Bytes straightRegionEdgesR; + } + + /// + /// Finds a direction to move in that is as close as possible to the desired direction while being clear of obstacles, if possible. + /// This keeps the agent from moving too close to walls. + /// + /// Current position of the agent. + /// Point the agent is moving towards. + /// The distance the agent should try to keep from obstacles. + /// Remaining distance in the path. + /// Essentially controls how much the agent will cut corners. A higher value will lead to a smoother path, + /// but it will also lead to the agent not staying as far away from corners as the desired wall distance parameter would suggest. + /// It is a unitless quantity. + /// Edges of obstacles. Each edge is represented by two points. + /// CommandBuilder to use for drawing debug information. + /// Flags to control what debug information to draw. + public static float2 OptimizeDirection (float2 start, float2 end, float desiredRadius, float remainingDistance, float pointiness, NativeArray edges, CommandBuilder draw, DebugFlags debugFlags) { + var length = math.length(end - start); + var direction0 = math.normalizesafe(end - start); + length *= 0.999f; + length = math.min(MAX_FRACTION_OF_REMAINING_DISTANCE * remainingDistance, length); + if (desiredRadius <= 0.0001f) return direction0; + + var lengthOrig = length; + var lengthInvOrig = 1 / lengthOrig; + + // Pre-process all edges by splitting them up and grouping them by zone. + // We have 4 zones that we care about: + // 1. Within the triangular region near the agent, on the left side of the line from #start to #end + // 2. Within the triangular region near the agent, on the right side + // 3. Outside the triangular region, on the left side + // 4. Outside the triangular region, on the right side + // We assume that about 32 edges for each zone is enough. If we find more edges, the remainder will be discarded. + // Usually there are only a few edges, so this is not a problem in practice. + var buffers = new EdgeBuffers(); + for (int i = 0; i < edges.Length; i += 2) { + // Rotate the edge so that the x-axis corresponds to #direction0 + var e1 = VectorMath.ComplexMultiplyConjugate(edges[i] - start, direction0); + var e2 = VectorMath.ComplexMultiplyConjugate(edges[i+1] - start, direction0); + SplitSegment(e1, e2, desiredRadius, length, pointiness, ref buffers); + } + + // if ((debugFlags & DebugFlags.ForwardClearance) != 0) { + // for (int i = 0; i < buffers.straightRegionEdgesL.Length; i += 2) { + // draw.xz.Line(start + VectorMath.ComplexMultiply(buffers.straightRegionEdgesL[i], direction0), start + VectorMath.ComplexMultiply(buffers.straightRegionEdgesL[i+1], direction0), Palette.Orange); + // } + // for (int i = 0; i < buffers.straightRegionEdgesR.Length; i += 2) { + // draw.xz.Line(start + VectorMath.ComplexMultiply(buffers.straightRegionEdgesR[i], direction0), start + VectorMath.ComplexMultiply(buffers.straightRegionEdgesR[i+1], direction0), Palette.Red); + // } + // for (int i = 0; i < buffers.triangleRegionEdgesL.Length; i += 2) { + // draw.xz.Line(start + VectorMath.ComplexMultiply(buffers.triangleRegionEdgesL[i], direction0), start + VectorMath.ComplexMultiply(buffers.triangleRegionEdgesL[i+1], direction0), Palette.Pink); + // } + // for (int i = 0; i < buffers.triangleRegionEdgesR.Length; i += 2) { + // draw.xz.Line(start + VectorMath.ComplexMultiply(buffers.triangleRegionEdgesR[i], direction0), start + VectorMath.ComplexMultiply(buffers.triangleRegionEdgesR[i+1], direction0), Palette.Purple); + // } + // } + + // Complex number representing how much to rotate the original direction by. + // The number (1,0) indicates no rotation. + var direction = new float2(1, 0); + + // The optimization usually converges very quickly. Error is approximately O(0.5^n) + for (int it = 0; it < OPTIMIZATION_ITERATIONS; it++) { + if ((debugFlags & DebugFlags.ForwardClearance) != 0) { + var col = Palette.Blue; + col.a = 0.5f; + var d = VectorMath.ComplexMultiply(direction, direction0); + DrawChisel(start, d, pointiness, length, desiredRadius, draw, col); + draw.xz.Ray(start, d * length, Palette.Purple); + draw.xz.Circle(start, remainingDistance, col); + } + + var leftReference = new float2(0, desiredRadius); + var rightReference = new float2(0, -desiredRadius); + var leftObstacleDir = new float2(length, 0); + var rightObstacleDir = new float2(length, 0); + + // Iterate through all edges and calculate how much we need to rotate the direction to avoid them. + // We store all directions as complex numbers. + for (int i = 0; i < buffers.straightRegionEdgesL.Length; i += 2) { + // Rotate the edge so that the x-axis corresponds to #direction + var e1 = VectorMath.ComplexMultiplyConjugate(buffers.straightRegionEdgesL[i], direction); + var e2 = VectorMath.ComplexMultiplyConjugate(buffers.straightRegionEdgesL[i+1], direction); + leftObstacleDir = MaxAngle(leftObstacleDir, e1 - leftReference, e2 - leftReference, true); + } + + for (int i = 0; i < buffers.straightRegionEdgesR.Length; i += 2) { + var e1 = VectorMath.ComplexMultiplyConjugate(buffers.straightRegionEdgesR[i], direction); + var e2 = VectorMath.ComplexMultiplyConjugate(buffers.straightRegionEdgesR[i+1], direction); + rightObstacleDir = MaxAngle(rightObstacleDir, e1 - rightReference, e2 - rightReference, false); + } + + var referenceDiagonalL = math.normalizesafe(VectorMath.ComplexMultiply(new float2(pointiness*desiredRadius, desiredRadius), direction)); + var referenceDiagonalR = math.normalizesafe(VectorMath.ComplexMultiply(new float2(pointiness*desiredRadius, -desiredRadius), direction)); + for (int i = 0; i < buffers.triangleRegionEdgesL.Length; i += 2) { + // Rotate the edge so that the x-axis corresponds to #referenceDiagonalL + var offset1 = VectorMath.ComplexMultiplyConjugate(buffers.triangleRegionEdgesL[i], referenceDiagonalL); + var offset2 = VectorMath.ComplexMultiplyConjugate(buffers.triangleRegionEdgesL[i+1], referenceDiagonalL); + var offset = offset2.y < offset1.y ? offset2 : offset1; + if (offset.y < 0) leftObstacleDir = MaxAngle(leftObstacleDir, offset, true); + } + + for (int i = 0; i < buffers.triangleRegionEdgesR.Length; i += 2) { + var offset1 = VectorMath.ComplexMultiplyConjugate(buffers.triangleRegionEdgesR[i], referenceDiagonalR); + var offset2 = VectorMath.ComplexMultiplyConjugate(buffers.triangleRegionEdgesR[i+1], referenceDiagonalR); + var offset = offset2.y > offset1.y ? offset2 : offset1; + if (offset.y > 0) rightObstacleDir = MaxAngle(rightObstacleDir, offset, false); + } + + // Do some kind of weighted average of the two directions. + // Here we map the length of the obstacle directions as 0=>0 and L=>infinity (but we clamp it to a finite but large value). + // Basically we want to give more weight to obstacles closer to the agent. + var leftInverseWeight = 1 / math.max(0.000001f, lengthOrig - leftObstacleDir.x*leftObstacleDir.x) - lengthInvOrig; + var rightInverseWeight = 1 / math.max(0.000001f, lengthOrig - rightObstacleDir.x*rightObstacleDir.x) - lengthInvOrig; + var rTot = math.normalizesafe(leftObstacleDir * rightInverseWeight + rightObstacleDir * leftInverseWeight); + + // Alternative averaging which only takes the sum of the angles + // var rTot2 = math.normalizesafe(VectorMath.ComplexMultiply(leftObstacleDir, rightObstacleDir)); + + // Approximately multiplying the angle by STEP_MULTIPLIER + var rStep = math.lerp(new float2(1, 0), rTot, STEP_MULTIPLIER); + direction = math.normalizesafe(VectorMath.ComplexMultiply(direction, rStep)); + if (leftObstacleDir.y == 0 && rightObstacleDir.y == 0) { + // Apparently there were NO obstacles. + // We can afford to increase our length check a little bit. + // This is important in case we encounter a corner which is on a very pointy obstacle. + // _______ + // _ _ _ _ / + // A _ _ _ _ <______ + // + // Where A is the agent, trying to move towards the corner marked with a '<'. + // In that case, we will find no edges to avoid and we will end up moving directly towards the corner instead + // of staying slightly away from the walls. Unless we increase the length check a little bit, that is. + // + // However, we don't want to increase the length more than the remaining distance to the target minus a small margin, + // as that can cause weird movement when approaching a target near a wall. It would try to unnecessarily avoid the wall + // causing ocillating movement. + length = math.min(remainingDistance * MAX_FRACTION_OF_REMAINING_DISTANCE, math.min(length * 1.1f, lengthOrig * 1.2f)); + } else { + // Decrease the length a bit, to bias the optimization towards closer obstacles + length = math.min(length, math.max(desiredRadius * 2.0f, math.min(leftObstacleDir.x, rightObstacleDir.x) * 2.0f)); + } + } + + direction = VectorMath.ComplexMultiply(direction, direction0); + + if ((debugFlags & DebugFlags.ForwardClearance) != 0) { + DrawChisel(start, direction, pointiness, length, desiredRadius, draw, Color.black); + } + + Assert.IsTrue(!math.any(math.isnan(direction))); + return direction; + } + + /// + /// Calculates the closest point on any point of an edge that is inside a wedge. + /// + /// Returns: The distance to the closest point on any edge that is inside the wedge. + /// + /// The origin point of the wedge (the pointy end). + /// The first direction of the wedge. + /// The second direction of the wedge. + /// The wedge is shrunk by this amount. In the same units as the input points. + /// The edges to check for intersection with. + public static float SmallestDistanceWithinWedge (float2 point, float2 dir1, float2 dir2, float shrinkAmount, NativeArray edges) { + dir1 = math.normalizesafe(dir1); + dir2 = math.normalizesafe(dir2); + + // Early out in case the wedge is very narrow. + // This is primarily a performance optimization. + // If the agent is almost facing the correct direction, then it shouldn't be heading towards an obstacle. + const float MIN_ANGLE_COS = 0.999f; + if (math.dot(dir1, dir2) > MIN_ANGLE_COS) return float.PositiveInfinity; + + var side = math.sign(VectorMath.Determinant(dir1, dir2)); + shrinkAmount *= side; + + var closestDistanceSq = float.PositiveInfinity; + for (int i = 0; i < edges.Length; i += 2) { + var e1 = edges[i] - point; + var e2 = edges[i+1] - point; + + // Clip the line by the two half planes that the wedge consists of + var e1a = VectorMath.ComplexMultiplyConjugate(e1, dir1); + var e2a = VectorMath.ComplexMultiplyConjugate(e2, dir1); + var e1b = VectorMath.ComplexMultiplyConjugate(e1, dir2); + var e2b = VectorMath.ComplexMultiplyConjugate(e2, dir2); + var mnT = 0f; + var mxT = 1f; + + ClipLineByHalfPlaneYt(e1a, e2a, shrinkAmount, side, ref mnT, ref mxT); + if (mnT > mxT) continue; + ClipLineByHalfPlaneYt(e1b, e2b, -shrinkAmount, -side, ref mnT, ref mxT); + if (mnT > mxT) continue; + + // Find the distance to the closest point on the clipped line segment + var lengthsq = math.lengthsq(e2 - e1); + var t = math.clamp(math.dot(e1, e1 - e2) * math.rcp(lengthsq), mnT, mxT); + var d = math.lengthsq(math.lerp(e1, e2, t)); + closestDistanceSq = math.select(closestDistanceSq, math.min(closestDistanceSq, d), lengthsq > math.FLT_MIN_NORMAL); + } + Assert.IsTrue(!float.IsNaN(closestDistanceSq)); + return math.sqrt(closestDistanceSq); + } + + public static float2 Linecast (float2 a, float2 b, NativeArray edges) { + var k = 1f; + for (int i = 0; i < edges.Length; i += 2) { + var e1 = edges[i]; + var e2 = edges[i+1]; + VectorMath.LineLineIntersectionFactors(a, b - a, e1, e2 - e1, out var t1, out var t2); + if (t2 >= 0 && t2 <= 1 && t1 > 0) { + k = math.min(k, t1); + } + } + return a + (b - a) * k; + } + + public struct ControlParams { + public Vector3 p; + public float speed; + public float rotation; + public float maxDesiredWallDistance; + public float3 endOfPath; + public float3 facingDirectionAtEndOfPath; + public NativeArray edges; + public float3 nextCorner; + public float agentRadius; + public float remainingDistance; + public float3 closestOnNavmesh; + public DebugFlags debugFlags; + public NativeMovementPlane movementPlane; + } + + /// + /// Finds the bounding box in which this controller is interested in navmesh edges. + /// + /// The edges should be assigned to . + /// The bounding box is relative to the given movement plane. + /// + public static Bounds InterestingEdgeBounds (ref PIDMovement settings, float3 position, float3 nextCorner, float height, NativeMovementPlane plane) { + // Convert the position and next corner to local space, relative to the movement plane + var localPos = math.mul(math.conjugate(plane.rotation), position); + var localNextCorner = math.mul(math.conjugate(plane.rotation), nextCorner); + // Default bounds which extend from 1/2*height below the agent to the agent's head (assuming its pivot is at the agent's feet). + var localBounds = new Bounds(localPos + new float3(0, height * 0.25f, 0), new Vector3(0, 1.5f*height, 0)); + // Don't allow the next corner to push the bounding box up or down too much, since that can let us include obstacle edges + // that are e.g. on a floor below or a floor above the agent. Especially if the agent is currently moving on a sloped surface. + localNextCorner.y = localPos.y; + + localBounds.Encapsulate(localNextCorner); + // If an agent needs to make a full 180 degree turn, then we need a diameter instead of a radius. + // However since the agent gets a lower rotation speed the closer it gets to the desired rotation, + // this factor of two gets sort of compensated for already. + if (settings.rotationSpeed > 0) { + var approximateTurningDiameter = settings.speed / math.radians(settings.rotationSpeed); //2.0f * AnglePIDController.ApproximateTurningRadius(settings.followingStrength); + // + localBounds.Expand(new Vector3(1, 0, 1) * math.max(approximateTurningDiameter, settings.desiredWallDistance * OPTIMIZATION_ITERATIONS * STEP_MULTIPLIER)); + } + return localBounds; + } + + static float2 OffsetCornerForApproach (float2 position2D, float2 endOfPath2D, float2 facingDir2D, ref PIDMovement settings, float2 nextCorner2D, ref float gammaAngle, ref float gammaAngleWeight, DebugFlags debugFlags, ref CommandBuilder draw, NativeArray edges) { + var d1 = endOfPath2D - position2D; + + // Cosine of the approach angle that is considered too steep to try to turn in an arc + const float STEEP_ANGLE_THRESHOLD_COS = -0.2f; + + if (math.dot(math.normalizesafe(d1), facingDir2D) < STEEP_ANGLE_THRESHOLD_COS) { + // Too steep + return nextCorner2D; + } + + // Line orthogonal to d1 + var n1 = new float2(-d1.y, d1.x); + + // Line orthogonal to facingDir2D + var n2 = new float2(-facingDir2D.y, facingDir2D.x); + var mid = (position2D + endOfPath2D) * 0.5f; + + // Find the center of the circle which touches both the points endOfPath2D and position2D, and has a tangent parallel to facingDir2D at endOfPath2D. + var circleCenter = (float2)VectorMath.LineIntersectionPoint(mid, mid + n1, endOfPath2D, endOfPath2D + n2, out bool intersects); + + if (!intersects) return nextCorner2D; + + // Do not try to approach the destination with a large arc if there might be an obstacle in the way + // Check within a wedge and offset it sliightly backwards to take care of the case when the end of the path + // is right at the end of the navmesh. This is a common case when for example ordering an agent to interact + // with some prop. + // + // A <- Agent approaching destination at wall + //###| | + //#<-x----/ + //###| + // + // A <- agent approaching destination facing away from wall, right next to a corner + // ###### + // <-x ###### + // + // + var distToObstacle = SmallestDistanceWithinWedge(endOfPath2D - 0.01f * facingDir2D, n2 - 0.1f * facingDir2D, -n2 - 0.1f * facingDir2D, 0.001f, edges); + var maxRadius = settings.leadInRadiusWhenApproachingDestination; + maxRadius = math.min(maxRadius, distToObstacle * 0.9f); + var circleRadius = math.length(circleCenter - endOfPath2D); + + // Calculate the intersection point of the two tangents of the circle, one at endOfPath2D and one at position2D. + // Offset is the distance from endOfPath2D to the intersection point + // + // |-------- offset ------| + // A ~ ~ - , p + // ' , / + // , / + // , / + // , / + // , / + // , + // x <- destination + // / + // |_ <- facingDir2D + // + var dot = math.abs(math.dot(math.normalizesafe(d1), n2)); + var offset = 1.0f / math.max(0.1f, math.sqrt(1 - dot*dot)) * math.length(d1) * 0.5f; + + // Tweak the offset slightly to account for the maximum radius. + // Limit the radius using a smooth thresholding function. + offset /= math.min(maxRadius, circleRadius); + offset = math.tanh(offset); + offset *= math.min(maxRadius, circleRadius); + + // Offset the next corner backwards along the facing direction, + // so that the agent will approach the destination along a curve. + var newNextCorner2D = nextCorner2D - facingDir2D * offset; + + if ((debugFlags & DebugFlags.ApproachWithOrientation) != 0) { + draw.xz.Circle(circleCenter, circleRadius, Color.blue); + draw.xz.Arrow(position2D, newNextCorner2D, Palette.Orange); + } + + // If the new corner is not visible from the agent's current position, + // then return the original corner, as we do not want to try to walk into a wall. + // + // Additionally, if the new corner is not visible from the end of the path, we also return the original corner. + // Since there's likely some obstacle in the way, and we don't want to try to walk into a wall. + if (math.lengthsq(Linecast(position2D, newNextCorner2D, edges) - newNextCorner2D) > 0.001f || math.lengthsq(Linecast(endOfPath2D, newNextCorner2D, edges) - newNextCorner2D) > 0.001f) { + return nextCorner2D; + } else { + return newNextCorner2D; + } + } + + public static AnglePIDControlOutput2D Control (ref PIDMovement settings, float dt, ref ControlParams controlParams, ref CommandBuilder draw, out float maxDesiredWallDistance) { + if (dt <= 0) { + // If the game is paused, then do not move or rotate. + maxDesiredWallDistance = controlParams.maxDesiredWallDistance; + return new AnglePIDControlOutput2D { + rotationDelta = 0, + positionDelta = float2.zero, + }; + } + var movementPlane = controlParams.movementPlane; + var position2D = movementPlane.ToPlane(controlParams.p, out float positionElevation); + + // If we are drawing any debug information, push a matrix so that we can draw in local space. + // If not, skip pushing the matrix to improve performance. + if (controlParams.debugFlags != 0) draw.PushMatrix(math.mul(new float4x4(movementPlane.rotation, float3.zero), float4x4.Translate(new float3(0, positionElevation, 0)))); + + if ((controlParams.debugFlags & DebugFlags.Position) != 0) { + draw.xz.Cross(controlParams.closestOnNavmesh, 0.05f, Color.red); + } + + var edges = controlParams.edges; + if ((controlParams.debugFlags & DebugFlags.Obstacles) != 0) { + draw.PushLineWidth(2); + draw.PushColor(Color.red); + for (int i = 0; i < edges.Length; i += 2) { + draw.xz.Line(edges[i], edges[i+1]); + } + draw.PopColor(); + draw.PopLineWidth(); + } + + var nextCorner2D = movementPlane.ToPlane(controlParams.nextCorner); + float gamma = 0; + float gammaAngle = 0; + float gammaAngleWeight = 0; + // +Y is our forward direction, so add 90 degrees so that rotation2D = curveAngle means we are following the curve. + // Mathematically it makes much more sense if rotations are relative to the +X axis. So we use this convention internally. + var rotation2D = controlParams.rotation + Mathf.PI / 2; + var facingDir2D = math.normalizesafe(movementPlane.ToPlane(controlParams.facingDirectionAtEndOfPath)); + bool isVeryCloseToEndOfPath = controlParams.remainingDistance < controlParams.agentRadius*0.1f; + + if (!isVeryCloseToEndOfPath && settings.leadInRadiusWhenApproachingDestination > 0 && math.any(facingDir2D != 0)) { + var endOfPath2D = movementPlane.ToPlane(controlParams.endOfPath); + bool isAtLastCorner = math.lengthsq(endOfPath2D - nextCorner2D) <= 0.1f; + if (isAtLastCorner) { + var c1 = OffsetCornerForApproach( + position2D, + endOfPath2D, + facingDir2D, + ref settings, + nextCorner2D, + ref gammaAngle, + ref gammaAngleWeight, + controlParams.debugFlags, + ref draw, + edges + ); + nextCorner2D = c1; + + var simDx = settings.speed * 0.1f; + if (simDx > 0.001f) { + math.sincos(rotation2D, out var sin, out var cos); + var forward = new float2(cos, sin); + var c2 = OffsetCornerForApproach( + position2D + forward * simDx, + endOfPath2D, + facingDir2D, + ref settings, + nextCorner2D, + ref gammaAngle, + ref gammaAngleWeight, + DebugFlags.Nothing, + ref draw, + edges + ); + + // Calculate the number of radians between c1 and c2 from the agent's perspective. + // This is the amount that the agent must rotate to stay on the desired curve. + var s = VectorMath.Determinant(math.normalizesafe(c1 - position2D), math.normalizesafe(c2 - position2D)); + gamma = math.asin(s)/simDx; + } + } + } + + var desiredForwardClearanceRadius = settings.desiredWallDistance; + desiredForwardClearanceRadius = math.max(0, math.min(desiredForwardClearanceRadius, (controlParams.remainingDistance - desiredForwardClearanceRadius) / DESTINATION_CLEARANCE_FACTOR)); + MarkerOptimizeDirection.Begin(); + + // In case the next corner is not visible from the agent's current position, then instead move towards the first intersection with an obstacle. + // This is important in some cases even when one would think that the next corner should be visible. + // This is because when unwrapping and flattening the funnel, the next corner may end up being move slightly due to various projections. + // This may cause it to end up inside a wall. If we didn't use a linecast here, the OptimizeDirection function + // would likely just give up and the agent would not stay away from the wall as it should. + nextCorner2D = Linecast(position2D, nextCorner2D, edges); + + const float Pointiness = 2f; + var estimatedForward = OptimizeDirection(position2D, nextCorner2D, desiredForwardClearanceRadius, controlParams.remainingDistance, Pointiness, edges, draw, controlParams.debugFlags); + MarkerOptimizeDirection.End(); + + // Increase the maxDesiredWallDistance over time, to slowly push the agent away from walls. + maxDesiredWallDistance = controlParams.maxDesiredWallDistance + settings.speed * 0.1f * dt; + var desiredPositionClearance = maxDesiredWallDistance; + var signedDist = 0f; + var signedDistToClearArea = 0f; + maxDesiredWallDistance = math.min(maxDesiredWallDistance, desiredPositionClearance); + + if ((controlParams.debugFlags & DebugFlags.Tangent) != 0) { + draw.Arrow(controlParams.p, controlParams.p + new Vector3(estimatedForward.x, 0, estimatedForward.y), Palette.Orange); + } + + AnglePIDControlOutput2D output; + if (isVeryCloseToEndOfPath) { + // When we are really close to the endpoint, move directly towards the end and do not rotate (unless a facing direction has been set). + + // Accelerate, but only up to the very low speed we use when we are very close to the endpoint. + // We must be able to accelerate here, as otherwise we may never reach the endpoint if we started + // very close to the endpoint with zero speed. + var speed = math.min(settings.Speed(controlParams.remainingDistance), settings.Accelerate(controlParams.speed, settings.slowdownTime, dt)); + + // TODO: Maybe add a settling mechanic. Once we are really close, lock the destination and do not change it until it gets a certain minimum distance away from the agent + // This would avoid the agent drifting without rotating to follow a destination that moves slowly. + var dirToEnd = nextCorner2D - position2D; + var distToEnd = math.length(dirToEnd); + if (math.any(facingDir2D != 0)) { + var desiredAngle = math.atan2(facingDir2D.y, facingDir2D.x); + var maxRotationDelta = dt * math.radians(settings.maxRotationSpeed); + output = new AnglePIDControlOutput2D { + rotationDelta = math.clamp(AstarMath.DeltaAngle(rotation2D, desiredAngle), -maxRotationDelta, maxRotationDelta), + // Convert back to a rotation convention where +Y is forward + targetRotation = desiredAngle - Mathf.PI / 2, + positionDelta = distToEnd > math.FLT_MIN_NORMAL ? dirToEnd * (dt * speed / distToEnd) : dirToEnd, + }; + } else { + output = new AnglePIDControlOutput2D { + rotationDelta = 0, + // Convert back to a rotation convention where +Y is forward + targetRotation = rotation2D - Mathf.PI / 2, + positionDelta = distToEnd > math.FLT_MIN_NORMAL ? dirToEnd * (dt * speed / distToEnd) : dirToEnd, + }; + } + } else { + var modifiedFollowingStrength = settings.CurveFollowingStrength(signedDistToClearArea, desiredPositionClearance, controlParams.remainingDistance); + var curveAngle = math.atan2(estimatedForward.y, estimatedForward.x); + + var minimumRotationSpeed = 0f; + // If we are not perfectly facing our desired direction, we need to rotate to face it. + // We try to ensure we will not hit any obstacles by checking for nearby obstacles + // in the direction we are moving. If there are any obstacles, we can calculate + // the approximate rotation speed we need to have to avoid them. + // + // If we are very close to our desired facing direction, we skip this check + // to improve performance. + if (math.abs(AstarMath.DeltaAngle(curveAngle, rotation2D)) > math.PI*0.001f) { + math.sincos(rotation2D, out var sin, out var cos); + var forward = new float2(cos, sin); + var closestWithinWedge = SmallestDistanceWithinWedge(position2D, estimatedForward, forward, controlParams.agentRadius*0.1f, edges); + + if ((controlParams.debugFlags & DebugFlags.ForwardClearance) != 0 && float.IsFinite(closestWithinWedge)) { + draw.xz.Arc(position2D, position2D + forward * closestWithinWedge, position2D + estimatedForward, Palette.Purple); + } + + if (closestWithinWedge > 0.001f && closestWithinWedge*1.01f < controlParams.remainingDistance) { + const float SAFETY_FACTOR = 2.0f; + minimumRotationSpeed = math.rcp(closestWithinWedge) * SAFETY_FACTOR; + } + } + + MarkerPID.Begin(); + output = AnglePIDController.Control( + ref settings, + modifiedFollowingStrength, + rotation2D, + curveAngle + AstarMath.DeltaAngle(curveAngle, gammaAngle) * gammaAngleWeight, + gamma, + signedDist, + controlParams.speed, + controlParams.remainingDistance, + minimumRotationSpeed, + controlParams.speed < settings.speed*0.1f, + dt + ); + // Convert back to a rotation convention where +Y is forward + output.targetRotation -= Mathf.PI / 2; + MarkerPID.End(); + } + if (controlParams.debugFlags != 0) draw.PopMatrix(); + return output; + } + } + + /// + /// Implements a PID controller for the angular velocity of an agent following a curve. + /// + /// The PID controller is formulated for small angles (see https://en.wikipedia.org/wiki/Small-angle_approximation), but extends well to large angles. + /// For small angles, if y(t) is the curve/agent position, then y'(t) is the angle and y''(t) is the angular velocity. + /// This controller outputs an angular velocity, meaning it controls y''(t). + /// + /// See https://en.wikipedia.org/wiki/PID_controller + /// + public static class AnglePIDController { + const float DampingRatio = 1.0f; + + /// + /// An approximate turning radius the agent will have in an open space. + /// + /// This is based on the PID controller in the method. + /// + public static float ApproximateTurningRadius (float followingStrength) { + // With dampingRatio = 1, this will result in critical damping + var alpha = followingStrength; + var beta = 2 * math.sqrt(math.abs(alpha)) * DampingRatio; + + // Some sort of mean value + // If a character turns around, the angleToCurveError will go from math.PI to 0. + const float angleToCurveError = math.PI * 0.5f; + + return 1.0f/(beta * angleToCurveError); + } + + /// + /// Given a speed and a rotation speed, what is the approximate corresponding following strength. + /// + /// This is based on the PID controller in the method. + /// + public static float RotationSpeedToFollowingStrength (float speed, float maxRotationSpeed) { + // Using the following identity: + // turningRadius = speed/rotationSpeed + // and using the implementation for ApproximateTurningRadius, we can solve for the rotation speed + // and we get the expression below. + + // Note that we use a different angleToCurveError here compared to in ApproximateTurningRadius. + // This is because here we use the maximum angleToCurveError that could happen, while in ApproximateTurningRadius + // we use an average value. This is reasonable because the input to this method is + // the maximum rotation speed, not the average rotation speed. + const float angleToCurveError = math.PI; + var k = maxRotationSpeed / (2.0f * angleToCurveError * speed * DampingRatio); + var alpha = k * k; + return alpha; + } + + public static float FollowingStrengthToRotationSpeed (float followingStrength) { + return 1.0f / (ApproximateTurningRadius(followingStrength) * 0.5f); + } + + /// + /// How much to rotate and move in order to smoothly follow a given curve. + /// + /// If the maximum rotation speed (settings.maxRotationSpeed) would be exceeded, the agent will slow down to avoid exceeding it (up to a point). + /// + /// Returns: A control value that can be used to move the agent. + /// + /// Various movement settings + /// The integral term of the PID controller. The higher this value is, the quicker the agent will try to align with the curve. + /// The current direction of the agent, in radians. + /// The angle of the curve tangent at the nearest point, in radians. + /// The curvature of the curve at the nearest point. Positive values means the curve is turning to the left, negative values means the curve is turning to the right. + /// The signed distance from the agent to the curve. Positive values means the agent is to the right of the curve, negative values means the agent is to the left of the curve. + /// How quickly the agent should move. In meters/second. + /// The remaining distance to where the agent should stop. In meters. + /// The minimum rotation speed of the agent. In radians/second. Unless the agent does not desire to rotate at all, it will rotate at least this fast. + /// Should be true if the agent is currently standing still (or close to it). This allows it to rotate in place. + /// How long the current time-step is. In seconds. + public static AnglePIDControlOutput2D Control (ref PIDMovement settings, float followingStrength, float angle, float curveAngle, float curveCurvature, float curveDistanceSigned, float speed, float remainingDistance, float minRotationSpeed, bool isStationary, float dt) { + Assert.IsTrue(math.isfinite(angle)); + Assert.IsTrue(math.isfinite(curveAngle)); + Assert.IsTrue(math.isfinite(curveDistanceSigned)); + Assert.IsTrue(math.isfinite(curveCurvature)); + Assert.IsTrue(minRotationSpeed >= 0); + + // With dampingRatio = 1, this will result in critical damping + + // Following strength may be infinite if the rotation speed of the agent is infinite. Clamp it to a finite value to avoid NaNs. + var alpha = math.min(followingStrength, 10000f); + var beta = 2 * math.sqrt(math.abs(alpha)) * DampingRatio; + var gamma = 1.0f; + var angleToCurveError = AstarMath.DeltaAngle(angle, curveAngle); + var angleTowardsCurve = curveAngle + math.sign(curveDistanceSigned) * math.PI * 0.5f; + var deltaAngleTowardsCurve = AstarMath.DeltaAngle(angle, angleTowardsCurve); // TODO: Divide by PI/2? + + // Desired primary rotation in radians per meter + var alphaAngle = alpha * math.abs(curveDistanceSigned) * deltaAngleTowardsCurve; + // Desired primary rotation during this timestep + var alphaAngleDelta = alphaAngle * speed * dt; + + // Desired secondary rotation in radians per meter + var betaAngle = beta * angleToCurveError; + + // Assuming that an agent is stationary, the rotation of the agent will reach a steady state after a short while (alphaAngle + betaAngle = 0). + // This is the remaining angle we have left until we reach that steady state. + var denominator = beta + alpha * math.abs(curveDistanceSigned); + var remainingAngle = denominator > math.FLT_MIN_NORMAL ? (betaAngle + alphaAngle)/denominator : 0; + Assert.IsTrue(math.isfinite(remainingAngle)); + float.IsFinite(remainingAngle); + + // If the agent has to rotate *a lot* then stop moving and rotate in-place. + // Once we are rotating in place, we should continue doing that until we are almost facing the desired direction. + isStationary = settings.allowRotatingOnSpot && (math.abs(remainingAngle) > math.PI*0.6666f || (isStationary && math.abs(remainingAngle) > 0.1f)); + if (isStationary) { + var newSpeed = settings.Accelerate(speed, settings.slowdownTimeWhenTurningOnSpot, -dt); + var maxOnSpotRotationSpeed = math.radians(settings.maxOnSpotRotationSpeed); + var canRotateInOneStep = maxOnSpotRotationSpeed*dt > math.abs(remainingAngle); + if (newSpeed > 0 && !canRotateInOneStep) { + // Slow down as quickly as possible + return AnglePIDControlOutput2D.WithMovementAtEnd( + currentRotation: angle, + targetRotation: angle, + rotationDelta: 0, + moveDistance: newSpeed * dt + ); + } else { + // If we are rotating in place, rotate with the maximum rotation speed + return AnglePIDControlOutput2D.WithMovementAtEnd( + currentRotation: angle, + targetRotation: angle + remainingAngle, + rotationDelta: math.clamp(remainingAngle, -maxOnSpotRotationSpeed*dt, maxOnSpotRotationSpeed*dt), + // Check if we can rotate in place in one time-step. If so, skip standing still for this time-step. + moveDistance: canRotateInOneStep ? speed * dt : 0.0f + ); + } + } + + speed = math.min(settings.Speed(remainingDistance), settings.Accelerate(speed, settings.slowdownTime, dt)); + + if (math.abs(angleToCurveError) > math.PI*0.5f) { + // Ensures that if the agent is moving in the completely wrong direction, it will not continue doing that + // because the alpha term tells it to move left, and the beta term tells it to move right, cancelling each other out. + alphaAngleDelta = 0; + } + + if (math.abs(betaAngle) > 0.0001f) { + betaAngle = math.max(math.abs(betaAngle), minRotationSpeed) * math.sign(betaAngle); + } + + var betaAngleDelta = betaAngle * speed * dt; + // The weights are "how much we want to rotate this timestep, divided by the maximum amount of rotation that is allowed" + // This is used to avoid overshooting when following strengths are very high or the fps is low. + var alphaWeight = math.abs(alphaAngleDelta / deltaAngleTowardsCurve); + var betaWeight = math.abs(betaAngleDelta / angleToCurveError); + var gammaWeight = 1.0f; + var directionComponentInCurveDirection = math.max(0, math.cos(angleToCurveError)); + var speedMultiplier = 1.0f; + var moveDistance = speed * speedMultiplier * dt; + var curvatureIntegral = curveCurvature * moveDistance; + var gammaAngleDelta = gamma * curvatureIntegral * directionComponentInCurveDirection; + // Don't allow individual contributions to contribute more than their limit (e.g. overshooting their rotation target). + // But still keep the relative contribution proportions the same. + var overflowWeight = math.max(1f, math.max(alphaWeight, math.max(betaWeight, gammaWeight))); + var angleDelta = (gammaAngleDelta + betaAngleDelta + alphaAngleDelta) / overflowWeight; + + // If we would have rotated too quickly, slow down the agent + var maxRotationSpeed = math.radians(settings.maxRotationSpeed); + var rotationMultiplier = math.max(0.1f, math.min(1.0f, maxRotationSpeed*dt / math.abs(angleDelta))); + + Assert.IsTrue(math.isfinite(angle)); + Assert.IsTrue(math.isfinite(rotationMultiplier)); + Assert.IsTrue(math.isfinite(angleDelta)); + Assert.IsTrue(math.isfinite(moveDistance)); + + return new AnglePIDControlOutput2D( + currentRotation: angle, + targetRotation: angle + remainingAngle, + rotationDelta: angleDelta * rotationMultiplier, + moveDistance: moveDistance * rotationMultiplier + ); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs.meta new file mode 100644 index 0000000..9080220 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDMovement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d19693db79b5eedab18d1362b58bca1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs new file mode 100644 index 0000000..5e43ad1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs @@ -0,0 +1,29 @@ +using Unity.Mathematics; + +namespace Pathfinding.PID { + public struct AnglePIDControlOutput2D { + /// How much to rotate in a single time-step. In radians. + public float rotationDelta; + public float targetRotation; + /// How much to move in a single time-step. In world units. + public float2 positionDelta; + + public AnglePIDControlOutput2D(float currentRotation, float targetRotation, float rotationDelta, float moveDistance) { + var midpointRotation = currentRotation + rotationDelta * 0.5f; + math.sincos(midpointRotation, out float s, out float c); + this.rotationDelta = rotationDelta; + this.positionDelta = new float2(c, s) * moveDistance; + this.targetRotation = targetRotation; + } + + public static AnglePIDControlOutput2D WithMovementAtEnd (float currentRotation, float targetRotation, float rotationDelta, float moveDistance) { + var finalRotation = currentRotation + rotationDelta; + math.sincos(finalRotation, out float s, out float c); + return new AnglePIDControlOutput2D { + rotationDelta = rotationDelta, + targetRotation = targetRotation, + positionDelta = new float2(c, s) * moveDistance, + }; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs.meta new file mode 100644 index 0000000..5641b86 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Control/PIDUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3e01131736f2ac179398e1f408f94eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS.meta new file mode 100644 index 0000000..5dfeeec --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4551b5f18d9b68d428cf336b3ab3c38b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components.meta new file mode 100644 index 0000000..662ec62 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c496a3fafab9fab4a8c139fc7295f219 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs new file mode 100644 index 0000000..9909d0f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs @@ -0,0 +1,18 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.ECS.RVO; + + /// An agent's shape represented as a cylinder + [System.Serializable] + public struct AgentCylinderShape : IComponentData { + /// Radius of the agent in world units + public float radius; + + /// Height of the agent in world units + public float height; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs.meta new file mode 100644 index 0000000..aa81963 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentCylinderShape.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9dd6a4018eb50a48b69d83cb69a09b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs new file mode 100644 index 0000000..96d2751 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs @@ -0,0 +1,34 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.Util; + using Unity.Mathematics; + + /// Holds an agent's movement plane + [System.Serializable] + public struct AgentMovementPlane : IComponentData { + /// + /// The movement plane for the agent. + /// + /// The movement plane determines what the "up" direction of the agent is. + /// For most typical 3D games, this will be aligned with the Y axis, but there are + /// games in which the agent needs to navigate on walls, or on spherical worlds. + /// For those games this movement plane will track the plane in which the agent is currently moving. + /// + /// See: spherical (view in online documentation for working links) + /// + public NativeMovementPlane value; + + /// Create a movement plane aligned with the XZ plane of the specified rotation + public AgentMovementPlane (quaternion rotation) { + value = new NativeMovementPlane(rotation); + } + + public AgentMovementPlane (NativeMovementPlane plane) { + value = plane; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs.meta new file mode 100644 index 0000000..9e6ebba --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlane.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7137ab6e696b37428b1bec8e09e78ad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs new file mode 100644 index 0000000..1bcfbb4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs @@ -0,0 +1,15 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// The movement plane source for an agent. + /// + /// See: + /// + [System.Serializable] + public struct AgentMovementPlaneSource : ISharedComponentData { + public MovementPlaneSource value; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs.meta new file mode 100644 index 0000000..e3a1b88 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentMovementPlaneSource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2198aa10fd82b94db223e3dbec9352b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs new file mode 100644 index 0000000..6b019f6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs @@ -0,0 +1,16 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Component which is enabled when the agent is traversing an off-mesh link, and the local avoidance logic should be disabled. + /// + /// You can toggle this using . + /// + /// This component is added to the agent when it starts traversing an off-mesh link. + /// The enabled/disabled state is managed by . + /// + public struct AgentOffMeshLinkLocalAvoidanceDisabled : IComponentData, IEnableableComponent { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs.meta new file mode 100644 index 0000000..706228c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkLocalAvoidanceDisabled.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5baf21ccb939745488a978d7307ef728 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs new file mode 100644 index 0000000..47a790d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs @@ -0,0 +1,16 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Component which is enabled when the agent is traversing an off-mesh link, and the built-in movement logic should be disabled. + /// + /// You can toggle this using . + /// + /// This component is added to the agent when it starts traversing an off-mesh link. + /// The enabled/disabled state is managed by . + /// + public struct AgentOffMeshLinkMovementDisabled : IComponentData, IEnableableComponent { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs.meta new file mode 100644 index 0000000..d31f370 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkMovementDisabled.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78113a0a45ae00049babb6406fd3abea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs new file mode 100644 index 0000000..cbab89d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs @@ -0,0 +1,413 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.Util; + using Unity.Collections.LowLevel.Unsafe; + using UnityEngine; + + /// + /// Holds unmanaged information about an off-mesh link that the agent is currently traversing. + /// This component is added to the agent when it starts traversing an off-mesh link. + /// It is removed when the agent has finished traversing the link. + /// + /// See: + /// + public struct AgentOffMeshLinkTraversal : IComponentData { + /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart} + public float3 relativeStart; + + /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd} + public float3 relativeEnd; + + /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}. Deprecated: Use relativeStart instead + [System.Obsolete("Use relativeStart instead")] + public float3 firstPosition => relativeStart; + + /// \copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}. Deprecated: Use relativeEnd instead + [System.Obsolete("Use relativeEnd instead")] + public float3 secondPosition => relativeEnd; + + /// \copydocref{OffMeshLinks.OffMeshLinkTracer.isReverse} + public bool isReverse; + + public AgentOffMeshLinkTraversal (OffMeshLinks.OffMeshLinkTracer linkInfo) { + relativeStart = linkInfo.relativeStart; + relativeEnd = linkInfo.relativeEnd; + isReverse = linkInfo.isReverse; + } + } + + /// + /// Holds managed information about an off-mesh link that the agent is currently traversing. + /// This component is added to the agent when it starts traversing an off-mesh link. + /// It is removed when the agent has finished traversing the link. + /// + /// See: + /// + public class ManagedAgentOffMeshLinkTraversal : IComponentData, System.ICloneable, ICleanupComponentData { + /// Internal context used to pass component data to the coroutine + public AgentOffMeshLinkTraversalContext context; + + /// Coroutine which is used to traverse the link + public System.Collections.IEnumerator coroutine; + public IOffMeshLinkHandler handler; + public IOffMeshLinkStateMachine stateMachine; + + public ManagedAgentOffMeshLinkTraversal() {} + + public ManagedAgentOffMeshLinkTraversal (AgentOffMeshLinkTraversalContext context, IOffMeshLinkHandler handler) { + this.context = context; + this.handler = handler; + this.coroutine = null; + this.stateMachine = null; + } + + public object Clone () { + // This will set coroutine and stateMachine to null. + // This is correct, as the coroutine cannot be cloned, and the state machine may be unique for a specific agent + return new ManagedAgentOffMeshLinkTraversal((AgentOffMeshLinkTraversalContext)context.Clone(), handler); + } + } + + public struct MovementTarget { + internal bool isReached; + public bool reached => isReached; + + public MovementTarget (bool isReached) { + this.isReached = isReached; + } + } + + /// + /// Context with helpers for traversing an off-mesh link. + /// + /// This will be passed to the code that is responsible for traversing the off-mesh link. + /// + /// Warning: This context should never be accessed outside of an implementation of the interface. + /// + public class AgentOffMeshLinkTraversalContext : System.ICloneable { + internal unsafe AgentOffMeshLinkTraversal* linkInfoPtr; + internal unsafe MovementControl* movementControlPtr; + internal unsafe MovementSettings* movementSettingsPtr; + internal unsafe LocalTransform* transformPtr; + internal unsafe AgentMovementPlane* movementPlanePtr; + internal EnabledRefRW movementDisabled; + internal EnabledRefRW localAvoidanceDisabled; + + /// The entity that is traversing the off-mesh link + public Entity entity; + + /// Some internal state of the agent + [Unity.Properties.DontCreateProperty] + public ManagedState managedState; + + /// + /// The off-mesh link that is being traversed. + /// + /// See: + /// + [Unity.Properties.DontCreateProperty] + internal OffMeshLinks.OffMeshLinkConcrete concreteLink; + + protected float backupRotationSmoothing = float.NaN; + + /// + /// Delta time since the last link simulation. + /// + /// During high time scales, the simulation may run multiple substeps per frame. + /// + /// This is not the same as Time.deltaTime. Inside the link coroutine, you should always use this field instead of Time.deltaTime. + /// + public float deltaTime; + + protected GameObject gameObjectCache; + + /// + /// GameObject associated with the agent. + /// + /// In most cases, an agent is associated with an agent, but this is not always the case. + /// For example, if you have created an entity without using the component, this property may return null. + /// + /// Note: When directly modifying the agent's transform during a link traversal, you should use the property instead of modifying the GameObject's transform. + /// + public virtual GameObject gameObject { + get { + if (gameObjectCache == null) { + var follower = BatchedEvents.Find(entity, (follower, entity) => follower.entity == entity); + if (follower != null) gameObjectCache = follower.gameObject; + } + return gameObjectCache; + } + } + + /// ECS LocalTransform component attached to the agent + public ref LocalTransform transform { + get { + unsafe { + return ref *transformPtr; + } + } + } + + /// The movement settings for the agent + public ref MovementSettings movementSettings { + get { + unsafe { + return ref *movementSettingsPtr; + } + } + } + + /// + /// How the agent should move. + /// + /// The agent will move according to this data, every frame, if is enabled. + /// + /// Note: needs to be enabled every tick to allow the agent to move. + /// + public ref MovementControl movementControl { + get { + unsafe { + return ref *movementControlPtr; + } + } + } + + /// Information about the off-mesh link that the agent is traversing + public OffMeshLinks.OffMeshLinkTracer link { + get { + unsafe { + return new OffMeshLinks.OffMeshLinkTracer(concreteLink, linkInfoPtr->relativeStart, linkInfoPtr->relativeEnd, linkInfoPtr->isReverse); + } + } + } + + /// + /// Information about the off-mesh link that the agent is traversing. + /// + /// Deprecated: Use the property instead + /// + [System.Obsolete("Use the link property instead")] + public AgentOffMeshLinkTraversal linkInfo { + get { + unsafe { + return *linkInfoPtr; + } + } + } + + /// + /// The plane in which the agent is moving. + /// + /// In a 3D game, this will typically be the XZ plane, but in a 2D game + /// it will typically be the XY plane. Games on spherical planets could have planes that are aligned with the surface of the planet. + /// + public ref NativeMovementPlane movementPlane { + get { + unsafe { + return ref movementPlanePtr->value; + } + } + } + + /// + /// True if the agent's built-in movement logic should be enabled. + /// + /// When traversing an off-mesh link, you typically want the agent's movement to be completely controlled by an animation, or some other code. + /// However, sometimes you may want to use the built-in movement logic to move the agent. + /// + /// Using the method will automatically enable the agent's movement logic during that frame. + /// + /// Note: This will be reset to false every frame. Right before the off-mesh link traversal coroutine is executed. + /// + /// See: + /// + public bool enableBuiltInMovement { + get => movementDisabled.IsValid ? !movementDisabled.ValueRW : true; + set { + if (movementDisabled.IsValid) movementDisabled.ValueRW = !value; + } + } + + public AgentOffMeshLinkTraversalContext (OffMeshLinks.OffMeshLinkConcrete link) { + this.concreteLink = link; + } + + /// + /// Internal method to set the data of the context. + /// + /// This is used by the job system to set the data of the context. + /// You should almost never need to use this. + /// + public virtual unsafe void SetInternalData (Entity entity, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, EnabledRefRW movementDisabled, EnabledRefRW localAvoidanceDisabled, ManagedState state, float deltaTime) { + this.linkInfoPtr = (AgentOffMeshLinkTraversal*)UnsafeUtility.AddressOf(ref linkInfo); + this.movementControlPtr = (MovementControl*)UnsafeUtility.AddressOf(ref movementControl); + this.movementSettingsPtr = (MovementSettings*)UnsafeUtility.AddressOf(ref movementSettings); + this.transformPtr = (LocalTransform*)UnsafeUtility.AddressOf(ref transform); + this.movementPlanePtr = (AgentMovementPlane*)UnsafeUtility.AddressOf(ref movementPlane); + this.movementDisabled = movementDisabled; + this.localAvoidanceDisabled = localAvoidanceDisabled; + this.managedState = state; + this.deltaTime = deltaTime; + this.entity = entity; + } + + /// + /// Disables local avoidance for the agent. + /// + /// Agents that traverse links are already marked as 'unstoppable' by the local avoidance system, + /// but calling this method will make other agents ignore them completely while traversing the link. + /// + public void DisableLocalAvoidance () { + if (localAvoidanceDisabled.IsValid) localAvoidanceDisabled.ValueRW = true; + } + + /// + /// Disables rotation smoothing for the agent. + /// + /// This disables the effect of while the agent is traversing the link. + /// Having rotation smoothing enabled can make the agent rotate towards its target rotation more slowly, + /// which is sometimes not desirable. + /// + /// Rotation smoothing will automatically be restored when the agent finishes traversing the link (if it was enabled before). + /// + /// The method automatically disables rotation smoothing when called. + /// + public void DisableRotationSmoothing () { + if (float.IsNaN(backupRotationSmoothing) && movementSettings.rotationSmoothing > 0) { + backupRotationSmoothing = movementSettings.rotationSmoothing; + movementSettings.rotationSmoothing = 0; + } + } + + /// + /// Restores the agent's settings to what it was before the link traversal started. + /// + /// This undos the changes made by and . + /// + /// This method is automatically called when the agent finishes traversing the link. + /// + public virtual void Restore () { + if (!float.IsNaN(backupRotationSmoothing)) { + movementSettings.rotationSmoothing = backupRotationSmoothing; + backupRotationSmoothing = float.NaN; + } + } + + /// Teleports the agent to the given position + public virtual void Teleport (float3 position) { + transform.Position = position; + } + + /// + /// Thrown when the off-mesh link traversal should be aborted. + /// + /// See: + /// + public class AbortOffMeshLinkTraversal : System.Exception {} + + /// + /// Aborts traversing the off-mesh link. + /// + /// This will immediately stop your off-mesh link traversal coroutine. + /// + /// This is useful if your agent was traversing an off-mesh link, but you have detected that it cannot continue. + /// Maybe the ladder it was climbing was destroyed, or the bridge it was walking on collapsed. + /// + /// Note: If you instead want to immediately make the agent move to the end of the link, you can call , and then use 'yield break;' from your coroutine. + /// + /// If true, the agent will be teleported back to the start of the link (from the perspective of the agent). Its rotation will remain unchanged. + public virtual void Abort (bool teleportToStart = true) { + if (teleportToStart) Teleport(link.relativeStart); + // Cancel the current path, as otherwise the agent will instantly try to traverse the off-mesh link again. + managedState.pathTracer.RemoveAllButFirstNode(); + throw new AbortOffMeshLinkTraversal(); + } + + /// + /// Move towards a point while ignoring the navmesh. + /// This method should be called repeatedly until the returned property is true. + /// + /// Returns: A struct which can be used to check if the target has been reached. + /// + /// Note: This method completely ignores the navmesh. It also overrides local avoidance, if enabled (other agents will still avoid it, but this agent will not avoid other agents). + /// + /// TODO: The gravity property is not yet implemented. Gravity is always applied. + /// + /// See: For more control, you can set directly. + /// + /// The position to move towards. + /// The rotation to rotate towards. + /// If true, gravity will be applied to the agent. + /// If true, the agent will slow down as it approaches the target. + public virtual MovementTarget MoveTowards (float3 position, quaternion rotation, bool gravity, bool slowdown) { + // If rotation smoothing was enabled, it could cause a very slow convergence to the target rotation. + // Therefore, we disable it here. + // The agent will try to remove its remaining rotation smoothing offset as quickly as possible. + // After the off-mesh link is traversed, the rotation smoothing will be automatically restored. + DisableRotationSmoothing(); + + // Make sure the agent's movement logic is enabled. + // This will reset every tick. + enableBuiltInMovement = true; + + var dirInPlane = movementPlane.ToPlane(position - transform.Position); + var remainingDistance = math.length(dirInPlane); + var maxSpeed = movementSettings.follower.Speed(slowdown ? remainingDistance : float.PositiveInfinity); + var speed = movementSettings.follower.Accelerate(movementControl.speed, movementSettings.follower.slowdownTime, deltaTime); + speed = math.min(speed, maxSpeed); + + var targetRot = movementPlane.ToPlane(rotation); + var currentRot = movementPlane.ToPlane(transform.Rotation); + var remainingRot = Mathf.Abs(AstarMath.DeltaAngle(currentRot, targetRot)); + movementControl = new MovementControl { + targetPoint = position, + endOfPath = position, + speed = speed, + maxSpeed = speed * 1.1f, + hierarchicalNodeIndex = -1, + overrideLocalAvoidance = true, + targetRotation = targetRot, + targetRotationHint = targetRot, + targetRotationOffset = 0, + rotationSpeed = math.radians(movementSettings.follower.rotationSpeed), + }; + + return new MovementTarget { + isReached = remainingDistance <= (slowdown ? 0.01f : speed * (1/30f)) && remainingRot < math.radians(1), + }; + } + + public virtual object Clone () { + var clone = (AgentOffMeshLinkTraversalContext)MemberwiseClone(); + clone.entity = Entity.Null; + clone.gameObjectCache = null; + clone.managedState = null; + clone.movementDisabled = default; + clone.localAvoidanceDisabled = default; + unsafe { + clone.linkInfoPtr = null; + clone.movementControlPtr = null; + clone.movementSettingsPtr = null; + clone.transformPtr = null; + clone.movementPlanePtr = null; + } + return clone; + } + } +} + +// ctx.MoveTowards (position, rotation, rvo = Auto | Disabled | AutoUnstoppable, gravity = auto|disabled) -> { reached() } + +// MovementTarget { ... } +// while (!movementTarget.reached) { +// ctx.SetMovementTarget(movementTarget); +// yield return null; +// } +// yield return ctx.MoveTo(position, rotation) +// ctx.TeleportTo(position, rotation) +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta new file mode 100644 index 0000000..28f8e71 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7b7b15e5b39fc142a7dd409c4c3a18d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs new file mode 100644 index 0000000..be35129 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs @@ -0,0 +1,12 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Tag component which, when enabled, indicates that the agent should recalculate its path immediately. + /// + /// The enabled state is updated every simulation loop. It cannot be set externally. + /// + public struct AgentShouldRecalculatePath : IComponentData, IEnableableComponent {} +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs.meta new file mode 100644 index 0000000..80b1d2d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AgentShouldRecalculatePath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22cd15868e8b11648a03d6dd39ef0ebf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs new file mode 100644 index 0000000..617b5cb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs @@ -0,0 +1,121 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + /// + /// Policy for how often to recalculate an agent's path. + /// + /// See: + /// + /// This is the unmanaged equivalent of . + /// + [System.Serializable] + public struct AutoRepathPolicy : IComponentData { + /// + /// How sensitive the agent should be to changes in its destination for Mode.Dynamic. + /// A higher value means the destination has to move less for the path to be recalculated. + /// + /// See: + /// + public const float Sensitivity = 10.0f; + + /// + /// Policy to use when recalculating paths. + /// + /// See: for more details. + /// + public Pathfinding.AutoRepathPolicy.Mode mode; + byte pathFailures; + + /// Number of seconds between each automatic path recalculation for Mode.EveryNSeconds, and the maximum interval for Mode.Dynamic + public float period; + + float3 lastDestination; + float lastRepathTime; + + public static AutoRepathPolicy Default => new AutoRepathPolicy { + mode = Pathfinding.AutoRepathPolicy.Mode.Dynamic, + period = 2, + lastDestination = float.PositiveInfinity, + lastRepathTime = float.NegativeInfinity + }; + + public AutoRepathPolicy (Pathfinding.AutoRepathPolicy policy) { + mode = policy.mode; + period = policy.mode == Pathfinding.AutoRepathPolicy.Mode.Dynamic ? policy.maximumPeriod : policy.period; + lastDestination = float.PositiveInfinity; + lastRepathTime = float.NegativeInfinity; + pathFailures = 0; + } + + /// + /// True if the path should be recalculated according to the policy + /// + /// The above parameters are relevant only if is . + /// + /// The current position of the agent. + /// The radius of the agent. You may pass 0.0 if the agent doesn't have a radius. + /// The goal of the agent right now + /// The current time in seconds + /// You may pass true if the agent knows that the current path is outdated for some reason (for example if some nodes in it have been destroyed). + public bool ShouldRecalculatePath (float3 position, float radius, float3 destination, float time, bool isPathStale) { + if (mode == Pathfinding.AutoRepathPolicy.Mode.Never || !float.IsFinite(destination.x)) return false; + + float timeSinceLast = time - lastRepathTime; + + var tmpPeriod = period; + if (isPathStale) { + // If the path is stale, we recalculate the path more often. + // But if the path just continues to fail, then we back off exponentially up to the maximum period. + // 0 failures => 0 + // 1 failure => period/4 + // 2 failures => period/2 + // 3+ failures => period + if (pathFailures == 0) return true; + tmpPeriod = period * math.min(1, 0.125f * (1 << pathFailures)); + } + + if (mode == Pathfinding.AutoRepathPolicy.Mode.EveryNSeconds) { + return timeSinceLast >= tmpPeriod; + } else { + // cost = change in destination / max(distance to destination, radius) = approximately the change in angle to the destination (in radians) + float squaredCost = math.lengthsq(destination - lastDestination) / math.max(math.lengthsq(position - lastDestination), radius*radius); + float fraction = squaredCost * (Sensitivity*Sensitivity); + if (float.IsNaN(fraction)) { + // The agent's radius is zero, and the destination is precisely at the agent's position, which is also the destination of the last calculated path + // This is a special case. It happens sometimes for the AILerp component when it reaches its + // destination, as the AILerp component has no radius. + // In this case we just use the maximum period. + fraction = 0; + } + + return timeSinceLast >= tmpPeriod*(1 - math.sqrt(fraction)); + } + } + + public void OnPathCalculated (bool hadError) { + if (hadError) { + pathFailures = (byte)math.min(255, pathFailures+1); + } else { + pathFailures = 0; + } + } + + public void Reset () { + lastDestination = float.PositiveInfinity; + lastRepathTime = float.NegativeInfinity; + } + + /// Must be called when a path request has been scheduled + public void OnScheduledPathRecalculation (float3 destination, float time) { + lastRepathTime = time; + lastDestination = destination; + // Randomize the repath time slightly so that all agents don't request a path at the same time + // in the future. This is useful when there are a lot of agents instantiated at exactly the same time. + const float JITTER_AMOUNT = 0.3f; + lastRepathTime -= (UnityEngine.Random.value - 0.5f) * JITTER_AMOUNT * period; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs.meta new file mode 100644 index 0000000..0f99f79 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/AutoRepathPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49e745654af51f043a68a105c85e2bae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs new file mode 100644 index 0000000..3d7332b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs @@ -0,0 +1,38 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Holds an agent's destination as an entity. + /// + /// Every frame, the position of this entity will be copied to the field of the agent, by the . + /// + /// See: + /// See: + /// See: + /// See: + /// + [System.Serializable] + [Unity.Properties.GeneratePropertyBag] + public struct DestinationEntity : IComponentData, IEnableableComponent { + /// + /// The entity whose position the agent is moving towards. + /// + /// Every frame, the position of this entity will be copied to the field of the agent. + /// + /// See: + /// See: + /// + public Entity destination; + + /// + /// If true, the agent will try to align itself with the rotation of the entity. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// + public bool useRotation; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs.meta new file mode 100644 index 0000000..8f2de61 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationEntity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d679e13cf3988734b90f11f0541b8a5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs new file mode 100644 index 0000000..d3ff623 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs @@ -0,0 +1,26 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + /// Holds an agent's destination point + public struct DestinationPoint : IComponentData { + /// + /// The destination point that the agent is moving towards. + /// + /// This is the point that the agent is trying to reach, but it may not always be possible to reach it. + /// + /// See: + /// See: + /// + public float3 destination; + + /// + /// The direction the agent should face when it reaches the destination. + /// + /// If zero, the agent will not try to face any particular direction when reaching the destination. + /// + public float3 facingDirection; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs.meta new file mode 100644 index 0000000..ca2370d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/DestinationPoint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87fc1fca9dfafa64b98ec33b24a358fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs new file mode 100644 index 0000000..4ab8700 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs @@ -0,0 +1,16 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + /// Agent state related to gravity + public struct GravityState : IComponentData, IEnableableComponent { + /// + /// Current vertical velocity of the agent. + /// This is the velocity that the agent is moving with due to gravity. + /// It is not necessarily the same as the Y component of the estimated velocity. + /// + public float verticalVelocity; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs.meta new file mode 100644 index 0000000..2275bbb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/GravityState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed943911778141b4988cbdcd7f5b3a07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs new file mode 100644 index 0000000..2f3ed64 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs @@ -0,0 +1,157 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + using Unity.Transforms; + + public delegate void BeforeControlDelegate(Entity entity, float dt, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings); + public delegate void AfterControlDelegate(Entity entity, float dt, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl); + public delegate void BeforeMovementDelegate(Entity entity, float dt, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl, ref ResolvedMovement resolvedMovement); + + /// + /// Helper for adding and removing hooks to the FollowerEntity component. + /// This is used to allow other systems to override the movement of the agent. + /// + /// See: + /// + public struct ManagedMovementOverrides { + Entity entity; + World world; + + public ManagedMovementOverrides (Entity entity, World world) { + this.entity = entity; + this.world = world; + } + + /// + /// Registers a callback that runs before the agent calculates how it wants to move, but after it has repaired its path. + /// + /// You can use this to tweak the agent's movement slightly. + /// + /// See: for example code. + /// + public void AddBeforeControlCallback (BeforeControlDelegate value) { + AddCallback(value); + } + + /// Removes a callback previously added with + public void RemoveBeforeControlCallback (BeforeControlDelegate value) { + RemoveCallback(value); + } + + /// Registers a callback that runs after the agent has calculated how it wants to move, except for local avoidance. + public void AddAfterControlCallback (AfterControlDelegate value) { + AddCallback(value); + } + + /// Removes a callback previously added with + public void RemoveAfterControlCallback (AfterControlDelegate value) { + RemoveCallback(value); + } + + /// + /// Registers a callback that will be called before the agent is moved, but after it has calculated how it wants to move. + /// + /// You can use this to tweak the agent's desired movement slightly (), or by also removing the component, you can take over the actual movement completely. + /// + /// This snippet replicates most of the built-in movement: + /// + /// var ai = GetComponent(); + /// + /// // Prevent the agent from moving itself, so that we can override it. + /// ai.world.EntityManager.RemoveComponent(ai.entity); + /// + /// // This will run once or more per frame, and allows you to hook into the movement logic + /// ai.movementOverrides.AddBeforeMovementCallback((Unity.Entities.Entity entity, float dt, ref Unity.Transforms.LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl, ref ResolvedMovement resolvedMovement) => { + /// // Just replicate the normal movement as an example, except for gravity and ground collision + /// localTransform.Rotation = JobMoveAgent.ResolveRotation(localTransform.Rotation, ref movementState, in resolvedMovement, in movementSettings, in movementPlane, dt); + /// localTransform.Position += JobMoveAgent.MoveWithoutGravity(localTransform.Position, in resolvedMovement, in movementPlane, dt); + /// }); + /// + /// + /// Or if you prefer to handle more things yourself: + /// + /// void Start () { + /// var ai = GetComponent(); + /// + /// // Prevent the agent from moving itself, so that we can override it. + /// ai.world.EntityManager.RemoveComponent(ai.entity); + /// } + /// + /// void Update () { + /// var ai = GetComponent(); + /// + /// // Read how the agent wants to move + /// var resolved = ai.world.EntityManager.GetComponentData(ai.entity); + /// var movementPlane = ai.world.EntityManager.GetComponentData(ai.entity); + /// var movementState = ai.world.EntityManager.GetComponentData(ai.entity); + /// var targetRot = movementPlane.value.ToWorldRotation(resolved.targetRotation + resolved.targetRotationOffset); + /// var movementSettings = ai.world.EntityManager.GetComponentData(ai.entity); + /// var dt = Time.deltaTime; + /// + /// // Move the agent. + /// // This is a very simplified movement logic which has some limitations (it won't work well with local avoidance for example, and since it always runs exactly once per frame, it cannot handle higher time scales), + /// // but it demonstrates the basic idea. Check out the source code for JobMoveAgent for more inspiration. + /// ai.transform.rotation = Quaternion.RotateTowards(ai.transform.rotation, targetRot, resolved.rotationSpeed * dt * Mathf.Rad2Deg); + /// ai.transform.position += Vector3.ClampMagnitude((Vector3)resolved.targetPoint - ai.transform.position, resolved.speed * dt); + /// + /// // Write back the movement state if we have made any changes + /// // In this example we don't, but it's common to want to do this. + /// ai.world.EntityManager.SetComponentData(ai.entity, movementState); + /// } + /// + /// + public void AddBeforeMovementCallback (BeforeMovementDelegate value) { + AddCallback(value); + } + + /// Removes a callback previously added with + public void RemoveBeforeMovementCallback (BeforeMovementDelegate value) { + RemoveCallback(value); + } + + void AddCallback(T callback) where T : System.Delegate where C : ManagedMovementOverride, IComponentData, new() { + if (callback == null) throw new System.ArgumentNullException(nameof(callback)); + if (world == null || !world.EntityManager.Exists(entity)) throw new System.InvalidOperationException("The entity does not exist. You can only set a callback when the FollowerEntity is active and has been enabled. If you are trying to set this during Awake or OnEnable, try setting it during Start instead."); + if (!world.EntityManager.HasComponent(entity)) world.EntityManager.AddComponentData(entity, new C()); + world.EntityManager.GetComponentData(entity).AddCallback(callback); + } + + void RemoveCallback(T callback) where T : System.Delegate where C : ManagedMovementOverride, IComponentData, new() { + if (callback == null) throw new System.ArgumentNullException(nameof(callback)); + if (world == null || !world.EntityManager.Exists(entity)) return; + if (!world.EntityManager.HasComponent(entity)) return; + + var comp = world.EntityManager.GetComponentData(entity); + if (!comp.RemoveCallback(callback)) { + world.EntityManager.RemoveComponent(entity); + } + } + } + + /// + /// Component that stores a delegate that can be used to override movement control and movement settings for a specific entity. + /// This is used by the FollowerEntity to allow other systems to override the movement of the entity. + /// + /// See: + /// + public class ManagedMovementOverride : IComponentData where T : class, System.Delegate { + public T callback; + + public void AddCallback(T callback) => this.callback = (T)System.Delegate.Combine(this.callback, callback); + public bool RemoveCallback(T callback) => (this.callback = (T)System.Delegate.Remove(this.callback, callback)) != null; + } + + // IJobEntity does not support generic jobs yet, so we have to make concrete component types for each delegate type + public class ManagedMovementOverrideBeforeControl : ManagedMovementOverride, System.ICloneable { + // No fields in this class can be cloned safely + public object Clone() => new ManagedMovementOverrideBeforeControl(); + } + public class ManagedMovementOverrideAfterControl : ManagedMovementOverride { + public object Clone() => new ManagedMovementOverrideAfterControl(); + } + public class ManagedMovementOverrideBeforeMovement : ManagedMovementOverride { + public object Clone() => new ManagedMovementOverrideBeforeMovement(); + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs.meta new file mode 100644 index 0000000..409fc88 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedMovementOverride.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce6a314668bdcdd498d5d9d3ebf753c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs new file mode 100644 index 0000000..363b71c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs @@ -0,0 +1,99 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Settings for agent movement that require managed types. + /// + /// This component is used to store settings for agent movement that cannot be put anywhere else. + /// For example, it can store delegates, interfaces and objects. + /// + /// It is used by the component to store settings for how the agent should move. + /// Fortunately, the settings here are not used often, and so putting them in a managed component does not affect performance much. + /// + /// In contrast to , these settings are persistent. + /// + /// See: + /// + [System.Serializable] + // Generate source code for a property bag for this struct. This improves performance for some ECS operations. Otherwise it will fall back on a slower reflection-based implementation. + [Unity.Properties.GeneratePropertyBag] + // Unity cannot guarantee that this struct does not contain any entity references (because we have, for example, some interface fields), + // so it will try to patch entity references sometimes (in particular when live-patching entities), which is slow. So we promise that we will not use entity references in this struct to improve performance. +#if MODULE_ENTITIES_1_3_0_OR_NEWER + [Unity.Entities.TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: true, hasNoUnityObjectReferences: true)] +#else + [Unity.Entities.TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: true)] +#endif + public class ManagedSettings : IComponentData, System.ICloneable, System.IEquatable { + /// + /// Callback for when the agent starts to traverse an off-mesh link. + /// + /// See: + /// See: + /// + [System.NonSerialized] + public IOffMeshLinkHandler onTraverseOffMeshLink; + + /// + /// Settings for how an agent searches for paths. + /// + /// This struct contains information about which graphs the agent can use, which nodes it can traverse, and if any nodes should be easier or harder to traverse. + /// + /// A good default value to start from is . + /// + /// See: + /// See: + /// + public PathRequestSettings pathfindingSettings; + + public object Clone () { + return CloneAndSimplifyDefaults(false); + } + + public ManagedSettings CloneAndSimplifyDefaults (bool simplify) { + // Replace some arrays with null if they are all default values. + // This saves some memory and makes the entity smaller. + // This has a side effect of making live-patching of entities in the editor quite a lot faster + var tagCostMultipliers = pathfindingSettings.tagCostMultipliers; + if (simplify && tagCostMultipliers != null) { + bool allOnes = true; + for (int i = 0; i < pathfindingSettings.tagCostMultipliers.Length; i++) { + allOnes &= pathfindingSettings.tagCostMultipliers[i] == 1; + } + if (allOnes) tagCostMultipliers = null; + } + if (tagCostMultipliers != null) tagCostMultipliers = (float[])tagCostMultipliers.Clone(); + + var tagEntryCosts = pathfindingSettings.tagEntryCosts; + if (simplify && tagEntryCosts != null) { + bool allZero = true; + for (int i = 0; i < pathfindingSettings.tagEntryCosts.Length; i++) { + allZero &= pathfindingSettings.tagEntryCosts[i] == 0; + } + if (allZero) tagEntryCosts = null; + } + if (tagEntryCosts != null) tagEntryCosts = (uint[])tagEntryCosts.Clone(); + + return new ManagedSettings { + pathfindingSettings = new PathRequestSettings { + graphMask = pathfindingSettings.graphMask, + tagEntryCosts = tagEntryCosts, + tagCostMultipliers = tagCostMultipliers, + traversableTags = pathfindingSettings.traversableTags, + traversalProvider = null, // Cannot be safely cloned or copied + }, + onTraverseOffMeshLink = null, // Cannot be safely cloned or copied + }; + } + + // Used by the unity editor when patching baked entities. If not defined it has to fall back to a slower method. + public bool Equals (ManagedSettings other) { + if (other == null) return false; + + return pathfindingSettings.Equals(other.pathfindingSettings) && + onTraverseOffMeshLink == other.onTraverseOffMeshLink; // Reference equality check + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs.meta new file mode 100644 index 0000000..42fa50c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 690231863fa4cb64cbd4e5223d481415 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs new file mode 100644 index 0000000..cddd710 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs @@ -0,0 +1,241 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.ECS.RVO; + using Unity.Properties; + using UnityEngine.Serialization; + + /// + /// Runtime state for agent movement that requires managed types. + /// + /// The Unity ECS in general wants everything in components to be unmanaged types. + /// However, some things cannot be unmanaged types, for example delegates, interfaces and objects. + /// There are also other things like path references and node references which are not unmanaged types at the moment. + /// + /// This component is used to store those things. + /// + /// This component is created at runtime and does not store any settings that need to be persistent. + /// + [System.Serializable] +#if MODULE_ENTITIES_1_3_0_OR_NEWER + [Unity.Entities.TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: true, hasNoUnityObjectReferences: true)] +#else + [Unity.Entities.TypeManager.TypeOverrides(hasNoEntityReferences: true, hasNoBlobReferences: true)] +#endif + public class ManagedState : IComponentData, System.IDisposable, System.ICloneable { + /// + /// Settings for when to recalculate the path. + /// + /// Deprecated: Use , or the component instead. + /// + [System.Obsolete("Use FollowerEntity.autoRepath, or the Pathfinding.ECS.AutoRepathPolicy component instead", true)] + public Pathfinding.AutoRepathPolicy autoRepath => null; + + /// Calculates in which direction to move to follow the path + public PathTracer pathTracer; + + /// + /// Local avoidance settings. + /// + /// When the agent has local avoidance enabled, these settings will be copied into a component which is attached to the agent. + /// + /// Note: When the agent is used in a subscene, this field has no effect at runtime. Instead, set the data for the component. + /// + /// See: + /// + [FormerlySerializedAs("rvoAgent")] + [System.Obsolete("Use FollowerEntity.rvoSettings or the RVOAgent ECS component instead", false)] + [DontCreateProperty] + public RVOAgent rvoSettings = RVOAgent.Default; + + /// Callback for when the agent starts to traverse an off-mesh link + [System.NonSerialized] + [System.Obsolete("Use ManagedSettings.onTraverseOffMeshLink instead", false)] + [DontCreateProperty] + public IOffMeshLinkHandler onTraverseOffMeshLink; + + [System.Obsolete("Use ManagedSettings.pathfindingSettings instead", false)] + [DontCreateProperty] + public PathRequestSettings pathfindingSettings; + + /// + /// True if local avoidance is enabled for this agent. + /// + /// Enabling this will automatically add a component to the entity. + /// + /// See: local-avoidance (view in online documentation for working links) + /// + /// Note: When the agent is used in a subscene, this field has no effect at runtime. Instead, add or remove the component. + /// + [FormerlySerializedAs("rvoEnabled")] + [System.Obsolete("Use FollowerEntity.enableLocalAvoidance or remove/add the RVOAgent ECS component instead", false)] + [DontCreateProperty] + public bool enableLocalAvoidance; + + /// + /// True if gravity is enabled for this agent. + /// + /// The agent will always fall down according to its own movement plane. + /// The gravity applied is Physics.gravity.y. + /// + /// Enabling this will enable the component of the entity. + /// + /// This has no effect if the agent's orientation is set to YAxisForward (2D mode). + /// Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode. + /// + /// Note: When the agent is used in a subscene, this field has no effect at runtime. Instead, disable or enable the component directly. + /// + [System.Obsolete("Use FollowerEntity.enableGravity or toggle the enabled state of the GravityState ECS component instead", false)] + [DontCreateProperty] + public bool enableGravity = true; + + /// Path that is being calculated, if any + // Do not create a property visitor for this field, as otherwise the ECS infrastructure will try to patch entities inside it, and get very confused. + // I haven't been able to replicate this issue recently, but it has caused problems in the past. + // [Unity.Properties.DontCreateProperty] + public Path pendingPath { get; private set; } + + /// + /// Path that is being followed, if any. + /// + /// The agent may have moved away from this path since it was calculated. So it may not be up to date. + /// + // Do not create a property visitor for this field, as otherwise the ECS infrastructure will try to patch entities inside it, and get very confused. + // [Unity.Properties.DontCreateProperty] + public Path activePath { get; private set; } + + /// + /// \copydocref{IAstarAI.SetPath}. + /// + /// Warning: In almost all cases you should use instead of this method. + /// + public static void SetPath (Path path, ManagedState state, in AgentMovementPlane movementPlane, ref DestinationPoint destination) { + if (path == null) { + state.CancelCurrentPathRequest(); + state.ClearPath(); + } else if (path.PipelineState == PathState.Created) { + // Path has not started calculation yet + state.CancelCurrentPathRequest(); + state.pendingPath = path; + path.Claim(state); + AstarPath.StartPath(path); + } else if (path.PipelineState >= PathState.ReturnQueue) { + // Path has already been calculated + + if (state.pendingPath == path) { + // The pending path is now obviously no longer pending + state.pendingPath = null; + } else { + // We might be calculating another path at the same time, and we don't want that path to override this one. So cancel it. + state.CancelCurrentPathRequest(); + + // Increase the refcount on the path. + // If the path was already our pending path, then the refcount will have already been incremented + path.Claim(state); + } + + var abPath = path as ABPath; + if (abPath == null) throw new System.ArgumentException("This function only works with ABPaths, or paths inheriting from ABPath"); + + if (!abPath.error) { + try { + state.pathTracer.SetPath(abPath, movementPlane.value); + + // Release the previous path back to the pool, to reduce GC pressure + if (state.activePath != null) state.activePath.Release(state); + + state.activePath = abPath; + } catch (System.Exception e) { + // If the path was so invalid that the path tracer throws an exception, then we should not use it. + abPath.Release(state); + state.ClearPath(); + UnityEngine.Debug.LogException(e); + } + + // If a RandomPath or MultiTargetPath have just been calculated, then we need + // to patch our destination point, to ensure the agent continues to move towards the end of the path. + // For these path types, the end point of the path is not known before the calculation starts. + if (!abPath.endPointKnownBeforeCalculation) { + destination = new DestinationPoint { destination = abPath.originalEndPoint, facingDirection = default }; + } + + // Right now, the pathTracer is almost fully up to date. + // To make it fully up to date, we'd also have to call pathTracer.UpdateStart and pathTracer.UpdateEnd after this function. + // During normal path recalculations, the JobRepairPath will be scheduled right after this function, and it will + // call those functions. The incomplete state will not be observable outside the system. + // When called from FollowerEntity, the SetPath method on that component will ensure that these methods are called. + } else { + // The path had an error, so we should stop moving. + // However, we keep the agent anchored to the graph it was traversing before (if any). + // Marking the path as unrepairable prevents the agent from immediately trying to locally repair its path, + // which may give incorrect results, or just use up a ton of cpu power. + // The behavior of the agent will be similar to if ai.isStopped is set: the agent will smoothly slow down until stationary. + // The next time a path is successfully calculated, the unrepairable state will be cleared. + abPath.Release(state); + state.pathTracer.SetEmptyAndUnrepairable(); + } + } else { + // Path calculation has been started, but it is not yet complete. Cannot really handle this. + throw new System.ArgumentException("You must call the SetPath method with a path that either has been completely calculated or one whose path calculation has not been started at all. It looks like the path calculation for the path you tried to use has been started, but is not yet finished."); + } + } + + public void ClearPath () { + pathTracer.Clear(); + if (activePath != null) { + activePath.Release(this); + activePath = null; + } + } + + public void CancelCurrentPathRequest () { + if (pendingPath != null) { + pendingPath.FailWithError("Canceled by script"); + pendingPath.Release(this); + pendingPath = null; + } + } + + public void Dispose () { + pathTracer.Dispose(); + if (pendingPath != null) { + pendingPath.FailWithError("Canceled because entity was destroyed"); + pendingPath.Release(this); + pendingPath = null; + } + if (activePath != null) { + activePath.Release(this); + activePath = null; + } + } + + /// + /// Pops the current part, and the next part from the start of the path. + /// + /// It is assumed that the agent is currently on a normal NodeSequence part, and that the next part in the path is an off-mesh link. + /// + public void PopNextLinkFromPath () { + if (pathTracer.partCount < 2 && pathTracer.GetPartType(1) != Funnel.PartType.OffMeshLink) { + throw new System.InvalidOperationException("The next part in the path is not an off-mesh link."); + } + pathTracer.PopParts(2); + } + + /// + /// Clones the managed state for when an entity is duplicated. + /// + /// Some fields are cleared instead of being cloned, such as the pending path, + /// which cannot reasonably be cloned. + /// + object System.ICloneable.Clone () { + return new ManagedState { + pathTracer = pathTracer.Clone(), + pendingPath = null, // Cannot be safely cloned or copied + activePath = null, // Cannot be safely cloned or copied + }; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs.meta new file mode 100644 index 0000000..6320c9e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ManagedState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33d1c95731798be41b90302b91409645 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs new file mode 100644 index 0000000..2d98bb1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs @@ -0,0 +1,94 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.Util; + + /// + /// Desired movement for an agent. + /// This data will be fed to the local avoidance system to calculate the final movement of the agent. + /// If no local avoidance is used, it will be directly copied to . + /// + /// See: + /// + public struct MovementControl : IComponentData { + /// The point the agent should move towards + public float3 targetPoint; + + /// + /// The end of the current path. + /// + /// This informs the local avoidance system about the final desired destination for the agent. + /// This is used to make agents stop if the destination is crowded and it cannot reach its destination. + /// + /// If this is not set, agents will often move forever around a crowded destination, always trying to find + /// some way to get closer, but never finding it. + /// + public float3 endOfPath; + + /// The speed at which the agent should move towards , in meters per second + public float speed; + + /// + /// The maximum speed at which the agent may move, in meters per second. + /// + /// It is recommended to keep this slightly above , to allow the local avoidance system to move agents around more efficiently when necessary. + /// + public float maxSpeed; + + /// + /// The index of the hierarchical node that the agent is currently in. + /// Will be -1 if the hierarchical node index is not known. + /// See: + /// + /// See: + /// + public int hierarchicalNodeIndex; + + /// + /// The desired rotation of the agent, in radians, relative to the current movement plane. + /// + /// When rotating the agent visually, the should also be added to this value. + /// + /// See: + /// See: + /// + public float targetRotation; + + /// + /// The desired rotation of the agent, in radians, over a longer time horizon, relative to the current movement plane. + /// + /// The is usually only over a very short time-horizon, usually a single simulation time step. + /// This variable is used to provide a hint of where the agent wants to rotate to over a slightly longer time scale (on the order of a second or so). + /// It is not used to control movement directly, but it may be used to guide animations, or rotation smoothing. + /// + /// If no better hint is available, this should be set to the same value as . + /// + /// See: + /// See: + /// + public float targetRotationHint; + + /// + /// Additive modifier to , in radians. + /// This is used by the local avoidance system to rotate the agent, without this causing a feedback loop. + /// This extra rotation will be ignored by the control system which decides how the agent *wants* to move. + /// It will instead be directly applied to the agent. + /// + /// See: , which must be updated after rotating the agent using this value. + /// + public float targetRotationOffset; + + /// The speed at which the agent should rotate towards + , in radians per second + public float rotationSpeed; + + /// + /// If true, this agent will ignore other agents during local avoidance, but other agents will still avoid this one. + /// This is useful for example for a player character which should not avoid other agents, but other agents should avoid the player. + /// + public bool overrideLocalAvoidance; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs.meta new file mode 100644 index 0000000..4c0b87a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 067b0510e83c84e43b21eb81fb804132 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs new file mode 100644 index 0000000..4509538 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs @@ -0,0 +1,127 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using UnityEngine; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + using Pathfinding.PID; + + /// How to calculate which direction is "up" for the agent + public enum MovementPlaneSource : byte { + /// + /// The graph's natural up direction will be used to align the agent. + /// This is the most common option. + /// + Graph, + /// + /// The agent will be aligned with the normal of the navmesh. + /// + /// This is useful when you have a spherical world, or some other strange shape. + /// + /// The agent will look at the normal of the navmesh around the point it is currently standing on to determine which way is up. + /// The radius of the agent will be used to determine the size of the area to sample the normal from. + /// A bit of smoothing is done to make sure sharp changes in the normal do not cause the agent to rotate too fast. + /// + /// Note: If you have a somewhat flat world, and you want to align the agent to the ground, this is not the option you want. + /// Instead, you might want to disable and then align the transform using a custom script. + /// + /// Warning: Using this option has a performance penalty. + /// + /// [Open online documentation to see videos] + /// + /// See: spherical (view in online documentation for working links) + /// + NavmeshNormal, + /// + /// The agent will be aligned with the ground normal. + /// + /// This is useful when you have a spherical world, or some other strange shape. + /// + /// You may want to use this instead of the NavmeshNormal option if your collider is smoother than your navmesh. + /// For example, if you have a spherical world with a sphere collider, you may want to use this option instead of the NavmeshNormal option. + /// + /// Note: If you have a somewhat flat world, and you want to align the agent to the ground, this is not the option you want. + /// Instead, you might want to disable and then align the transform using a custom script. + /// + /// Warning: Using this option has a performance penalty. + /// + Raycast, + } + + [System.Serializable] + public struct MovementSettings : IComponentData { + /// Additional movement settings + public PIDMovement follower; + + /// Flags for enabling debug rendering in the scene view + public PIDMovement.DebugFlags debugFlags; + + /// + /// How far away from the destination should the agent aim to stop, in world units. + /// + /// If the agent is within this distance from the destination point it will be considered to have reached the destination. + /// + /// Even if you want the agent to stop precisely at a given point, it is recommended to keep this slightly above zero. + /// If it is exactly zero, the agent may have a hard time deciding that it + /// has actually reached the end of the path, due to floating point errors and such. + /// + /// Note: This will not be multiplied the agent's scale. + /// + public float stopDistance; + + /// + /// How much to smooth the visual rotation of the agent. + /// + /// This does not affect movement, but smoothes out how the agent rotates visually. + /// + /// Recommended values are between 0.0 and 0.5. + /// A value of zero will disable smoothing completely. + /// + /// The smoothing is done primarily using an exponential moving average, but with + /// a small linear term to make the rotation converge faster when the agent is almost facing the desired direction. + /// + /// Adding smoothing will make the visual rotation of the agent lag a bit behind the actual rotation. + /// Too much smoothing may make the agent seem sluggish, and appear to move sideways. + /// + /// The unit for this field is seconds. + /// + public float rotationSmoothing; + + /// + /// How much to smooth the visual position of the agent. + /// + /// This does not affect movement, but smoothes out the position of the agent visually. + /// + /// Recommended values are between 0.0 and 0.5. + /// A value of zero will disable smoothing completely. + /// + /// This will make the agent seem to lag slightly behind the internal position of the agent. + /// It may also cut corners slightly. + /// + /// The unit for this field is seconds. + /// + public float positionSmoothing; + + /// + /// Layer mask to use for ground placement. + /// Make sure this does not include the layer of any colliders attached to this gameobject. + /// + /// See: + /// See: https://docs.unity3d.com/Manual/Layers.html + /// + public LayerMask groundMask; + + /// + /// How to calculate which direction is "up" for the agent. + /// See: + /// + /// Deprecated: Use the AgentMovementPlaneSource component instead, or the movementPlaneSource property on the FollowerEntity component + /// + [System.Obsolete("Use the AgentMovementPlaneSource component instead, or the movementPlaneSource property on the FollowerEntity component", true)] + public MovementPlaneSource movementPlaneSource { get; set; } + + /// \copydocref{IAstarAI.isStopped} + public bool isStopped; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs.meta new file mode 100644 index 0000000..734fcc4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4fafdd860735074e8ca2abad75c3992 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs new file mode 100644 index 0000000..49197bb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs @@ -0,0 +1,224 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Profiling; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.PID; + + public struct MovementState : IComponentData { + /// State of the PID controller for the movement + public PIDMovement.PersistentState followerState; + + /// The next corner in the path + public float3 nextCorner; + + /// + /// The end of the current path. + /// Note that the agent may be heading towards an off-mesh link which is not the same as this point. + /// + public float3 endOfPath; + + /// + /// The closest point on the navmesh to the agent. + /// The agent will be snapped to this point. + /// + public float3 closestOnNavmesh; + + /// + /// Offset from the agent's internal position to its visual position. + /// + /// This is used when position smoothing is enabled. Otherwise it is zero. + /// + public float3 positionOffset; + + /// + /// The index of the hierarchical node that the agent is currently in. + /// Will be -1 if the hierarchical node index is not known. + /// + /// This field is valid during all system updates in the . + /// It will be cleared at the end of that group by , so you'll typically not be able to observe it during other times. + /// It is cleared as a safety measure, as it is not guaranteed to be valid after that group has finished running, as graph updates may have changed the graph. + /// + /// See: + /// + public int hierarchicalNodeIndex; + + /// The remaining distance until the end of the path, or the next off-mesh link + public float remainingDistanceToEndOfPart; + + /// + /// The current additional rotation that is applied to the agent. + /// This is used by the local avoidance system to rotate the agent, without this causing a feedback loop. + /// + /// See: + /// + public float rotationOffset; + + /// + /// An additional, purely visual, rotation offset. + /// This is used for rotation smoothing, but does not affect the movement of the agent. + /// + public float rotationOffset2; + + /// + /// Version number of when the movement state was last updated. + /// In particular, , , , , and will only + /// be considered up to date if this is equal to the current version number of the path tracer. + /// + public ushort pathTracerVersion; + + /// Bitmask for various flags + ushort flags; + + const int ReachedDestinationFlag = 1 << 0; + const int reachedDestinationAndOrientationFlag = 1 << 1; + const int ReachedEndOfPathFlag = 1 << 2; + const int reachedEndOfPathAndOrientationFlag = 1 << 3; + const int ReachedEndOfPartFlag = 1 << 4; + const int TraversingLastPartFlag = 1 << 5; + const int HasValidEndPointFlag = 1 << 6; + const int GraphIndexOffsetInFlags = 8; + const ushort GraphIndexMaskInFlags = (ushort)(0xFF << GraphIndexOffsetInFlags); + + /// + /// True if the agent has reached its destination. + /// The destination will be considered reached if all of these conditions are met: + /// - The agent has a path + /// - The path is not stale + /// - The destination is not significantly below the agent's feet. + /// - The destination is not significantly above the agent's head. + /// - The agent is on the last part of the path (there are no more remaining off-mesh links). + /// - The remaining distance to the end of the path + the distance from the end of the path to the destination is less than . + /// + public bool reachedDestination { + // This attribute is used to avoid adding too much overhead when accessing this property when Deep Profiling is enabled, and to declutter the profiling view. + [IgnoredByDeepProfiler] + get => (flags & ReachedDestinationFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~ReachedDestinationFlag) | (value ? ReachedDestinationFlag : 0)); + } + + /// + /// True if the agent has reached its destination and is facing the desired orientation. + /// This will become true if all of these conditions are met: + /// - is true + /// - The agent is facing the desired facing direction as specified in . + /// + public bool reachedDestinationAndOrientation { + [IgnoredByDeepProfiler] + get => (flags & reachedDestinationAndOrientationFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~reachedDestinationAndOrientationFlag) | (value ? reachedDestinationAndOrientationFlag : 0)); + } + + /// + /// True if the agent has reached the end of the path. + /// The end of the path will be considered reached if all of these conditions are met: + /// - The agent has a path + /// - The path is not stale + /// - The end of the path is not significantly below the agent's feet. + /// - The end of the path is not significantly above the agent's head. + /// - The agent is on the last part of the path (there are no more remaining off-mesh links). + /// - The remaining distance to the end of the path is less than . + /// + public bool reachedEndOfPath { + [IgnoredByDeepProfiler] + get => (flags & ReachedEndOfPathFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~ReachedEndOfPathFlag) | (value ? ReachedEndOfPathFlag : 0)); + } + + /// + /// True if the agent has reached its destination and is facing the desired orientation. + /// This will become true if all of these conditions are met: + /// - is true + /// - The agent is facing the desired facing direction as specified in . + /// + public bool reachedEndOfPathAndOrientation { + [IgnoredByDeepProfiler] + get => (flags & reachedEndOfPathAndOrientationFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~reachedEndOfPathAndOrientationFlag) | (value ? reachedEndOfPathAndOrientationFlag : 0)); + } + + /// + /// True if the agent has reached the end of the current part in the path. + /// The end of the current part will be considered reached if all of these conditions are met: + /// - The agent has a path + /// - The path is not stale + /// - The end of the current part is not significantly below the agent's feet. + /// - The end of the current part is not significantly above the agent's head. + /// - The remaining distance to the end of the part is not significantly larger than the agent's radius. + /// + public bool reachedEndOfPart { + [IgnoredByDeepProfiler] + get => (flags & ReachedEndOfPartFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~ReachedEndOfPartFlag) | (value ? ReachedEndOfPartFlag : 0)); + } + + /// + /// True if the agent is traversing the last part of the path. + /// + /// If false, the agent will have to traverse at least one off-mesh link before it gets to its destination. + /// + public bool traversingLastPart { + [IgnoredByDeepProfiler] + get => (flags & TraversingLastPartFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~TraversingLastPartFlag) | (value ? TraversingLastPartFlag : 0)); + } + + public bool hasValidEndPoint { + [IgnoredByDeepProfiler] + get => (flags & HasValidEndPointFlag) != 0; + [IgnoredByDeepProfiler] + set => flags = (ushort)((flags & ~HasValidEndPointFlag) | (value ? HasValidEndPointFlag : 0)); + } + + /// + /// The index of the graph that the agent is currently traversing. + /// + /// Will be if the agent has no path, or the node that the agent is traversing has been destroyed. + /// + public uint graphIndex { + [IgnoredByDeepProfiler] + get => (uint)(flags >> GraphIndexOffsetInFlags); + [IgnoredByDeepProfiler] + internal set => flags = (ushort)((flags & ~GraphIndexMaskInFlags) | (ushort)(value << GraphIndexOffsetInFlags)); + } + + /// + /// True if the agent is currently on a valid node. + /// + /// This is true if the agent has a path, and the node that the agent is traversing is walkable and not destroyed. + /// + /// If false, the and fields are invalid. + /// + public bool isOnValidNode => hierarchicalNodeIndex != -1; + + public MovementState(UnityEngine.Vector3 agentPosition) { + this = default; + SetPathIsEmpty(agentPosition); + } + + /// Sets the appropriate fields to indicate that the agent has no path + public void SetPathIsEmpty (UnityEngine.Vector3 agentPosition) { + nextCorner = agentPosition; + endOfPath = agentPosition; + closestOnNavmesh = agentPosition; + hierarchicalNodeIndex = -1; + remainingDistanceToEndOfPart = float.PositiveInfinity; + reachedEndOfPath = false; + reachedDestination = false; + reachedEndOfPart = false; + reachedDestinationAndOrientation = false; + reachedEndOfPathAndOrientation = false; + traversingLastPart = true; + graphIndex = GraphNode.InvalidGraphIndex; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs.meta new file mode 100644 index 0000000..71f1c38 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd27960b09d0034419af8c9451a551fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs new file mode 100644 index 0000000..d29781c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs @@ -0,0 +1,17 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + public struct MovementStatistics : IComponentData { + /// + /// The estimated velocity that the agent is moving with. + /// This includes all form of movement, including local avoidance and gravity. + /// + public float3 estimatedVelocity; + + /// The position of the agent at the end of the last movement simulation step + public float3 lastPosition; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs.meta new file mode 100644 index 0000000..3b36a89 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/MovementStatistics.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40111712788bfc3409f6b39341f91e2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs new file mode 100644 index 0000000..d78026b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs @@ -0,0 +1,26 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using UnityEngine; +using System; + +namespace Pathfinding.ECS { + /// + /// The physics scene to use for raycasting. + /// + /// For most games, this will be the default physics scene. But if your game has more than one physics scene, you can use this component to specify which one to use. + /// + /// The will use PhysicsSceneExtensions.GetPhysicsScene to get the physics scene from the GameObject that the FollowerEntity component is attached to. + /// + public struct PhysicsSceneRef : ISharedComponentData, IQueryTypeParameter, IEquatable { + public PhysicsScene physicsScene; + + public bool Equals (PhysicsSceneRef other) { + return physicsScene == other.physicsScene; + } + + override public int GetHashCode () { + return physicsScene.GetHashCode(); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs.meta new file mode 100644 index 0000000..534c922 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/PhysicsSceneRef.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd44ff65922c2384396cc4e6411e49b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO.meta new file mode 100644 index 0000000..30598c4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 233cdeb50c94c714ab4c82711f977368 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs new file mode 100644 index 0000000..4618d09 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs @@ -0,0 +1,91 @@ +#if MODULE_ENTITIES +using Unity.Entities; +#endif + +namespace Pathfinding.ECS.RVO { + using Pathfinding.RVO; + using Unity.Collections; + + /// + /// Index of an RVO agent in the local avoidance simulation. + /// + /// If this component is present, that indicates that the agent is part of a local avoidance simulation. + /// The is responsible for adding and removing this component as necessary. + /// Any other systems should only concern themselves with the component. + /// + /// Warning: This component does not support cloning. You must not clone entities that use this component. + /// There doesn't seem to be any way to make this work with the Unity.Entities API at the moment. + /// +#if MODULE_ENTITIES + [WriteGroup(typeof(ResolvedMovement))] +#endif + public readonly struct AgentIndex +#if MODULE_ENTITIES + : Unity.Entities.ICleanupComponentData +#endif + { + const int DeletedBit = 1 << 31; + const int IndexMask = (1 << 24) - 1; + const int VersionOffset = 24; + const int VersionMask = 0b1111_111 << VersionOffset; + + readonly int packedAgentIndex; + + /// + /// Index of the agent in the simulation's data arrays. + /// + /// See: + /// + internal int Index => packedAgentIndex & IndexMask; + int Version => packedAgentIndex & VersionMask; + internal bool Valid => (packedAgentIndex & DeletedBit) == 0; + + internal AgentIndex(int packedAgentIndex) { + this.packedAgentIndex = packedAgentIndex; + } + + internal AgentIndex(int version, int index) { + version <<= VersionOffset; + UnityEngine.Assertions.Assert.IsTrue((index & IndexMask) == index); + packedAgentIndex = (version & VersionMask) | (index & IndexMask); + } + + internal readonly AgentIndex WithIncrementedVersion () { + return new AgentIndex((((packedAgentIndex & VersionMask) + (1 << VersionOffset)) & VersionMask) | Index); + } + + internal readonly AgentIndex WithDeleted () { + return new AgentIndex(packedAgentIndex | DeletedBit); + } + + /// True if the agent exists in the simulation + public readonly bool Exists (ref SimulatorBurst.AgentData agentData) { + return TryGetIndex(ref agentData, out _); + } + + /// + /// Returns the index of the agent in the simulation's data arrays, if the agent exists. + /// + /// If the agent does not exist, the index will be set to -1 and the method returns false. + /// + public readonly bool TryGetIndex (ref SimulatorBurst.AgentData agentData, out int index) { + return TryGetIndex(ref agentData.version, out index); + } + + /// + /// Returns the index of the agent in the simulation's data arrays, if the agent exists. + /// + /// If the agent does not exist, the index will be set to -1 and the method returns false. + /// + public readonly bool TryGetIndex (ref NativeArray agentDataVersions, out int index) { + var tmpIndex = Index; + index = -1; + if (!agentDataVersions.IsCreated) return false; + if (tmpIndex >= agentDataVersions.Length) return false; + if (agentDataVersions[tmpIndex].Version != Version) return false; + + index = tmpIndex; + return true; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs.meta new file mode 100644 index 0000000..b67b67e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/AgentIndex.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd00f859416fc5c4f984c5680d19fc7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs new file mode 100644 index 0000000..5049190 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs @@ -0,0 +1,94 @@ +#if MODULE_ENTITIES +using Pathfinding.RVO; +using Unity.Entities; +using UnityEngine; +using Unity.Transforms; +using Unity.Mathematics; + +namespace Pathfinding.ECS.RVO { + using Pathfinding.RVO; + + /// + /// Agent data for the local avoidance system. + /// + /// See: local-avoidance (view in online documentation for working links) + /// + [System.Serializable] + public struct RVOAgent : IComponentData { + /// How far into the future to look for collisions with other agents (in seconds) + [Tooltip("How far into the future to look for collisions with other agents (in seconds)")] + public float agentTimeHorizon; + + /// How far into the future to look for collisions with obstacles (in seconds) + [Tooltip("How far into the future to look for collisions with obstacles (in seconds)")] + public float obstacleTimeHorizon; + + /// + /// Max number of other agents to take into account. + /// A smaller value can reduce CPU load, a higher value can lead to better local avoidance quality. + /// + [Tooltip("Max number of other agents to take into account.\n" + + "A smaller value can reduce CPU load, a higher value can lead to better local avoidance quality.")] + public int maxNeighbours; + + /// + /// Specifies the avoidance layer for this agent. + /// The mask on other agents will determine if they will avoid this agent. + /// + public RVOLayer layer; + + /// + /// Layer mask specifying which layers this agent will avoid. + /// You can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ... + /// + /// This can be very useful in games which have multiple teams of some sort. For example you usually + /// want the agents in one team to avoid each other, but you do not want them to avoid the enemies. + /// + /// This field only affects which other agents that this agent will avoid, it does not affect how other agents + /// react to this agent. + /// + /// See: bitmasks (view in online documentation for working links) + /// See: http://en.wikipedia.org/wiki/Mask_(computing) + /// + [Pathfinding.EnumFlag] + public RVOLayer collidesWith; + + /// \copydocref{Pathfinding.RVO.IAgent.Priority} + [Tooltip("How strongly other agents will avoid this agent")] + [UnityEngine.Range(0, 1)] + public float priority; + + /// + /// Priority multiplier. + /// This functions identically to the , however it is not exposed in the Unity inspector. + /// It is primarily used by the . + /// + [System.NonSerialized] + public float priorityMultiplier; + + [System.NonSerialized] + public float flowFollowingStrength; + + /// Enables drawing debug information in the scene view + public AgentDebugFlags debug; + + /// A locked unit cannot move. Other units will still avoid it but avoidance quality is not the best. + [Tooltip("A locked unit cannot move. Other units will still avoid it. But avoidance quality is not the best")] + public bool locked; + + /// Good default settings for an RVO agent + public static readonly RVOAgent Default = new RVOAgent { + locked = false, + agentTimeHorizon = 1.0f, + obstacleTimeHorizon = 0.5f, + maxNeighbours = 10, + layer = RVOLayer.DefaultAgent, + collidesWith = (RVOLayer)(-1), + priority = 0.5f, + priorityMultiplier = 1.0f, + flowFollowingStrength = 0.0f, + debug = AgentDebugFlags.Nothing, + }; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs.meta new file mode 100644 index 0000000..046cc35 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/RVO/RVOAgent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 910690cbba23a2745a85046d13e5c03b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs new file mode 100644 index 0000000..43e179e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs @@ -0,0 +1,10 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.ECS { + /// Enabled if the agnet is ready to start traversing an off-mesh link + [System.Serializable] + public struct ReadyToTraverseOffMeshLink : IComponentData, IEnableableComponent { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta new file mode 100644 index 0000000..bcfbe64 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8473eb53e4d194545b1395c9301ffc55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs new file mode 100644 index 0000000..68da063 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs @@ -0,0 +1,32 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + /// + /// Holds the final movement data for an entity. + /// This is the data that is used by the movement system to move the entity. + /// + public struct ResolvedMovement : IComponentData { + /// \copydocref{MovementControl.targetPoint} + public float3 targetPoint; + + /// \copydocref{MovementControl.speed} + public float speed; + + public float turningRadiusMultiplier; + + /// \copydocref{MovementControl.targetRotation} + public float targetRotation; + + /// \copydocref{MovementControl.targetRotationHint} + public float targetRotationHint; + + /// \copydocref{MovementControl.targetRotationOffset} + public float targetRotationOffset; + + /// \copydocref{MovementControl.rotationSpeed} + public float rotationSpeed; + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs.meta new file mode 100644 index 0000000..74542a5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/ResolvedMovement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 659b0f455df2f744189544436f88bf05 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs new file mode 100644 index 0000000..44c5eac --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs @@ -0,0 +1,11 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + using Pathfinding; + + public struct SearchState : IComponentData { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs.meta new file mode 100644 index 0000000..5198363 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SearchState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe063c5087e811f47aec8d8889a66d68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs new file mode 100644 index 0000000..cb8ada7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs @@ -0,0 +1,98 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + using Pathfinding; + + /// + /// Tag component to enable movement for an entity. + /// Without this component, most systems will completely ignore the entity. + /// + /// There are some more specific components that can be used to selectively enable/disable some jobs: + /// - + /// - + /// - + /// + /// Removing one of the above components can be useful if you want to override the movement of an agent in some way. + /// + public struct SimulateMovement : IComponentData { + } + + /// + /// Tag component to allow the agent to repair its path and recalculate various statistics. + /// + /// Allows the to run. + /// + /// Note: will, at the moment, run even if this component is not present, when manually setting some properties on the agent (e.g. ai.position, ai.destination, etc.). + /// Create a forum post if you have a use case which isn't supported by this. + /// + public struct SimulateMovementRepair : IComponentData { + } + + /// + /// Tag component to allow the agent to calculate how it wants to move. + /// + /// Allows the to run. + /// + public struct SimulateMovementControl : IComponentData { + } + + /// + /// Tag component to allow the agent to move according to its desired movement parameters. + /// + /// Allows to run the and jobs. + /// + /// By removing this, you can override how the agent's desired movement is converted to actual movement. + /// + /// This snippet replicates most of the built-in movement: + /// + /// var ai = GetComponent(); + /// + /// // Prevent the agent from moving itself, so that we can override it. + /// ai.world.EntityManager.RemoveComponent(ai.entity); + /// + /// // This will run once or more per frame, and allows you to hook into the movement logic + /// ai.movementOverrides.AddBeforeMovementCallback((Unity.Entities.Entity entity, float dt, ref Unity.Transforms.LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl, ref ResolvedMovement resolvedMovement) => { + /// // Just replicate the normal movement as an example, except for gravity and ground collision + /// localTransform.Rotation = JobMoveAgent.ResolveRotation(localTransform.Rotation, ref movementState, in resolvedMovement, in movementSettings, in movementPlane, dt); + /// localTransform.Position += JobMoveAgent.MoveWithoutGravity(localTransform.Position, in resolvedMovement, in movementPlane, dt); + /// }); + /// + /// + /// Or if you prefer to handle more things yourself: + /// + /// void Start () { + /// var ai = GetComponent(); + /// + /// // Prevent the agent from moving itself, so that we can override it. + /// ai.world.EntityManager.RemoveComponent(ai.entity); + /// } + /// + /// void Update () { + /// var ai = GetComponent(); + /// + /// // Read how the agent wants to move + /// var resolved = ai.world.EntityManager.GetComponentData(ai.entity); + /// var movementPlane = ai.world.EntityManager.GetComponentData(ai.entity); + /// var movementState = ai.world.EntityManager.GetComponentData(ai.entity); + /// var targetRot = movementPlane.value.ToWorldRotation(resolved.targetRotation + resolved.targetRotationOffset); + /// var movementSettings = ai.world.EntityManager.GetComponentData(ai.entity); + /// var dt = Time.deltaTime; + /// + /// // Move the agent. + /// // This is a very simplified movement logic which has some limitations (it won't work well with local avoidance for example, and since it always runs exactly once per frame, it cannot handle higher time scales), + /// // but it demonstrates the basic idea. Check out the source code for JobMoveAgent for more inspiration. + /// ai.transform.rotation = Quaternion.RotateTowards(ai.transform.rotation, targetRot, resolved.rotationSpeed * dt * Mathf.Rad2Deg); + /// ai.transform.position += Vector3.ClampMagnitude((Vector3)resolved.targetPoint - ai.transform.position, resolved.speed * dt); + /// + /// // Write back the movement state if we have made any changes + /// // In this example we don't, but it's common to want to do this. + /// ai.world.EntityManager.SetComponentData(ai.entity, movementState); + /// } + /// + /// + public struct SimulateMovementFinalize : IComponentData { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs.meta new file mode 100644 index 0000000..485bb36 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SimulateMovement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38abdbf4c8ebfd64aa17d17cfd43cc8e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs new file mode 100644 index 0000000..497b8b5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs @@ -0,0 +1,35 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + using Pathfinding; + + /// + /// Tag component to enable syncing between an agent's Transform and the agent entity's position. + /// + /// See: + /// + public struct SyncPositionWithTransform : IComponentData { + } + + /// + /// Tag component to enable syncing between an agent's Transform and the agent entity's rotation. + /// + /// See: + /// + public struct SyncRotationWithTransform : IComponentData { + } + + /// + /// Tag component to indicate that the agent's forward direction is along the Y axis. + /// + /// This is used to convert between the forward direction of the GameObject and the internal forward direction, which always uses +Z as forward. + /// + /// See: + /// See: + /// + public struct OrientationYAxisForward : IComponentData { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs.meta new file mode 100644 index 0000000..c3f179f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Components/SyncWithTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 04e21506cb33eb847838b0a75fda6bf0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs new file mode 100644 index 0000000..8119a74 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs @@ -0,0 +1,249 @@ +#if MODULE_ENTITIES +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; + +namespace Pathfinding.ECS { + /// Helper for EntityAccess + static class EntityAccessHelper { + public static readonly int GlobalSystemVersionOffset = UnsafeUtility.GetFieldOffset(typeof(ComponentTypeHandle).GetField("m_GlobalSystemVersion", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)); + } + + /// + /// Wrapper for a pointer. + /// + /// Very similar to the entities package RefRW struct. But unfortunately that one cannot be used because the required constructor is not exposed. + /// + public ref struct ComponentRef where T : unmanaged { + unsafe byte* ptr; + + public unsafe ComponentRef(byte* ptr) { + this.ptr = ptr; + } + + public ref T value { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get { + unsafe { + return ref *(T*)ptr; + } + } + } + } + + /// Utility for efficient random access to entity storage data from the main thread + public struct EntityStorageCache { + EntityStorageInfo storage; + Entity entity; + int lastWorldHash; + + /// + /// Retrieves the storage for a given entity. + /// + /// This method is very fast if the entity is the same as the last call to this method. + /// If the entity is different, it will be slower. + /// + /// Returns: True if the entity exists, and false if it does not. + /// + // Inlining makes this method about 20% faster. It's hot when accessing properties on the FollowerEntity component. + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool Update (World world, Entity entity, out EntityManager entityManager, out EntityStorageInfo storage) { + storage = this.storage; + if (world == null || !world.IsCreated) { + entityManager = default; + return false; + } + entityManager = world.EntityManager; + // We must use entityManager.EntityOrderVersion here, not GlobalSystemVersion, because + // the GlobalSystemVersion does not necessarily update when structural changes happen. + var worldHash = entityManager.EntityOrderVersion ^ ((int)world.SequenceNumber << 8); + if (worldHash != lastWorldHash || entity != this.entity) { + if (!entityManager.Exists(entity)) return false; + this.storage = storage = entityManager.GetStorageInfo(entity); + this.entity = entity; + lastWorldHash = worldHash; + } + return true; + } + + /// + /// Retrieves a component for a given entity. + /// + /// This is a convenience method to call on this object and update on the access object, and then retrieve the component data. + /// + /// This method is very fast if the entity is the same as the last call to this method. + /// If the entity is different, it will be slower. + /// + /// Warning: You must not store the returned reference past a structural change in the ECS world. + /// + /// Warning: Assumes that if the entity exists, it has the given component. + /// + /// Returns: True if the entity exists, and false if it does not. + /// Throws: An exception if the entity does not have the given component. + /// + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool GetComponentData(World world, Entity entity, ref EntityAccess access, out ComponentRef value) where A : unmanaged, IComponentData { + if (Update(world, entity, out var entityManager, out var storage)) { + access.Update(entityManager); + unsafe { + value = new ComponentRef((byte*)UnsafeUtility.AddressOf(ref access[storage])); + } + return true; + } else { + value = default; + return false; + } + } + + /// + /// Retrieves a component for a given entity. + /// + /// This is a convenience method to call on this object and update on the access object, and then retrieve the component data. + /// + /// This method is very fast if the entity is the same as the last call to this method. + /// If the entity is different, it will be slower. + /// + /// Returns: True if the entity exists, and false if it does not. + /// Throws: An exception if the entity does not have the given component. + /// + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool GetComponentData(World world, Entity entity, ref ManagedEntityAccess access, out A value) where A : class, IComponentData { + if (Update(world, entity, out var entityManager, out var storage)) { + access.Update(entityManager); + value = access[storage]; + return true; + } else { + value = null; + return false; + } + } + } + + /// + /// Utility for efficient random access to entity component data from the main thread. + /// + /// Since this struct returns a reference to the component data, it is faster than using EntityManager.GetComponentData, + /// in particular for larger component types. + /// + /// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that + /// this struct does not survive past an ECS system update. If you only use this struct from the main thread + /// and only store it locally on the stack, this should not be a problem. + /// This struct also does not enforce that you only read to the component data if the readOnly flag is set. + /// + public struct EntityAccess where T : unmanaged, IComponentData { + public ComponentTypeHandle handle; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + SystemHandle systemHandle; +#endif + uint lastSystemVersion; + ulong worldSequenceNumber; + bool readOnly; + + public EntityAccess(bool readOnly) { + handle = default; + this.readOnly = readOnly; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + systemHandle = default; +#endif + + // Version 0 is never used by EntityManager.GlobalSystemVersion + lastSystemVersion = 0; + worldSequenceNumber = 0; + } + + /// + /// Update the component type handle if necessary. + /// + /// This must be called if any jobs or system might have been scheduled since the struct was created or since the last call to Update. + /// + public void Update (EntityManager entityManager) { + // If the global system version has changed, jobs may have been scheduled which writes + // to the component data. Therefore we need to complete all dependencies before we can + // safely read or write to the component data. + + var systemVersion = entityManager.GlobalSystemVersion; + var sequenceNumber = entityManager.WorldUnmanaged.SequenceNumber; + if (systemVersion != lastSystemVersion || worldSequenceNumber != sequenceNumber) { + if (lastSystemVersion == 0 || worldSequenceNumber != sequenceNumber) { + handle = entityManager.GetComponentTypeHandle(readOnly); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + systemHandle = entityManager.WorldUnmanaged.GetExistingUnmanagedSystem(); +#endif + } + + lastSystemVersion = systemVersion; + worldSequenceNumber = sequenceNumber; + if (readOnly) entityManager.CompleteDependencyBeforeRO(); + else entityManager.CompleteDependencyBeforeRW(); + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + handle.Update(ref entityManager.WorldUnmanaged.ResolveSystemStateRef(systemHandle)); +#else + // handle.Update just does the same thing as this unsafe code, but in a much more roundabout way + unsafe { + var ptr = (byte*)UnsafeUtility.AddressOf(ref handle); + *(uint*)(ptr + EntityAccessHelper.GlobalSystemVersionOffset) = systemVersion; + } +#endif + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool HasComponent (EntityStorageInfo storage) { + return storage.Chunk.Has(ref handle); + } + + public ref T this[EntityStorageInfo storage] { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get { + unsafe { + var ptr = readOnly ? ((T*)storage.Chunk.GetRequiredComponentDataPtrRO(ref handle) + storage.IndexInChunk) : ((T*)storage.Chunk.GetRequiredComponentDataPtrRW(ref handle) + storage.IndexInChunk); + return ref *ptr; + } + } + } + } + + /// + /// Utility for efficient random access to managed entity component data from the main thread. + /// + /// Warning: Some checks are not enforced by this API. It is the user's responsibility to ensure that + /// this struct does not survive past an ECS system update. If you only use this struct from the main thread + /// and only store it locally on the stack, this should not be a problem. + /// This struct also does not enforce that you only read to the component data if the readOnly flag is set. + /// + public struct ManagedEntityAccess where T : class, IComponentData { + EntityManager entityManager; + ComponentTypeHandle handle; + bool readOnly; + + public ManagedEntityAccess(bool readOnly) { + entityManager = default; + handle = default; + this.readOnly = readOnly; + } + + public ManagedEntityAccess(EntityManager entityManager, bool readOnly) : this(readOnly) { + Update(entityManager); + } + + public void Update (EntityManager entityManager) { + if (readOnly) entityManager.CompleteDependencyBeforeRO(); + else entityManager.CompleteDependencyBeforeRW(); + handle = entityManager.GetComponentTypeHandle(readOnly); + this.entityManager = entityManager; + } + + public T this[EntityStorageInfo storage] { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + get { + return storage.Chunk.GetManagedComponentAccessor(ref handle, entityManager)[storage.IndexInChunk]; + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + set { + var accessor = storage.Chunk.GetManagedComponentAccessor(ref handle, entityManager); + accessor[storage.IndexInChunk] = value; + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs.meta new file mode 100644 index 0000000..5a10e58 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/EntityAccess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eeb1b5067974c4947bb6751ab2a33627 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs new file mode 100644 index 0000000..077ba9e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs @@ -0,0 +1,9 @@ +#if MODULE_ENTITIES +using Unity.Entities; + +namespace Pathfinding.Util { + interface IRuntimeBaker { + void OnCreatedEntity(World world, Entity entity); + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs.meta new file mode 100644 index 0000000..afec79b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/IRuntimeBaker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c44cc7455a88b82419ef53a61dd5626c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs.meta new file mode 100644 index 0000000..d357f8a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fabe887d69022ae459e7619f5b0fbdf6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs.meta new file mode 100644 index 0000000..fad37c1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DisposeGCHandleJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96a344d62fc6bb64b8b59d973ac868a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs.meta new file mode 100644 index 0000000..377d6ac --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/DrawFollowerGizmosJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f202855630112e347bc6d3e68ee6f314 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs new file mode 100644 index 0000000..ef3aea4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs @@ -0,0 +1,51 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Pathfinding.ECS { + /// + /// Aligns the agent's rotation with its movement direction, if the local avoidance system is causing it to move differently than expected. + /// + /// Sometimes the agent may want to move in one direction (or stand still), but the local avoidance system tells it to move somewhere completely different. + /// In those cases, we need to adjust the agent's desired rotation to avoid it looking like it's strafing. + /// + [BurstCompile] + [WithAll(typeof(SimulateMovement))] + public partial struct JobAlignAgentWithMovementDirection : IJobEntity { + public float dt; + + public void Execute (ref LocalTransform transform, in MovementSettings movementSettings, in MovementState movementState, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, in MovementControl movementControl, ref ResolvedMovement resolvedMovement) { + // Check if the local avoidance system is forcing the agent to move + if (math.lengthsq(movementControl.targetPoint - resolvedMovement.targetPoint) > 0.001f && resolvedMovement.speed > movementSettings.follower.speed * 0.1f) { + // If the agent is moving, align it with the movement direction + var desiredDirection = movementPlane.value.ToPlane(movementControl.targetPoint - transform.Position); + var actualDirection = movementPlane.value.ToPlane(resolvedMovement.targetPoint - transform.Position); + + float desiredAngle; + if (math.lengthsq(desiredDirection) > math.pow(movementSettings.follower.speed * 0.1f, 2)) { + desiredAngle = math.atan2(desiredDirection.y, desiredDirection.x); + } else { + // If the agent did not desire to move at all, use the agent's current rotation + desiredAngle = movementPlane.value.ToPlane(transform.Rotation) + math.PI*0.5f; + } + + // The agent only moves if the actual movement direction is non-zero + if (math.lengthsq(actualDirection) > math.pow(movementSettings.follower.speed * 0.1f, 2)) { + var actualAngle = math.atan2(actualDirection.y, actualDirection.x); + resolvedMovement.targetRotationOffset = AstarMath.DeltaAngle(desiredAngle, actualAngle); + return; + } + } + + { + // Decay the rotation offset + // var da = AstarMath.DeltaAngle(movementState.rotationOffset, 0); + // resolvedMovement.targetRotationOffset += da * dt * 2.0f; + resolvedMovement.targetRotationOffset = AstarMath.DeltaAngle(0, resolvedMovement.targetRotationOffset) * (1 - dt * 2.0f); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta new file mode 100644 index 0000000..e8059c1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b348dcdafdb36a946afe26daf64739a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs new file mode 100644 index 0000000..bf50a78 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs @@ -0,0 +1,63 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; + +namespace Pathfinding.ECS { + [BurstCompile] + public partial struct JobApplyGravity : IJobEntity { + [ReadOnly] + public NativeArray raycastHits; + [ReadOnly] + public NativeArray raycastCommands; + public float dt; + + void ResolveGravity (RaycastHit hit, bool grounded, ref LocalTransform transform, in AgentMovementPlane movementPlane, ref GravityState gravityState) { + var localPosition = movementPlane.value.ToPlane(transform.Position, out var currentElevation); + if (grounded) { + // Grounded + // Make the vertical velocity fall off exponentially. This is reasonable from a physical standpoint as characters + // are not completely stiff and touching the ground will not immediately negate all velocity downwards. The AI will + // stop moving completely due to the raycast penetration test but it will still *try* to move downwards. This helps + // significantly when moving down along slopes, because if the vertical velocity would be set to zero when the character + // was grounded it would lead to a kind of 'bouncing' behavior (try it, it's hard to explain). Ideally this should + // use a more physically correct formula but this is a good approximation and is much more performant. The constant + // CONVERGENCE_SPEED in the expression below determines how quickly it converges but high values can lead to too much noise. + const float CONVERGENCE_SPEED = 5f; + gravityState.verticalVelocity *= math.max(0, 1 - CONVERGENCE_SPEED * dt); + + movementPlane.value.ToPlane(hit.point, out var hitElevation); + var elevationDelta = gravityState.verticalVelocity * dt; + const float VERTICAL_COLLISION_ADJUSTMENT_SPEED = 6f; + if (hitElevation > currentElevation) { + // Already below ground, only allow upwards movement + currentElevation = Mathf.MoveTowards(currentElevation, hitElevation, VERTICAL_COLLISION_ADJUSTMENT_SPEED * math.sqrt(math.abs(hitElevation - currentElevation)) * dt); + } else { + // Was above the ground, allow downwards movement until we are at the ground + currentElevation = math.max(hitElevation, currentElevation + elevationDelta); + } + } else { + var elevationDelta = gravityState.verticalVelocity * dt; + currentElevation += elevationDelta; + } + transform.Position = movementPlane.value.ToWorld(localPosition, currentElevation); + } + + public void Execute (ref LocalTransform transform, in MovementSettings movementSettings, ref AgentMovementPlane movementPlane, ref GravityState gravityState, in AgentMovementPlaneSource movementPlaneSource, [Unity.Entities.EntityIndexInQuery] int entityIndexInQuery) { + var hit = raycastHits[entityIndexInQuery]; +#if UNITY_6000_4_OR_NEWER + var hitAnything = hit.colliderEntityId != default; +#else + var hitAnything = hit.colliderInstanceID != 0; +#endif + if (hitAnything && movementPlaneSource.value == MovementPlaneSource.Raycast) { + movementPlane.value = movementPlane.value.MatchUpDirection(hit.normal); + } + ResolveGravity(hit, hitAnything, ref transform, in movementPlane, ref gravityState); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs.meta new file mode 100644 index 0000000..a53f5e1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobApplyGravity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 618ca53d45ce69442aaae615eb4f3291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs new file mode 100644 index 0000000..248a042 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs @@ -0,0 +1,16 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; + +namespace Pathfinding.ECS { + [BurstCompile] + public partial struct JobClearTemporaryData : IJobEntity { + public void Execute (ref MovementState movementState, ref MovementControl movementControl) { + // The hierarchicalNodeIndex is only valid within the AIMovementSystemGroup. + // We clear the value at the end of that group to prevent it from being accidentally used later. + movementState.hierarchicalNodeIndex = -1; + movementControl.hierarchicalNodeIndex = -1; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs.meta new file mode 100644 index 0000000..a1b6d79 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobClearTemporaryData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79a118e742366900a8cebf6fcc722885 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs new file mode 100644 index 0000000..25c6a74 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs @@ -0,0 +1,163 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Profiling; +using Unity.Transforms; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Pathfinding.Drawing; +using Pathfinding.PID; +using Unity.Burst.Intrinsics; + +namespace Pathfinding.ECS { + [BurstCompile] + [WithNone(typeof(AgentOffMeshLinkTraversal))] + [WithAll(typeof(SimulateMovement), typeof(SimulateMovementControl))] + public partial struct JobControl : IJobEntity, IJobEntityChunkBeginEnd { + public float dt; + public CommandBuilder draw; + [ReadOnly] + [NativeDisableContainerSafetyRestriction] + public NavmeshEdges.NavmeshBorderData navmeshEdgeData; + + [NativeDisableContainerSafetyRestriction] + public NativeList edgesScratch; + + [NativeDisableContainerSafetyRestriction] + public NativeList indicesScratch; + + private static readonly ProfilerMarker MarkerConvertObstacles = new ProfilerMarker("ConvertObstacles"); + + public static float3 ClampToNavmesh (float3 position, float3 closestOnNavmesh, in AgentCylinderShape shape, in AgentMovementPlane movementPlane) { + // Don't clamp the elevation except to make sure it's not too far below the navmesh. + // One *could* do the same calculation by using movementPlane.ToPlane(closestOnNavmesh) and movementPlane.ToPlane(position, out elevation), + // and then creating a new world position with the elevation from the position and in-plane coordinates from closestOnNavmesh. + // However, this seems to lead to more floating point errors than the approach below (the approach below is also faster). + var up = movementPlane.value.up; + var elevationDifferenceToNavmesh = math.dot(position - closestOnNavmesh, up); + elevationDifferenceToNavmesh = math.max(elevationDifferenceToNavmesh, -shape.height * 0.4f); + position = closestOnNavmesh + elevationDifferenceToNavmesh * up; + return position; + } + + public bool OnChunkBegin (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { + if (!edgesScratch.IsCreated) edgesScratch = new NativeList(64, Allocator.Temp); + if (!indicesScratch.IsCreated) indicesScratch = new NativeList(64, Allocator.Temp); + return true; + } + + public void OnChunkEnd (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted) {} + + public void Execute (ref LocalTransform transform, ref MovementState state, in DestinationPoint destination, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, in MovementSettings settings, in ResolvedMovement resolvedMovement, ref MovementControl controlOutput) { + // Clamp the agent to the navmesh. + var position = ClampToNavmesh(transform.Position, state.closestOnNavmesh, in shape, in movementPlane); + + edgesScratch.Clear(); + + var scale = math.abs(transform.Scale); + var settingsTemp = settings.follower; + // Scale the settings by the agent's scale + settingsTemp.ScaleByAgentScale(scale); + settingsTemp.desiredWallDistance *= resolvedMovement.turningRadiusMultiplier; + + if (state.isOnValidNode) { + MarkerConvertObstacles.Begin(); + var localBounds = PIDMovement.InterestingEdgeBounds(ref settingsTemp, state.closestOnNavmesh, state.nextCorner, shape.height, movementPlane.value); + navmeshEdgeData.GetEdgesInRange(state.hierarchicalNodeIndex, localBounds, edgesScratch, indicesScratch, movementPlane.value); + MarkerConvertObstacles.End(); + } + + // To ensure we detect that the end of the path is reached robustly we make the agent move slightly closer. + // to the destination than the stopDistance. + const float FUZZ = 0.005f; + // If we are moving towards an off-mesh link, then we want the agent to stop precisely at the off-mesh link. + // TODO: Depending on the link, we may want the agent to move towards the link at full speed, instead of slowing down. + var stopDistance = state.traversingLastPart ? math.max(0, settings.stopDistance - FUZZ) : 0f; + var distanceToSteeringTarget = math.max(0, state.remainingDistanceToEndOfPart - stopDistance); + var rotation = movementPlane.value.ToPlane(transform.Rotation) - state.rotationOffset - state.rotationOffset2; + + transform.Position = position; + + if (dt > 0.000001f) { + if (!math.isfinite(distanceToSteeringTarget)) { + // The agent has no path, just stay still + controlOutput = new MovementControl { + targetPoint = position, + speed = 0, + endOfPath = position, + maxSpeed = settings.follower.speed, + overrideLocalAvoidance = false, + hierarchicalNodeIndex = state.hierarchicalNodeIndex, + targetRotation = resolvedMovement.targetRotation, + targetRotationHint = resolvedMovement.targetRotation, + rotationSpeed = settings.follower.maxRotationSpeed, + targetRotationOffset = state.rotationOffset, // May be modified by other systems + }; + } else if (settings.isStopped || !state.hasValidEndPoint) { + // The user has requested that the agent slow down as quickly as possible. + // TODO: If the agent is not clamped to the navmesh, it should still move towards the navmesh if it is outside it. + controlOutput = new MovementControl { + // Keep moving in the same direction as during the last frame, but slow down + targetPoint = position + math.normalizesafe(resolvedMovement.targetPoint - position) * 10.0f, + speed = settings.follower.Accelerate(resolvedMovement.speed, settings.follower.slowdownTime, -dt), + endOfPath = state.endOfPath, + maxSpeed = settings.follower.speed, + overrideLocalAvoidance = false, + hierarchicalNodeIndex = state.hierarchicalNodeIndex, + targetRotation = rotation, + targetRotationHint = rotation, + rotationSpeed = settings.follower.maxRotationSpeed, + targetRotationOffset = state.rotationOffset, // May be modified by other systems + }; + } else { + var controlParams = new PIDMovement.ControlParams { + edges = edgesScratch.AsArray(), + nextCorner = state.nextCorner, + agentRadius = shape.radius, + facingDirectionAtEndOfPath = destination.facingDirection, + endOfPath = state.endOfPath, + remainingDistance = distanceToSteeringTarget, + closestOnNavmesh = state.closestOnNavmesh, +#if UNITY_EDITOR + debugFlags = settings.debugFlags, +#else + // Do not even try to draw debug info if we are not in the editor + debugFlags = PIDMovement.DebugFlags.Nothing, +#endif + p = position, + rotation = rotation, + maxDesiredWallDistance = state.followerState.maxDesiredWallDistance, + speed = controlOutput.speed, + movementPlane = movementPlane.value, + }; + + var control = PIDMovement.Control(ref settingsTemp, dt, ref controlParams, ref draw, out state.followerState.maxDesiredWallDistance); + var positionDelta = movementPlane.value.ToWorld(control.positionDelta, 0); + var speed = math.length(positionDelta) / dt; + + controlOutput = new MovementControl { + targetPoint = position + math.normalizesafe(positionDelta) * distanceToSteeringTarget, + speed = speed, + endOfPath = state.endOfPath, + maxSpeed = settingsTemp.speed * 1.1f, + overrideLocalAvoidance = false, + hierarchicalNodeIndex = state.hierarchicalNodeIndex, + // It may seem sketchy to use a target rotation so close to the current rotation. One might think + // there's risk of overshooting this target rotation if the frame rate is uneven. + // But the TimeScaledRateManager ensures that this is not the case. + // The cheap simulation's time (which is the one actually rotating the agent) is always guaranteed to be + // behind (or precisely caught up with) the full simulation's time (that's the simulation which runs this system). + targetRotation = rotation + control.rotationDelta, + targetRotationHint = rotation + AstarMath.DeltaAngle(rotation, control.targetRotation), + rotationSpeed = math.abs(control.rotationDelta / dt), + targetRotationOffset = state.rotationOffset, // May be modified by other systems + }; + } + } else { + controlOutput.hierarchicalNodeIndex = -1; + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs.meta new file mode 100644 index 0000000..c4b5992 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b24028677256624696bab00b6f43ac0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs new file mode 100644 index 0000000..e111838 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs @@ -0,0 +1,112 @@ +#if MODULE_ENTITIES +using System.Runtime.InteropServices; +using Pathfinding.Drawing; +using Pathfinding.PID; +using Pathfinding.Util; +using Pathfinding.Collections; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Pathfinding.ECS { + [BurstCompile] + struct DrawGizmosJobUtils { + [BurstCompile] + internal static void DrawPath (ref CommandBuilder draw, ref UnsafeSpan vertices, ref AgentCylinderShape shape) { + // Some people will set the agent's radius to zero. In that case we just draw the path as a polyline as we have no good reference for how to space the symbols. + if (shape.radius > 0.01f) { + var generator = new CommandBuilder.PolylineWithSymbol(CommandBuilder.SymbolDecoration.ArrowHead, shape.radius * 0.5f, shape.radius * 0.0f, shape.radius * 4f, true); + for (int i = vertices.Length - 1; i >= 0; i--) generator.MoveTo(ref draw, vertices[i]); + } else { + for (int i = 0; i < vertices.Length - 1; i++) draw.Line(vertices[i], vertices[i+1]); + } + } + } + + public partial struct JobDrawFollowerGizmos : IJobChunk { + public CommandBuilder draw; + public GCHandle entityManagerHandle; + [ReadOnly] + public ComponentTypeHandle LocalTransformTypeHandleRO; + [ReadOnly] + public ComponentTypeHandle AgentCylinderShapeHandleRO; + [ReadOnly] + public ComponentTypeHandle MovementSettingsHandleRO; + [ReadOnly] + public ComponentTypeHandle AgentMovementPlaneHandleRO; + // This is actually not read only, because the GetNextCorners function can modify internal state + // See JobRepairPath.Scheduler.ManagedStateTypeHandleRW for details about why NativeDisableContainerSafetyRestriction is required + [NativeDisableContainerSafetyRestriction] + public ComponentTypeHandle ManagedStateHandleRW; + [ReadOnly] + public ComponentTypeHandle MovementStateHandleRO; + [ReadOnly] + public ComponentTypeHandle ResolvedMovementHandleRO; + + [NativeDisableContainerSafetyRestriction] + public NativeList scratchBuffer1; + [NativeDisableContainerSafetyRestriction] + public NativeArray scratchBuffer2; + + public void Execute (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in Unity.Burst.Intrinsics.v128 chunkEnabledMask) { + if (!scratchBuffer1.IsCreated) scratchBuffer1 = new NativeList(32, Allocator.Temp); + if (!scratchBuffer2.IsCreated) scratchBuffer2 = new NativeArray(32, Allocator.Temp); + + unsafe { + var localTransforms = (LocalTransform*)chunk.GetNativeArray(ref LocalTransformTypeHandleRO).GetUnsafeReadOnlyPtr(); + var agentCylinderShapes = (AgentCylinderShape*)chunk.GetNativeArray(ref AgentCylinderShapeHandleRO).GetUnsafeReadOnlyPtr(); + var movementSettings = (MovementSettings*)chunk.GetNativeArray(ref MovementSettingsHandleRO).GetUnsafeReadOnlyPtr(); + var movementPlanes = (AgentMovementPlane*)chunk.GetNativeArray(ref AgentMovementPlaneHandleRO).GetUnsafeReadOnlyPtr(); + var managedStates = chunk.GetManagedComponentAccessor(ref ManagedStateHandleRW, (EntityManager)entityManagerHandle.Target); + var movementStates = (MovementState*)chunk.GetNativeArray(ref MovementStateHandleRO).GetUnsafeReadOnlyPtr(); + var resolvedMovement = (ResolvedMovement*)chunk.GetNativeArray(ref ResolvedMovementHandleRO).GetUnsafeReadOnlyPtr(); + + for (int i = 0; i < chunk.Count; i++) { + Execute(ref localTransforms[i], ref movementPlanes[i], ref agentCylinderShapes[i], managedStates[i], ref movementSettings[i], ref movementStates[i], ref resolvedMovement[i]); + } + } + } + + public static readonly UnityEngine.Color VisualRotationColor = Palette.Colorbrewer.Set1.Blue; + public static readonly UnityEngine.Color UnsmoothedRotation = Palette.Colorbrewer.Set1.Purple; + public static readonly UnityEngine.Color InternalRotation = Palette.Colorbrewer.Set1.Orange; + public static readonly UnityEngine.Color TargetInternalRotation = Palette.Colorbrewer.Set1.Yellow; + public static readonly UnityEngine.Color TargetInternalRotationHint = Palette.Colorbrewer.Set1.Pink; + public static readonly UnityEngine.Color Path = Palette.Colorbrewer.Set1.Orange; + + public void Execute (ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref AgentCylinderShape shape, ManagedState managedState, ref MovementSettings settings, ref MovementState movementState, ref ResolvedMovement resolvedMovement) { + if ((settings.debugFlags & PIDMovement.DebugFlags.Funnel) != 0) { + managedState.pathTracer.DrawFunnel(draw, movementPlane.value); + } + if ((settings.debugFlags & PIDMovement.DebugFlags.Rotation) != 0) { + var p2D = movementPlane.value.ToPlane(transform.Position, out float positionElevation); + draw.PushMatrix(math.mul(new float4x4(movementPlane.value.rotation, float3.zero), float4x4.Translate(new float3(0, positionElevation, 0)))); + var visualRotation = movementPlane.value.ToPlane(transform.Rotation); + var unsmoothedRotation = visualRotation - movementState.rotationOffset2; + var internalRotation = unsmoothedRotation - movementState.rotationOffset; + var targetInternalRotation = resolvedMovement.targetRotation; + var targetInternalRotationHint = resolvedMovement.targetRotationHint; + math.sincos(math.PI*0.5f + new float3(visualRotation, unsmoothedRotation, internalRotation), out var s, out var c); + draw.xz.ArrowheadArc(p2D, new float2(c.x, s.x), shape.radius * 1.1f, VisualRotationColor); + draw.xz.ArrowheadArc(p2D, new float2(c.y, s.y), shape.radius * 1.1f, UnsmoothedRotation); + draw.xz.ArrowheadArc(p2D, new float2(c.z, s.z), shape.radius * 1.1f, InternalRotation); + math.sincos(math.PI*0.5f + new float2(targetInternalRotation, targetInternalRotationHint), out var s2, out var c2); + draw.xz.ArrowheadArc(p2D, new float2(c2.x, s2.x), shape.radius * 1.2f, TargetInternalRotation); + draw.xz.ArrowheadArc(p2D, new float2(c2.y, s2.y), shape.radius * 1.2f, TargetInternalRotationHint); + draw.PopMatrix(); + } + if ((settings.debugFlags & PIDMovement.DebugFlags.Path) != 0 && managedState.pathTracer.hasPath) { + scratchBuffer1.Clear(); + managedState.pathTracer.GetNextCorners(scratchBuffer1, int.MaxValue, ref scratchBuffer2, Allocator.Temp, false); + var span = scratchBuffer1.AsUnsafeSpan(); + draw.PushColor(Path); + DrawGizmosJobUtils.DrawPath(ref draw, ref span, ref shape); + draw.PopColor(); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta new file mode 100644 index 0000000..973c7bf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f561c88b2cd64a5fa25f190f2a152a11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs new file mode 100644 index 0000000..baa0bd2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs @@ -0,0 +1,65 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using Unity.Burst; +using Pathfinding.Drawing; +using Unity.Burst.Intrinsics; + +namespace Pathfinding.ECS { + [BurstCompile] + public partial struct JobDrawFollowerGizmosBase : IJobEntity, IJobEntityChunkBeginEnd { + public CommandBuilder draw; + OrientationMode orientation; + + internal static readonly UnityEngine.Color ShapeGizmoColor = new UnityEngine.Color(240/255f, 213/255f, 30/255f); + + public bool OnChunkBegin (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { + orientation = chunk.Has() ? OrientationMode.YAxisForward : OrientationMode.ZAxisForward; + return true; + } + + public void OnChunkEnd (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted) { + } + + public void Execute ( + in LocalTransform transform, + in AgentCylinderShape shape, + in DestinationPoint destination + ) { + // Note: The entity's internal rotation always uses Z-axis forward. But Y axis forward is a hint to use a 2D visualization + DrawGizmos(ref draw, in transform, in shape, in destination, orientation == OrientationMode.YAxisForward); + } + + [BurstCompile] + public static void DrawGizmos ( + ref CommandBuilder draw, + in LocalTransform transform, + in AgentCylinderShape shape, + in DestinationPoint destination, + bool draw2D + ) { + var radius = shape.radius; + var color = ShapeGizmoColor; + + draw.PushMatrix(transform.ToMatrix()); + if (draw2D) { + draw.Circle(float3.zero, new float3(0, 1, 0), radius, color); + } else { + draw.WireCylinder(float3.zero, new float3(0, 1, 0), shape.height, radius, color); + } + + draw.ArrowheadArc(float3.zero, new float3(0, 0, 1), radius * 1.05f, color); + draw.PopMatrix(); + + if (math.all(math.isfinite(destination.destination))) { + var dir = destination.facingDirection; + if (math.any(dir != float3.zero)) { + draw.xz.ArrowheadArc(destination.destination, dir, 0.25f, UnityEngine.Color.blue); + } + draw.xz.Circle(destination.destination, 0.2f, UnityEngine.Color.blue); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs.meta new file mode 100644 index 0000000..ee80d98 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobDrawFollowerGizmosBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e28e1e2d34d21e64da5fa05b54858d1d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs new file mode 100644 index 0000000..abdd98e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs @@ -0,0 +1,45 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; + +namespace Pathfinding.ECS { + public partial struct JobManagedMovementOverrideBeforeControl : IJobEntity { + public float dt; + + public void Execute (ManagedMovementOverrideBeforeControl managedOverride, Entity entity, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings) { + if (managedOverride.callback != null) { + managedOverride.callback(entity, dt, ref localTransform, ref shape, ref movementPlane, ref destination, ref movementState, ref movementSettings); + // The callback may have modified the movement state, so we need to reset the path tracer version to indicate that the movement state is not up to date. + // This will cause the repair job to avoid optimizing some updates away. + movementState.pathTracerVersion--; + } + } + } + + public partial struct JobManagedMovementOverrideAfterControl : IJobEntity { + public float dt; + + public void Execute (ManagedMovementOverrideAfterControl managedOverride, Entity entity, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl) { + if (managedOverride.callback != null) { + managedOverride.callback(entity, dt, ref localTransform, ref shape, ref movementPlane, ref destination, ref movementState, ref movementSettings, ref movementControl); + // The callback may have modified the movement state, so we need to reset the path tracer version to indicate that the movement state is not up to date. + // This will cause the repair job to avoid optimizing some updates away. + movementState.pathTracerVersion--; + } + } + } + + public partial struct JobManagedMovementOverrideBeforeMovement : IJobEntity { + public float dt; + + public void Execute (ManagedMovementOverrideBeforeMovement managedOverride, Entity entity, ref LocalTransform localTransform, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref DestinationPoint destination, ref MovementState movementState, ref MovementSettings movementSettings, ref MovementControl movementControl, ref ResolvedMovement resolvedMovement) { + if (managedOverride.callback != null) { + managedOverride.callback(entity, dt, ref localTransform, ref shape, ref movementPlane, ref destination, ref movementState, ref movementSettings, ref movementControl, ref resolvedMovement); + // The callback may have modified the movement state, so we need to reset the path tracer version to indicate that the movement state is not up to date. + // This will cause the repair job to avoid optimizing some updates away. + movementState.pathTracerVersion--; + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta new file mode 100644 index 0000000..24d261a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e9dcafc22a15f547984558ee0fc626f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs new file mode 100644 index 0000000..aad1a11 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs @@ -0,0 +1,101 @@ +#pragma warning disable 0282 // Allows the 'partial' keyword without warnings +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Collections; +using UnityEngine; +using Unity.Transforms; +using Unity.Collections.LowLevel.Unsafe; + +namespace Pathfinding.ECS { + using Pathfinding; + + [WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)] // Required so that it doesn't filter out disabled AgentOffMeshLinkMovementDisabled components. WithPresent should work, but it seems to be buggy. + public partial struct JobManagedOffMeshLinkTransition : IJobEntity { + public EntityCommandBuffer commandBuffer; + public float deltaTime; + + public void Execute (Entity entity, ManagedState state, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, ManagedAgentOffMeshLinkTraversal managedLinkInfo, EnabledRefRW movementDisabled, EnabledRefRW localAvoidanceDisabled) { + if (!MoveNext(entity, state, ref transform, ref movementPlane, ref movementControl, ref movementSettings, ref linkInfo, managedLinkInfo, movementDisabled, localAvoidanceDisabled, deltaTime)) { + commandBuffer.RemoveComponent(entity); + commandBuffer.RemoveComponent(entity); + commandBuffer.RemoveComponent(entity); + commandBuffer.RemoveComponent(entity); + } + } + + public static bool MoveNext (Entity entity, ManagedState state, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, ManagedAgentOffMeshLinkTraversal managedLinkInfo, EnabledRefRW movementDisabled, EnabledRefRW localAvoidanceDisabled, float deltaTime) { + unsafe { + managedLinkInfo.context.SetInternalData(entity, ref transform, ref movementPlane, ref movementControl, ref movementSettings, ref linkInfo, movementDisabled, localAvoidanceDisabled, state, deltaTime); + } + + // Initialize the coroutine during the first step. + // This can also happen if the entity is duplicated, since the coroutine cannot be cloned. + if (managedLinkInfo.coroutine == null) { + // If we are calculating a path right now, cancel that path calculation. + // We don't want to calculate a path while we are traversing an off-mesh link. + state.CancelCurrentPathRequest(); + + if (managedLinkInfo.stateMachine == null) { + managedLinkInfo.stateMachine = managedLinkInfo.handler != null? managedLinkInfo.handler.GetOffMeshLinkStateMachine(managedLinkInfo.context) : null; + } + managedLinkInfo.coroutine = managedLinkInfo.stateMachine != null? managedLinkInfo.stateMachine.OnTraverseOffMeshLink(managedLinkInfo.context).GetEnumerator() : JobStartOffMeshLinkTransition.DefaultOnTraverseOffMeshLink(managedLinkInfo.context).GetEnumerator(); + + // Don't disable local avoidance during off-mesh links by default. The link traversal code can do that itself if it wants to. + if (localAvoidanceDisabled.IsValid) localAvoidanceDisabled.ValueRW = false; + } + + bool finished; + bool error = false; + bool popParts = true; + + // Disable the agent's normal movement logic while traversing the off-mesh link + // This can be re-enabled by the state machine if it wants to, but it needs to do it every tick. + // It is enabled automatically by the AgentOffMeshLinkTraversal.MoveTowards method. + // The reference could be invalid when called from the project dawn navigation package + if (movementDisabled.IsValid) movementDisabled.ValueRW = true; + + try { + finished = !managedLinkInfo.coroutine.MoveNext(); + } catch (AgentOffMeshLinkTraversalContext.AbortOffMeshLinkTraversal) { + error = true; + finished = true; + popParts = false; + } + catch (System.Exception e) { + Debug.LogException(e, managedLinkInfo.context.gameObject); + // Teleport the agent to the end of the link as a fallback, if there's an exception + managedLinkInfo.context.Teleport(managedLinkInfo.context.link.relativeEnd); + finished = true; + error = true; + } + + if (finished) { + try { + if (managedLinkInfo.stateMachine != null) { + if (error) managedLinkInfo.stateMachine.OnAbortTraversingOffMeshLink(); + else managedLinkInfo.stateMachine.OnFinishTraversingOffMeshLink(managedLinkInfo.context); + } + } catch (System.Exception e) { + // If an exception happens when exiting the state machine, log it, and then continue with the cleanup + Debug.LogException(e, managedLinkInfo.context.gameObject); + } + + managedLinkInfo.context.Restore(); + if (popParts) { + // Pop the part leading up to the link, and the link itself + state.PopNextLinkFromPath(); + } + } + return !finished; + } + } + + public partial struct JobManagedOffMeshLinkTransitionCleanup : IJobEntity { + public void Execute (ManagedAgentOffMeshLinkTraversal managedLinkInfo) { + // The state machine may be null if the default off-mesh link logic is used, or if the entity is destroyed on the first frame + // that it starts to traverse an off-mesh link. + if (managedLinkInfo.stateMachine != null) managedLinkInfo.stateMachine.OnAbortTraversingOffMeshLink(); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta new file mode 100644 index 0000000..b62153c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1526037023c1413d8ead81bfcb5f4c6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs new file mode 100644 index 0000000..99d8abd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs @@ -0,0 +1,95 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Pathfinding.ECS { + [BurstCompile] + [WithAll(typeof(SimulateMovement), typeof(SimulateMovementFinalize))] + [WithNone(typeof(AgentOffMeshLinkMovementDisabled))] + public partial struct JobMoveAgent : IJobEntity { + public float dt; + + /// Calculates the final rotation for the agent on this simulation step, based on the component + public static quaternion ResolveRotation (quaternion rotation, ref MovementState state, in ResolvedMovement resolvedMovement, in MovementSettings movementSettings, in AgentMovementPlane movementPlane, float dt) { + var currentRotation = movementPlane.value.ToPlane(rotation); + var currentInternalRotation = currentRotation - state.rotationOffset - state.rotationOffset2; + var deltaRotation = math.clamp(AstarMath.DeltaAngle(currentInternalRotation, resolvedMovement.targetRotation), -resolvedMovement.rotationSpeed * dt, resolvedMovement.rotationSpeed * dt); + var extraRotationSpeed = math.radians(movementSettings.follower.maxRotationSpeed) * 0.5f; + var deltaExtraRotation = math.clamp(AstarMath.DeltaAngle(state.rotationOffset, resolvedMovement.targetRotationOffset), -extraRotationSpeed * dt, extraRotationSpeed * dt); + var currentUnsmoothedRotation = currentInternalRotation + state.rotationOffset; + var newInternalRotation = currentInternalRotation + deltaRotation; + // Keep track of how much extra rotation we are applying. This is done so that + // the movement calculation can separate this out when doing its movement calculations. + state.rotationOffset += deltaExtraRotation; + // Make sure the rotation offset is between -pi/2 and pi/2 radians + state.rotationOffset = AstarMath.DeltaAngle(0, state.rotationOffset); + var newUnsmoothedRotation = newInternalRotation + state.rotationOffset; + + if (movementSettings.rotationSmoothing > 0) { + // Apply compensation to rotationOffset2 to precisely cancel out the agent's rotation during this frame + state.rotationOffset2 += currentUnsmoothedRotation - newUnsmoothedRotation; + + state.rotationOffset2 = AstarMath.DeltaAngle(0, state.rotationOffset2); + + // Decay the rotationOffset2. This implicitly adds an exponential moving average to the visual rotation + var decay = math.abs(AstarMath.DeltaAngle(currentRotation, resolvedMovement.targetRotationHint)) / movementSettings.rotationSmoothing; + var exponentialDecay = decay*dt; + + // In addition to an exponential decay, we also add a linear decay. + // This is important to relatively quickly zero out the error when the agent is almost + // facing the right direction. With an exponential decay, it would take far too long to look good. + const float LINEAR_DECAY_AMOUNT = 0.1f; + var linearDecay = (LINEAR_DECAY_AMOUNT/movementSettings.rotationSmoothing)*dt; + + if (math.abs(state.rotationOffset2) > 0) state.rotationOffset2 *= math.max(0, 1 - exponentialDecay - linearDecay/math.abs(state.rotationOffset2)); + } else if (state.rotationOffset2 != 0) { + // Rotation smoothing is disabled, decay the rotation offset very quickly, but still avoid jarring changes + state.rotationOffset2 += math.clamp(-state.rotationOffset2, -extraRotationSpeed * dt, extraRotationSpeed * dt); + } + + return movementPlane.value.ToWorldRotation(newInternalRotation + state.rotationOffset + state.rotationOffset2); + } + + /// Calculates the amount the agent should move during this simulation step, based on the component + public static float3 MoveWithoutGravity (float3 position, in ResolvedMovement resolvedMovement, in AgentMovementPlane movementPlane, float dt) { + UnityEngine.Assertions.Assert.IsTrue(math.all(math.isfinite(resolvedMovement.targetPoint))); + // Move only along the movement plane + var localDir = movementPlane.value.ToPlane(resolvedMovement.targetPoint - position); + var magn = math.length(localDir); + var localDelta = math.select(localDir, localDir * math.clamp(resolvedMovement.speed * dt / magn, 0, 1.0f), magn > 0.0001f); + var delta = movementPlane.value.ToWorld(localDelta, 0); + return delta; + } + + public static void ResolvePositionSmoothing (float3 movementDelta, ref MovementState state, in MovementSettings movementSettings, float dt) { + if (movementSettings.positionSmoothing > 0) { + state.positionOffset -= movementDelta; + var exponentialDecay = 1f/movementSettings.positionSmoothing*dt; + var linearDecay = 0.1f/movementSettings.positionSmoothing*dt; + var positionOffsetMagnitude = math.length(state.positionOffset); + if (positionOffsetMagnitude > 0) state.positionOffset *= math.max(0, 1 - exponentialDecay - linearDecay/positionOffsetMagnitude); + } else { + state.positionOffset = float3.zero; + } + } + + public void Execute (ref LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, ref MovementState state, in MovementSettings movementSettings, in ResolvedMovement resolvedMovement, ref MovementStatistics movementStatistics) { + MoveAgent(ref transform, in shape, in movementPlane, ref state, in movementSettings, in resolvedMovement, ref movementStatistics, dt); + } + + public static void MoveAgent (ref LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, ref MovementState state, in MovementSettings movementSettings, in ResolvedMovement resolvedMovement, ref MovementStatistics movementStatistics, float dt) { + var delta = MoveWithoutGravity(transform.Position, in resolvedMovement, in movementPlane, dt); + UnityEngine.Assertions.Assert.IsTrue(math.all(math.isfinite(delta)), "Refusing to set the agent's position to a non-finite vector"); + transform.Position += delta; + // In 2D games, the agent may move slightly in the Z direction, due to floating point errors. + // Some users get confused about this, so if the Z coordinate is very close to zero, just set it to zero. + if (math.abs(transform.Position.z) < 0.00001f) transform.Position.z = 0; + + ResolvePositionSmoothing(delta, ref state, in movementSettings, dt); + transform.Rotation = ResolveRotation(transform.Rotation, ref state, in resolvedMovement, in movementSettings, in movementPlane, dt); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs.meta new file mode 100644 index 0000000..c989a0d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobMoveAgent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae237f49f264df546bb908b1d4f3d658 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs new file mode 100644 index 0000000..d01fd47 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs @@ -0,0 +1,46 @@ +#if MODULE_ENTITIES +using Pathfinding.Drawing; +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine; + +namespace Pathfinding.ECS { + [BurstCompile] + public partial struct JobPrepareAgentRaycasts : IJobEntity { + public NativeArray raycastCommands; + public QueryParameters raycastQueryParameters; + public float dt; + public float gravity; + + void ApplyGravity (ref GravityState gravityState) { + gravityState.verticalVelocity += gravity * dt; + } + + RaycastCommand CalculateRaycastCommands (ref LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, in MovementSettings movementSettings, ref GravityState gravityState, PhysicsScene physicsScene) { + // TODO: Might be more performant to convert the movement plane to two matrices + + movementPlane.value.ToPlane(transform.Position, out var lastElevation); + + var elevationDelta = gravityState.verticalVelocity * dt; + var localPosition = movementPlane.value.ToPlane(transform.Position, out var elevation); + var rayStartElevation = math.max(elevation + elevationDelta, lastElevation) + shape.height * 0.5f; + var rayStopElevation = math.min(elevation + elevationDelta, lastElevation); + float rayLength = rayStartElevation - rayStopElevation; // TODO: Multiply by scale + var down = movementPlane.value.ToWorld(0, -1); + raycastQueryParameters.layerMask = movementSettings.groundMask; + return new RaycastCommand(physicsScene, movementPlane.value.ToWorld(localPosition, rayStartElevation), down, raycastQueryParameters, rayLength); + } + + public void Execute (ref LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, ref MovementState state, in MovementSettings movementSettings, ref ResolvedMovement resolvedMovement, ref MovementStatistics movementStatistics, ref GravityState gravityState, in PhysicsSceneRef physicsWorldIndex, [Unity.Entities.EntityIndexInQuery] int entityIndexInQuery) { + // Move only along the movement plane + // JobMoveAgent.MoveAgent(ref transform, in shape, in movementPlane, ref state, in movementSettings, in resolvedMovement, ref movementStatistics, dt); + + ApplyGravity(ref gravityState); + raycastCommands[entityIndexInQuery] = CalculateRaycastCommands(ref transform, in shape, in movementPlane, in movementSettings, ref gravityState, physicsWorldIndex.physicsScene); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta new file mode 100644 index 0000000..7d0a011 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9de01002f503a3e4abd6467cd6de7a44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs new file mode 100644 index 0000000..1eca0a7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs @@ -0,0 +1,285 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Profiling; +using Unity.Transforms; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using GCHandle = System.Runtime.InteropServices.GCHandle; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.Util; + using Pathfinding.Collections; + using Unity.Burst; + using Unity.Jobs; + + /// + /// Repairs the path of agents. + /// + /// This job will repair the agent's path based on the agent's current position and its destination. + /// It will also recalculate various statistics like how far the agent is from the destination, + /// and if it has reached the destination or not. + /// + public struct JobRepairPath : IJobChunk { + public struct Scheduler { + [ReadOnly] + public ComponentTypeHandle LocalTransformTypeHandleRO; + public ComponentTypeHandle MovementStateTypeHandleRW; + [ReadOnly] + public ComponentTypeHandle AgentCylinderShapeTypeHandleRO; + // NativeDisableContainerSafetyRestriction seems to be necessary because otherwise we will get an error: + // "The ComponentTypeHandle ... can not be accessed. Nested native containers are illegal in jobs." + // However, Unity doesn't seem to check for this at all times. Currently, I can only replicate the error if DoTween Pro is also installed. + // I have no idea how this unrelated package influences unity to actually do the check. + // We know it is safe to access the managed state because we make sure to never access an entity from multiple threads at the same time. + [NativeDisableContainerSafetyRestriction] + public ComponentTypeHandle ManagedStateTypeHandleRW; + [ReadOnly] + public ComponentTypeHandle MovementSettingsTypeHandleRO; + public ComponentTypeHandle AutoRepathPolicyRW; + [ReadOnly] + public ComponentTypeHandle DestinationPointTypeHandleRO; + [ReadOnly] + public ComponentTypeHandle AgentMovementPlaneTypeHandleRO; + public ComponentTypeHandle ReadyToTraverseOffMeshLinkTypeHandleRW; + public GCHandle entityManagerHandle; + public bool onlyApplyPendingPaths; + + public EntityQueryBuilder GetEntityQuery (Allocator allocator) { + return new EntityQueryBuilder(Allocator.Temp) + .WithAllRW() + .WithAllRW() + .WithAll() + .WithAll() + // .WithAny() // TODO: Use WithPresent in newer versions + .WithAbsent(); + } + + public Scheduler(ref SystemState systemState) { + entityManagerHandle = GCHandle.Alloc(systemState.EntityManager); + LocalTransformTypeHandleRO = systemState.GetComponentTypeHandle(true); + MovementStateTypeHandleRW = systemState.GetComponentTypeHandle(false); + AgentCylinderShapeTypeHandleRO = systemState.GetComponentTypeHandle(true); + AutoRepathPolicyRW = systemState.GetComponentTypeHandle(false); + DestinationPointTypeHandleRO = systemState.GetComponentTypeHandle(true); + AgentMovementPlaneTypeHandleRO = systemState.GetComponentTypeHandle(true); + MovementSettingsTypeHandleRO = systemState.GetComponentTypeHandle(true); + ReadyToTraverseOffMeshLinkTypeHandleRW = systemState.GetComponentTypeHandle(false); + // Need to bypass the T : unmanaged check in systemState.GetComponentTypeHandle + ManagedStateTypeHandleRW = systemState.EntityManager.GetComponentTypeHandle(false); + onlyApplyPendingPaths = false; + } + + public void Dispose () { + entityManagerHandle.Free(); + } + + public void Update (ref SystemState systemState) { + LocalTransformTypeHandleRO.Update(ref systemState); + MovementStateTypeHandleRW.Update(ref systemState); + AgentCylinderShapeTypeHandleRO.Update(ref systemState); + AutoRepathPolicyRW.Update(ref systemState); + DestinationPointTypeHandleRO.Update(ref systemState); + ManagedStateTypeHandleRW.Update(ref systemState); + MovementSettingsTypeHandleRO.Update(ref systemState); + AgentMovementPlaneTypeHandleRO.Update(ref systemState); + ReadyToTraverseOffMeshLinkTypeHandleRW.Update(ref systemState); + } + + public JobHandle ScheduleParallel (ref SystemState systemState, EntityQuery query, JobHandle dependency) { + Update(ref systemState); + return new JobRepairPath { + scheduler = this, + onlyApplyPendingPaths = onlyApplyPendingPaths + }.ScheduleParallel(query, dependency); + } + } + + public Scheduler scheduler; + + [NativeDisableContainerSafetyRestriction] + public NativeArray indicesScratch; + [NativeDisableContainerSafetyRestriction] + public NativeList nextCornersScratch; + public bool onlyApplyPendingPaths; + + + public void Execute (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in Unity.Burst.Intrinsics.v128 chunkEnabledMask) { + if (!indicesScratch.IsCreated) { + nextCornersScratch = new NativeList(Allocator.Temp); + indicesScratch = new NativeArray(8, Allocator.Temp); + } + + unsafe { + var localTransforms = (LocalTransform*)chunk.GetNativeArray(ref scheduler.LocalTransformTypeHandleRO).GetUnsafeReadOnlyPtr(); + var movementStates = (MovementState*)chunk.GetNativeArray(ref scheduler.MovementStateTypeHandleRW).GetUnsafePtr(); + var agentCylinderShapes = (AgentCylinderShape*)chunk.GetNativeArray(ref scheduler.AgentCylinderShapeTypeHandleRO).GetUnsafeReadOnlyPtr(); + var autoRepathPolicy = (AutoRepathPolicy*)chunk.GetNativeArray(ref scheduler.AutoRepathPolicyRW).GetUnsafePtr(); + var destinationPoints = (DestinationPoint*)chunk.GetNativeArray(ref scheduler.DestinationPointTypeHandleRO).GetUnsafeReadOnlyPtr(); + var movementSettings = (MovementSettings*)chunk.GetNativeArray(ref scheduler.MovementSettingsTypeHandleRO).GetUnsafeReadOnlyPtr(); + var agentMovementPlanes = (AgentMovementPlane*)chunk.GetNativeArray(ref scheduler.AgentMovementPlaneTypeHandleRO).GetUnsafeReadOnlyPtr(); + var mask = chunk.GetEnabledMask(ref scheduler.ReadyToTraverseOffMeshLinkTypeHandleRW); + + var managedStates = chunk.GetManagedComponentAccessor(ref scheduler.ManagedStateTypeHandleRW, (EntityManager)scheduler.entityManagerHandle.Target); + + for (int i = 0; i < chunk.Count; i++) { + Execute( + ref localTransforms[i], + ref movementStates[i], + ref agentCylinderShapes[i], + ref agentMovementPlanes[i], + ref autoRepathPolicy[i], + ref destinationPoints[i], + mask.GetEnabledRefRW(i), + managedStates[i], + in movementSettings[i], + nextCornersScratch, + ref indicesScratch, + Allocator.Temp, + onlyApplyPendingPaths + ); + } + } + } + + private static readonly ProfilerMarker MarkerRepair = new ProfilerMarker("Repair"); + private static readonly ProfilerMarker MarkerGetNextCorners = new ProfilerMarker("GetNextCorners"); + private static readonly ProfilerMarker MarkerUpdateReachedEndInfo = new ProfilerMarker("UpdateReachedEndInfo"); + + public static void Execute (ref LocalTransform transform, ref MovementState state, ref AgentCylinderShape shape, ref AgentMovementPlane movementPlane, ref AutoRepathPolicy autoRepathPolicy, ref DestinationPoint destination, EnabledRefRW readyToTraverseOffMeshLink, ManagedState managedState, in MovementSettings settings, NativeList nextCornersScratch, ref NativeArray indicesScratch, Allocator allocator, bool onlyApplyPendingPaths) { + // Only enabled by the PollPendingPathsSystem + if (onlyApplyPendingPaths) { + if (managedState.pendingPath != null && managedState.pendingPath.IsDone()) { + // The path has been calculated, apply it to the agent + // Immediately after this we must also repair the path to ensure that it is valid and that + // all properties like #MovementState.reachedEndOfPath are correct. + autoRepathPolicy.OnPathCalculated(managedState.pendingPath.error); + ManagedState.SetPath(managedState.pendingPath, managedState, in movementPlane, ref destination); + } else { + // The agent has no path that has just been calculated, skip it + return; + } + } + + ref var pathTracer = ref managedState.pathTracer; + + if (pathTracer.hasPath) { + MarkerRepair.Begin(); + // Update the start and end points of the path based on the current position and destination. + // This will repair the path if necessary, ensuring that the agent has a valid, if not necessarily optimal, path. + // If it cannot be repaired well, the path will be marked as stale. + state.closestOnNavmesh = pathTracer.UpdateStart(transform.Position, PathTracer.RepairQuality.High, movementPlane.value); + state.endOfPath = pathTracer.UpdateEnd(destination.destination, PathTracer.RepairQuality.High, movementPlane.value); + MarkerRepair.End(); + + if (state.pathTracerVersion != pathTracer.version) { + nextCornersScratch.Clear(); + + MarkerGetNextCorners.Begin(); + // Find the next corners of the path. The first corner is our current position, + // the second corner is the one we are moving towards and the third corner is the one after that. + // + // Using GetNextCorners with the default transformation instead of ConvertCornerIndicesToPathProjected + // is about 20% faster, but it does not work well at all on spherical worlds. + // In the future we might want to switch dynamically between these modes, + // but on the other hand, it is very nice to be able to use the exact same code path for everything. + // pathTracer.GetNextCorners(nextCornersScratch, 3, ref indicesScratch, allocator); + var numCorners = pathTracer.GetNextCornerIndices(ref indicesScratch, pathTracer.desiredCornersForGoodSimplification, allocator, true, out bool lastCorner); + pathTracer.ConvertCornerIndicesToPathProjected(indicesScratch, numCorners, lastCorner, nextCornersScratch, movementPlane.value.up); + MarkerGetNextCorners.End(); + + // We need to copy a few fields to a new struct, in order to be able to pass it to a burstified function + var pathTracerInfo = new JobRepairPathHelpers.PathTracerInfo { + endPointOfFirstPart = pathTracer.endPointOfFirstPart, + hasValidEndPoint = pathTracer.hasValidEndPoint, + partCount = pathTracer.partCount, + isStale = pathTracer.isStale + }; + var nextCorners = nextCornersScratch.AsUnsafeSpan(); + JobRepairPathHelpers.UpdateReachedEndInfo(ref nextCorners, ref state, ref movementPlane, ref transform, ref shape, ref destination, settings.stopDistance, ref pathTracerInfo); + state.pathTracerVersion = pathTracer.version; + } else { + JobRepairPathHelpers.UpdateReachedOrientation(ref state, ref transform, ref movementPlane, ref destination); + } + + if (pathTracer.startNode != null && !pathTracer.startNode.Destroyed && pathTracer.startNode.Walkable) { + state.graphIndex = pathTracer.startNode.GraphIndex; + state.hierarchicalNodeIndex = pathTracer.startNode.HierarchicalNodeIndex; + } else { + state.graphIndex = GraphNode.InvalidGraphIndex; + state.hierarchicalNodeIndex = -1; + } + } else { + state.SetPathIsEmpty(transform.Position); + } + + if (readyToTraverseOffMeshLink.IsValid) readyToTraverseOffMeshLink.ValueRW = state.reachedEndOfPart && managedState.pathTracer.isNextPartValidLink; + } + } + + [BurstCompile] + static class JobRepairPathHelpers { + public struct PathTracerInfo { + public float3 endPointOfFirstPart; + public int partCount; + // Bools are not blittable by burst so we must add this attribute. Very ugly, but it is what it is. + [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U1)] + public bool isStale; + [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.U1)] + public bool hasValidEndPoint; + } + + /// Checks if the agent has reached its destination, or the end of the path + [BurstCompile] + public static void UpdateReachedEndInfo (ref UnsafeSpan nextCorners, ref MovementState state, ref AgentMovementPlane movementPlane, ref LocalTransform transform, ref AgentCylinderShape shape, ref DestinationPoint destination, float stopDistance, ref PathTracerInfo pathTracer) { + // TODO: Edit GetNextCorners so that it gets corners until at least stopDistance units from the agent + state.nextCorner = nextCorners.length > 1 ? nextCorners[1] : transform.Position; + state.remainingDistanceToEndOfPart = PathTracer.RemainingDistanceLowerBound(in nextCorners, in pathTracer.endPointOfFirstPart, in movementPlane.value); + + // TODO: Check if end node is the globally closest node + movementPlane.value.ToPlane(pathTracer.endPointOfFirstPart - transform.Position, out var elevationDiffEndOfPart); + var validHeightRangeEndOfPart = elevationDiffEndOfPart< shape.height && elevationDiffEndOfPart > -0.5f*shape.height; + + movementPlane.value.ToPlane(destination.destination - transform.Position, out var elevationDiffDestination); + var validHeightRangeDestination = elevationDiffDestination< shape.height && elevationDiffDestination > -0.5f*shape.height; + var endOfPathToDestination = math.length(movementPlane.value.ToPlane(destination.destination - state.endOfPath)); + // If reachedEndOfPath is true we allow a slightly larger margin of error for reachedDestination. + // This is to ensure that if reachedEndOfPath becomes true, it is very likely that reachedDestination becomes + // true during the same frame. + const float FUZZ = 0.01f; + // When checking if the agent has reached the end of the current part (mostly used for off-mesh-links), we check against + // the agent's radius. This is because when there are many agents trying to reach the same off-mesh-link, the agents will + // crowd up and it may become hard to get to a point closer than the agent's radius. + state.reachedEndOfPart = !pathTracer.isStale && validHeightRangeEndOfPart && state.remainingDistanceToEndOfPart <= shape.radius*1.1f; + state.reachedEndOfPath = !pathTracer.isStale && validHeightRangeEndOfPart && pathTracer.partCount == 1 && state.remainingDistanceToEndOfPart <= stopDistance; + state.reachedDestination = !pathTracer.isStale && validHeightRangeDestination && pathTracer.partCount == 1 && state.remainingDistanceToEndOfPart + endOfPathToDestination <= stopDistance + (state.reachedEndOfPath ? FUZZ : 0); + state.traversingLastPart = pathTracer.partCount == 1; + state.hasValidEndPoint = pathTracer.hasValidEndPoint; + UpdateReachedOrientation(ref state, ref transform, ref movementPlane, ref destination); + } + + /// Checks if the agent is oriented towards the desired facing direction + public static void UpdateReachedOrientation (ref MovementState state, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref DestinationPoint destination) { + state.reachedEndOfPathAndOrientation = state.reachedEndOfPath; + state.reachedDestinationAndOrientation = state.reachedDestination; + if (state.reachedEndOfPathAndOrientation || state.reachedDestinationAndOrientation) { + var reachedOrientation = ReachedDesiredOrientation(ref transform, ref movementPlane, ref destination); + state.reachedEndOfPathAndOrientation &= reachedOrientation; + state.reachedDestinationAndOrientation &= reachedOrientation; + } + } + + static bool ReachedDesiredOrientation (ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref DestinationPoint destination) { + var facingDirection2D = math.normalizesafe(movementPlane.value.ToPlane(destination.facingDirection)); + + // If no desired facing direction is set, then we always treat the orientation as correct + if (math.all(facingDirection2D == 0)) return true; + + var forward2D = math.normalizesafe(movementPlane.value.ToPlane(transform.Forward())); + const float ANGLE_THRESHOLD_COS = 0.9999f; + return math.dot(forward2D, facingDirection2D) >= ANGLE_THRESHOLD_COS; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs.meta new file mode 100644 index 0000000..0a45699 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobRepairPath.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a08f70357a4844d59d3d7fa8d7d8b0f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs new file mode 100644 index 0000000..994c68d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs @@ -0,0 +1,33 @@ +#pragma warning disable 0282 // Allows the 'partial' keyword without warnings +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; + +namespace Pathfinding.ECS { + public partial struct JobStartOffMeshLinkTransition { + public EntityCommandBuffer commandBuffer; + + /// + /// This is a fallback for traversing off-mesh links in case the user has not specified a custom traversal method. + /// It is a coroutine which will move the agent from the start point of the link to the end point of the link. + /// It will also disable RVO for the agent while traversing the link. + /// + public static System.Collections.Generic.IEnumerable DefaultOnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) { + var linkInfo = ctx.link; + var up = ctx.movementPlane.ToWorld(float2.zero, 1); + var dirInPlane = ctx.movementPlane.ToWorld(ctx.movementPlane.ToPlane(linkInfo.relativeEnd - linkInfo.relativeStart), 0); + var rot = quaternion.LookRotationSafe(dirInPlane, up); + + while (!ctx.MoveTowards(linkInfo.relativeStart, rot, true, false).reached) yield return null; + + ctx.DisableLocalAvoidance(); + + while (!ctx.MoveTowards(linkInfo.relativeEnd, rot, true, false).reached) yield return null; + + // ctx.Teleport(linkInfo.endPoint); + + yield break; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta new file mode 100644 index 0000000..31fa8a5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc9dc25315394c9caf43a303397f47ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs new file mode 100644 index 0000000..041f03d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs @@ -0,0 +1,47 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Collections; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using UnityEngine.Jobs; + +namespace Pathfinding.ECS { + [BurstCompile] + struct JobSyncEntitiesToTransforms : IJobParallelForTransform { + [ReadOnly] + public NativeArray entities; + [ReadOnly] + public ComponentLookup entityPositions; + [ReadOnly] + public ComponentLookup movementState; + [ReadOnly] + public ComponentLookup syncPositionWithTransform; + [ReadOnly] + public ComponentLookup syncRotationWithTransform; + [ReadOnly] + public ComponentLookup orientationYAxisForward; + + public void Execute (int index, TransformAccess transform) { + var entity = entities[index]; + if (!entityPositions.HasComponent(entity)) return; + float3 offset = float3.zero; + if (movementState.TryGetComponent(entity, out var ms)) { + offset = ms.positionOffset; + } + + var tr = entityPositions.GetRefRO(entity); + if (syncPositionWithTransform.HasComponent(entity)) transform.position = tr.ValueRO.Position + offset; + if (syncRotationWithTransform.HasComponent(entity)) { + if (orientationYAxisForward.HasComponent(entity)) { + // Y axis forward + transform.rotation = math.mul(tr.ValueRO.Rotation, SyncTransformsToEntitiesSystem.ZAxisForwardToYAxisForward); + } else { + // Z axis forward + transform.rotation = tr.ValueRO.Rotation; + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta new file mode 100644 index 0000000..8edba2e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbb93f3ffd0760d4e9b68f4376e30aeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs new file mode 100644 index 0000000..ce9d007 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs @@ -0,0 +1,25 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; + +namespace Pathfinding.ECS { + [BurstCompile] + [WithAll(typeof(SimulateMovement))] + public partial struct JobUpdateMovementStatistics : IJobEntity { + public float dt; + + static void UpdateStatistics (in LocalTransform transform, ref MovementStatistics movementStatistics, float dt) { + if (dt > 0.000001f) { + movementStatistics.estimatedVelocity = (transform.Position - movementStatistics.lastPosition) / dt; + } + movementStatistics.lastPosition = transform.Position; + } + + public void Execute (in LocalTransform transform, ref MovementStatistics movementStatistics) { + UpdateStatistics(in transform, ref movementStatistics, dt); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs.meta new file mode 100644 index 0000000..d517ef7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/JobUpdateMovementStatistics.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 958426d1b402f3f47a6bbcc0bddc65f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs.meta new file mode 100644 index 0000000..1c23899 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/ManagedOffMeshLinkTransitionJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a279ed93b8648d4982ea23f87877a3c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs.meta new file mode 100644 index 0000000..d1ba13b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/RepairPathJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db3650f148245bf4ba822d172a34cc65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs.meta new file mode 100644 index 0000000..69baaa9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Jobs/StartOffMeshLinkTransitionJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fee7292c827674f4eb6ef066b3fd1c99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems.meta new file mode 100644 index 0000000..ad610e5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 380dbe46ad736aa4ab00113f94f0ddf2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs new file mode 100644 index 0000000..cccd77f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs @@ -0,0 +1,92 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; +using Pathfinding.Drawing; +using Unity.Jobs; + +namespace Pathfinding.ECS { + /// + /// Draws gizmos and debug information for agents. + /// + /// When outside of play mode, the method will draw some of the same gizmos, + /// since this system cannot be used then (entities only exist in play mode). + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + public partial struct AIGizmosSystem : ISystem { + static bool manuallyTriggered; + JobRepairPath.Scheduler jobRepairPathScheduler; + ComponentTypeHandle MovementStateTypeHandleRO; + ComponentTypeHandle ResolvedMovementHandleRO; + + class DrawerCallback : IDrawGizmos { + World world; + + public DrawerCallback(World world) { + this.world = world; + } + + public void DrawGizmos () { + if (!world.IsCreated) return; + var handle = world.GetExistingSystem(); + if (handle != SystemHandle.Null) { + manuallyTriggered = true; + try { + handle.Update(world.Unmanaged); + } finally { + manuallyTriggered = false; + } + } + } + + public bool Exists => world.IsCreated && world.GetExistingSystem() != SystemHandle.Null; + } + + public void OnCreate (ref SystemState state) { + jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state); + MovementStateTypeHandleRO = state.GetComponentTypeHandle(true); + ResolvedMovementHandleRO = state.GetComponentTypeHandle(true); + + DrawingManager.Register(new DrawerCallback(state.World), typeof(FollowerEntity)); + } + + public void OnUpdate (ref SystemState systemState) { + if (manuallyTriggered) DrawGizmos(ref systemState); + } + + void DrawGizmos (ref SystemState systemState) { + var entityQueryGizmos = SystemAPI.QueryBuilder() + .WithAll() + .WithAll() + .WithAll().Build(); + + if (entityQueryGizmos.IsEmptyIgnoreFilter) return; + + jobRepairPathScheduler.Update(ref systemState); + MovementStateTypeHandleRO.Update(ref systemState); + ResolvedMovementHandleRO.Update(ref systemState); + + var draw = DrawingManager.GetBuilder(); + + var job1 = new JobDrawFollowerGizmos { + draw = draw, + entityManagerHandle = jobRepairPathScheduler.entityManagerHandle, + LocalTransformTypeHandleRO = jobRepairPathScheduler.LocalTransformTypeHandleRO, + AgentCylinderShapeHandleRO = jobRepairPathScheduler.AgentCylinderShapeTypeHandleRO, + MovementSettingsHandleRO = jobRepairPathScheduler.MovementSettingsTypeHandleRO, + AgentMovementPlaneHandleRO = jobRepairPathScheduler.AgentMovementPlaneTypeHandleRO, + ManagedStateHandleRW = jobRepairPathScheduler.ManagedStateTypeHandleRW, + MovementStateHandleRO = MovementStateTypeHandleRO, + ResolvedMovementHandleRO = ResolvedMovementHandleRO, + }.ScheduleParallel(entityQueryGizmos, systemState.Dependency); + + // This can actually run in parallel with the first job, because the command builder is thread-safe, and we only read the same components. + var job2 = new JobDrawFollowerGizmosBase { + draw = draw, + }.ScheduleParallel(systemState.Dependency); + systemState.Dependency = JobHandle.CombineDependencies(job1, job2); + + draw.DisposeAfter(systemState.Dependency, AllowedDelay.EndOfFrame); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs.meta new file mode 100644 index 0000000..74a6091 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIGizmosSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73857a45eca4b924b9f6b3050464c8a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs new file mode 100644 index 0000000..33f2b08 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs @@ -0,0 +1,137 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; +using Unity.Burst; +using Unity.Jobs; +using Unity.Collections; +using UnityEngine; + +namespace Pathfinding.ECS { + using Pathfinding.ECS.RVO; + using Pathfinding.Drawing; + using Pathfinding.Util; + using Unity.Profiling; + using UnityEngine.Profiling; + + [BurstCompile] + [UpdateAfter(typeof(FollowerControlSystem))] + [UpdateAfter(typeof(RVOSystem))] + [UpdateAfter(typeof(FallbackResolveMovementSystem))] + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [RequireMatchingQueriesForUpdate] + public partial struct AIMoveSystem : ISystem { + EntityQuery entityQueryWithGravity; + EntityQuery entityQueryMovementOverride; + + public void OnCreate (ref SystemState state) { + entityQueryWithGravity = state.GetEntityQuery( + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + + // When in 2D mode, gravity is always disabled + ComponentType.Exclude(), + + ComponentType.Exclude(), + + ComponentType.ReadOnly(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + + entityQueryMovementOverride = state.GetEntityQuery( + ComponentType.ReadWrite(), + + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + + ComponentType.Exclude(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + } + + static readonly ProfilerMarker MarkerMovementOverride = new ProfilerMarker("MovementOverrideBeforeMovement"); + + public void OnUpdate (ref SystemState systemState) { + // This system is executed at least every frame to make sure the agent is moving smoothly even at high fps. + // The control loop and local avoidance may be running less often. + // So this is designated a "cheap" system, and we use the corresponding delta time for that. + var dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime; + + systemState.Dependency = new JobAlignAgentWithMovementDirection { + dt = dt, + }.Schedule(systemState.Dependency); + + RunMovementOverrideBeforeMovement(ref systemState, dt); + + // Move all agents + systemState.Dependency = new JobMoveAgent { + dt = dt, + }.ScheduleParallel(systemState.Dependency); + + ScheduleApplyGravity(ref systemState, dt); + } + + void ScheduleApplyGravity (ref SystemState systemState, float dt) { + Profiler.BeginSample("Gravity"); + // Allocate one raycast command and one hit for each entity that needs gravity. + // Note: We don't want to use CalculateEntityCountWithoutFiltering here, because the GravityState component can be disabled. + // We could get only an upper bound, but fortunately using CalculateEntityCount here is fine, because the dependencies it injects + // are usually long done by this point, so it doesn't add much overhead. + var count = entityQueryWithGravity.CalculateEntityCount(); + var raycastCommands = CollectionHelper.CreateNativeArray(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); + var raycastHits = CollectionHelper.CreateNativeArray(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); + + // Prepare raycasts for all entities that have a GravityState component + systemState.Dependency = new JobPrepareAgentRaycasts { + raycastQueryParameters = new QueryParameters(-1, false, QueryTriggerInteraction.Ignore, false), + raycastCommands = raycastCommands, + dt = dt, + gravity = Physics.gravity.y, + }.ScheduleParallel(entityQueryWithGravity, systemState.Dependency); + + var raycastJob = RaycastCommand.ScheduleBatch(raycastCommands, raycastHits, 32, 1, systemState.Dependency); + + // Apply gravity and move all agents that have a GravityState component + systemState.Dependency = new JobApplyGravity { + raycastHits = raycastHits, + raycastCommands = raycastCommands, + dt = dt, + }.ScheduleParallel(entityQueryWithGravity, JobHandle.CombineDependencies(systemState.Dependency, raycastJob)); + + Profiler.EndSample(); + } + + void RunMovementOverrideBeforeMovement (ref SystemState systemState, float dt) { + if (!entityQueryMovementOverride.IsEmptyIgnoreFilter) { + MarkerMovementOverride.Begin(); + // The movement overrides always run on the main thread. + // This adds a sync point, but only if people actually add a movement override (which is rare). + systemState.CompleteDependency(); + new JobManagedMovementOverrideBeforeMovement { + dt = dt, + // TODO: Add unit test to make sure it fires/not fires when it should + }.Run(entityQueryMovementOverride); + MarkerMovementOverride.End(); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs.meta new file mode 100644 index 0000000..31a2d28 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMoveSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f857e04ce9382d74989b3d469a0b956e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs new file mode 100644 index 0000000..2d0c539 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs @@ -0,0 +1,244 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; +using UnityEngine; +using Unity.Collections; +using Unity.Core; +using Unity.Jobs; + +namespace Pathfinding.ECS { + [UpdateAfter(typeof(TransformSystemGroup))] + public partial class AIMovementSystemGroup : ComponentSystemGroup { + /// Rate manager which runs a system group multiple times if the delta time is higher than desired, but always executes the group at least once per frame + public class TimeScaledRateManager : IRateManager, System.IDisposable { + int numUpdatesThisFrame; + int updateIndex; + float stepDt; + float maximumDt = 1.0f / 30.0f; + float ownProcessingTimePerIteration = 0; + NativeList cheapTimeDataQueue; + NativeList timeDataQueue; + double lastFullSimulation; + double lastCheapSimulation; + static bool cheapSimulationOnly; + static bool isLastSubstep, isFirstSubstep; + static bool inGroup; + static TimeData cheapTimeData; + + /// + /// Additional time scale multiplier to apply to the simulation. + /// This can be used to speed up or slow down the simulation instead of using Unity's Time.timeScale. + /// + /// The final time scale will be Unity's Time.timeScale multiplied by this value. + /// + /// Default value is 1.0. + /// + /// + /// var aiMovementSystemGroup = Unity.Entities.World.DefaultGameObjectInjectionWorld.GetExistingSystemManaged(); + /// aiMovementSystemGroup.CustomTimeScale = 3.0; + /// + /// + public double CustomTimeScale = 1.0; + + /// + /// True if it was determined that zero substeps should be simulated. + /// In this case all systems will get an opportunity to run a single update, + /// but they should avoid systems that don't have to run every single frame. + /// + public static bool CheapSimulationOnly { + get { + if (!inGroup) throw new System.InvalidOperationException("Cannot call this method outside of a simulation group using TimeScaledRateManager"); + return cheapSimulationOnly; + } + } + + public static float CheapStepDeltaTime { + get { + if (!inGroup) throw new System.InvalidOperationException("Cannot call this method outside of a simulation group using TimeScaledRateManager"); + return cheapTimeData.DeltaTime; + } + } + + /// True when this is the last substep of the current simulation + public static bool IsLastSubstep { + get { + if (!inGroup) throw new System.InvalidOperationException("Cannot call this method outside of a simulation group using TimeScaledRateManager"); + return isLastSubstep; + } + } + + /// True when this is the first substep of the current simulation + public static bool IsFirstSubstep { + get { + if (!inGroup) throw new System.InvalidOperationException("Cannot call this method outside of a simulation group using TimeScaledRateManager"); + return isFirstSubstep; + } + } + + public int NumUpdatesThisFrame => numUpdatesThisFrame; + + public TimeScaledRateManager () { + cheapTimeDataQueue = new NativeList(Allocator.Persistent); + timeDataQueue = new NativeList(Allocator.Persistent); + } + + public void Dispose () { + cheapTimeDataQueue.Dispose(); + timeDataQueue.Dispose(); + } + + public void OnSimulationStepsFinished (float totalSimulationProcessingTime) { + if (cheapSimulationOnly) return; + + ownProcessingTimePerIteration = totalSimulationProcessingTime / numUpdatesThisFrame; + } + + public bool ShouldGroupUpdate (ComponentSystemGroup group) { + // if this is true, means we're being called a second or later time in a loop. + if (inGroup) { + group.World.PopTime(); + updateIndex++; + if (updateIndex >= numUpdatesThisFrame) { + inGroup = false; + return false; + } + } else { + cheapTimeDataQueue.Clear(); + timeDataQueue.Clear(); + + if (inGroup) throw new System.InvalidOperationException("Cannot nest simulation groups using TimeScaledRateManager"); + var currentTime = group.World.Time.ElapsedTime*CustomTimeScale; + var fullDt = (float)(currentTime - lastFullSimulation); + + // It has been observed that the time move backwards. + // Not quite sure when it happens, but we need to guard against it. + if (fullDt < 0) fullDt = 0; + + // If the delta time is large enough we may want to perform multiple simulation sub-steps per frame. + // This is done to improve simulation stability. In particular at high time scales, but it also + // helps at low fps, or if the game has a sudden long stutter. + // We raise the value to a power slightly smaller than 1 to make the number of sub-steps increase + // more slowly as the delta time increases. This is important to avoid the edge case when + // the time it takes to run the simulation is longer than maximumDt. Otherwise the number of + // simulation sub-steps would increase without bound. However, the simulation quality + // may decrease a bit as the number of sub-steps increases. + // + // If the time it takes to run a single iteration grows too large, + // the number of simulation steps will also be reduced. In the limit where the simulation + // takes up essentially the whole frame's cpu time, then we only do 1 simulation step per frame. + numUpdatesThisFrame = Mathf.FloorToInt(Mathf.Pow(fullDt / (maximumDt + ownProcessingTimePerIteration), 0.8f)); + cheapSimulationOnly = numUpdatesThisFrame == 0; + if (cheapSimulationOnly) { + timeDataQueue.Add(new TimeData( + lastFullSimulation, + 0.0f + )); + cheapTimeDataQueue.Add(new TimeData( + currentTime, + (float)(currentTime - lastCheapSimulation) + )); + lastCheapSimulation = currentTime; + } else { + stepDt = fullDt / numUpdatesThisFrame; + // Push the time for each sub-step + for (int i = 0; i < numUpdatesThisFrame; i++) { + var stepTime = lastFullSimulation + (i+1) * stepDt; + timeDataQueue.Add(new TimeData( + stepTime, + stepDt + )); + cheapTimeDataQueue.Add(new TimeData( + stepTime, + (float)(stepTime - lastCheapSimulation) + )); + lastCheapSimulation = stepTime; + } + lastFullSimulation = currentTime; + } + numUpdatesThisFrame = Mathf.Max(1, numUpdatesThisFrame); + inGroup = true; + updateIndex = 0; + } + + group.World.PushTime(timeDataQueue[updateIndex]); + cheapTimeData = cheapTimeDataQueue[updateIndex]; + isLastSubstep = updateIndex + 1 >= numUpdatesThisFrame; + isFirstSubstep = updateIndex == 0; + + return true; + } + + public float Timestep { + get => maximumDt; + set => maximumDt = value; + } + } + + /// \copydocref{AIMovementSystemGroup.TimeScaledRateManager.CustomTimeScale} + public double CustomTimeScale { + get => (RateManager as AIMovementSystemGroup.TimeScaledRateManager).CustomTimeScale; + set { + (RateManager as AIMovementSystemGroup.TimeScaledRateManager).CustomTimeScale = value; + } + } + + protected override void OnUpdate () { + // Various jobs (e.g. the JobRepairPath) in this system group may use graph data, + // and they also need the graph data to be consistent during the whole update. + // For example the MovementState.hierarchicalNodeIndex field needs to be valid + // during the whole group update, as it may be used by the RVOSystem and FollowerControlSystem. + // Locking the graph data as read-only here means that no graph updates will be performed + // while these jobs are running. + var readLock = AstarPath.active != null? AstarPath.active.LockGraphDataForReading() : default; + + // And here I thought the entities package reaching 1.0 would mean that they wouldn't just rename + // properties without any compatibility code... but nope... +#if MODULE_ENTITIES_1_0_8_OR_NEWER + var systems = this.GetUnmanagedSystems(); + for (int i = 0; i < systems.Length; i++) { + ref var state = ref this.World.Unmanaged.ResolveSystemStateRef(systems[i]); + state.Dependency = JobHandle.CombineDependencies(state.Dependency, readLock.dependency); + } +#else + var systems = this.Systems; + for (int i = 0; i < systems.Count; i++) { + ref var state = ref this.World.Unmanaged.ResolveSystemStateRef(systems[i].SystemHandle); + state.Dependency = JobHandle.CombineDependencies(state.Dependency, readLock.dependency); + } +#endif + + var t1 = System.Diagnostics.Stopwatch.GetTimestamp(); + base.OnUpdate(); + var t2 = System.Diagnostics.Stopwatch.GetTimestamp(); + var rateManager = RateManager as TimeScaledRateManager; + var timePerSimulationStep = (t2 - t1) / (rateManager.NumUpdatesThisFrame * (double)System.Diagnostics.Stopwatch.Frequency); + rateManager.OnSimulationStepsFinished((float)((t2 - t1) / (double)System.Diagnostics.Stopwatch.Frequency)); + + JobHandle readDependency = default; +#if MODULE_ENTITIES_1_0_8_OR_NEWER + for (int i = 0; i < systems.Length; i++) { + ref var state = ref this.World.Unmanaged.ResolveSystemStateRef(systems[i]); + readDependency = JobHandle.CombineDependencies(readDependency, state.Dependency); + } + systems.Dispose(); +#else + for (int i = 0; i < systems.Count; i++) { + ref var state = ref this.World.Unmanaged.ResolveSystemStateRef(systems[i].SystemHandle); + readDependency = JobHandle.CombineDependencies(readDependency, state.Dependency); + } +#endif + readLock.UnlockAfter(readDependency); + } + + protected override void OnDestroy () { + base.OnDestroy(); + (this.RateManager as TimeScaledRateManager).Dispose(); + } + + protected override void OnCreate () { + base.OnCreate(); + this.RateManager = new TimeScaledRateManager(); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs.meta new file mode 100644 index 0000000..da24779 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/AIMovementSystemGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84577891deac65d458d801d960c6fcee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs new file mode 100644 index 0000000..02dd0f5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs @@ -0,0 +1,44 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using Unity.Collections; + +namespace Pathfinding.ECS { + /// + /// System that updates the destination of agents with a component. + /// + /// Every frame, the position of the target entity will be copied to the field of the agent. + /// If is true, the facing direction will also be copied. + /// + /// See: + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(SchedulePathSearchSystem))] + [BurstCompile] + [RequireMatchingQueriesForUpdate] + public partial struct DestinationEntitySystem : ISystem { + [BurstCompile] + public void OnUpdate (ref SystemState state) { + state.Dependency = new UpdateDestinationJob { + TransformLookup = SystemAPI.GetComponentLookup(true), + }.Schedule(state.Dependency); + } + + [BurstCompile] + partial struct UpdateDestinationJob : IJobEntity { + [ReadOnly] + public ComponentLookup TransformLookup; + + public void Execute (ref DestinationPoint destPoint, in DestinationEntity destEntity) { + if (TransformLookup.HasComponent(destEntity.destination)) { + var transform = TransformLookup[destEntity.destination]; + destPoint.destination = transform.Position; + destPoint.facingDirection = destEntity.useRotation ? transform.Forward : float3.zero; + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs.meta new file mode 100644 index 0000000..6d3ebda --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationEntitySystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76160a5142d6f094fb82bac84aee0fb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs new file mode 100644 index 0000000..22984a0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs @@ -0,0 +1,43 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using Unity.Collections; + +namespace Pathfinding.ECS { + /// + /// System that updates the destination of agents with a component. + /// + /// Every frame, the calculated destination will be copied to the field of the agent. + /// + /// See: + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(SchedulePathSearchSystem))] + [BurstCompile] + [RequireMatchingQueriesForUpdate] + public partial struct DestinationMoveInCircleSystem : ISystem { + [BurstCompile] + public void OnUpdate (ref SystemState state) { + state.Dependency = new UpdateDestinationJob { + TransformLookup = SystemAPI.GetComponentLookup(true), + }.Schedule(state.Dependency); + } + + [BurstCompile] + partial struct UpdateDestinationJob : IJobEntity { + [ReadOnly] + public ComponentLookup TransformLookup; + + public void Execute (ref DestinationPoint destPoint, in LocalTransform localTransform, in MoveInCircle.DestinationMoveInCircle destCircle) { + if (TransformLookup.HasComponent(destCircle.target)) { + var targetTransform = TransformLookup[destCircle.target]; + destPoint.destination = MoveInCircle.CalculateDestination(localTransform.Position, targetTransform.Position, targetTransform.Up, destCircle.radius, destCircle.offset); + destPoint.facingDirection = float3.zero; + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs.meta new file mode 100644 index 0000000..73cc3ce --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/DestinationMoveInCircleSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a937d6141f84d1f42943b7b23cbd8b61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs new file mode 100644 index 0000000..19974f6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs @@ -0,0 +1,46 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Burst; +using Unity.Collections; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.ECS.RVO; + + /// Copies to when no local avoidance is used + [BurstCompile] + [UpdateAfter(typeof(FollowerControlSystem))] + [UpdateAfter(typeof(RVOSystem))] // Has to execute after RVOSystem in case that system detects that some agents should not be simulated using the RVO system anymore. + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [RequireMatchingQueriesForUpdate] + public partial struct FallbackResolveMovementSystem : ISystem { + public void OnUpdate (ref SystemState systemState) { + new CopyJob {}.Schedule(); + new CopyRotationJob {}.Schedule(); + } + + [BurstCompile] + [WithAll(typeof(SimulateMovement))] + [WithOptions(EntityQueryOptions.FilterWriteGroup)] // May be overriden by the RVO system + public partial struct CopyJob : IJobEntity { + public void Execute (in MovementControl control, ref ResolvedMovement resolved) { + resolved.targetPoint = control.targetPoint; + resolved.speed = control.speed; + resolved.turningRadiusMultiplier = 1.0f; + } + } + + [BurstCompile] + [WithAll(typeof(SimulateMovement))] + public partial struct CopyRotationJob : IJobEntity { + public void Execute (in MovementControl control, ref ResolvedMovement resolved) { + resolved.targetRotation = control.targetRotation; + resolved.targetRotationHint = control.targetRotationHint; + resolved.targetRotationOffset = control.targetRotationOffset; + resolved.rotationSpeed = control.rotationSpeed; + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta new file mode 100644 index 0000000..fbac068 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5502c9f6a3e7fc448803d8b0607c6eac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs new file mode 100644 index 0000000..c606655 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs @@ -0,0 +1,119 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using UnityEngine.Profiling; +using Unity.Profiling; +using Unity.Transforms; +using Unity.Burst; +using Unity.Jobs; +using GCHandle = System.Runtime.InteropServices.GCHandle; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.ECS.RVO; + using Pathfinding.Drawing; + using Pathfinding.RVO; + using Unity.Collections; + using Unity.Burst.Intrinsics; + using System.Diagnostics; + + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [BurstCompile] + public partial struct FollowerControlSystem : ISystem { + EntityQuery entityQueryControlManaged; + EntityQuery entityQueryControlManaged2; + RedrawScope redrawScope; + + static readonly ProfilerMarker MarkerMovementOverrideBeforeControl = new ProfilerMarker("MovementOverrideBeforeControl"); + static readonly ProfilerMarker MarkerMovementOverrideAfterControl = new ProfilerMarker("MovementOverrideAfterControl"); + + public void OnCreate (ref SystemState state) { + redrawScope = DrawingManager.GetRedrawScope(); + + entityQueryControlManaged = state.GetEntityQuery( + ComponentType.ReadWrite(), + + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + + ComponentType.Exclude(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + + entityQueryControlManaged2 = state.GetEntityQuery( + ComponentType.ReadWrite(), + + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + ComponentType.ReadWrite(), + + ComponentType.Exclude(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + } + + public void OnDestroy (ref SystemState state) { + redrawScope.Dispose(); + } + + public void OnUpdate (ref SystemState systemState) { + if (AstarPath.active == null) return; + + // Skip system if there are no ECS agents that need movement control + if (SystemAPI.QueryBuilder().WithAll().Build().IsEmptyIgnoreFilter) return; + + ProcessControlLoop(ref systemState, AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime); + } + + void ProcessControlLoop (ref SystemState systemState, float dt) { + // This is a hook for other systems to modify the movement of agents. + // Normally it is not used. + if (!entityQueryControlManaged.IsEmpty) { + MarkerMovementOverrideBeforeControl.Begin(); + systemState.Dependency.Complete(); + new JobManagedMovementOverrideBeforeControl { + dt = dt, + }.Run(entityQueryControlManaged); + MarkerMovementOverrideBeforeControl.End(); + } + + redrawScope.Rewind(); + var draw = DrawingManager.GetBuilder(redrawScope); + var navmeshEdgeData = AstarPath.active.GetNavmeshBorderData(out var readLock); + systemState.Dependency = new JobControl { + navmeshEdgeData = navmeshEdgeData, + draw = draw, + dt = dt, + }.ScheduleParallel(JobHandle.CombineDependencies(systemState.Dependency, readLock.dependency)); + readLock.UnlockAfter(systemState.Dependency); + draw.DisposeAfter(systemState.Dependency); + + if (!entityQueryControlManaged2.IsEmpty) { + MarkerMovementOverrideAfterControl.Begin(); + systemState.Dependency.Complete(); + new JobManagedMovementOverrideAfterControl { + dt = dt, + }.Run(entityQueryControlManaged2); + MarkerMovementOverrideAfterControl.End(); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs.meta new file mode 100644 index 0000000..653caa6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/FollowerControlSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce754b44fa448624dac9bbefe12d03e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs new file mode 100644 index 0000000..64bc00f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs @@ -0,0 +1,39 @@ +#if MODULE_ENTITIES +using Unity.Collections; +using Unity.Entities; + +namespace Pathfinding.ECS { + /// + /// Creates a ManagedState component for every entity with a ManagedSettings component. + /// + /// This ensures all baked FollowerEntity entities get a component when they are created. + /// + /// See: + /// See: + /// See: + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(MovementPlaneFromGraphSystem))] + [UpdateBefore(typeof(SchedulePathSearchSystem))] + [RequireMatchingQueriesForUpdate] + public partial struct InitManagedStateSystem : ISystem { + public void OnUpdate (ref SystemState state) { + var query = SystemAPI.QueryBuilder().WithAll().WithNone().Build(); + var entities = query.ToEntityArray(Allocator.Temp); + state.EntityManager.AddComponent(entities); + for (int i = 0; i < entities.Length; i++) { + state.EntityManager.SetComponentData(entities[i], new ManagedState { + pathTracer = new PathTracer(Allocator.Persistent), + }); + } + + for (int i = 0; i < entities.Length; i++) { + // This will attach the entity to the navmesh (if one exists), to make things like #currentNode and being snapped to the graph surface work immediately. + // Otherwise, we'd have to wait for the first path calculation to finish. + var proxy = new FollowerEntityProxy(state.World, entities[i]); + proxy.Teleport(proxy.position, false); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs.meta new file mode 100644 index 0000000..02dbcd2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/InitManagedStateSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c8ec842b8dff3d4bb43ca92d8eb65c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs new file mode 100644 index 0000000..45f742d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs @@ -0,0 +1,73 @@ +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; +using Unity.Jobs; +using Unity.Collections; +using UnityEngine.Profiling; +using Pathfinding.Util; +using UnityEngine.Jobs; + +namespace Pathfinding.ECS { + /// + /// System that runs after all movement systems and repairs the agent's path and syncs its orientation to the associated Transform component, if one exists. + /// + /// Repairing the path after running all the movement simulations is not strictly necessary, but it ensures that any other scripts querying properties like the remaining path, or if the agent has reached its destination, + /// will see up to date information immediately. + /// + /// If the was not in a subscene, we need to sync the internal entity's position and rotation to the Transform that the FollowerEntity component is attached to. + /// However, if it was placed in a subscene, the Transform and FollowerEntity component won't even exist at runtime (except when debugging in the unity editor), and so the sync can be skipped. + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateAfter(typeof(AIMoveSystem))] + [UpdateAfter(typeof(MovementStatisticsSystem))] + [RequireMatchingQueriesForUpdate] + public partial struct LateSyncSystem : ISystem { + JobRepairPath.Scheduler jobRepairPathScheduler; + EntityQuery entityQueryPrepareMovement; + + public void OnCreate (ref SystemState systemState) { + jobRepairPathScheduler = new JobRepairPath.Scheduler(ref systemState); + entityQueryPrepareMovement = jobRepairPathScheduler.GetEntityQuery(Allocator.Temp).WithAll().Build(ref systemState); + } + + public void OnDestroy (ref SystemState systemState) { + jobRepairPathScheduler.Dispose(); + } + + public void OnUpdate (ref SystemState systemState) { + systemState.Dependency = ScheduleRepairPaths(ref systemState, systemState.Dependency); + systemState.Dependency = ScheduleSyncEntitiesToTransforms(ref systemState, systemState.Dependency); + systemState.Dependency = new JobClearTemporaryData().Schedule(systemState.Dependency); + } + + JobHandle ScheduleRepairPaths (ref SystemState systemState, JobHandle dependency) { + Profiler.BeginSample("RepairPaths"); + // This job accesses graph data, but this is safe because the AIMovementSystemGroup + // holds a read lock on the graph data while its subsystems are running. + dependency = jobRepairPathScheduler.ScheduleParallel(ref systemState, entityQueryPrepareMovement, dependency); + Profiler.EndSample(); + return dependency; + } + + JobHandle ScheduleSyncEntitiesToTransforms (ref SystemState systemState, JobHandle dependency) { + Profiler.BeginSample("SyncEntitiesToTransforms"); + int numComponents = BatchedEvents.GetComponents(BatchedEvents.Event.None, out var transforms, out var components); + if (numComponents > 0) { + var entities = CollectionHelper.CreateNativeArray(numComponents, systemState.WorldUpdateAllocator); + for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity; + + dependency = new JobSyncEntitiesToTransforms { + entities = entities, + syncPositionWithTransform = SystemAPI.GetComponentLookup(true), + syncRotationWithTransform = SystemAPI.GetComponentLookup(true), + orientationYAxisForward = SystemAPI.GetComponentLookup(true), + entityPositions = SystemAPI.GetComponentLookup(true), + movementState = SystemAPI.GetComponentLookup(true), + }.Schedule(transforms, dependency); + } + Profiler.EndSample(); + return dependency; + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs.meta new file mode 100644 index 0000000..479036a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/LateSyncSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9687ec46c4e4dfb43b4f80d2a0d26a50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs new file mode 100644 index 0000000..d8b278d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs @@ -0,0 +1,247 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using Unity.Burst; +using Unity.Collections; + +namespace Pathfinding.ECS { + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Pathfinding; + using Pathfinding.Util; + using Pathfinding.Collections; + using Unity.Transforms; + using UnityEngine.Profiling; + + [UpdateBefore(typeof(SchedulePathSearchSystem))] // Only needs to run before TraverseOffMeshLinkSystem, but doing this leads to better scheduling + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [RequireMatchingQueriesForUpdate] + [BurstCompile] + public partial struct MovementPlaneFromGraphSystem : ISystem { + public EntityQuery entityQueryGraph; + public EntityQuery entityQueryNormal; + // Store the queue in a GCHandle to avoid restrictions on ISystem + GCHandle graphNodeQueue; + + public void OnCreate (ref SystemState state) { + entityQueryGraph = state.GetEntityQuery(ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadOnly()); + entityQueryGraph.SetSharedComponentFilter(new AgentMovementPlaneSource { value = MovementPlaneSource.Graph }); + entityQueryNormal = state.GetEntityQuery( + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + ComponentType.ReadWrite(), + ComponentType.ReadOnly(), + ComponentType.ReadOnly() + ); + entityQueryNormal.AddSharedComponentFilter(new AgentMovementPlaneSource { value = MovementPlaneSource.NavmeshNormal }); + + graphNodeQueue = GCHandle.Alloc(new List(32)); + } + + public void OnDestroy (ref SystemState state) { + graphNodeQueue.Free(); + } + + public void OnUpdate (ref SystemState systemState) { + var graphs = AstarPath.active?.data.graphs; + if (graphs == null) return; + + var movementPlanes = CollectionHelper.CreateNativeArray(graphs.Length, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < graphs.Length; i++) { + movementPlanes[i] = new AgentMovementPlane(MovementPlaneFromGraph(graphs[i])); + } + + if (!entityQueryNormal.IsEmpty) { + Profiler.BeginSample("MovementPlaneSource.NavmeshNormal"); + systemState.CompleteDependency(); + var vertices = new NativeList(16, Allocator.Temp); + new JobMovementPlaneFromNavmeshNormal { + dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime, + vertices = vertices, + que = (List)graphNodeQueue.Target, + }.Run(entityQueryNormal); + Profiler.EndSample(); + } + + systemState.Dependency = new JobMovementPlaneFromGraph { + movementPlanes = movementPlanes, + }.Schedule(entityQueryGraph, systemState.Dependency); + } + + /// + /// Natural movement plane for a graph traversing a given graph. + /// + /// This is the movement plane used for .Graph. + /// + /// See: + /// + public static NativeMovementPlane MovementPlaneFromGraph (NavGraph graph) { + if (graph is NavmeshBase navmesh) { + return new NativeMovementPlane(navmesh.transform.rotation); + } else if (graph is GridGraph grid) { + return new NativeMovementPlane(grid.transform.rotation); + } else { + return new NativeMovementPlane(quaternion.identity); + } + } + + partial struct JobMovementPlaneFromNavmeshNormal : IJobEntity { + public float dt; + public NativeList vertices; + public List que; + + public void Execute (ManagedState managedState, in LocalTransform localTransform, ref AgentMovementPlane agentMovementPlane, in AgentCylinderShape shape) { + var node = managedState.pathTracer.startNode as TriangleMeshNode; + if (node != null) { + // TODO: Expose this parameter? + const float InverseSmoothness = 20f; + var radius = math.max(0.01f, shape.radius); + SampleSmoothNavmeshNormal(node, que, vertices, localTransform.Position, radius, ref agentMovementPlane, dt * InverseSmoothness); + } + } + } + + [BurstCompile] + partial struct JobMovementPlaneFromGraph : IJobEntity { + [ReadOnly] + public NativeArray movementPlanes; + + public void Execute (in MovementState movementState, ref AgentMovementPlane movementPlane) { + if (movementState.graphIndex < (uint)movementPlanes.Length) { + movementPlane = movementPlanes[(int)movementState.graphIndex]; + } else { + // This can happen if the agent has no path, or if the path is stale. + // Potentially also if a graph has been removed. + } + } + } + + public static void SampleSmoothNavmeshNormal (TriangleMeshNode node, List scratchList, NativeList scratchBuffer, float3 position, float agentRadius, ref AgentMovementPlane agentMovementPlane, float alpha) { + var vertices = scratchBuffer; + var que = scratchList; + vertices.Clear(); + que.Clear(); + int queStart = 0; + node.TemporaryFlag1 = true; + que.Add(node); + var i0 = node.v0; + var i1 = node.v1; + var i2 = node.v2; + + while (queStart < que.Count) { + var current = que[queStart++] as TriangleMeshNode; + if (current == null) continue; + var anyVertex = current.v0 == i0 | current.v1 == i0 | current.v2 == i0 | current.v0 == i1 | current.v1 == i1 | current.v2 == i1 | current.v0 == i2 | current.v1 == i2 | current.v2 == i2; + if (anyVertex) { + current.GetVertices(out var v0, out var v1, out var v2); + vertices.Add(v0); + vertices.Add(v1); + vertices.Add(v2); + current.GetConnections((GraphNode con, ref List que) => { + if (!con.TemporaryFlag1) { + con.TemporaryFlag1 = true; + que.Add(con); + } + }, ref que); + } + } + + // Reset temporary flags + for (int i = 0; i < que.Count; i++) { + que[i].TemporaryFlag1 = false; + } + + var verticesSpan = vertices.AsUnsafeSpan(); + SampleSmoothTriangleNormal(ref position, ref verticesSpan, ref agentMovementPlane, agentRadius, alpha); + } + + static float Square (float x) { + return x * x; + } + + /// Sine of the angle ABC + static float SinAngle (float3 a, float3 b, float3 c) { + return math.sqrt(1 - Square(math.dot(math.normalizesafe(a - b), math.normalizesafe(c - b)))); + } + + [BurstCompile(FloatMode = FloatMode.Fast)] + static void SampleSmoothTriangleNormal (ref float3 position, ref UnsafeSpan _triangleVertices, ref AgentMovementPlane agentMovementPlane, float agentRadius, float alpha) { + var triangleVertices = _triangleVertices.Reinterpret(); + if (triangleVertices.Length < 3) throw new System.ArgumentException("triangleVertices.Length < 3"); + unsafe { + // First 3 vertices represent the triangle we start on + var sourceVertices = triangleVertices.ptr; + var normals = stackalloc float3[3]; + var weights = stackalloc float[3]; + normals[0] = normals[1] = normals[2] = float3.zero; + weights[0] = weights[1] = weights[2] = 0; + var currentNormal = agentMovementPlane.value.up; + + for (uint i = 0; i < triangleVertices.length; i += 3) { + var p0 = triangleVertices[i + 0]; + var p1 = triangleVertices[i + 1]; + var p2 = triangleVertices[i + 2]; + var f0 = (float3)p0 * Int3.PrecisionFactor; + var f1 = (float3)p1 * Int3.PrecisionFactor; + var f2 = (float3)p2 * Int3.PrecisionFactor; + + var triangleNormal = math.normalizesafe(math.cross(f1 - f0, f2 - f0)); + + const float COS_SMOOTH_ANGLE_LIMIT = 0.86f; + float weight = 1; + var cosAngle = math.dot(triangleNormal, currentNormal); + if (cosAngle < COS_SMOOTH_ANGLE_LIMIT) { + // Hard angle. Lower the weight of this triangle to avoid starting to rotate too early. + Polygon.ClosestPointOnTriangleByRef(in f0, in f1, in f2, in position, out var closest); + var distance = math.lengthsq(closest - position) / Square(1.5f * agentRadius); + var distanceWeight = math.max(0.1f, 1 - distance); + var angleWeight = (COS_SMOOTH_ANGLE_LIMIT - math.max(0, cosAngle)) / COS_SMOOTH_ANGLE_LIMIT; + weight = math.lerp(1, distanceWeight, angleWeight); + } + + for (int j = 0; j < 3; j++) { + if (math.all(p0 == sourceVertices[j])) { + // When calculating smooth normals, we ideally want to weigh the contributions from + // differnt triangles by the angle of the triangle at the vertex. + // We use the sine of that angle instead, which is a decent approximation. + var w = weight * SinAngle(p2, p0, p1); + weights[j] += w; + normals[j] += w * triangleNormal; + } + } + + for (int j = 0; j < 3; j++) { + if (math.all(p1 == sourceVertices[j])) { + var w = weight * SinAngle(p0, p1, p2); + weights[j] += w; + normals[j] += w * triangleNormal; + } + } + + for (int j = 0; j < 3; j++) { + if (math.all(p2 == sourceVertices[j])) { + var w = weight * SinAngle(p1, p2, p0); + weights[j] += w; + normals[j] += w * triangleNormal; + } + } + } + + for (int j = 0; j < 3; j++) { + if (weights[j] > 0) normals[j] /= weights[j]; + } + + var v0 = (float3)sourceVertices[0] * Int3.PrecisionFactor; + var v1 = (float3)sourceVertices[1] * Int3.PrecisionFactor; + var v2 = (float3)sourceVertices[2] * Int3.PrecisionFactor; + var barycentric = Polygon.ClosestPointOnTriangleBarycentric(v0, v1, v2, position); + var targetNormal = math.normalizesafe(normals[0] * barycentric.x + normals[1] * barycentric.y + normals[2] * barycentric.z); + + var nextNormal = math.lerp(currentNormal, targetNormal, math.clamp(alpha, 0, 1)); + agentMovementPlane.value = agentMovementPlane.value.MatchUpDirection(nextNormal); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta new file mode 100644 index 0000000..f8563f3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e03b8b6eb150263419cf52d753bc4bc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs new file mode 100644 index 0000000..36fa267 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs @@ -0,0 +1,33 @@ +#if MODULE_ENTITIES +using Unity.Burst; +using Unity.Entities; +using Unity.Mathematics; +using Unity.Transforms; +using Unity.Jobs; +using Unity.Collections; + +namespace Pathfinding.ECS { + /// + /// System that updates the destination of agents with a component. + /// + /// Every frame, the position of the target entity will be copied to the field of the agent. + /// If is true, the facing direction will also be copied. + /// + /// See: + /// + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateAfter(typeof(AIMoveSystem))] + [RequireMatchingQueriesForUpdate] + public partial struct MovementStatisticsSystem : ISystem { + public void OnUpdate (ref SystemState state) { + // TODO: Can be bursted if we provide the cheap delta time via some burst-readable field + state.Dependency = new JobUpdateMovementStatistics { + // This system is executed at least every frame to make sure the agent is moving smoothly even at high fps. + // The control loop and local avoidance may be running less often. + // So this is designated a "cheap" system, and we use the corresponding delta time for that. + dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime + }.Schedule(state.Dependency); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs.meta new file mode 100644 index 0000000..0403c9a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/MovementStatisticsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3be1969525bacbf4eb87783ff13617dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs new file mode 100644 index 0000000..35ea267 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs @@ -0,0 +1,84 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Burst; +using GCHandle = System.Runtime.InteropServices.GCHandle; +using Unity.Transforms; + +namespace Pathfinding.ECS { + /// + /// Checks if paths have been calculated, and updates the agent's paths if they have. + /// + /// This is essentially a replacement for for ECS agents. + /// + /// This system is a bit different in that it doesn't run in the normal update loop, + /// but instead it will run when the event fires. + /// This is to avoid having to call a separate callback for every agent, since that + /// would result in excessive overhead as it would have to synchronize with the ECS world + /// on every such call. + /// + /// See: + /// + [BurstCompile] + public partial struct PollPendingPathsSystem : ISystem { + GCHandle onPathsCalculated; + static bool anyPendingPaths; + + JobRepairPath.Scheduler jobRepairPathScheduler; + EntityQuery entityQueryPrepare; + + public void OnCreate (ref SystemState state) { + jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state) { + onlyApplyPendingPaths = true, + }; + entityQueryPrepare = jobRepairPathScheduler.GetEntityQuery(Unity.Collections.Allocator.Temp).Build(ref state); + + var world = state.WorldUnmanaged; + System.Action onPathsCalculated = () => { + // Allow the system to run + anyPendingPaths = true; + try { + // Update the system manually + world.GetExistingUnmanagedSystem().Update(world); + } finally { + anyPendingPaths = false; + } + }; + AstarPath.OnPathsCalculated += onPathsCalculated; + // Store the callback in a GCHandle to get around limitations on unmanaged systems. + this.onPathsCalculated = GCHandle.Alloc(onPathsCalculated); + } + + public void OnDestroy (ref SystemState state) { + AstarPath.OnPathsCalculated -= (System.Action)onPathsCalculated.Target; + onPathsCalculated.Free(); + jobRepairPathScheduler.Dispose(); + } + + void OnUpdate (ref SystemState systemState) { + // Only run the system when we have triggered it manually + if (!anyPendingPaths) return; + + // During an off-mesh link traversal, we shouldn't calculate any paths, because it's somewhat undefined where they should start. + // Paths are already cancelled when the off-mesh link traversal starts, but just in case it has been started by a user manually in some way, we also cancel them every frame. + foreach (var state in SystemAPI.Query().WithAll()) state.CancelCurrentPathRequest(); + + // The JobRepairPath may access graph data, so we need to lock it for reading. + // Otherwise a graph update could start while the job was running, which could cause all kinds of problems. + var readLock = AstarPath.active.LockGraphDataForReading(); + + // Iterate over all agents and check if they have any pending paths, and if they have been calculated. + // If they have, we update the agent's current path to the newly calculated one. + // + // We do this by running the JobRepairPath for all agents that have just had their path calculated. + // This ensures that all properties like remainingDistance are up to date immediately after + // a path recalculation. + // This may seem wasteful, but during the next update, the regular JobRepairPath job + // will most likely be able to early out, because we did most of the work here. + systemState.Dependency = jobRepairPathScheduler.ScheduleParallel(ref systemState, entityQueryPrepare, systemState.Dependency); + + readLock.UnlockAfter(systemState.Dependency); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs.meta new file mode 100644 index 0000000..6f6c2c7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/PollPendingPathsSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12ab30eb86c3d4841b72f49aa252574c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs new file mode 100644 index 0000000..2e61af1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs @@ -0,0 +1,269 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Mathematics; +using Unity.Burst; +using Unity.Entities; +using Unity.Transforms; +using Unity.Collections; +using GCHandle = System.Runtime.InteropServices.GCHandle; + +namespace Pathfinding.ECS.RVO { + using Pathfinding.RVO; + using Unity.Jobs; + + /// + /// Simulates local avoidance in an ECS context. + /// + /// All agent entities must have the following ECS components: + /// - LocalTransform + /// - + /// - + /// - + /// - : where you store how you want the agent to move + /// - : where this system will output how the agent should move, when using RVO + /// + /// The system will use the data from , and output the following fields to : + /// + /// : Where the agent should move to. + /// : At what speed the agent should move, in world units. + /// : This will go up if its more crowded, to indicate that the agent should try to take wider turns to improve crowd flow. + /// + /// The component will be added to the agent automatically by this system. You do not need to care about it. + /// + [BurstCompile] + [UpdateAfter(typeof(FollowerControlSystem))] + [UpdateInGroup(typeof(AIMovementSystemGroup))] + public partial struct RVOSystem : ISystem { + /// + /// Keeps track of the last simulator that this RVOSystem saw. + /// This is a weak GCHandle to allow it to be stored in an ISystem. + /// + GCHandle lastSimulator; + ComponentLookup agentOffMeshLinkTraversalLookup; + + public void OnCreate (ref SystemState state) { + lastSimulator = GCHandle.Alloc(null, System.Runtime.InteropServices.GCHandleType.Weak); + agentOffMeshLinkTraversalLookup = state.GetComponentLookup(true); + } + + public void OnDestroy (ref SystemState state) { + lastSimulator.Free(); + } + + public void OnUpdate (ref SystemState systemState) { + var simulator = RVOSimulator.active?.GetSimulator(); + + if (simulator != lastSimulator.Target) { + // If the simulator has been destroyed, we need to remove all AgentIndex components + RemoveAllAgentsFromSimulation(ref systemState); + lastSimulator.Target = simulator; + } + if (simulator == null) return; + + AddAndRemoveAgentsFromSimulation(ref systemState, simulator); + + // The full movement calculations do not necessarily need to be done every frame if the fps is high + if (AIMovementSystemGroup.TimeScaledRateManager.CheapSimulationOnly) { + return; + } + + CopyFromEntitiesToRVOSimulator(ref systemState, simulator, SystemAPI.Time.DeltaTime); + + // Schedule RVO update + simulator.Update( + systemState.Dependency, + SystemAPI.Time.DeltaTime, + AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep, + systemState.WorldUpdateAllocator + ); + + CopyFromRVOSimulatorToEntities(ref systemState, simulator); + } + + void RemoveAllAgentsFromSimulation (ref SystemState systemState) { + var buffer = new EntityCommandBuffer(Allocator.Temp); + var entities = SystemAPI.QueryBuilder().WithAllRW().Build().ToEntityArray(systemState.WorldUpdateAllocator); + buffer.RemoveComponent(entities); + buffer.Playback(systemState.EntityManager); + buffer.Dispose(); + } + + void AddAndRemoveAgentsFromSimulation (ref SystemState systemState, SimulatorBurst simulator) { + var shouldBeRemovedFromSimulation = SystemAPI.QueryBuilder() + .WithAll() + .WithNone() + .WithOptions(EntityQueryOptions.IncludeDisabledEntities) + .Build(); + + var shouldBeRemovedFromSimulation2 = SystemAPI.QueryBuilder() + .WithAll() + .WithOptions(EntityQueryOptions.IncludeDisabledEntities) + .Build(); + + var shouldBeAddedToSimulation = SystemAPI.QueryBuilder() + .WithAll() + .WithNone() + .Build(); + + // Remove all agents from the simulation that do not have an RVOAgent component, but have an AgentIndex + var indicesToRemove = shouldBeRemovedFromSimulation.ToComponentDataArray(systemState.WorldUpdateAllocator); + var indicesToRemove2 = shouldBeRemovedFromSimulation2.ToComponentDataArray(systemState.WorldUpdateAllocator); + // Add all agents to the simulation that have an RVOAgent component, but not AgentIndex component + var entitiesToAdd = shouldBeAddedToSimulation.ToEntityArray(systemState.WorldUpdateAllocator); + // Avoid a sync point in the common case + if (indicesToRemove.Length > 0 || indicesToRemove2.Length > 0 || entitiesToAdd.Length > 0) { + var buffer = new EntityCommandBuffer(Allocator.Temp); +#if MODULE_ENTITIES_1_0_8_OR_NEWER + buffer.RemoveComponent(shouldBeRemovedFromSimulation, EntityQueryCaptureMode.AtPlayback); + buffer.RemoveComponent(shouldBeRemovedFromSimulation2, EntityQueryCaptureMode.AtPlayback); +#else + buffer.RemoveComponent(shouldBeRemovedFromSimulation); + buffer.RemoveComponent(shouldBeRemovedFromSimulation2); +#endif + for (int i = 0; i < indicesToRemove.Length; i++) { + simulator.RemoveAgent(indicesToRemove[i]); + } + for (int i = 0; i < indicesToRemove2.Length; i++) { + // Note: In very rare cases, we might have already removed the agent in the first loop. + simulator.RemoveAgent(indicesToRemove2[i], true); + } + for (int i = 0; i < entitiesToAdd.Length; i++) { + buffer.AddComponent(entitiesToAdd[i], simulator.AddAgentBurst(UnityEngine.Vector3.zero)); + } + + buffer.Playback(systemState.EntityManager); + buffer.Dispose(); + } + } + + void CopyFromEntitiesToRVOSimulator (ref SystemState systemState, SimulatorBurst simulator, float dt) { + agentOffMeshLinkTraversalLookup.Update(ref systemState); + var writeLock = simulator.LockSimulationDataReadWrite(); + systemState.Dependency = new JobCopyFromEntitiesToRVOSimulator { + agentData = simulator.simulationData, + agentOutputData = simulator.outputData, + movementPlaneMode = simulator.movementPlane, + agentOffMeshLinkTraversalLookup = agentOffMeshLinkTraversalLookup, + dt = dt, + }.ScheduleParallel(JobHandle.CombineDependencies(writeLock.dependency, systemState.Dependency)); + writeLock.UnlockAfter(systemState.Dependency); + } + + void CopyFromRVOSimulatorToEntities (ref SystemState systemState, SimulatorBurst simulator) { + var writeLock = simulator.LockSimulationDataReadWrite(); + systemState.Dependency = new JobCopyFromRVOSimulatorToEntities { + quadtree = simulator.quadtree, + agentDataVersions = simulator.simulationData.version, + agentOutputData = simulator.outputData, + }.ScheduleParallel(JobHandle.CombineDependencies(writeLock.dependency, systemState.Dependency)); + writeLock.UnlockAfter(systemState.Dependency); + } + + [BurstCompile] + public partial struct JobCopyFromEntitiesToRVOSimulator : IJobEntity { + [NativeDisableParallelForRestriction] + public SimulatorBurst.AgentData agentData; + [ReadOnly] + public SimulatorBurst.AgentOutputData agentOutputData; + public MovementPlane movementPlaneMode; + [ReadOnly] + public ComponentLookup agentOffMeshLinkTraversalLookup; + public float dt; + + public void Execute (Entity entity, in LocalTransform transform, in AgentCylinderShape shape, in AgentMovementPlane movementPlane, in AgentIndex agentIndex, in RVOAgent controller, in MovementControl target) { + var scale = math.abs(transform.Scale); + if (!agentIndex.TryGetIndex(ref agentData, out var index)) throw new System.InvalidOperationException("RVOAgent has an invalid entity index"); + + // Actual infinity is not handled well by some algorithms, but very large values are ok. + // This should be larger than any reasonable value a user might want to use. + const float VERY_LARGE = 100000; + + // Copy all fields to the rvo simulator, and clamp them to reasonable values + agentData.radius[index] = math.clamp(shape.radius * scale, 0.001f, VERY_LARGE); + agentData.agentTimeHorizon[index] = math.clamp(controller.agentTimeHorizon, 0, VERY_LARGE); + agentData.obstacleTimeHorizon[index] = math.clamp(controller.obstacleTimeHorizon, 0, VERY_LARGE); + agentData.locked[index] = controller.locked; + agentData.maxNeighbours[index] = math.max(controller.maxNeighbours, 0); + agentData.debugFlags[index] = controller.debug; + agentData.layer[index] = controller.layer; + agentData.collidesWith[index] = controller.collidesWith; + agentData.targetPoint[index] = target.targetPoint; + agentData.desiredSpeed[index] = math.clamp(target.speed, 0, VERY_LARGE); + agentData.maxSpeed[index] = math.clamp(target.maxSpeed, 0, VERY_LARGE); + agentData.manuallyControlled[index] = target.overrideLocalAvoidance; + agentData.endOfPath[index] = target.endOfPath; + agentData.hierarchicalNodeIndex[index] = target.hierarchicalNodeIndex; + agentData.movementPlane[index] = movementPlane.value; + + // Use the position from the movement script if one is attached + // as the movement script's position may not be the same as the transform's position + // (in particular if IAstarAI.updatePosition is false). + var pos = movementPlane.value.ToPlane(transform.Position, out float elevation); + if (movementPlaneMode == MovementPlane.XY) { + // In 2D it is assumed the Z coordinate differences of agents is ignored. + agentData.height[index] = 1; + agentData.position[index] = movementPlane.value.ToWorld(pos, 0); + } else { + var center = 0.5f * shape.height; + agentData.height[index] = math.clamp(shape.height * scale, 0, VERY_LARGE); + agentData.position[index] = movementPlane.value.ToWorld(pos, elevation + (center - 0.5f * shape.height) * scale); + } + + + // TODO: Move this to a separate file + var reached = agentOutputData.effectivelyReachedDestination[index]; + var prio = math.clamp(controller.priority * controller.priorityMultiplier, 0, VERY_LARGE); + var flow = math.clamp(controller.flowFollowingStrength, 0, 1); + // TODO: This is gettting overriden every frame, right? + if (reached == ReachedEndOfPath.Reached) { + // Override flow following strength and make it go towards 1 + flow = math.lerp(agentData.flowFollowingStrength[index], 1.0f, 6.0f * dt); + prio *= 0.3f; + } else if (reached == ReachedEndOfPath.ReachedSoon) { + // Override flow following strength and make it go towards 1 + flow = math.lerp(agentData.flowFollowingStrength[index], 1.0f, 6.0f * dt); + prio *= 0.45f; + } + agentData.priority[index] = prio; + agentData.flowFollowingStrength[index] = flow; + + if (agentOffMeshLinkTraversalLookup.HasComponent(entity)) { + // Agents traversing off-mesh links should not avoid other agents, + // but other agents may still avoid them. + agentData.manuallyControlled[index] = true; + } + } + } + + [BurstCompile] + public partial struct JobCopyFromRVOSimulatorToEntities : IJobEntity { + [ReadOnly] + public NativeArray agentDataVersions; + [ReadOnly] + public RVOQuadtreeBurst quadtree; + [ReadOnly] + public SimulatorBurst.AgentOutputData agentOutputData; + + /// See https://en.wikipedia.org/wiki/Circle_packing + const float MaximumCirclePackingDensity = 0.9069f; + + public void Execute (in LocalTransform transform, in AgentCylinderShape shape, in AgentIndex agentIndex, in RVOAgent controller, in MovementControl control, ref ResolvedMovement resolved) { + if (!agentIndex.TryGetIndex(ref agentDataVersions, out var index)) return; + + var scale = math.abs(transform.Scale); + var r = shape.radius * scale * 3f; + var area = quadtree.QueryArea(transform.Position, r); + + // Calculate the agent density in a circle around the agent and compare it to optimal circle packing + // This should be between 0 and 1, but if agents are overlapping it can be larger than 1, which is why we clamp it. + var density = math.min(1.0f, area / (MaximumCirclePackingDensity * math.PI * r * r)); + + resolved.targetPoint = agentOutputData.targetPoint[index]; + resolved.speed = agentOutputData.speed[index]; + var rnd = 1.0f; // (agentIndex.Index % 1024) / 1024f; + resolved.turningRadiusMultiplier = math.max(1f, math.pow(density * 2.0f, 4.0f) * rnd); + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs.meta new file mode 100644 index 0000000..a291705 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RVOSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ab994574a30005439b0db78c01279f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs new file mode 100644 index 0000000..3289751 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs @@ -0,0 +1,49 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Burst; + +namespace Pathfinding.ECS { + using Pathfinding; + + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(FollowerControlSystem))] + [BurstCompile] + public partial struct RepairPathSystem : ISystem { + EntityQuery entityQueryPrepare; + JobRepairPath.Scheduler jobRepairPathScheduler; + + public void OnCreate (ref SystemState state) { + jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state); + entityQueryPrepare = jobRepairPathScheduler.GetEntityQuery(Unity.Collections.Allocator.Temp).WithAll().Build(ref state); + } + + public void OnDestroy (ref SystemState state) { + jobRepairPathScheduler.Dispose(); + } + + public void OnUpdate (ref SystemState systemState) { + if (AstarPath.active == null) return; + + // Skip system if there are no ECS agents that need path repair + if (SystemAPI.QueryBuilder().WithAll().Build().IsEmptyIgnoreFilter) return; + + // This job accesses managed component data in a somewhat unsafe way. + // It should be safe to run it in parallel with other systems, but I'm not 100% sure. + // This job also accesses graph data, but this is safe because the AIMovementSystemGroup + // holds a read lock on the graph data while its subsystems are running. + systemState.Dependency = jobRepairPathScheduler.ScheduleParallel(ref systemState, entityQueryPrepare, systemState.Dependency); + } + + [System.Obsolete("Use TraverseOffMeshLinkSystem.NextLinkToTraverse instead")] + public static OffMeshLinks.OffMeshLinkTracer NextLinkToTraverse (ManagedState state) { + return TraverseOffMeshLinkSystem.NextLinkToTraverse(state); + } + + [System.Obsolete("Use TraverseOffMeshLinkSystem.ResolveOffMeshLinkHandler instead")] + public static IOffMeshLinkHandler ResolveOffMeshLinkHandler (ManagedSettings settings, AgentOffMeshLinkTraversalContext ctx) { + return TraverseOffMeshLinkSystem.ResolveOffMeshLinkHandler(settings, ctx); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs.meta new file mode 100644 index 0000000..e356fed --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/RepairPathSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b48a3bd9c8f676b4bbb586cb85b4acaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs new file mode 100644 index 0000000..5bb2660 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs @@ -0,0 +1,137 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Transforms; +using Unity.Burst; + +namespace Pathfinding.ECS { + using Pathfinding; + using Unity.Burst.Intrinsics; + using Unity.Collections; + using Unity.Mathematics; + using Unity.Profiling; + using UnityEngine; + + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(RepairPathSystem))] + [UpdateBefore(typeof(TraverseOffMeshLinkSystem))] + [BurstCompile] + public partial struct SchedulePathSearchSystem : ISystem { + static readonly ProfilerMarker MarkerSchedulePathSearch = new ProfilerMarker("Schedule Path Search"); + static readonly ProfilerMarker MarkerCheckStaleness = new ProfilerMarker("Check Path Staleness"); + static readonly ProfilerMarker MarkerShouldRecalculatePaths = new ProfilerMarker("Check Should Recalculate Paths"); + static readonly ProfilerMarker MarkerRecalculatePaths = new ProfilerMarker("Schedule Path Calculations"); + + public void OnUpdate (ref SystemState systemState) { + // While the agent can technically discover that the path is stale during a simulation step, + // only scheduling paths during the first substep is typically good enough. + if (AstarPath.active == null || !AIMovementSystemGroup.TimeScaledRateManager.IsFirstSubstep) return; + + // Skip system if there are no ECS agents that use pathfinding + if (SystemAPI.QueryBuilder().WithAll().Build().IsEmptyIgnoreFilter) return; + + MarkerSchedulePathSearch.Begin(); + var bits = new NativeBitArray(512, Allocator.TempJob); + systemState.CompleteDependency(); + + // Block the pathfinding threads from starting new path calculations while this loop is running. + // This is done to reduce lock contention and significantly improve performance. + // If we did not do this, all pathfinding threads would immediately wake up when a path was pushed to the queue. + // Immediately when they wake up they will try to acquire a lock on the path queue. + // If we are scheduling a lot of paths, this causes significant contention, and can make this loop take 100 times + // longer to complete, compared to if we block the pathfinding threads. + // TODO: Switch to a lock-free queue to avoid this issue altogether. + var pathfindingLock = AstarPath.active.PausePathfindingSoon(); + + // Propagate staleness + MarkerCheckStaleness.Begin(); + new JobCheckStaleness { + isPathStale = bits, + }.Run(); + MarkerCheckStaleness.End(); + + MarkerShouldRecalculatePaths.Begin(); + // Calculate which agents want to recalculate their path (using burst) + new JobShouldRecalculatePaths { + time = (float)SystemAPI.Time.ElapsedTime, + isPathStale = bits, + }.Run(); + MarkerShouldRecalculatePaths.End(); + + MarkerRecalculatePaths.Begin(); + // Schedule the path calculations + new JobRecalculatePaths { + time = (float)SystemAPI.Time.ElapsedTime, + }.Run(); + MarkerRecalculatePaths.End(); + + pathfindingLock.Release(); + bits.Dispose(); + MarkerSchedulePathSearch.End(); + } + + [WithAbsent(typeof(ManagedAgentOffMeshLinkTraversal))] // Do not recalculate the path of agents that are currently traversing an off-mesh link. + [WithPresent(typeof(AgentShouldRecalculatePath))] + partial struct JobCheckStaleness : IJobEntity, IJobEntityChunkBeginEnd { + public NativeBitArray isPathStale; + int index; + + public void Execute (ManagedState state) { + isPathStale.Set(index++, state.pathTracer.isStale); + isPathStale.Set(index++, state.pendingPath != null); + } + + public bool OnChunkBegin (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { + if (index + chunk.Count*2 > isPathStale.Length) isPathStale.Resize(math.ceilpow2(index + chunk.Count*2), NativeArrayOptions.ClearMemory); + return true; + } + + public void OnChunkEnd (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted) {} + } + + + [BurstCompile] + [WithAbsent(typeof(ManagedAgentOffMeshLinkTraversal))] // Do not recalculate the path of agents that are currently traversing an off-mesh link. + [WithPresent(typeof(AgentShouldRecalculatePath))] + partial struct JobShouldRecalculatePaths : IJobEntity { + public float time; + public NativeBitArray isPathStale; + int index; + + public void Execute (ref ECS.AutoRepathPolicy autoRepathPolicy, in LocalTransform transform, in AgentCylinderShape shape, in DestinationPoint destination, EnabledRefRW shouldRecalculatePath) { + var isPathStale = this.isPathStale.IsSet(index++); + // If a path is pending, we always want to run JobRecalculatePaths for that agent, to refresh the path endpoints. + var isPathPending = this.isPathStale.IsSet(index++); + shouldRecalculatePath.ValueRW = isPathPending || autoRepathPolicy.ShouldRecalculatePath(transform.Position, shape.radius, destination.destination, time, isPathStale); + } + } + + [WithAbsent(typeof(ManagedAgentOffMeshLinkTraversal))] // Do not recalculate the path of agents that are currently traversing an off-mesh link. + [WithAll(typeof(AgentShouldRecalculatePath))] + public partial struct JobRecalculatePaths : IJobEntity { + public float time; + + public void Execute (ManagedState state, ManagedSettings settings, ref ECS.AutoRepathPolicy autoRepathPolicy, ref LocalTransform transform, ref DestinationPoint destination, ref AgentMovementPlane movementPlane) { + // If we reach this point, the agent always wants to recalculate its path, because the AgentShouldRecalculatePath component is enabled + MaybeRecalculatePath(state, settings, ref autoRepathPolicy, ref transform, ref destination, ref movementPlane, time, true); + } + + public static void MaybeRecalculatePath (ManagedState state, ManagedSettings settings, ref ECS.AutoRepathPolicy autoRepathPolicy, ref LocalTransform transform, ref DestinationPoint destination, ref AgentMovementPlane movementPlane, float time, bool wantsToRecalculatePath) { + if (wantsToRecalculatePath) { + if (state.pendingPath == null) { + var path = ABPath.Construct(transform.Position, destination.destination, null); + path.UseSettings(settings.pathfindingSettings); + path.nearestNodeDistanceMetric = DistanceMetric.ClosestAsSeenFromAboveSoft(movementPlane.value.up); + ManagedState.SetPath(path, state, in movementPlane, ref destination); + autoRepathPolicy.OnScheduledPathRecalculation(destination.destination, time); + } else if (state.pendingPath is ABPath aBPath) { + // Refresh the endpoints of the pending path. + // This is useful if the agent has moved significantly since the path was requested. + aBPath.RefreshPathEndpoints(transform.Position, destination.destination); + } + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs.meta new file mode 100644 index 0000000..68e8ab5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SchedulePathSearchSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d0be3a2ed445d9bd79af7e31e0e477f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs new file mode 100644 index 0000000..b85a8a9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs @@ -0,0 +1,30 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using UnityEngine; + +namespace Pathfinding.ECS { + using Pathfinding; + + [UpdateBefore(typeof(SchedulePathSearchSystem))] + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [RequireMatchingQueriesForUpdate] + public partial struct SyncDestinationTransformSystem : ISystem { + public void OnUpdate (ref SystemState systemState) { + // If there will be multiple simulation steps during this frame, only update the destination points on the first step. + // It cannot change between simulation steps anyway. + if (!AIMovementSystemGroup.TimeScaledRateManager.IsFirstSubstep) return; + + foreach (var(point, destinationSetterWrapper) in SystemAPI.Query, SystemAPI.ManagedAPI.UnityEngineComponent >()) { + var destinationSetter = destinationSetterWrapper.Value; + if (destinationSetter.target != null) { + point.ValueRW = new DestinationPoint { + destination = destinationSetter.target.position, + facingDirection = destinationSetter.useRotation ? destinationSetter.target.forward : Vector3.zero + }; + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta new file mode 100644 index 0000000..274915b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6ec5674da0fa5043bc982e1a4afed11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs new file mode 100644 index 0000000..c48d8ff --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs @@ -0,0 +1,88 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using Unity.Mathematics; +using UnityEngine.Profiling; +using Unity.Transforms; +using Unity.Burst; +using Unity.Jobs; +using Unity.Collections; +using UnityEngine.Jobs; + +namespace Pathfinding.ECS { + using Pathfinding; + using Pathfinding.Util; + + [UpdateBefore(typeof(TransformSystemGroup))] + [UpdateBefore(typeof(AIMovementSystemGroup))] + [UpdateInGroup(typeof(SimulationSystemGroup))] + public partial struct SyncTransformsToEntitiesSystem : ISystem { + public static readonly quaternion ZAxisForwardToYAxisForward = quaternion.Euler(math.PI / 2, 0, 0); + public static readonly quaternion YAxisForwardToZAxisForward = quaternion.Euler(-math.PI / 2, 0, 0); + + public void OnUpdate (ref SystemState systemState) { + int numComponents = BatchedEvents.GetComponents(BatchedEvents.Event.None, out var transforms, out var components); + if (numComponents > 0) { + var entities = new NativeArray(numComponents, Allocator.TempJob); + + for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity; + + systemState.Dependency = new SyncTransformsToEntitiesJob { + entities = entities, + entityPositions = SystemAPI.GetComponentLookup(), + syncPositionWithTransform = SystemAPI.GetComponentLookup(true), + syncRotationWithTransform = SystemAPI.GetComponentLookup(true), + orientationYAxisForward = SystemAPI.GetComponentLookup(true), + movementState = SystemAPI.GetComponentLookup(true), + }.Schedule(transforms, systemState.Dependency); + } + } + + [BurstCompile] + struct SyncTransformsToEntitiesJob : IJobParallelForTransform { + [ReadOnly] + [DeallocateOnJobCompletion] + public NativeArray entities; + + // Safety: All entities are unique + [NativeDisableParallelForRestriction] + public ComponentLookup entityPositions; + [ReadOnly] + public ComponentLookup syncPositionWithTransform; + [ReadOnly] + public ComponentLookup syncRotationWithTransform; + [ReadOnly] + public ComponentLookup orientationYAxisForward; + [ReadOnly] + public ComponentLookup movementState; + + public void Execute (int index, TransformAccess transform) { + var entity = entities[index]; + if (entityPositions.HasComponent(entity)) { +#if MODULE_ENTITIES_1_0_8_OR_NEWER + ref var tr = ref entityPositions.GetRefRW(entity).ValueRW; +#else + ref var tr = ref entityPositions.GetRefRW(entity, false).ValueRW; +#endif + + float3 offset = float3.zero; + if (movementState.TryGetComponent(entity, out var ms)) { + offset = ms.positionOffset; + } + + if (syncPositionWithTransform.HasComponent(entity)) tr.Position = (float3)transform.position - offset; + if (syncRotationWithTransform.HasComponent(entity)) { + if (orientationYAxisForward.HasComponent(entity)) { + tr.Rotation = math.mul(transform.rotation, YAxisForwardToZAxisForward); + } else { + // Z axis forward + tr.Rotation = transform.rotation; + } + } + tr.Scale = transform.localScale.y; + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta new file mode 100644 index 0000000..70b3391 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea4380d40e1bfa745a1ddb6638ed46b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs new file mode 100644 index 0000000..d959b88 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs @@ -0,0 +1,107 @@ +#pragma warning disable CS0282 +#if MODULE_ENTITIES +using Unity.Entities; +using UnityEngine.Profiling; + +namespace Pathfinding.ECS { + using Pathfinding; + + [UpdateInGroup(typeof(AIMovementSystemGroup))] + [UpdateBefore(typeof(FollowerControlSystem))] + [UpdateBefore(typeof(RepairPathSystem))] // Must run before RepairPathSystem to allow the agent to instantly start moving correctly after an agent finishes traversing an off-mesh link. + public partial struct TraverseOffMeshLinkSystem : ISystem { + EntityQuery entityQueryOffMeshLinkCleanup; + public JobRepairPath.Scheduler jobRepairPathScheduler; + + public void OnCreate (ref SystemState state) { + jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state); + + entityQueryOffMeshLinkCleanup = state.GetEntityQuery( + // ManagedAgentOffMeshLinkTraversal is a cleanup component. + // If it exists, but the AgentOffMeshLinkTraversal does not exist, + // then the agent must have been destroyed while traversing the off-mesh link. + ComponentType.ReadOnly(), + ComponentType.Exclude() + ); + } + + public void OnDestroy (ref SystemState state) { + jobRepairPathScheduler.Dispose(); + } + + public void OnUpdate (ref SystemState systemState) { + if (AstarPath.active == null) return; + + // Skip system if there are no agents with support for using off-mesh links + if (SystemAPI.QueryBuilder().WithAny().Build().IsEmptyIgnoreFilter) return; + + var commandBuffer = new EntityCommandBuffer(systemState.WorldUpdateAllocator); + StartOffMeshLinkTraversal(ref systemState, commandBuffer); + + commandBuffer.Playback(systemState.EntityManager); + commandBuffer.Dispose(); + + ProcessActiveOffMeshLinkTraversal(ref systemState); + } + + void StartOffMeshLinkTraversal (ref SystemState systemState, EntityCommandBuffer commandBuffer) { + Profiler.BeginSample("Start off-mesh link traversal"); + foreach (var(state, settings, entity) in SystemAPI.Query().WithAll() + .WithEntityAccess() + // Do not try to add another off-mesh link component to agents that already have one. + .WithNone()) { + // UnityEngine.Assertions.Assert.IsTrue(movementState.ValueRO.reachedEndOfPart && state.pathTracer.isNextPartValidLink); + if (!state.pathTracer.isNextPartValidLink) { + // The ReadyToTraverseOffMeshLink component is set at the end of a frame by the RepairPathSystem. + // In rare cases, the link may have been invalidated between then and now. + // In that case, just skip this agent and let the RepairPathSystem add the component again later if needed. + continue; + } + var linkInfo = NextLinkToTraverse(state); + var ctx = new AgentOffMeshLinkTraversalContext(linkInfo.link); + // Add the AgentOffMeshLinkTraversal and ManagedAgentOffMeshLinkTraversal components when the agent should start traversing an off-mesh link. + commandBuffer.AddComponent(entity, new AgentOffMeshLinkTraversal(linkInfo)); + commandBuffer.AddComponent(entity, new ManagedAgentOffMeshLinkTraversal(ctx, ResolveOffMeshLinkHandler(settings, ctx))); + commandBuffer.AddComponent(entity, new AgentOffMeshLinkMovementDisabled()); + commandBuffer.AddComponent(entity, new AgentOffMeshLinkLocalAvoidanceDisabled()); + } + Profiler.EndSample(); + } + + public static OffMeshLinks.OffMeshLinkTracer NextLinkToTraverse (ManagedState state) { + return state.pathTracer.GetLinkInfo(1); + } + + public static IOffMeshLinkHandler ResolveOffMeshLinkHandler (ManagedSettings settings, AgentOffMeshLinkTraversalContext ctx) { + var handler = settings.onTraverseOffMeshLink ?? ctx.concreteLink.handler; + return handler; + } + + void ProcessActiveOffMeshLinkTraversal (ref SystemState systemState) { + var commandBuffer = new EntityCommandBuffer(systemState.WorldUpdateAllocator); + systemState.CompleteDependency(); + + new JobManagedOffMeshLinkTransition { + commandBuffer = commandBuffer, + deltaTime = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime, + }.Run(); + + if (!entityQueryOffMeshLinkCleanup.IsEmptyIgnoreFilter) { + new JobManagedOffMeshLinkTransitionCleanup().Run(entityQueryOffMeshLinkCleanup); +#if MODULE_ENTITIES_1_0_8_OR_NEWER + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup, EntityQueryCaptureMode.AtPlayback); + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup, EntityQueryCaptureMode.AtPlayback); + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup, EntityQueryCaptureMode.AtPlayback); +#else + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup); + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup); + commandBuffer.RemoveComponent(entityQueryOffMeshLinkCleanup); +#endif + } + + commandBuffer.Playback(systemState.EntityManager); + commandBuffer.Dispose(); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs.meta new file mode 100644 index 0000000..7b75fb6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/ECS/Systems/TraverseOffMeshLinkSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 107b045bf113ec65485bced3edf26f6e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry.meta new file mode 100644 index 0000000..39515fa --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b7af884023dec8a49b7bdaf160ea6110 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs new file mode 100644 index 0000000..c5913f2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs @@ -0,0 +1,284 @@ +using UnityEngine; +using Unity.Mathematics; +using System.Runtime.CompilerServices; +using Unity.Profiling; + +namespace Pathfinding { + /// + /// Holds a coordinate in (integer) millimeters. + /// + /// This is used for node coordinates and other things, primarily to avoid floating point calculations in the core pathfinding routines (as they can be slow and non-deterministic if you are not careful). + /// + /// You can cast back and forth between Vector3s and Int3s like: + /// + /// Int3 intPoint = (Int3)transform.position; + /// transform.position = (Vector3)intPoint; + /// + /// + /// During the cast, the coordinates will be rounded to the nearest millimeter. + /// + public struct Int3 : System.IEquatable { + public int x; + public int y; + public int z; + + //These should be set to the same value (only PrecisionFactor should be 1 divided by Precision) + + /// + /// Precision for the integer coordinates. + /// One world unit is divided into [value] pieces. A value of 1000 would mean millimeter precision, a value of 1 would mean meter precision (assuming 1 world unit = 1 meter). + /// This value affects the maximum coordinates for nodes as well as how large the cost values are for moving between two nodes. + /// A higher value means that you also have to set all penalty values to a higher value to compensate since the normal cost of moving will be higher. + /// + public const int Precision = 1000; + + /// as a float + public const float FloatPrecision = 1000F; + + /// 1 divided by + public const float PrecisionFactor = 0.001F; + + public static Int3 zero => new Int3(); + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] + public Int3 (Vector3 position) { + x = (int)System.Math.Round(position.x*FloatPrecision); + y = (int)System.Math.Round(position.y*FloatPrecision); + z = (int)System.Math.Round(position.z*FloatPrecision); + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] + public Int3 (int _x, int _y, int _z) { + x = _x; + y = _y; + z = _z; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static bool operator == (Int3 lhs, Int3 rhs) { + return lhs.x == rhs.x && + lhs.y == rhs.y && + lhs.z == rhs.z; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static bool operator != (Int3 lhs, Int3 rhs) { + return lhs.x != rhs.x || + lhs.y != rhs.y || + lhs.z != rhs.z; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static explicit operator Int3 (Vector3 ob) { + return new Int3( + (int)System.Math.Round(ob.x*FloatPrecision), + (int)System.Math.Round(ob.y*FloatPrecision), + (int)System.Math.Round(ob.z*FloatPrecision) + ); + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static explicit operator Vector3 (Int3 ob) { + return new Vector3(ob.x*PrecisionFactor, ob.y*PrecisionFactor, ob.z*PrecisionFactor); + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static explicit operator float3 (Int3 ob) { + return (float3)(int3)ob*PrecisionFactor; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static explicit operator int3 (Int3 ob) { + return new int3(ob.x, ob.y, ob.z); + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static Int3 operator - (Int3 lhs, Int3 rhs) { + lhs.x -= rhs.x; + lhs.y -= rhs.y; + lhs.z -= rhs.z; + return lhs; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static Int3 operator - (Int3 lhs) { + lhs.x = -lhs.x; + lhs.y = -lhs.y; + lhs.z = -lhs.z; + return lhs; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static Int3 operator + (Int3 lhs, Int3 rhs) { + lhs.x += rhs.x; + lhs.y += rhs.y; + lhs.z += rhs.z; + return lhs; + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + public static Int3 operator * (Int3 lhs, int rhs) { + lhs.x *= rhs; + lhs.y *= rhs; + lhs.z *= rhs; + + return lhs; + } + + public static Int3 operator * (Int3 lhs, float rhs) { + lhs.x = (int)System.Math.Round(lhs.x * rhs); + lhs.y = (int)System.Math.Round(lhs.y * rhs); + lhs.z = (int)System.Math.Round(lhs.z * rhs); + + return lhs; + } + + public static Int3 operator * (Int3 lhs, double rhs) { + lhs.x = (int)System.Math.Round(lhs.x * rhs); + lhs.y = (int)System.Math.Round(lhs.y * rhs); + lhs.z = (int)System.Math.Round(lhs.z * rhs); + + return lhs; + } + + public static Int3 operator / (Int3 lhs, float rhs) { + lhs.x = (int)System.Math.Round(lhs.x / rhs); + lhs.y = (int)System.Math.Round(lhs.y / rhs); + lhs.z = (int)System.Math.Round(lhs.z / rhs); + return lhs; + } + + public int this[int i] { + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + get { + return i == 0 ? x : (i == 1 ? y : z); + } + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + set { + if (i == 0) x = value; + else if (i == 1) y = value; + else z = value; + } + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] + public static Int3 Max (Int3 lhs, Int3 rhs) { + return new Int3(System.Math.Max(lhs.x, rhs.x), System.Math.Max(lhs.y, rhs.y), System.Math.Max(lhs.z, rhs.z)); + } + + [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] + public static Int3 Min (Int3 lhs, Int3 rhs) { + return new Int3(System.Math.Min(lhs.x, rhs.x), System.Math.Min(lhs.y, rhs.y), System.Math.Min(lhs.z, rhs.z)); + } + + /// Angle between the vectors in radians + public static float Angle (Int3 lhs, Int3 rhs) { + double cos = Dot(lhs, rhs)/ ((double)lhs.magnitude*(double)rhs.magnitude); + + cos = cos < -1 ? -1 : (cos > 1 ? 1 : cos); + return (float)System.Math.Acos(cos); + } + + public static int Dot (Int3 lhs, Int3 rhs) { + return + lhs.x * rhs.x + + lhs.y * rhs.y + + lhs.z * rhs.z; + } + + public static long DotLong (Int3 lhs, Int3 rhs) { + return + (long)lhs.x * (long)rhs.x + + (long)lhs.y * (long)rhs.y + + (long)lhs.z * (long)rhs.z; + } + + /// + /// Normal in 2D space (XZ). + /// Equivalent to Cross(this, Int3(0,1,0) ) + /// except that the Y coordinate is left unchanged with this operation. + /// + public Int3 Normal2D () { + return new Int3(z, y, -x); + } + + /// + /// Returns the magnitude of the vector. The magnitude is the 'length' of the vector from 0,0,0 to this point. Can be used for distance calculations: + /// Debug.Log ("Distance between 3,4,5 and 6,7,8 is: "+(new Int3(3,4,5) - new Int3(6,7,8)).magnitude); + /// + public float magnitude { + get { + //It turns out that using doubles is just as fast as using ints with Mathf.Sqrt. And this can also handle larger numbers (possibly with small errors when using huge numbers)! + + double _x = x; + double _y = y; + double _z = z; + + return (float)System.Math.Sqrt(_x*_x+_y*_y+_z*_z); + } + } + + /// + /// Magnitude used for the cost between two nodes. The default cost between two nodes can be calculated like this: + /// int cost = (node1.position-node2.position).costMagnitude; + /// + /// This is simply the magnitude, rounded to the nearest integer + /// + public int costMagnitude { + get { + return (int)System.Math.Round(magnitude); + } + } + + /// The squared magnitude of the vector + public float sqrMagnitude { + get { + double _x = x; + double _y = y; + double _z = z; + return (float)(_x*_x+_y*_y+_z*_z); + } + } + + /// The squared magnitude of the vector + public long sqrMagnitudeLong { + get { + long _x = x; + long _y = y; + long _z = z; + return (_x*_x+_y*_y+_z*_z); + } + } + + public static implicit operator string (Int3 obj) { + return obj.ToString(); + } + + /// Returns a nicely formatted string representing the vector + public override string ToString () { + return "( "+x+", "+y+", "+z+")"; + } + + public override bool Equals (System.Object obj) { + if (!(obj is Int3)) return false; + + var rhs = (Int3)obj; + + return x == rhs.x && + y == rhs.y && + z == rhs.z; + } + + #region IEquatable implementation + + public bool Equals (Int3 other) { + return x == other.x && y == other.y && z == other.z; + } + + #endregion + + public override int GetHashCode () { + return x*73856093 + y*19349669 + z*83492791; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs.meta new file mode 100644 index 0000000..99f9118 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Int3.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5826dd4a1809b448291582cd06deadc1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs new file mode 100644 index 0000000..b997622 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs @@ -0,0 +1,68 @@ +using Unity.Mathematics; + +namespace Pathfinding { + /// + /// Integer bounding box. + /// Works almost like UnityEngine.BoundsInt but with a slightly nicer and more efficient api. + /// + /// Uses an exclusive upper bound (max field). + /// + public struct IntBounds { + public int3 min, max; + + public IntBounds (int xmin, int ymin, int zmin, int xmax, int ymax, int zmax) { + min = new int3(xmin, ymin, zmin); + max = new int3(xmax, ymax, zmax); + } + + public IntBounds(int3 min, int3 max) { + this.min = min; + this.max = max; + } + + public int3 size => max - min; + public int volume { + get { + var s = size; + return s.x * s.y * s.z; + } + } + + /// + /// Returns the intersection bounding box between the two bounds. + /// The intersection bounds is the volume which is inside both bounds. + /// If the rects do not have an intersection, an invalid rect is returned. + /// See: IsValid + /// + public static IntBounds Intersection (IntBounds a, IntBounds b) { + return new IntBounds( + math.max(a.min, b.min), + math.min(a.max, b.max) + ); + } + + public static bool Intersects (IntBounds a, IntBounds b) { + return math.all(a.min< b.max & a.max > b.min); + } + + public IntBounds Offset (int3 offset) { + return new IntBounds(min + offset, max + offset); + } + + public bool Contains (IntBounds other) { + return math.all(other.min >= min & other.max <= max); + } + + public override string ToString() => "(" + min.ToString() + " <= x < " + max.ToString() + ")"; + public override bool Equals (object _b) { + var b = (IntBounds)_b; + return this == b; + } + + public override int GetHashCode() => min.GetHashCode() ^ (max.GetHashCode() << 2); + + public static bool operator ==(IntBounds a, IntBounds b) => math.all(a.min == b.min & a.max == b.max); + + public static bool operator !=(IntBounds a, IntBounds b) => !(a == b); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs.meta new file mode 100644 index 0000000..476fc03 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/IntBounds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd3215aff835b0e49925b61562b0a83e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation.meta new file mode 100644 index 0000000..b2a19cd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aa02e060392acd24a985d56e2dfc2b52 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md new file mode 100644 index 0000000..d4a7c92 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Andrzej Więckowski, Ph.D. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md.meta new file mode 100644 index 0000000..2864100 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4777e4e500a981c4eac1a2b308e16b73 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs new file mode 100644 index 0000000..62300c4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs @@ -0,0 +1,121 @@ +using Unity.Collections; +using Unity.Mathematics; + +namespace andywiecko.BurstTriangulator { + public struct Status { + // Allow unused fields outside the editor +#pragma warning disable 0414 + int value1, value2, value3, value4; +#pragma warning restore 0414 + public TriangulatorErrorType type; + + /// + /// If true, then something went wrong during triangulation. + /// Check or for more information. + /// + public bool IsError => type != TriangulatorErrorType.Ok; + + public static Status Ok => new Status { type = TriangulatorErrorType.Ok }; + public static Status PositionsLengthLessThan3 (int length) => new Status { value1 = length, type = TriangulatorErrorType.PositionsLengthLessThan3 }; + public static Status PositionsMustBeFinite (int index) => new Status { value1 = index, type = TriangulatorErrorType.PositionsMustBeFinite }; + public static Status ConstraintsLengthNotDivisibleBy2 (int length) => new Status { value1 = length, type = TriangulatorErrorType.ConstraintsLengthNotDivisibleBy2 }; + public static Status DuplicatePosition (int index) => new Status { value1 = index, type = TriangulatorErrorType.DuplicatePosition }; + public static Status DuplicateConstraint (int index1, int index2) => new Status { value1 = index1, value2 = index2, type = TriangulatorErrorType.DuplicateConstraint }; + public static Status ConstraintOutOfBounds (int index, int2 constraint, int positionLength) => new Status { value1 = index, value2 = constraint.x, value3 = constraint.y, value4 = positionLength, type = TriangulatorErrorType.ConstraintOutOfBounds }; + public static Status ConstraintSelfLoop (int index, int2 constraint) => new Status { value1 = index, value2 = constraint.x, value3 = constraint.y, type = TriangulatorErrorType.ConstraintSelfLoop }; + public static Status ConstraintIntersection (int index1, int index2) => new Status { value1 = index1, value2 = index2, type = TriangulatorErrorType.ConstraintIntersection }; + public static Status DegenerateInput => new Status { type = TriangulatorErrorType.DegenerateInput }; + public static Status SloanMaxItersExceeded => new Status { type = TriangulatorErrorType.SloanMaxItersExceeded }; + public static Status IntegersDoNotSupportMeshRefinement => new Status { type = TriangulatorErrorType.IntegersDoNotSupportMeshRefinement }; + public static Status ConstraintArrayLengthMismatch (int constraintLength, int constraintTypeLength) => new Status { value1 = constraintLength, value2 = constraintTypeLength, type = TriangulatorErrorType.ConstraintArrayLengthMismatch }; + public static Status HoleMustBeFinite (int index) => new Status { value1 = index, type = TriangulatorErrorType.HoleMustBeFinite }; + public static Status RedudantHolesArray => new Status { type = TriangulatorErrorType.RedudantHolesArray }; + public static Status ConstraintEdgesMissingForAutoHolesAndBoundary => new Status { type = TriangulatorErrorType.ConstraintEdgesMissingForAutoHolesAndBoundary }; + public static Status ConstraintEdgesMissingForRestoreBoundary => new Status { type = TriangulatorErrorType.ConstraintEdgesMissingForRestoreBoundary }; + public static Status RefinementNotSupportedForCoordinateType => new Status { type = TriangulatorErrorType.RefinementNotSupportedForCoordinateType }; + public static Status SloanMaxItersMustBePositive (int sloanMaxIters) => new Status { type = TriangulatorErrorType.SloanMaxItersMustBePositive, value1 = sloanMaxIters }; + public static Status RefinementThresholdAreaMustBePositive => new Status { type = TriangulatorErrorType.RefinementThresholdAreaMustBePositive }; + public static Status RefinementThresholdAngleOutOfRange => new Status { type = TriangulatorErrorType.RefinementThresholdAngleOutOfRange }; + +#if UNITY_EDITOR + internal FixedString512Bytes ToFixedString () { + switch (type) { + case TriangulatorErrorType.Ok: + return "Ok"; + case TriangulatorErrorType.PositionsLengthLessThan3: + return $"Position array's length must be greater than 3, but was {value1}."; + case TriangulatorErrorType.PositionsMustBeFinite: + return $"Positions must be finite, but position at index {value1} is not finite."; + case TriangulatorErrorType.HoleMustBeFinite: + return $"Hole must be finite, but hole at index {value1} is not finite."; + case TriangulatorErrorType.ConstraintsLengthNotDivisibleBy2: + return $"Input constraint array's length must be divisible by 2, but was {value1}."; + case TriangulatorErrorType.DuplicatePosition: + return $"Duplicate position at index {value1}."; + case TriangulatorErrorType.DuplicateConstraint: + return $"Constraints at indices {value1} and {value2} are equivalent."; + case TriangulatorErrorType.ConstraintOutOfBounds: + return $"Constraint[{value1}] = ({value2}, {value3}) is out of bounds of the positions array (length={value4})."; + case TriangulatorErrorType.ConstraintSelfLoop: + return $"Constraint[{value1}] = ({value2}, {value3}) is a self-loop."; + case TriangulatorErrorType.ConstraintIntersection: + return $"Constraints at indices {value1} and {value2} intersect."; + case TriangulatorErrorType.DegenerateInput: + return "Input is degenerate. It seems to consist only of duplicate or collinear points."; + case TriangulatorErrorType.SloanMaxItersExceeded: + return $"Sloan max iterations exceeded! This usually happens when the scale of the input positions is not uniform. Try to pre-process the input data or increase {nameof(TriangulationSettings.SloanMaxIters)}."; + case TriangulatorErrorType.IntegersDoNotSupportMeshRefinement: + return "Integer coordinates do not support mesh refinement. Please use float or double coordinates."; + case TriangulatorErrorType.ConstraintArrayLengthMismatch: + return $"Constraint type array's length ({value2}) must be exactly half of the constraint array's length ({value1})."; + case TriangulatorErrorType.RedudantHolesArray: + return "HoleSeeds buffer is provided, but ConstraintEdges is missing. Using holes requires constrained edges."; + case TriangulatorErrorType.ConstraintEdgesMissingForAutoHolesAndBoundary: + return "ConstraintEdges buffer is missing. This is required when using the AutoHolesAndBoundary settings."; + case TriangulatorErrorType.ConstraintEdgesMissingForRestoreBoundary: + return "ConstraintEdges buffer is missing. This is required when using the RestoreBoundary settings."; + case TriangulatorErrorType.RefinementNotSupportedForCoordinateType: + return "Mesh refinement is not supported for the coordinate type T."; + case TriangulatorErrorType.SloanMaxItersMustBePositive: + return $"Sloan max iterations must be positive. But found {value1}."; + case TriangulatorErrorType.RefinementThresholdAreaMustBePositive: + return "Refinement threshold area must be positive."; + case TriangulatorErrorType.RefinementThresholdAngleOutOfRange: + return "RefinementThresholdAngle must be in the range [0, π / 4]. Note that in the literature, the upper boundary for convergence is approximately π / 6."; + default: + return "Unknown error."; + } + } +#else + internal FixedString64Bytes ToFixedString() => "Triangulation error. Run in editor for more info."; +#endif + + public override string ToString () { + return ToFixedString().ToString(); + } + } + + public enum TriangulatorErrorType: byte { + Ok, + PositionsLengthLessThan3, + PositionsMustBeFinite, + ConstraintsLengthNotDivisibleBy2, + DuplicatePosition, + DuplicateConstraint, + ConstraintOutOfBounds, + ConstraintSelfLoop, + ConstraintIntersection, + DegenerateInput, + SloanMaxItersExceeded, + IntegersDoNotSupportMeshRefinement, + ConstraintArrayLengthMismatch, + HoleMustBeFinite, + RedudantHolesArray, + ConstraintEdgesMissingForAutoHolesAndBoundary, + ConstraintEdgesMissingForRestoreBoundary, + RefinementNotSupportedForCoordinateType, + SloanMaxItersMustBePositive, + RefinementThresholdAreaMustBePositive, + RefinementThresholdAngleOutOfRange, + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs.meta new file mode 100644 index 0000000..1dd61ef --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/TriangulationError.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44a41ae21b8b5a549a97fd31a287e96a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs new file mode 100644 index 0000000..8f55de2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs @@ -0,0 +1,3510 @@ +/* +MIT License + +Copyright (c) 2021 Andrzej Więckowski, Ph.D., https://github.com/andywiecko/BurstTriangulator + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +using andywiecko.BurstTriangulator.LowLevel.Unsafe; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Profiling; +using UnityEngine; +#if UNITY_MATHEMATICS_FIXEDPOINT +using Unity.Mathematics.FixedPoint; +#endif + +[assembly: InternalsVisibleTo("andywiecko.BurstTriangulator.Tests")] + +namespace andywiecko.BurstTriangulator { + public enum Preprocessor { + None = 0, + /// + /// Transforms to local coordinate system using center of mass. + /// + COM, + /// + /// Transforms using coordinate system obtained from principal component analysis. + /// + PCA + } + + [Serializable] + public class RefinementThresholds { + /// + /// Specifies the maximum area constraint for triangles in the resulting mesh refinement. + /// Ensures that no triangle in the mesh has an area larger than the specified value. + /// + [field: SerializeField] + public float Area { get; set; } = 1f; + /// + /// Specifies the refinement angle constraint for triangles in the resulting mesh. + /// Ensures that no triangle in the mesh has an angle smaller than the specified value. + /// + /// + /// Expressed in radians. + /// + [field: SerializeField] + public float Angle { get; set; } = math.radians(5); + } + + [Serializable] + public class TriangulationSettings { + /// + /// If set to , holes and boundaries will be created automatically + /// depending on the provided . + /// + /// + /// This implements the odd-even fill rule. + /// + /// When this mode is used, you should ensure that all constraints form closed loops. If any constraints are part of open chains, + /// then the result is not well-defined. + /// + [field: SerializeField] + public bool AutoHolesAndBoundary { get; set; } = false; + [field: SerializeField] + public RefinementThresholds RefinementThresholds { get; } = new(); + /// + /// If refines mesh using + /// Ruppert's algorithm. + /// + [field: SerializeField] + public bool RefineMesh { get; set; } = false; + /// + /// If set to , the provided and + /// will be validated before executing the triangulation procedure. The input , + /// , and have certain restrictions. + /// For more details, see the manual. + /// If any of the validation conditions are not met, the triangulation will not be performed. + /// This can be detected as an error by checking the value (native, and usable in jobs). + /// Additionally, if is set to , corresponding errors/warnings will be logged in the Console. + /// Note that some conditions may result in warnings only. + /// + /// + /// Input validation can be expensive. If you are certain of your input, consider disabling this option for additional performance. + /// + [field: SerializeField] + public bool ValidateInput { get; set; } = true; + /// + /// If set to , caught errors and warnings with will be logged in the Console. + /// + /// + /// See also the settings. + /// + /// + [field: SerializeField] + public bool Verbose { get; set; } = true; + /// + /// If the mesh boundary is restored using . + /// + [field: SerializeField] + public bool RestoreBoundary { get; set; } = false; + /// + /// Max iteration count during Sloan's algorithm (constraining edges). + /// Modify this only if you know what you are doing. + /// + [field: SerializeField] + public int SloanMaxIters { get; set; } = 1_000_000; + /// + /// Preprocessing algorithm for the input data. Default is . + /// + [field: SerializeField] + public Preprocessor Preprocessor { get; set; } = Preprocessor.None; + } + + public enum ConstraintType: byte { + /// + /// A constrained edge will always be present in the output mesh. + /// + /// In rare cases where a vertex lies exactly on the constraint, this exact edge may not be present, but it may have been split up into smaller + /// edges that together form the original constraint. + /// + Constrained, + /// + /// A hole boundary edge works like a constrained edge, but will additionally define where the holes/boundaries of the mesh are. + /// + /// This applies when using the or setting. + /// + /// Boundary constraints must form closed loops. If they do not, the result is not well-defined. + /// + /// If a boundary constraint overlaps and is collinear with another non-boundary constraint, then the boundary constraint will take precedence. + /// + ConstrainedAndHoleBoundary, + } + + public enum HalfedgeState: byte { + Unconstrained, + Constrained, + ConstrainedAndHoleBoundary, + } + + public class InputData where T2 : unmanaged { + /// + /// Positions of points used in triangulation. + /// + public NativeArray Positions { get; set; } + /// + /// Optional buffer for constraint edges. This array constrains specific edges to be included in the final + /// triangulation result. It should contain indexes corresponding to the of the edges + /// in the format [a₀, a₁, b₀, b₁, c₀, c₁, ...], where (a₀, a₁), (b₀, b₁), (c₀, c₁), etc., represent the constraint edges. + /// + /// + /// Note: If refinement is enabled, the provided constraints may be split during the refinement process. + /// + public NativeArray ConstraintEdges { get; set; } + /// + /// An array of values corresponding to each edge in . + /// + /// If created, the length must be exactly half of the length of . + /// + /// If not set, all constraints will be treated as . + /// + public NativeArray ConstraintEdgeTypes { get; set; } + /// + /// Optional buffer containing seeds for holes. These hole seeds serve as starting points for a removal process that + /// mimics the spread of a virus. During this process, act as barriers to prevent further propagation. + /// For more information, refer to the documentation. + /// + public NativeArray HoleSeeds { get; set; } + } + + /// + /// Allocation free input class with implicit cast to . + /// + /// + [Obsolete("Use AsNativeArray(out Handle) instead! You can learn more in the project manual.")] + public class ManagedInput where T2 : unmanaged { + public T2[] Positions { get; set; } + public int[] ConstraintEdges { get; set; } + public T2[] HoleSeeds { get; set; } + + public static implicit operator InputData(ManagedInput input) => new() + { + Positions = input.Positions == null ? default : input.Positions.AsNativeArray(), + ConstraintEdges = input.ConstraintEdges == null ? default : input.ConstraintEdges.AsNativeArray(), + HoleSeeds = input.HoleSeeds == null ? default : input.HoleSeeds.AsNativeArray(), + }; + } + + public class OutputData where T2 : unmanaged { + /// + /// Positions of triangulation points. + /// + /// + /// Note: This buffer may include additional points than if refinement is enabled. + /// Additionally, the positions might differ slightly (by a small ε) if a is applied. + /// + public NativeList Positions => owner.outputPositions; + /// + /// Continuous buffer of resulting triangles. All triangles are guaranteed to be oriented clockwise. + /// + public NativeList Triangles => owner.triangles; + /// + /// Status of the triangulation. Retrieve this value to detect any errors that occurred during triangulation. + /// + public NativeReference Status => owner.status; + /// + /// Continuous buffer of resulting halfedges. A value of -1 indicates that there is no corresponding opposite halfedge. + /// For more information, refer to the documentation on halfedges. + /// + public NativeList Halfedges => owner.halfedges; + /// + /// Buffer corresponding to . + /// + public NativeList ConstrainedHalfedges => owner.constrainedHalfedges; + private readonly Triangulator owner; + [Obsolete("This will be converted into internal ctor.")] + public OutputData(Triangulator owner) => this.owner = owner; + } + + /// + /// A handle that prevents an object from being deallocated by the garbage collector (GC). + /// Call to release the object. + /// + /// + public readonly struct Handle { + private readonly ulong gcHandle; + /// + /// Creates a . + /// + /// The handle value, which can be obtained e.g. from + /// or + /// . + /// + /// + public Handle(ulong gcHandle) => this.gcHandle = gcHandle; + /// + /// Releases the handle, allowing the object to be collected by the garbage collector. + /// + public readonly void Free() => UnsafeUtility.ReleaseGCObject(gcHandle); + } + + /// + /// A wrapper for where T2 is . + /// + /// + public class Triangulator : IDisposable { + public TriangulationSettings Settings => impl.Settings; + public InputData Input { get => impl.Input; set => impl.Input = value; } + public OutputData Output => impl.Output; + private readonly Triangulator impl; + public Triangulator(int capacity, Allocator allocator) => impl = new(capacity, allocator); + public Triangulator(Allocator allocator) => impl = new(allocator); + + /// + /// Releases all resources (memory and safety handles). + /// + public void Dispose() => impl.Dispose(); + + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public void Run() => impl.Run(); + + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public JobHandle Schedule(JobHandle dependencies = default) => impl.Schedule(dependencies); + } + + public class Triangulator : IDisposable where T2 : unmanaged { + public TriangulationSettings Settings { get; } = new(); + public InputData Input { get; set; } = new(); + public OutputData Output { get; } + + internal NativeList outputPositions; + internal NativeList triangles; + internal NativeList halfedges; + internal NativeList constrainedHalfedges; + internal NativeReference status; + + public Triangulator(int capacity, Allocator allocator) { + outputPositions = new(capacity, allocator); + triangles = new(6 * capacity, allocator); + status = new(Status.Ok, allocator); + halfedges = new(6 * capacity, allocator); + constrainedHalfedges = new(6 * capacity, allocator); +#pragma warning disable CS0618 + Output = new(this); +#pragma warning restore CS0618 + } + + public Triangulator(Allocator allocator) : this(capacity: 16 * 1024, allocator) { } + + /// + /// Releases all resources (memory and safety handles). + /// + public void Dispose () { + outputPositions.Dispose(); + triangles.Dispose(); + status.Dispose(); + halfedges.Dispose(); + constrainedHalfedges.Dispose(); + } + } + + public static class Extensions { + /// + /// Returns view on managed . + /// + /// The type of the elements. + /// Array to + /// View on managed with . + /// + [Obsolete("Use AsNativeArray(out Handle) instead! You can learn more in the project manual.")] + unsafe public static NativeArray AsNativeArray(this T[] array) where T : unmanaged { + var ret = default(NativeArray); + // In Unity 2023.2+ pointers are not required, one can use Span instead. + fixed (void* ptr = array) { + ret = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, array.Length, Allocator.None); + } +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var m_SafetyHandle = AtomicSafetyHandle.Create(); + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref ret, m_SafetyHandle); +#endif + return ret; + } + + /// + /// Returns view on managed + /// with to prevents from deallocation. + /// + /// Warning! User has to call + /// manually to release the data for GC! Read more in the project manual. + /// + /// The type of the elements. + /// Array to view. + /// A handle that prevents the from being deallocated by the GC. + /// view on managed with . + public static unsafe NativeArray AsNativeArray(this T[] array, out Handle handle) where T : unmanaged { + var ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(array, out var gcHandle); + var ret = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, array.Length, Allocator.None); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + var m_SafetyHandle = AtomicSafetyHandle.Create(); + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref ret, m_SafetyHandle); +#endif + handle = new(gcHandle); + return ret; + } + + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public static void Run(this Triangulator @this) => + new TriangulationJob(@this).Run(); + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => + new TriangulationJob(@this).Schedule(dependencies); + + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public static void Run(this Triangulator @this) => + new TriangulationJob( + input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, ConstraintEdgeTypes = @this.Input.ConstraintEdgeTypes, HoleSeeds = @this.Input.HoleSeeds.Reinterpret() }, + output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList >(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges }, + args: @this.Settings + ).Run(); + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => + new TriangulationJob( + input: new() { Positions = @this.Input.Positions.Reinterpret(), ConstraintEdges = @this.Input.ConstraintEdges, ConstraintEdgeTypes = @this.Input.ConstraintEdgeTypes, HoleSeeds = @this.Input.HoleSeeds.Reinterpret() }, + output: new() { Triangles = @this.triangles, Halfedges = @this.halfedges, Positions = UnsafeUtility.As, NativeList >(ref @this.outputPositions), Status = @this.status, ConstrainedHalfedges = @this.constrainedHalfedges }, + args: @this.Settings + ).Schedule(dependencies); + + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public static void Run(this Triangulator @this) => + new TriangulationJob(@this).Run(); + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => + new TriangulationJob(@this).Schedule(dependencies); + + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public static void Run(this Triangulator @this) => + new TriangulationJob(@this).Run(); + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => + new TriangulationJob(@this).Schedule(dependencies); + +#if UNITY_MATHEMATICS_FIXEDPOINT + /// + /// Perform the job's Execute method immediately on the same thread. + /// + public static void Run(this Triangulator @this) => + new TriangulationJob(@this).Run(); + /// + /// Schedule the job for execution on a worker thread. + /// + /// + /// Dependencies are used to ensure that a job executes on worker threads after the dependency has completed execution. + /// Making sure that two jobs reading or writing to same data do not run in parallel. + /// + /// + /// The handle identifying the scheduled job. Can be used as a dependency for a later job or ensure completion on the main thread. + /// + public static JobHandle Schedule(this Triangulator @this, JobHandle dependencies = default) => + new TriangulationJob(@this).Schedule(dependencies); +#endif + } +} + +namespace andywiecko.BurstTriangulator.LowLevel.Unsafe { + /// + /// Native correspondence to . + /// + /// + public struct InputData where T2 : unmanaged { + /// + /// Positions of points used in triangulation. + /// + public NativeArray Positions; + /// + /// Optional buffer for constraint edges. This array constrains specific edges to be included in the final + /// triangulation result. It should contain indexes corresponding to the of the edges + /// in the format [a₀, a₁, b₀, b₁, c₀, c₁, ...], where (a₀, a₁), (b₀, b₁), (c₀, c₁), etc., represent the constraint edges. + /// + /// + /// Note: If refinement is enabled, the provided constraints may be split during the refinement process. + /// + public NativeArray ConstraintEdges; + public NativeArray ConstraintEdgeTypes; + /// + /// Optional buffer containing seeds for holes. These hole seeds serve as starting points for a removal process that + /// mimics the spread of a virus. During this process, act as barriers to prevent further propagation. + /// For more information, refer to the documentation. + /// + public NativeArray HoleSeeds; + } + + /// + /// Native correspondence to . + /// + /// + public struct OutputData where T2 : unmanaged { + /// + /// Positions of triangulation points. + /// + /// + /// Note: This buffer may include additional points than if refinement is enabled. + /// Additionally, the positions might differ slightly (by a small ε) if a is applied. + /// + public NativeList Positions; + /// + /// Continuous buffer of resulting triangles. All triangles are guaranteed to be oriented clockwise. + /// + public NativeList Triangles; + /// + /// Status of the triangulation. Retrieve this value to detect any errors that occurred during triangulation. + /// + public NativeReference Status; + /// + /// Continuous buffer of resulting halfedges. A value of -1 indicates that there is no corresponding opposite halfedge. + /// For more information, refer to the documentation on halfedges. + /// + public NativeList Halfedges; + /// + /// Buffer corresponding to . + /// + public NativeList ConstrainedHalfedges; + } + + /// + /// Native correspondence to . + /// + /// + public readonly struct Args { + public readonly Preprocessor Preprocessor; + public readonly int SloanMaxIters; + // NOTE: Only blittable types are supported for Burst compiled static methods. + // Unfortunately bool type is non-blittable and required marshaling for compilation. + // Learn more about blittable here: https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types + [MarshalAs(UnmanagedType.U1)] + public readonly bool AutoHolesAndBoundary, RefineMesh, RestoreBoundary, ValidateInput, Verbose; + public readonly float RefinementThresholdAngle, RefinementThresholdArea; + + /// + /// Constructs a new . + /// + /// + /// Use and for easy construction. + /// + public Args( + Preprocessor preprocessor, + int sloanMaxIters, + bool autoHolesAndBoundary, bool refineMesh, bool restoreBoundary, bool validateInput, bool verbose, + float refinementThresholdAngle, float refinementThresholdArea + ) { + AutoHolesAndBoundary = autoHolesAndBoundary; + Preprocessor = preprocessor; + RefineMesh = refineMesh; + RestoreBoundary = restoreBoundary; + SloanMaxIters = sloanMaxIters; + ValidateInput = validateInput; + Verbose = verbose; + RefinementThresholdAngle = refinementThresholdAngle; + RefinementThresholdArea = refinementThresholdArea; + } + + /// + /// Construct with default values (same as defaults). + /// + public static Args Default( + Preprocessor preprocessor = Preprocessor.None, + int sloanMaxIters = 1_000_000, + bool autoHolesAndBoundary = false, bool refineMesh = false, bool restoreBoundary = false, bool validateInput = true, bool verbose = true, + float refinementThresholdAngle = 0.0872664626f, float refinementThresholdArea = 1f + ) => new( + preprocessor, + sloanMaxIters, + autoHolesAndBoundary, refineMesh, restoreBoundary, validateInput, verbose, + refinementThresholdAngle, refinementThresholdArea + ); + + public static implicit operator Args(TriangulationSettings settings) => new( + autoHolesAndBoundary: settings.AutoHolesAndBoundary, + preprocessor: settings.Preprocessor, + refineMesh: settings.RefineMesh, + restoreBoundary: settings.RestoreBoundary, + sloanMaxIters: settings.SloanMaxIters, + validateInput: settings.ValidateInput, + verbose: settings.Verbose, + refinementThresholdAngle: settings.RefinementThresholds.Angle, + refinementThresholdArea: settings.RefinementThresholds.Area + ); + + /// + /// Returns a new but with changed selected parameter(s) values. + /// + public Args With( + Preprocessor? preprocessor = null, + int? sloanMaxIters = null, + bool? autoHolesAndBoundary = null, bool? refineMesh = null, bool? restoreBoundary = null, bool? validateInput = null, bool? verbose = null, + float? refinementThresholdAngle = null, float? refinementThresholdArea = null + ) => new( + preprocessor ?? Preprocessor, + sloanMaxIters ?? SloanMaxIters, + autoHolesAndBoundary ?? AutoHolesAndBoundary, refineMesh ?? RefineMesh, restoreBoundary ?? RestoreBoundary, validateInput ?? ValidateInput, verbose ?? Verbose, + refinementThresholdAngle ?? RefinementThresholdAngle, refinementThresholdArea ?? RefinementThresholdArea + ); + } + + /// + /// A wrapper for where T2 is . + /// + /// + /// + public readonly struct UnsafeTriangulator { } + + /// + /// A readonly struct that corresponds to . + /// This struct can be used directly in a native context within the jobs pipeline. + /// The API is accessible through . + /// + /// + /// Unsafe in this context indicates that using the method may be challenging for beginner users. + /// The user is responsible for managing data allocation (both input and output). + /// Some permutations of the method calls may not be supported. + /// Refer to the documentation for more details. The term unsafe does not refer to memory safety. + /// + /// The coordinate type. Supported types include: + /// , + /// , + /// , + /// , + /// and + /// . + /// For more information on type restrictions, refer to the documentation. + /// + /// + public readonly struct UnsafeTriangulator where T2 : unmanaged { } + + public static class Extensions { + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(input, output, args, allocator); + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(input, output, args, allocator); + /// + /// Refines the mesh for a valid triangulation in . + /// Refinement parameters can be provided with the selected precision type T in generics, which is especially useful for fixed-point arithmetic. + /// Refinement parameters in are restricted to precision. + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + /// Expressed in radians. Default: 5° = 0.0872664626 rad. + /// Used to constrain boundary halfedges. Since the refinement algorithm (whether for constrained triangulation or not) requires constrained halfedges at the boundary, not setting this option may cause unexpected behavior, especially when the restoreBoundary option is disabled. + public static void RefineMesh(this UnsafeTriangulator @this, OutputData output, Allocator allocator, double areaThreshold = 1, double angleThreshold = 0.0872664626, bool constrainBoundary = false) => new UnsafeTriangulator().RefineMesh(output, allocator, 2 * areaThreshold, angleThreshold, constrainBoundary); + + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(input, output, args, allocator); + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(input, output, args, allocator); + /// + /// Refines the mesh for a valid triangulation in . + /// Refinement parameters can be provided with the selected precision type T in generics, which is especially useful for fixed-point arithmetic. + /// Refinement parameters in are restricted to precision. + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + /// Expressed in radians. Default: 5° = 0.0872664626 rad. + /// Used to constrain boundary halfedges. Since the refinement algorithm (whether for constrained triangulation or not) requires constrained halfedges at the boundary, not setting this option may cause unexpected behavior, especially when the restoreBoundary option is disabled. + public static void RefineMesh(this UnsafeTriangulator @this, OutputData output, Allocator allocator, float areaThreshold = 1, float angleThreshold = 0.0872664626f, bool constrainBoundary = false) => new UnsafeTriangulator().RefineMesh(output, allocator, 2 * areaThreshold, angleThreshold, constrainBoundary); + + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(UnsafeUtility.As, InputData >(ref input), UnsafeUtility.As, OutputData >(ref output), args, allocator); + + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(UnsafeUtility.As, InputData >(ref input), UnsafeUtility.As, OutputData >(ref output), args, allocator); + + /// + /// Refines the mesh for a valid triangulation in . + /// Refinement parameters can be provided with the selected precision type T in generics, which is especially useful for fixed-point arithmetic. + /// Refinement parameters in are restricted to precision. + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + /// Expressed in radians. Default: 5° = 0.0872664626 rad. + /// Used to constrain boundary halfedges. Since the refinement algorithm (whether for constrained triangulation or not) requires constrained halfedges at the boundary, not setting this option may cause unexpected behavior, especially when the restoreBoundary option is disabled. + public static void RefineMesh(this UnsafeTriangulator @this, OutputData output, Allocator allocator, float areaThreshold = 1, float angleThreshold = 0.0872664626f, bool constrainBoundary = false) => new UnsafeTriangulator().RefineMesh(UnsafeUtility.As, OutputData >(ref output), allocator, 2 * areaThreshold, angleThreshold, constrainBoundary); + + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(input, output, args, allocator); + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(input, output, args, allocator); + + /// + /// Refines the mesh for a valid triangulation in . + /// Refinement parameters can be provided with the selected precision type T in generics, which is especially useful for fixed-point arithmetic. + /// Refinement parameters in are restricted to precision. + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + /// Expressed in radians. Default: 5° = 0.0872664626 rad. + /// Used to constrain boundary halfedges. Since the refinement algorithm (whether for constrained triangulation or not) requires constrained halfedges at the boundary, not setting this option may cause unexpected behavior, especially when the restoreBoundary option is disabled. + public static void RefineMesh(this UnsafeTriangulator @this, OutputData output, Allocator allocator, double areaThreshold = 1, double angleThreshold = 0.0872664626, bool constrainBoundary = false) => new UnsafeTriangulator().RefineMesh(output, allocator, 2 * areaThreshold, angleThreshold, constrainBoundary); + + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(input, output, args, allocator); + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(input, output, args, allocator); + +#if UNITY_MATHEMATICS_FIXEDPOINT + /// + /// Performs triangulation on the given , producing the result in based on the settings specified in . + /// This method corresponds to the native implementation of . + /// + /// + /// Note: + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void Triangulate(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().Triangulate(input, output, args, allocator); + /// + /// Plants hole seeds defined in (or restores boundaries or auto-holes if specified in ) + /// within the triangulation data in , using the settings specified in . + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + public static void PlantHoleSeeds(this UnsafeTriangulator @this, InputData input, OutputData output, Args args, Allocator allocator) => new UnsafeTriangulator().PlantHoleSeeds(input, output, args, allocator); + /// + /// Refines the mesh for a valid triangulation in . + /// Refinement parameters can be provided with the selected precision type T in generics, which is especially useful for fixed-point arithmetic. + /// Refinement parameters in are restricted to precision. + /// + /// + /// Note: + /// This method requires that contains valid triangulation data. + /// The and native containers must be allocated by the user. Some buffers are optional; refer to the documentation for more details. + /// + /// The allocator to use. If called from a job, consider using . + /// Expressed in radians. Default: 5° = 0.0872664626 rad. + /// Used to constrain boundary halfedges. Since the refinement algorithm (whether for constrained triangulation or not) requires constrained halfedges at the boundary, not setting this option may cause unexpected behavior, especially when the restoreBoundary option is disabled. + public static void RefineMesh(this UnsafeTriangulator @this, OutputData output, Allocator allocator, fp? areaThreshold = null, fp? angleThreshold = null, fp? concentricShells = null, bool constrainBoundary = false) => new UnsafeTriangulator().RefineMesh(output, allocator, 2 * (areaThreshold ?? 1), angleThreshold ?? fp.FromRaw(374806602) /*Raw value for (fp)0.0872664626*/, constrainBoundary); +#endif + } + + [BurstCompile] + internal struct TriangulationJob : IJob + where T : unmanaged, IComparable + where T2 : unmanaged + where TBig : unmanaged, IComparable + where TTransform : unmanaged, ITransform + where TUtils : unmanaged, IUtils { + private NativeArray inputPositions; + [NativeDisableContainerSafetyRestriction] + private NativeArray constraints; + [NativeDisableContainerSafetyRestriction] + private NativeArray constraintTypes; + [NativeDisableContainerSafetyRestriction] + private NativeArray holeSeeds; + + private NativeList outputPositions; + private NativeList triangles; + private NativeList halfedges; + private NativeList constrainedHalfedges; + private NativeReference status; + + private readonly Args args; + + public TriangulationJob(InputData input, OutputData output, Args args) { + inputPositions = input.Positions; + constraints = input.ConstraintEdges; + constraintTypes = input.ConstraintEdgeTypes; + holeSeeds = input.HoleSeeds; + + outputPositions = output.Positions; + triangles = output.Triangles; + halfedges = output.Halfedges; + constrainedHalfedges = output.ConstrainedHalfedges; + status = output.Status; + + this.args = args; + } + + public TriangulationJob(Triangulator @this) { + inputPositions = @this.Input.Positions; + constraints = @this.Input.ConstraintEdges; + constraintTypes = @this.Input.ConstraintEdgeTypes; + holeSeeds = @this.Input.HoleSeeds; + + outputPositions = @this.Output.Positions; + triangles = @this.Output.Triangles; + halfedges = @this.Output.Halfedges; + constrainedHalfedges = @this.Output.ConstrainedHalfedges; + status = @this.Output.Status; + + args = @this.Settings; + } + + public void Execute () { + new UnsafeTriangulator().Triangulate( + input: new() + { + Positions = inputPositions, + ConstraintEdges = constraints, + ConstraintEdgeTypes = constraintTypes, + HoleSeeds = holeSeeds, + }, + output: new() + { + Positions = outputPositions, + Triangles = triangles, + Halfedges = halfedges, + ConstrainedHalfedges = constrainedHalfedges, + Status = status, + }, args, Allocator.Temp); + } + } + + internal readonly struct UnsafeTriangulator + where T : unmanaged, IComparable + where T2 : unmanaged + where TBig : unmanaged, IComparable + where TTransform : unmanaged, ITransform + where TUtils : unmanaged, IUtils { + // NOTE: Caching ProfileMarker can boost performance for triangulations with small input (~10² triangles). + private readonly struct Markers { + public static readonly ProfilerMarker PreProcessInputStep = new(nameof(UnsafeTriangulator.PreProcessInputStep)); + public static readonly ProfilerMarker PostProcessInputStep = new(nameof(UnsafeTriangulator.PostProcessInputStep)); + public static readonly ProfilerMarker ValidateInputStep = new(nameof(UnsafeTriangulator.ValidateInputStep)); + public static readonly ProfilerMarker DelaunayTriangulationStep = new(nameof(UnsafeTriangulator.DelaunayTriangulationStep)); + public static readonly ProfilerMarker ConstrainEdgesStep = new(nameof(UnsafeTriangulator.ConstrainEdgesStep)); + public static readonly ProfilerMarker PlantingSeedStep = new(nameof(UnsafeTriangulator.PlantingSeedStep)); + public static readonly ProfilerMarker RefineMeshStep = new(nameof(UnsafeTriangulator.RefineMeshStep)); + } + + private static readonly TUtils utils = default; + + public void Triangulate (InputData input, OutputData output, Args args, Allocator allocator) { + var tmpStatus = default(NativeReference); + var tmpPositions = default(NativeList); + var tmpHalfedges = default(NativeList); + var tmpConstrainedHalfedges = default(NativeList); + if (!output.Status.IsCreated) output.Status = tmpStatus = new(allocator); + if (!output.Positions.IsCreated) output.Positions = tmpPositions = new(16 * 1024, allocator); + if (!output.Halfedges.IsCreated) output.Halfedges = tmpHalfedges = new(6 * 16 * 1024, allocator); + if (!output.ConstrainedHalfedges.IsCreated) output.ConstrainedHalfedges = tmpConstrainedHalfedges = new(6 * 16 * 1024, allocator); + + output.Status.Value = Status.Ok; + output.Triangles.Clear(); + output.Positions.Clear(); + output.Halfedges.Clear(); + output.ConstrainedHalfedges.Clear(); + + // After this step, the positions have been moved to output.Positions (possibly modified by a preprocessor) + PreProcessInputStep(input, output, args, out var localHoles, out var lt, allocator); + new ValidateInputStep(input, output, args).Execute(); + new DelaunayTriangulationStep(output, args).Execute(allocator); + new ConstrainEdgesStep(input, output, args).Execute(allocator); + new PlantingSeedStep(output, args, localHoles).Execute(allocator, input.ConstraintEdges.IsCreated); + new RefineMeshStep(output, args, lt).Execute(allocator, refineMesh: args.RefineMesh, constrainBoundary: !input.ConstraintEdges.IsCreated || !args.RestoreBoundary); + PostProcessInputStep(output, args, lt); + + var status = output.Status.Value; + if (localHoles.IsCreated) localHoles.Dispose(); + if (tmpStatus.IsCreated) tmpStatus.Dispose(); + if (tmpPositions.IsCreated) tmpPositions.Dispose(); + if (tmpHalfedges.IsCreated) tmpHalfedges.Dispose(); + if (tmpConstrainedHalfedges.IsCreated) tmpConstrainedHalfedges.Dispose(); + + if (args.Verbose && status.IsError) Debug.LogError(status.ToFixedString()); + } + + public void PlantHoleSeeds (InputData input, OutputData output, Args args, Allocator allocator) { + new PlantingSeedStep(input, output, args).Execute(allocator, true); + } + + public void RefineMesh (OutputData output, Allocator allocator, T area2Threshold, T angleThreshold, bool constrainBoundary = false) { + new RefineMeshStep(output, area2Threshold, angleThreshold).Execute(allocator, refineMesh: true, constrainBoundary); + } + + private void PreProcessInputStep (InputData input, OutputData output, Args args, out NativeArray localHoles, out TTransform lt, Allocator allocator) { + using var _ = Markers.PreProcessInputStep.Auto(); + + var localPositions = output.Positions; + localPositions.ResizeUninitialized(input.Positions.Length); + if (args.Preprocessor == Preprocessor.PCA || args.Preprocessor == Preprocessor.COM) { + lt = args.Preprocessor == Preprocessor.PCA ? default(TTransform).CalculatePCATransformation(input.Positions) : default(TTransform).CalculateLocalTransformation(input.Positions); + for (int i = 0; i < input.Positions.Length; i++) { + localPositions[i] = lt.Transform(input.Positions[i]); + } + + localHoles = input.HoleSeeds.IsCreated ? new(input.HoleSeeds.Length, allocator) : default; + for (int i = 0; i < input.HoleSeeds.Length; i++) { + localHoles[i] = lt.Transform(input.HoleSeeds[i]); + } + } else if (args.Preprocessor == Preprocessor.None) { + localPositions.CopyFrom(input.Positions); + localHoles = input.HoleSeeds.IsCreated ? new(input.HoleSeeds, allocator) : default; + lt = default(TTransform).Identity; + } else { + throw new ArgumentException(); + } + } + + private void PostProcessInputStep (OutputData output, Args args, TTransform lt) { + if (args.Preprocessor == Preprocessor.None) { + return; + } + + using var _ = Markers.PostProcessInputStep.Auto(); + var inverse = lt.Inverse(); + for (int i = 0; i < output.Positions.Length; i++) { + output.Positions[i] = inverse.Transform(output.Positions[i]); + } + } + + private struct ValidateInputStep { + private NativeArray.ReadOnly positions; + private NativeReference status; + private readonly Args args; + private NativeArray.ReadOnly constraints; + private NativeArray.ReadOnly constraintTypes; + private NativeArray.ReadOnly holes; + + public ValidateInputStep(InputData input, OutputData output, Args args) { + positions = output.Positions.AsArray().AsReadOnly(); + status = output.Status; + this.args = args; + constraints = input.ConstraintEdges.AsReadOnly(); + constraintTypes = input.ConstraintEdgeTypes.AsReadOnly(); + holes = input.HoleSeeds.AsReadOnly(); + } + + public void Execute () { + if (!args.ValidateInput) { + return; + } + + using var _ = Markers.ValidateInputStep.Auto(); + + ValidateArgs(); + ValidatePositions(); + ValidateConstraints(); + ValidateHoles(); + } + + private void ValidateArgs () { + if (args.AutoHolesAndBoundary && !constraints.IsCreated) { + status.Value = Status.ConstraintEdgesMissingForAutoHolesAndBoundary; + } + + if (args.RestoreBoundary && !constraints.IsCreated) { + status.Value = Status.ConstraintEdgesMissingForRestoreBoundary; + } + + if (args.RefineMesh && !utils.SupportsRefinement()) { + status.Value = Status.RefinementNotSupportedForCoordinateType; + } + + if (constraints.IsCreated && args.SloanMaxIters < 1) { + status.Value = Status.SloanMaxItersMustBePositive(args.SloanMaxIters); + } + + if (args.RefineMesh && args.RefinementThresholdArea < 0) { + status.Value = Status.RefinementThresholdAreaMustBePositive; + } + + if (args.RefineMesh && args.RefinementThresholdAngle < 0 || args.RefinementThresholdAngle > math.PI / 4) { + status.Value = Status.RefinementThresholdAngleOutOfRange; + } + } + + private void ValidatePositions () { + if (positions.Length < 3) { + status.Value = Status.PositionsLengthLessThan3(positions.Length); + return; + } + + for (int i = 0; i < positions.Length; i++) { + if (math.any(!utils.isfinite(positions[i]))) { + status.Value = Status.PositionsMustBeFinite(i); + return; + } + + var pi = positions[i]; + for (int j = i + 1; j < positions.Length; j++) { + var pj = positions[j]; + if (math.all(utils.eq(pi, pj))) { + status.Value = Status.DuplicatePosition(i); + return; + } + } + } + } + + private void ValidateConstraints () { + if (!constraints.IsCreated) { + return; + } + + if (constraints.Length % 2 != 0) { + status.Value = Status.ConstraintsLengthNotDivisibleBy2(constraints.Length); + return; + } + + if (constraintTypes.IsCreated && constraintTypes.Length*2 != constraints.Length) { + status.Value = Status.ConstraintArrayLengthMismatch(constraints.Length, constraintTypes.Length); + return; + } + + // Edge validation + for (int i = 0; i < constraints.Length / 2; i++) { + var (a0Id, a1Id) = (constraints[2 * i], constraints[2 * i + 1]); + var count = positions.Length; + if (a0Id >= count || a0Id < 0 || a1Id >= count || a1Id < 0) { + status.Value = Status.ConstraintOutOfBounds(i, new int2(a0Id, a1Id), count); + return; + } + + if (a0Id == a1Id) { + status.Value = Status.ConstraintSelfLoop(i, new int2(a0Id, a1Id)); + return; + } + } + + // Edge-edge validation + for (int i = 0; i < constraints.Length / 2; i++) { + var (a0Id, a1Id) = (constraints[2 * i], constraints[2 * i + 1]); + var (a0, a1) = (positions[a0Id], positions[a1Id]); + + for (int j = i + 1; j < constraints.Length / 2; j++) { + var (b0Id, b1Id) = (constraints[2 * j], constraints[2 * j + 1]); + + if (a0Id == b0Id && a1Id == b1Id || a0Id == b1Id && a1Id == b0Id) { + status.Value = Status.DuplicateConstraint(i, j); + return; + } + + // One common point, cases should be filtered out at edge-point validation + if (a0Id == b0Id || a0Id == b1Id || a1Id == b0Id || a1Id == b1Id) { + continue; + } + + var (b0, b1) = (positions[b0Id], positions[b1Id]); + // Check if the two constraints intersect, but ignore if they only overlap at endpoints + if (EdgeEdgeIntersection(a0, a1, b0, b1) && !(PointLineSegmentIntersection(a0, b0, b1) || PointLineSegmentIntersection(a1, b0, b1) || PointLineSegmentIntersection(b0, a0, a1) || PointLineSegmentIntersection(b1, a0, a1))) { + status.Value = Status.ConstraintIntersection(i, j); + return; + } + } + } + } + + private void ValidateHoles () { + if (!holes.IsCreated) { + return; + } + + if (!constraints.IsCreated) { + status.Value = Status.RedudantHolesArray; + } + + for (int i = 0; i < holes.Length; i++) { + if (math.any(!utils.isfinite(holes[i]))) { + status.Value = Status.HoleMustBeFinite(i); + return; + } + } + } + } + + /// + /// This step is based on the following projects: + /// + /// delaunator + /// delaunator-sharp + /// + /// + private struct DelaunayTriangulationStep { + private struct DistComparer : IComparer { + private NativeArray dist; + public DistComparer(NativeArray dist) => this.dist = dist; + public int Compare(int x, int y) => dist[x].CompareTo(dist[y]); + } + + private NativeReference status; + private NativeArray.ReadOnly positions; + private NativeList triangles; + private NativeList halfedges; + private NativeList constrainedHalfedges; + private NativeArray hullNext, hullPrev, hullTri, hullHash; + private NativeArray EDGE_STACK; + + private readonly int hashSize; + private readonly bool verbose; + private int hullStart; + private int trianglesLen; + + public DelaunayTriangulationStep(OutputData output, Args args) { + status = output.Status; + // Note: At this point these are the input positions (possibly transformed by a preprocessor) + positions = output.Positions.AsArray().AsReadOnly(); + triangles = output.Triangles; + halfedges = output.Halfedges; + constrainedHalfedges = output.ConstrainedHalfedges; + hullStart = int.MaxValue; + verbose = args.Verbose; + hashSize = (int)math.ceil(math.sqrt(positions.Length)); + trianglesLen = default; + + hullNext = default; + hullPrev = default; + hullTri = default; + hullHash = default; + EDGE_STACK = default; + } + + public void Execute (Allocator allocator) { + if (status.Value.IsError) { + return; + } + + using var _ = Markers.DelaunayTriangulationStep.Auto(); + + var n = positions.Length; + var maxTriangles = math.max(2 * n - 5, 0); + triangles.Length = 3 * maxTriangles; + halfedges.Length = 3 * maxTriangles; + + var ids = new NativeArray(n, allocator); + + var min = utils.MaxValue2(); + var max = utils.MinValue2(); + for (int i = 0; i < positions.Length; i++) { + var p = positions[i]; + min = utils.min(min, p); + max = utils.max(max, p); + ids[i] = i; + } + + var center = utils.avg(min, max); + + int i0 = int.MaxValue, i1 = int.MaxValue, i2 = int.MaxValue; + var minDistSq = utils.MaxValue(); + for (int i = 0; i < positions.Length; i++) { + var distSq = utils.distancesq(center, positions[i]); + if (utils.less(distSq, minDistSq)) { + i0 = i; + minDistSq = distSq; + } + } + + // Centermost vertex + var p0 = positions[i0]; + + minDistSq = utils.MaxValue(); + for (int i = 0; i < positions.Length; i++) { + if (i == i0) continue; + var distSq = utils.distancesq(p0, positions[i]); + if (utils.less(distSq, minDistSq)) { + i1 = i; + minDistSq = distSq; + } + } + + // Second closest to the center + var p1 = positions[i1]; + + var minRadius = utils.MaxValue(); + for (int i = 0; i < positions.Length; i++) { + if (i == i0 || i == i1) continue; + var p = positions[i]; + var r = CircumRadiusSq(p0, p1, p); + if (utils.less(r, minRadius)) { + i2 = i; + minRadius = r; + } + } + + // NOTE: Since `int` does not support NaN or infinity, a circumcenter check is required for int2 validation. + // The CircumRadiusSq calculation may have overflowed and returned garbage values. + if (i2 == int.MaxValue || math.any(utils.eq(utils.CircumCenter(p0, p1, positions[i2]), utils.MaxValue2()))) { + status.Value = Status.DegenerateInput; + ids.Dispose(); + return; + } + + using var _hullPrev = hullPrev = new(n, allocator); + using var _hullNext = hullNext = new(n, allocator); + using var _hullTri = hullTri = new(n, allocator); + using var _hullHash = hullHash = new(hashSize, allocator); + using var _EDGE_STACK = EDGE_STACK = new(math.min(3 * maxTriangles, 512), allocator); + var dists = new NativeArray(n, allocator); + + // Vertex closest to p1 and p2, as measured by the circumscribed circle radius of p1, p2, p3 + // Thus (p1,p2,p3) form a triangle close to the center of the point set, and it's guaranteed that there + // are no other vertices inside this triangle. + var p2 = positions[i2]; + + // Swap the order of the vertices if the triangle is not oriented in the right direction + if (utils.less(Orient2dFast(p0, p1, p2), utils.ZeroTBig())) { + (i1, i2) = (i2, i1); + (p1, p2) = (p2, p1); + } + + // Sort all other vertices by their distance to the circumcenter of the initial triangle + var c = utils.CircumCenter(p0, p1, p2); + + for (int i = 0; i < positions.Length; i++) { + dists[i] = utils.distancesq(c, positions[i]); + } + + ids.Sort(new DistComparer(dists)); + + hullStart = i0; + + hullNext[i0] = hullPrev[i2] = i1; + hullNext[i1] = hullPrev[i0] = i2; + hullNext[i2] = hullPrev[i1] = i0; + + hullTri[i0] = 0; + hullTri[i1] = 1; + hullTri[i2] = 2; + + hullHash[utils.hashkey(p0, c, hashSize)] = i0; + hullHash[utils.hashkey(p1, c, hashSize)] = i1; + hullHash[utils.hashkey(p2, c, hashSize)] = i2; + + // Add the initial triangle + AddTriangle(i0, i1, i2, -1, -1, -1); + + for (var k = 0; k < ids.Length; k++) { + var i = ids[k]; + if (i == i0 || i == i1 || i == i2) continue; + + var p = positions[i]; + + // Find a visible edge on the convex hull using edge hash + var start = 0; + var key = utils.hashkey(p, c, hashSize); + for (var j = 0; j < hashSize; j++) { + start = hullHash[(key + j) % hashSize]; + if (start != -1 && start != hullNext[start]) break; + } + + start = hullPrev[start]; + var e = start; + var q = hullNext[e]; + + while (!utils.less(Orient2dFast(p, positions[e], positions[q]), utils.ZeroTBig())) { + e = q; + if (e == start) { + e = int.MaxValue; + break; + } + + q = hullNext[e]; + } + + if (e == int.MaxValue) continue; + + // Add the first triangle from the point + var t = AddTriangle(e, i, hullNext[e], -1, -1, hullTri[e]); + + // Recursively flip triangles from the point until they satisfy the Delaunay condition + hullTri[i] = Legalize(t + 2); + // Keep track of boundary triangles on the hull + hullTri[e] = t; + + var next = hullNext[e]; + q = hullNext[next]; + + // Walk forward through the hull, adding more triangles and flipping recursively + while (utils.less(Orient2dFast(p, positions[next], positions[q]), utils.ZeroTBig())) { + t = AddTriangle(next, i, q, hullTri[i], -1, hullTri[next]); + hullTri[i] = Legalize(t + 2); + hullNext[next] = next; + next = q; + + q = hullNext[next]; + } + + // Walk backward from the other side, adding more triangles and flipping + if (e == start) { + q = hullPrev[e]; + + while (utils.less(Orient2dFast(p, positions[q], positions[e]), utils.ZeroTBig())) { + t = AddTriangle(q, i, e, -1, hullTri[e], hullTri[q]); + Legalize(t + 2); + hullTri[q] = t; + hullNext[e] = e; // mark as removed + e = q; + q = hullPrev[e]; + } + } + + // Update the hull indices + hullStart = hullPrev[i] = e; + hullNext[e] = hullPrev[next] = i; + hullNext[i] = next; + + // Save the two new edges in the hash table + hullHash[utils.hashkey(p, c, hashSize)] = i; + hullHash[utils.hashkey(positions[e], c, hashSize)] = e; + } + + // Trim lists to their actual size + triangles.Length = trianglesLen; + halfedges.Length = trianglesLen; + constrainedHalfedges.Length = trianglesLen; + + ids.Dispose(); + dists.Dispose(); + } + + private int Legalize (int a) { + var stackSize = 0; + int ar; + + // recursion eliminated with a fixed-size stack + while (true) { + var b = halfedges[a]; + /* if the pair of triangles doesn't satisfy the Delaunay condition + * (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + * then do the same check/flip recursively for the new pair of triangles + * + * pl pl + * /||\ / \ + * al/ || \bl al/ \a + * / || \ / \ + * / a||b \ flip /___ar___\ + * p0\ || /p1 => p0\---bl---/p1 + * \ || / \ / + * ar\ || /br b\ /br + * \||/ \ / + * pr pr + */ + + int a0 = a - a % 3; + ar = a0 + (a + 2) % 3; + + // Check if we are on a convex hull edge + if (b == -1) { + if (stackSize == 0) break; + a = EDGE_STACK[--stackSize]; + continue; + } + + var b0 = b - b % 3; + var al = a0 + (a + 1) % 3; + var bl = b0 + (b + 2) % 3; + + var p0 = triangles[ar]; + var pr = triangles[a]; + var pl = triangles[al]; + var p1 = triangles[bl]; + + var illegal = utils.InCircle(positions[p0], positions[pr], positions[pl], positions[p1]); + + if (illegal) { + triangles[a] = p1; + triangles[b] = p0; + + var hbl = halfedges[bl]; + + // Edge swapped on the other side of the hull (rare); fix the halfedge reference + if (hbl == -1) { + var e = hullStart; + do{ + if (hullTri[e] == bl) { + hullTri[e] = a; + break; + } + e = hullPrev[e]; + } while (e != hullStart); + } + Link(a, hbl); + Link(b, halfedges[ar]); + Link(ar, bl); + + var br = b0 + (b + 1) % 3; + + // Don't worry about hitting the cap: it can only happen on extremely degenerate input + if (stackSize < EDGE_STACK.Length) { + EDGE_STACK[stackSize++] = br; + } + } else { + if (stackSize == 0) break; + a = EDGE_STACK[--stackSize]; + } + } + + return ar; + } + + private int AddTriangle (int i0, int i1, int i2, int a, int b, int c) { + var t = trianglesLen; + + triangles[t + 0] = i0; + triangles[t + 1] = i1; + triangles[t + 2] = i2; + + Link(t + 0, a); + Link(t + 1, b); + Link(t + 2, c); + + trianglesLen += 3; + + return t; + } + + private void Link (int a, int b) { + halfedges[a] = b; + if (b != -1) halfedges[b] = a; + } + } + + /// + /// This step implements Sloan algorithm. + /// Read more in the paper: + /// + /// S. W. Sloan. "A fast algorithm for generating constrained Delaunay triangulations." Comput. Struct. 47.3:441-450 (1993). + /// + /// + private struct ConstrainEdgesStep { + private NativeReference status; + private NativeArray.ReadOnly positions; + private NativeArray triangles; + private NativeArray.ReadOnly inputConstraintEdges; + private NativeArray.ReadOnly inputConstraintEdgeTypes; + /// + /// There are 3 halfedges per triangle. For a triangle ABC, the halfedges are A->B, B->C, and C->A. + /// + /// For each halfedge index, the value is the index of the opposite halfedge in the adjacent triangle, + /// or -1 if the halfedge is on the convex hull of the triangulation. + /// + // NOTE: `halfedges` and `constrainedHalfedges` can be NativeArray, however, Job system can throw here the exception: + // + // ``` + // InvalidOperationException: The Unity.Collections.NativeList`1[System.Int32] + // has been declared as [WriteOnly] in the job, but you are reading from it. + // ``` + // + // See the `UsingTempAllocatorInJobTest` to learn more. + private NativeList halfedges; + private NativeList constrainedHalfedges; + private readonly Args args; + + private NativeList intersections; + private NativeList unresolvedIntersections; + + /// + /// Maps vertex indices to halfedge indices. + /// If a vertex is part of multiple halfedges, an arbitrary representative halfedge is stored. + /// + private NativeArray pointToHalfedge; + + public ConstrainEdgesStep(InputData input, OutputData output, Args args) { + status = output.Status; + positions = output.Positions.AsArray().AsReadOnly(); + triangles = output.Triangles.AsArray(); + inputConstraintEdges = input.ConstraintEdges.AsReadOnly(); + inputConstraintEdgeTypes = input.ConstraintEdgeTypes.AsReadOnly(); + halfedges = output.Halfedges; + constrainedHalfedges = output.ConstrainedHalfedges; + this.args = args; + + intersections = default; + unresolvedIntersections = default; + pointToHalfedge = default; + } + + public void Execute (Allocator allocator) { + if (!inputConstraintEdges.IsCreated || status.Value.IsError) { + return; + } + + using var _ = Markers.ConstrainEdgesStep.Auto(); + + using var _intersections = intersections = new NativeList(allocator); + using var _unresolvedIntersections = unresolvedIntersections = new NativeList(allocator); + using var _pointToHalfedge = pointToHalfedge = new NativeArray(positions.Length, allocator); + + // build point to halfedge + for (int i = 0; i < triangles.Length; i++) { + pointToHalfedge[triangles[i]] = i; + } + + for (int index = 0; index < inputConstraintEdges.Length / 2; index++) { + var c = math.int2( + inputConstraintEdges[2 * index + 0], + inputConstraintEdges[2 * index + 1] + ); + c = c.x < c.y ? c.xy : c.yx; // Backward compatibility. To remove in the future. + var type = inputConstraintEdgeTypes.IsCreated ? inputConstraintEdgeTypes[index] : ConstraintType.ConstrainedAndHoleBoundary; + TryApplyConstraint(c, type == ConstraintType.Constrained ? HalfedgeState.Constrained : HalfedgeState.ConstrainedAndHoleBoundary); + } + } + + private void TryResolveIntersections (int2 c, HalfedgeState constrainValue, ref int iter) { + for (int i = 0; i < intersections.Length; i++) { + if (IsMaxItersExceeded(iter++, args.SloanMaxIters)) { + return; + } + + // p i + // h2 -----------> h0 h4 + // ^ .' .^: + // : .' .' : + // : .' .' : + // : .' .' : + // : .' .' : + // : .' .' : + // : v' .' v + // h1 h3 <----------- h5 + // j q + // + // p i + // h2 h3 -----------> h4 + // ^ '. ^. : + // : '. '. : + // : '. '. : + // : '. '. : + // : '. '. : + // : '. '. : + // : 'v '.v + // h1 <----------- h0 h5 + // j q + // + // Changes: + // --------------------------------------------- + // h0 h1 h2 | h3 h4 h5 + // --------------------------------------------- + // triangles i j p | j i q + // triangles' *q* j p | *p* i q + // --------------------------------------------- + // halfedges h3 g1 g2 | h0 f1 f2 + // halfedges' *h5'* g1 *h5* | *h2'* f1 *h2*, where hi' = halfedge[hi] + // --------------------------------------------- + // intersec.' X X h3 | X X h0 + // --------------------------------------------- + + var h0 = intersections[i]; + var h1 = NextHalfedge(h0); + var h2 = NextHalfedge(h1); + + var h3 = halfedges[h0]; + var h4 = NextHalfedge(h3); + var h5 = NextHalfedge(h4); + + var _i = triangles[h0]; + var _j = triangles[h1]; + var _p = triangles[h2]; + var _q = triangles[h5]; + + var (p0, p1, p2, p3) = (positions[_i], positions[_q], positions[_j], positions[_p]); + if (!IsConvexQuadrilateral(p0, p1, p2, p3)) { + unresolvedIntersections.Add(h0); + continue; + } + + // Swap edge (see figure above) + triangles[h0] = _q; + triangles[h3] = _p; + pointToHalfedge[_q] = h0; + pointToHalfedge[_p] = h3; + pointToHalfedge[_i] = h4; + pointToHalfedge[_j] = h1; + ReplaceHalfedge(h5, h0); + ReplaceHalfedge(h2, h3); + halfedges[h2] = h5; + halfedges[h5] = h2; + constrainedHalfedges[h2] = HalfedgeState.Unconstrained; + constrainedHalfedges[h5] = HalfedgeState.Unconstrained; + + // Fix intersections + for (int j = i + 1; j < intersections.Length; j++) { + var tmp = intersections[j]; + intersections[j] = tmp == h2 ? h3 : tmp == h5 ? h0 : tmp; + } + for (int j = 0; j < unresolvedIntersections.Length; j++) { + var tmp = unresolvedIntersections[j]; + unresolvedIntersections[j] = tmp == h2 ? h3 : tmp == h5 ? h0 : tmp; + } + + var swapped = math.int2(_p, _q); + if (math.all(c.xy == swapped.xy) || math.all(c.xy == swapped.yx)) { + constrainedHalfedges[h2] = constrainValue; + constrainedHalfedges[h5] = constrainValue; + } + if (EdgeEdgeIntersection(c, swapped)) { + unresolvedIntersections.Add(h2); + } + } + + intersections.Clear(); + } + + /// + /// Replaces with . + /// + private void ReplaceHalfedge (int h0, int h1) { + var h0p = halfedges[h0]; + halfedges[h1] = h0p; + constrainedHalfedges[h1] = constrainedHalfedges[h0]; + + if (h0p != -1) { + halfedges[h0p] = h1; + constrainedHalfedges[h0p] = constrainedHalfedges[h0]; + } + } + + private bool EdgeEdgeIntersection (int2 e1, int2 e2) { + var (a0, a1) = (positions[e1.x], positions[e1.y]); + var (b0, b1) = (positions[e2.x], positions[e2.y]); + return !(math.any(e1.xy == e2.xy | e1.xy == e2.yx)) && UnsafeTriangulator.EdgeEdgeIntersection(a0, a1, b0, b1); + } + + void MarkHalfedgeConstrained (int halfedge, HalfedgeState constrainValue) { + // If two constraints overlap, the one which is more constrained is used + constrainValue = (HalfedgeState)math.max((int)constrainedHalfedges[halfedge], (int)constrainValue); + constrainedHalfedges[halfedge] = constrainValue; + var oh = halfedges[halfedge]; + if (oh != -1) { + constrainedHalfedges[oh] = constrainValue; + } + } + + private void TryApplyConstraint (int2 edge, HalfedgeState constrainValue) { + intersections.Clear(); + unresolvedIntersections.Clear(); + + // We start at the vertex ci=edge.x, + // then we walk in a straight line through the triangulated mesh + // until we reach the vertex cj=edge.y. + // Along the way, we collect all halfedges that intersect with the edge ci-cj + // and add them as unresolved intersections. + // + // If we find a vertex k (not ci or cj) that lies exactly on the edge ci-cj, + // then the constraint cannot be satisfied as is, and we instead split the + // constraint into two: ci-k and k-cj. This can happen multiple times. + + // 1. Check if h1 is cj + // 2. Check if h1-h2 intersects with ci-cj + // 3. After each iteration: h0 <- h0' + // + // h1 + // .^ | + // .' | + // .' v + // h0 <---- h2 + // h0'----> h1' + // ^. | + // '. | + // '. v + // h2' + var tunnelInit = -1; + var (ci, cj) = (edge.x, edge.y); + var h0init = pointToHalfedge[ci]; + var h0 = h0init; + do{ + var h1 = NextHalfedge(h0); + if (triangles[h1] == cj) { + MarkHalfedgeConstrained(h0, constrainValue); + break; + } + var h2 = NextHalfedge(h1); + + if (PointLineSegmentIntersection(positions[triangles[h1]], positions[ci], positions[cj])) { + // h1 lies on the edge ci-cj, split the constraint + // Note: h1 and h2 cannot both lie on the constraint, since that would mean that the triangle h0,h1,h2 is degenerate + MarkHalfedgeConstrained(h0, constrainValue); + cj = triangles[h1]; + break; + } + if (PointLineSegmentIntersection(positions[triangles[h2]], positions[ci], positions[cj]) && triangles[h2] != cj) { + // h2 lies on the edge ci-cj, split the constraint + MarkHalfedgeConstrained(h2, constrainValue); + cj = triangles[h2]; + break; + } + if (EdgeEdgeIntersection(new int2(ci, cj), new(triangles[h1], triangles[h2]))) { + unresolvedIntersections.Add(h1); + tunnelInit = halfedges[h1]; + break; + } + + h0 = halfedges[h2]; + + // Boundary reached check other side + if (h0 == -1) { + if (triangles[h2] == cj) { + MarkHalfedgeConstrained(h2, constrainValue); + } + + // possible that triangles[h2] == cj, not need to check + break; + } + } while (h0 != h0init); + + h0 = halfedges[h0init]; + if (tunnelInit == -1 && h0 != -1) { + h0 = NextHalfedge(h0); + // Same but reversed + do{ + var h1 = NextHalfedge(h0); + if (triangles[h1] == cj) { + MarkHalfedgeConstrained(h0, constrainValue); + break; + } + var h2 = NextHalfedge(h1); + + if (PointLineSegmentIntersection(positions[triangles[h1]], positions[ci], positions[cj])) { + // h1 lies on the edge ci-cj, split the constraint + MarkHalfedgeConstrained(h0, constrainValue); + cj = triangles[h1]; + break; + } + if (PointLineSegmentIntersection(positions[triangles[h2]], positions[ci], positions[cj]) && triangles[h2] != cj) { + // h2 lies on the edge ci-cj, split the constraint + MarkHalfedgeConstrained(h2, constrainValue); + cj = triangles[h2]; + break; + } + if (EdgeEdgeIntersection(new int2(ci, cj), new(triangles[h1], triangles[h2]))) { + unresolvedIntersections.Add(h1); + tunnelInit = halfedges[h1]; + break; + } + + h0 = halfedges[h0]; + // Boundary reached + if (h0 == -1) { + break; + } + h0 = NextHalfedge(h0); + } while (h0 != h0init); + } + + // Tunnel algorithm + // At this point we know that the segment ci->cj enters the triangle h0-h1-h2 via the edge h0-h1. + // In each iteration we have three options: + // * The segment ci-cj exits the triangle via one of its other edges, + // in which case we follow to a the next triangle, + // * h2 is cj, in which case we are done. + // * h2 lies on the segment ci-cj, in which case we split the constraint as above. + // + // h2' + // ^'. + // | '. + // | 'v + // h1'<-- h0' + // h1 --> h2 h1'' + // ^ .' ^ | + // | .' .' | + // |v .' v + // h0 h0''<--h2'' + // + // 1. if h2 == cj break + // 2. if h1-h2 intersects ci-cj, repeat with h0 <- halfedges[h1] = h0' + // 3. if h2-h0 intersects ci-cj, repeat with h0 <- halfedges[h2] = h0'' + while (tunnelInit != -1) { + var h0p = tunnelInit; + tunnelInit = -1; + var h1p = NextHalfedge(h0p); + var h2p = NextHalfedge(h1p); + + if (triangles[h2p] == cj) { + break; + } + if (PointLineSegmentIntersection(positions[triangles[h2p]], positions[ci], positions[cj])) { + cj = triangles[h2p]; + break; + } else if (EdgeEdgeIntersection(new int2(ci, cj), new(triangles[h1p], triangles[h2p]))) { + unresolvedIntersections.Add(h1p); + tunnelInit = halfedges[h1p]; + } else if (EdgeEdgeIntersection(new int2(ci, cj), new(triangles[h2p], triangles[h0p]))) { + unresolvedIntersections.Add(h2p); + tunnelInit = halfedges[h2p]; + } + } + + var iter = 0; + do{ + if (status.Value.IsError) { + return; + } + + (intersections, unresolvedIntersections) = (unresolvedIntersections, intersections); + TryResolveIntersections(new int2(ci, cj), constrainValue, ref iter); + } while (!unresolvedIntersections.IsEmpty); + + // If the constraint was split, continue with the remaining part + if (edge.y != cj) { + TryApplyConstraint(new int2(cj, edge.y), constrainValue); + } + } + + private bool IsMaxItersExceeded (int iter, int maxIters) { + if (iter >= maxIters) { + status.Value = Status.SloanMaxItersExceeded; + return true; + } + return false; + } + } + + private struct PlantingSeedStep { + private NativeReference status; + private NativeList triangles; + [ReadOnly] + private NativeList positions; + private NativeList constrainedHalfedges; + private NativeList halfedges; + private NativeArray shouldRemoveTriangle; + private NativeQueue trianglesQueue; + private NativeArray holes; + private bool anyRemovedTriangles; + + private readonly Args args; + + public PlantingSeedStep(InputData input, OutputData output, Args args) : this(output, args, input.HoleSeeds) { } + + public PlantingSeedStep(OutputData output, Args args, NativeArray localHoles) { + status = output.Status; + triangles = output.Triangles; + positions = output.Positions; + constrainedHalfedges = output.ConstrainedHalfedges; + halfedges = output.Halfedges; + holes = localHoles; + this.args = args; + + shouldRemoveTriangle = default; + trianglesQueue = default; + anyRemovedTriangles = false; + } + + public void Execute (Allocator allocator, bool constraintsIsCreated) { + if (!constraintsIsCreated || status.IsCreated && status.Value.IsError) { + return; + } + + using var _ = Markers.PlantingSeedStep.Auto(); + + using var _shouldRemoveTriangle = shouldRemoveTriangle = new(triangles.Length / 3, allocator); + + if (args.AutoHolesAndBoundary) PlantAuto(allocator); + if (holes.IsCreated || args.RestoreBoundary) { + trianglesQueue = new(allocator); + if (holes.IsCreated) PlantHoleSeeds(holes); + if (args.RestoreBoundary) PlantBoundarySeeds(); + trianglesQueue.Dispose(); + } + + RemoveVisitedTriangles(allocator); + } + + private void PlantBoundarySeeds () { + for (int he = 0; he < halfedges.Length; he++) { + if (halfedges[he] == -1 && + !shouldRemoveTriangle[he / 3] && + constrainedHalfedges[he] != HalfedgeState.ConstrainedAndHoleBoundary) { + PlantSeed(he / 3); + } + } + } + + private void PlantHoleSeeds (NativeArray holeSeeds) { + foreach (var s in holeSeeds) { + var tId = FindTriangle(s); + if (tId != -1) { + PlantSeed(tId); + } + } + } + + + private void RemoveVisitedTriangles (Allocator allocator) { + if (!anyRemovedTriangles) { + return; + } + + // Indices to remove are marked with -1, otherwise they are assigned with incremental id. + var indexRemap = new NativeArray(triangles.Length / 3, allocator); + var count = 0; + for (int tId = 0; tId < shouldRemoveTriangle.Length; tId++) { + indexRemap[tId] = shouldRemoveTriangle[tId] ? -1 : count++; + } + + int RemapHalfedge (int he) { + if (he == -1) { + return -1; + } + var newIndex = indexRemap[he / 3]; + return newIndex == -1 ? -1 : 3 * newIndex + he % 3; + } + + // Reinterpret to a larger struct to make copies of whole triangles slightly more efficient + var constrainedHalfedges3 = constrainedHalfedges.AsArray().Reinterpret(1); + var triangles3 = triangles.AsArray().Reinterpret(4); + + // Copy the triangles, constrained halfedges, and halfedges to new indices in-place. + for (int tId = 0; tId < indexRemap.Length; tId++) { + var tIdNew = indexRemap[tId]; + if (tIdNew != -1) { + triangles3[tIdNew] = triangles3[tId]; + constrainedHalfedges3[tIdNew] = constrainedHalfedges3[tId]; + halfedges[3 * tIdNew + 0] = RemapHalfedge(halfedges[3 * tId + 0]); + halfedges[3 * tIdNew + 1] = RemapHalfedge(halfedges[3 * tId + 1]); + halfedges[3 * tIdNew + 2] = RemapHalfedge(halfedges[3 * tId + 2]); + } + } + + // Trim the data to reflect removed triangles. + triangles.Length = 3 * count; + constrainedHalfedges.Length = 3 * count; + halfedges.Length = 3 * count; + + indexRemap.Dispose(); + } + + private void PlantSeed (int tId) { + var shouldRemoveTriangle = this.shouldRemoveTriangle; + var trianglesQueue = this.trianglesQueue; + + if (shouldRemoveTriangle[tId]) { + return; + } + + shouldRemoveTriangle[tId] = true; + trianglesQueue.Enqueue(tId); + anyRemovedTriangles = true; + + // Search outwards from the seed triangle and mark all triangles + // until we get to a hole boundary, or a previously visited triangle. + while (trianglesQueue.TryDequeue(out tId)) { + for (int i = 0; i < 3; i++) { + var he = 3 * tId + i; + var ohe = halfedges[he]; + if (constrainedHalfedges[he] == HalfedgeState.ConstrainedAndHoleBoundary || ohe == -1) { + continue; + } + + var otherId = ohe / 3; + if (!shouldRemoveTriangle[otherId]) { + shouldRemoveTriangle[otherId] = true; + trianglesQueue.Enqueue(otherId); + } + } + } + } + + private int FindTriangle (T2 p) { + for (int tId = 0; tId < triangles.Length / 3; tId++) { + var (i, j, k) = (triangles[3 * tId + 0], triangles[3 * tId + 1], triangles[3 * tId + 2]); + var (a, b, c) = (positions[i], positions[j], positions[k]); + if (utils.PointInsideTriangle(p, a, b, c)) { + return tId; + } + } + + return -1; + } + + private void PlantAuto (Allocator allocator) { + var triCount = triangles.Length / 3; + var queue = new NativeQueue(allocator); + var nextQueue = new NativeQueue(allocator); + var visitedTriangles = new NativeArray(triCount, allocator); + + // Start at the boundary of the triangulation + for (int tId = 0; tId < triCount; tId++) { + for (int i = 0; i < 3; i++) { + if (halfedges[3*tId + i] == -1) { + (constrainedHalfedges[3*tId + i] == HalfedgeState.ConstrainedAndHoleBoundary ? nextQueue : queue).Enqueue(tId); + visitedTriangles[tId] = true; + break; + } + } + } + + // Search inwards from the boundary + // When crossing a hole bondary, we flip between removing triangles and keeping triangles + // This effectively implements the EvenOdd fill mode (https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule). + bool anyRemovedTriangles = false; + for (bool remove = true; !queue.IsEmpty() || !nextQueue.IsEmpty(); remove = !remove) { + while (queue.TryDequeue(out var tId)) { + if (remove) { + shouldRemoveTriangle[tId] = true; + anyRemovedTriangles = true; + } + + for (int i = 0; i < 3; i++) { + var he = 3 * tId + i; + var ohe = halfedges[he]; + var oTri = ohe / 3; + if (ohe != -1 && !visitedTriangles[oTri]) { + (constrainedHalfedges[he] == HalfedgeState.ConstrainedAndHoleBoundary ? nextQueue : queue).Enqueue(oTri); + visitedTriangles[oTri] = true; + } + } + } + + (queue, nextQueue) = (nextQueue, queue); + } + + this.anyRemovedTriangles = anyRemovedTriangles; + queue.Dispose(); + nextQueue.Dispose(); + visitedTriangles.Dispose(); + } + } + + private struct RefineMeshStep { + private readonly struct Circle { + public readonly T2 Center; + public readonly T RadiusSq; + public Circle((T2 center, T radiusSq) circle) => (Center, RadiusSq) = (circle.center, circle.radiusSq); + } + + private NativeReference status; + private NativeList triangles; + private NativeList outputPositions; + private NativeList halfedges; + private NativeList constrainedHalfedges; + + private NativeList circles; + private NativeQueue trianglesQueue; + private NativeList badTriangles; + private NativeList pathPoints; + private NativeList pathHalfedges; + private NativeList visitedTriangles; + + private readonly T maximumArea2, angleThreshold; + private readonly int initialPointsCount; + + /// + /// A parameter for the concentric shells edge splitting algorithm. + /// This is a pretty arbitrary constant. Changing it has a minor effect on the triangulation, but only in rare situations. + /// Pretty much any value is as good as any other. + /// + /// See `Delaunay Refinement Algorithm for Quality 2-Dimensional Mesh Generation` page 40. + /// + const float ConcentricShellReferenceRadius = 0.001f; + + public RefineMeshStep(OutputData output, Args args, TTransform lt) : this(output, + area2Threshold: utils.Cast(utils.mul(utils.Cast(utils.mul(utils.Const(2), utils.Const(args.RefinementThresholdArea))), lt.AreaScalingFactor)), + angleThreshold: utils.Const(args.RefinementThresholdAngle)) + { } + + public RefineMeshStep(OutputData output, T area2Threshold, T angleThreshold) { + status = output.Status; + initialPointsCount = output.Positions.Length; + maximumArea2 = area2Threshold; + this.angleThreshold = angleThreshold; + triangles = output.Triangles; + outputPositions = output.Positions; + halfedges = output.Halfedges; + constrainedHalfedges = output.ConstrainedHalfedges; + + circles = default; + trianglesQueue = default; + badTriangles = default; + pathPoints = default; + pathHalfedges = default; + visitedTriangles = default; + } + + public void Execute (Allocator allocator, bool refineMesh, bool constrainBoundary) { + if (!refineMesh || status.IsCreated && status.Value.IsError) { + return; + } + + using var _ = Markers.RefineMeshStep.Auto(); + + if (!utils.SupportsRefinement()) { + status.Value = Status.IntegersDoNotSupportMeshRefinement; + return; + } + + if (constrainBoundary) { + for (int he = 0; he < constrainedHalfedges.Length; he++) { + constrainedHalfedges[he] = halfedges[he] == -1 ? HalfedgeState.ConstrainedAndHoleBoundary : HalfedgeState.Unconstrained; + } + } + + using var _circles = circles = new(allocator) { Length = triangles.Length / 3 }; + using var _trianglesQueue = trianglesQueue = new(allocator); + using var _badTriangles = badTriangles = new(triangles.Length / 3, allocator); + using var _pathPoints = pathPoints = new(allocator); + using var _pathHalfedges = pathHalfedges = new(allocator); + using var _visitedTriangles = visitedTriangles = new(triangles.Length / 3, allocator); + + using var heQueue = new NativeList(triangles.Length, allocator); + using var tQueue = new NativeList(triangles.Length, allocator); + + for (int tId = 0; tId < triangles.Length / 3; tId++) { + var (i, j, k) = (triangles[3 * tId + 0], triangles[3 * tId + 1], triangles[3 * tId + 2]); + circles[tId] = new(CalculateCircumCircle(i, j, k, outputPositions.AsArray())); + } + + // Collect encroached half-edges. + for (int he = 0; he < constrainedHalfedges.Length; he++) { + if (constrainedHalfedges[he] >= HalfedgeState.Constrained && IsEncroached(he)) { + heQueue.Add(he); + } + } + + SplitEncroachedEdges(heQueue, tQueue: default); // ignore bad triangles in this run + + // Collect encroached triangles + for (int tId = 0; tId < triangles.Length / 3; tId++) { + if (IsBadTriangle(tId)) { + tQueue.Add(tId); + } + } + + // Split triangles + for (int i = 0; i < tQueue.Length; i++) { + var tId = tQueue[i]; + if (tId != -1) { + SplitTriangle(tId, heQueue, tQueue, allocator); + } + } + } + + private void SplitEncroachedEdges (NativeList heQueue, NativeList tQueue) { + for (int i = 0; i < heQueue.Length; i++) { + var he = heQueue[i]; + if (he != -1) { + SplitEdge(he, heQueue, tQueue); + } + } + heQueue.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsEncroached (int he0) { + var he1 = NextHalfedge(he0); + var he2 = NextHalfedge(he1); + + var p0 = outputPositions[triangles[he0]]; + var p1 = outputPositions[triangles[he1]]; + var p2 = outputPositions[triangles[he2]]; + + return utils.le(utils.dot(utils.diff(p0, p2), utils.diff(p1, p2)), utils.Zero()); + } + + private void SplitEdge (int he, NativeList heQueue, NativeList tQueue) { + var (i, j) = (triangles[he], triangles[NextHalfedge(he)]); + var (e0, e1) = (outputPositions[i], outputPositions[j]); + + T2 p; + // Use midpoint method for: + // - the first segment split, + // - subsegment not made of input vertices. + // Otherwise, use "concentric circular shells". + if (i < initialPointsCount && j < initialPointsCount || + i >= initialPointsCount && j >= initialPointsCount) { + p = utils.avg(e0, e1); + } else { + var alpha = utils.alpha(utils.Const(ConcentricShellReferenceRadius), dSquare: utils.Cast(utils.distancesq(e0, e1))); + // Swap points to provide symmetry in splitting + p = i < initialPointsCount ? utils.lerp(e0, e1, alpha) : utils.lerp(e1, e0, alpha); + } + + constrainedHalfedges[he] = HalfedgeState.Unconstrained; + var ohe = halfedges[he]; + if (ohe != -1) { + constrainedHalfedges[ohe] = HalfedgeState.Unconstrained; + } + + if (halfedges[he] != -1) { + UnsafeInsertPointBulk(p, initTriangle: he / 3, heQueue, tQueue); + + var h0 = triangles.Length - 3; + var hi = -1; + var hj = -1; + while (hi == -1 || hj == -1) { + var h1 = NextHalfedge(h0); + if (triangles[h1] == i) { + hi = h0; + } + if (triangles[h1] == j) { + hj = h0; + } + + var h2 = NextHalfedge(h1); + h0 = halfedges[h2]; + } + + if (IsEncroached(hi)) { + heQueue.Add(hi); + } + var ohi = halfedges[hi]; + if (IsEncroached(ohi)) { + heQueue.Add(ohi); + } + if (IsEncroached(hj)) { + heQueue.Add(hj); + } + var ohj = halfedges[hj]; + if (IsEncroached(ohj)) { + heQueue.Add(ohj); + } + + constrainedHalfedges[hi] = HalfedgeState.Constrained; + constrainedHalfedges[ohi] = HalfedgeState.Constrained; + constrainedHalfedges[hj] = HalfedgeState.Constrained; + constrainedHalfedges[ohj] = HalfedgeState.Constrained; + } else { + UnsafeInsertPointBoundary(p, initHe: he, heQueue, tQueue); + + //var h0 = triangles.Length - 3; + var id = 3 * (pathPoints.Length - 1); + var hi = halfedges.Length - 1; + var hj = halfedges.Length - id; + + if (IsEncroached(hi)) { + heQueue.Add(hi); + } + + if (IsEncroached(hj)) { + heQueue.Add(hj); + } + + constrainedHalfedges[hi] = HalfedgeState.Constrained; + constrainedHalfedges[hj] = HalfedgeState.Constrained; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsBadTriangle (int tId) { + var (i, j, k) = (triangles[3 * tId + 0], triangles[3 * tId + 1], triangles[3 * tId + 2]); + var (a, b, c) = (outputPositions[i], outputPositions[j], outputPositions[k]); + var area2 = Area2(a, b, c); + return utils.greater(area2, maximumArea2) || AngleIsTooSmall(tId, angleThreshold); + } + + private void SplitTriangle (int tId, NativeList heQueue, NativeList tQueue, Allocator allocator) { + var c = circles[tId]; + var edges = new NativeList(allocator); + + for (int he = 0; he < constrainedHalfedges.Length; he++) { + if (constrainedHalfedges[he] == HalfedgeState.Unconstrained) { + continue; + } + + var (i, j) = (triangles[he], triangles[NextHalfedge(he)]); + if (halfedges[he] == -1 || i < j) { + var (p0, p1) = (outputPositions[i], outputPositions[j]); + if (utils.le(utils.dot(utils.diff(p0, c.Center), utils.diff(p1, c.Center)), utils.Zero())) { + edges.Add(he); + } + } + } + + if (edges.IsEmpty) { + UnsafeInsertPointBulk(c.Center, initTriangle: tId, heQueue, tQueue); + } else { + var (i, j, k) = (triangles[3 * tId + 0], triangles[3 * tId + 1], triangles[3 * tId + 2]); + var (xi, xj, xk) = (outputPositions[i], outputPositions[j], outputPositions[k]); + var area2 = Area2(xi, xj, xk); + if (utils.greater(area2, maximumArea2)) { // TODO split permited + foreach (var he in edges.AsArray().AsReadOnly()) { + heQueue.Add(he); + } + } + if (!heQueue.IsEmpty) { + tQueue.Add(tId); + SplitEncroachedEdges(heQueue, tQueue); + } + } + + edges.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool AngleIsTooSmall (int tId, T minimumAngle) { + var (i, j, k) = (triangles[3 * tId + 0], triangles[3 * tId + 1], triangles[3 * tId + 2]); + var (pA, pB, pC) = (outputPositions[i], outputPositions[j], outputPositions[k]); + return UnsafeTriangulator.AngleIsTooSmall(pA, pB, pC, minimumAngle); + } + + private int UnsafeInsertPointCommon (T2 p, int initTriangle) { + var pId = outputPositions.Length; + outputPositions.Add(p); + + badTriangles.Clear(); + trianglesQueue.Clear(); + pathPoints.Clear(); + pathHalfedges.Clear(); + + visitedTriangles.Clear(); + visitedTriangles.Length = triangles.Length / 3; + + trianglesQueue.Enqueue(initTriangle); + badTriangles.Add(initTriangle); + visitedTriangles[initTriangle] = true; + RecalculateBadTriangles(p); + + return pId; + } + + private void UnsafeInsertPointBulk (T2 p, int initTriangle, NativeList heQueue = default, NativeList tQueue = default) { + var pId = UnsafeInsertPointCommon(p, initTriangle); + BuildStarPolygon(); + ProcessBadTriangles(heQueue, tQueue); + BuildNewTrianglesForStar(pId, heQueue, tQueue); + } + + private void UnsafeInsertPointBoundary (T2 p, int initHe, NativeList heQueue = default, NativeList tQueue = default) { + var pId = UnsafeInsertPointCommon(p, initHe / 3); + BuildAmphitheaterPolygon(initHe); + ProcessBadTriangles(heQueue, tQueue); + BuildNewTrianglesForAmphitheater(pId, heQueue, tQueue); + } + + private void RecalculateBadTriangles (T2 p) { + while (trianglesQueue.TryDequeue(out var tId)) { + VisitEdge(p, 3 * tId + 0); + VisitEdge(p, 3 * tId + 1); + VisitEdge(p, 3 * tId + 2); + } + } + + private void VisitEdge (T2 p, int t0) { + var he = halfedges[t0]; + if (he == -1 || constrainedHalfedges[he] >= HalfedgeState.Constrained) { + return; + } + + var otherId = he / 3; + if (visitedTriangles[otherId]) { + return; + } + + var circle = circles[otherId]; + if (utils.le(utils.Cast(utils.distancesq(circle.Center, p)), circle.RadiusSq)) { + badTriangles.Add(otherId); + trianglesQueue.Enqueue(otherId); + visitedTriangles[otherId] = true; + } + } + + private void BuildAmphitheaterPolygon (int initHe) { + var id = initHe; + var initPoint = triangles[id]; + while (true) { + id = NextHalfedge(id); + if (triangles[id] == initPoint) { + break; + } + + var he = halfedges[id]; + if (he == -1 || !badTriangles.Contains(he / 3)) { + pathPoints.Add(triangles[id]); + pathHalfedges.Add(he); + continue; + } + id = he; + } + pathPoints.Add(triangles[initHe]); + pathHalfedges.Add(-1); + } + + private void BuildStarPolygon () { + // Find the "first" halfedge of the polygon. + var initHe = -1; + for (int i = 0; i < badTriangles.Length; i++) { + var tId = badTriangles[i]; + for (int t = 0; t < 3; t++) { + var he = 3 * tId + t; + var ohe = halfedges[he]; + if (ohe == -1 || !badTriangles.Contains(ohe / 3)) { + pathPoints.Add(triangles[he]); + pathHalfedges.Add(ohe); + initHe = he; + break; + } + } + if (initHe != -1) { + break; + } + } + + // Build polygon path from halfedges and points. + var id = initHe; + var initPoint = pathPoints[0]; + while (true) { + id = NextHalfedge(id); + if (triangles[id] == initPoint) { + break; + } + + var he = halfedges[id]; + if (he == -1 || !badTriangles.Contains(he / 3)) { + pathPoints.Add(triangles[id]); + pathHalfedges.Add(he); + continue; + } + id = he; + } + } + + private void ProcessBadTriangles (NativeList heQueue, NativeList tQueue) { + // Remove bad triangles and recalculate polygon path halfedges. + badTriangles.Sort(); + for (int t = badTriangles.Length - 1; t >= 0; t--) { + var tId = badTriangles[t]; + triangles.RemoveAt(3 * tId + 2); + triangles.RemoveAt(3 * tId + 1); + triangles.RemoveAt(3 * tId + 0); + circles.RemoveAt(tId); + RemoveHalfedge(3 * tId + 2, 0); + RemoveHalfedge(3 * tId + 1, 1); + RemoveHalfedge(3 * tId + 0, 2); + constrainedHalfedges.RemoveAt(3 * tId + 2); + constrainedHalfedges.RemoveAt(3 * tId + 1); + constrainedHalfedges.RemoveAt(3 * tId + 0); + + for (int i = 3 * tId; i < halfedges.Length; i++) { + var he = halfedges[i]; + if (he == -1) { + continue; + } + halfedges[he < 3 * tId ? he : i] -= 3; + } + + for (int i = 0; i < pathHalfedges.Length; i++) { + if (pathHalfedges[i] > 3 * tId + 2) { + pathHalfedges[i] -= 3; + } + } + + // Adapt he queue + if (heQueue.IsCreated) { + for (int i = 0; i < heQueue.Length; i++) { + var he = heQueue[i]; + if (he == 3 * tId + 0 || he == 3 * tId + 1 || he == 3 * tId + 2) { + heQueue[i] = -1; + continue; + } + + if (he > 3 * tId + 2) { + heQueue[i] -= 3; + } + } + } + + // Adapt t queue + if (tQueue.IsCreated) { + for (int i = 0; i < tQueue.Length; i++) { + var q = tQueue[i]; + if (q == tId) { + tQueue[i] = -1; + continue; + } + + if (q > tId) { + tQueue[i]--; + } + } + } + } + } + + private void RemoveHalfedge (int he, int offset) { + var ohe = halfedges[he]; + var o = ohe > he ? ohe - offset : ohe; + if (o > -1) { + halfedges[o] = -1; + } + halfedges.RemoveAt(he); + } + + private void BuildNewTrianglesForStar (int pId, NativeList heQueue, NativeList tQueue) { + // Build triangles/circles for inserted point pId. + var initTriangles = triangles.Length; + triangles.Length += 3 * pathPoints.Length; + circles.Length += pathPoints.Length; + for (int i = 0; i < pathPoints.Length - 1; i++) { + triangles[initTriangles + 3 * i + 0] = pId; + triangles[initTriangles + 3 * i + 1] = pathPoints[i]; + triangles[initTriangles + 3 * i + 2] = pathPoints[i + 1]; + circles[initTriangles / 3 + i] = new(CalculateCircumCircle(pId, pathPoints[i], pathPoints[i + 1], outputPositions.AsArray())); + } + triangles[^3] = pId; + triangles[^2] = pathPoints[^1]; + triangles[^1] = pathPoints[0]; + circles[^1] = new(CalculateCircumCircle(pId, pathPoints[^1], pathPoints[0], outputPositions.AsArray())); + + // Build half-edges for inserted point pId. + var heOffset = halfedges.Length; + halfedges.Length += 3 * pathPoints.Length; + constrainedHalfedges.Length += 3 * pathPoints.Length; + for (int i = 0; i < pathPoints.Length - 1; i++) { + var he = pathHalfedges[i]; + halfedges[3 * i + 1 + heOffset] = he; + if (he != -1) { + halfedges[he] = 3 * i + 1 + heOffset; + constrainedHalfedges[3 * i + 1 + heOffset] = constrainedHalfedges[he]; + } else { + constrainedHalfedges[3 * i + 1 + heOffset] = HalfedgeState.Constrained; + } + halfedges[3 * i + 2 + heOffset] = 3 * i + 3 + heOffset; + halfedges[3 * i + 3 + heOffset] = 3 * i + 2 + heOffset; + } + var phe = pathHalfedges[^1]; + halfedges[heOffset + 3 * (pathPoints.Length - 1) + 1] = phe; + if (phe != -1) { + halfedges[phe] = heOffset + 3 * (pathPoints.Length - 1) + 1; + constrainedHalfedges[heOffset + 3 * (pathPoints.Length - 1) + 1] = constrainedHalfedges[phe]; + } else { + constrainedHalfedges[heOffset + 3 * (pathPoints.Length - 1) + 1] = HalfedgeState.Constrained; + } + halfedges[heOffset] = heOffset + 3 * (pathPoints.Length - 1) + 2; + halfedges[heOffset + 3 * (pathPoints.Length - 1) + 2] = heOffset; + + if (heQueue.IsCreated) { + for (int i = 0; i < pathPoints.Length - 1; i++) { + var he = heOffset + 3 * i + 1; + if (constrainedHalfedges[he] >= HalfedgeState.Constrained && IsEncroached(he)) { + heQueue.Add(he); + } else if (tQueue.IsCreated && IsBadTriangle(he / 3)) { + tQueue.Add(he / 3); + } + } + } + } + + private void BuildNewTrianglesForAmphitheater (int pId, NativeList heQueue, NativeList tQueue) { + // Build triangles/circles for inserted point pId. + var initTriangles = triangles.Length; + triangles.Length += 3 * (pathPoints.Length - 1); + circles.Length += pathPoints.Length - 1; + for (int i = 0; i < pathPoints.Length - 1; i++) { + triangles[initTriangles + 3 * i + 0] = pId; + triangles[initTriangles + 3 * i + 1] = pathPoints[i]; + triangles[initTriangles + 3 * i + 2] = pathPoints[i + 1]; + circles[initTriangles / 3 + i] = new(CalculateCircumCircle(pId, pathPoints[i], pathPoints[i + 1], outputPositions.AsArray())); + } + + // Build half-edges for inserted point pId. + var heOffset = halfedges.Length; + halfedges.Length += 3 * (pathPoints.Length - 1); + constrainedHalfedges.Length += 3 * (pathPoints.Length - 1); + for (int i = 0; i < pathPoints.Length - 2; i++) { + var he = pathHalfedges[i]; + halfedges[3 * i + 1 + heOffset] = he; + if (he != -1) { + halfedges[he] = 3 * i + 1 + heOffset; + constrainedHalfedges[3 * i + 1 + heOffset] = constrainedHalfedges[he]; + } else { + constrainedHalfedges[3 * i + 1 + heOffset] = HalfedgeState.Constrained; + } + halfedges[3 * i + 2 + heOffset] = 3 * i + 3 + heOffset; + halfedges[3 * i + 3 + heOffset] = 3 * i + 2 + heOffset; + } + + var phe = pathHalfedges[^2]; + halfedges[heOffset + 3 * (pathPoints.Length - 2) + 1] = phe; + if (phe != -1) { + halfedges[phe] = heOffset + 3 * (pathPoints.Length - 2) + 1; + constrainedHalfedges[heOffset + 3 * (pathPoints.Length - 2) + 1] = constrainedHalfedges[phe]; + } else { + constrainedHalfedges[heOffset + 3 * (pathPoints.Length - 2) + 1] = HalfedgeState.Constrained; + } + halfedges[heOffset] = -1; + halfedges[heOffset + 3 * (pathPoints.Length - 2) + 2] = -1; + + if (heQueue.IsCreated) { + for (int i = 0; i < pathPoints.Length - 1; i++) { + var he = heOffset + 3 * i + 1; + if (constrainedHalfedges[he] >= HalfedgeState.Constrained && IsEncroached(he)) { + heQueue.Add(he); + } else if (tQueue.IsCreated && IsBadTriangle(he / 3)) { + tQueue.Add(he / 3); + } + } + } + } + } + + internal static bool AngleIsTooSmall (T2 pA, T2 pB, T2 pC, T minimumAngle) { + // Implementation is based in dot product property: + // a·b = |a| |b| cos α + var threshold = utils.cos(minimumAngle); + + var pAB = utils.normalizesafe(utils.diff(pB, pA)); + var pBC = utils.normalizesafe(utils.diff(pC, pB)); + var pCA = utils.normalizesafe(utils.diff(pA, pC)); + + return utils.anygreaterthan( + utils.dot(pAB, utils.neg(pCA)), + utils.dot(pBC, utils.neg(pAB)), + utils.dot(pCA, utils.neg(pBC)), + threshold + ); + } + internal static T Area2(T2 a, T2 b, T2 c) => utils.abs(Cross(utils.diff(b, a), utils.diff(c, a))); + private static T Cross(T2 a, T2 b) => utils.Cast(utils.diff(utils.mul(utils.X(a), utils.Y(b)), utils.mul(utils.Y(a), utils.X(b)))); + private static TBig CircumRadiusSq(T2 a, T2 b, T2 c) => utils.distancesq(utils.CircumCenter(a, b, c), a); + private static (T2, T) CalculateCircumCircle (int i, int j, int k, NativeArray positions) { + var (pA, pB, pC) = (positions[i], positions[j], positions[k]); + return (utils.CircumCenter(pA, pB, pC), utils.Cast(CircumRadiusSq(pA, pB, pC))); + } + private static bool ccw(T2 a, T2 b, T2 c) => utils.greater( + utils.mul(utils.diff(utils.Y(c), utils.Y(a)), utils.diff(utils.X(b), utils.X(a))), + utils.mul(utils.diff(utils.Y(b), utils.Y(a)), utils.diff(utils.X(c), utils.X(a))) + ); + /// + /// Returns if edge (, ) intersects + /// (, ), otherwise. + /// + /// + /// This method will not catch intersecting collinear segments. See unit tests for more details. + /// Segments intersecting only at their endpoints may or may not return , depending on their orientation. + /// + internal static bool EdgeEdgeIntersection(T2 a0, T2 a1, T2 b0, T2 b1) => ccw(a0, a1, b0) != ccw(a0, a1, b1) && ccw(b0, b1, a0) != ccw(b0, b1, a1); + + private static int NextHalfedge(int he) => he % 3 == 2 ? he - 2 : he + 1; + internal static bool IsConvexQuadrilateral(T2 a, T2 b, T2 c, T2 d) => true + && utils.greater(utils.abs(Orient2dFast(a, c, b)), utils.EPSILON()) + && utils.greater(utils.abs(Orient2dFast(a, c, d)), utils.EPSILON()) + && utils.greater(utils.abs(Orient2dFast(b, d, a)), utils.EPSILON()) + && utils.greater(utils.abs(Orient2dFast(b, d, c)), utils.EPSILON()) + && EdgeEdgeIntersection(a, c, b, d) + ; + private static TBig Orient2dFast(T2 a, T2 b, T2 c) => utils.diff( + utils.mul(utils.diff(utils.Y(a), utils.Y(c)), utils.diff(utils.X(b), utils.X(c))), + utils.mul(utils.diff(utils.X(a), utils.X(c)), utils.diff(utils.Y(b), utils.Y(c))) + ); + internal static bool PointLineSegmentIntersection(T2 a, T2 b0, T2 b1) => true + && utils.le(utils.abs(Orient2dFast(a, b0, b1)), utils.EPSILON()) + && math.all(utils.ge(a, utils.min(b0, b1)) & utils.le(a, utils.max(b0, b1))); + } + + internal interface ITransform where T : unmanaged where T2 : unmanaged { + T AreaScalingFactor { get; } + TSelf Identity { get; } + TSelf Inverse(); + T2 Transform(T2 point); + /// + /// Returns PCA transformation of given . + /// Read more in the project manual. + /// + TSelf CalculatePCATransformation(NativeArray positions); + /// + /// Returns COM transformation of given . + /// Read more in the project manual about method restrictions for given type. + /// + TSelf CalculateLocalTransformation(NativeArray positions); + } + + internal readonly struct TransformFloat : ITransform { + public readonly TransformFloat Identity => new(float2x2.identity, float2.zero); + public readonly float AreaScalingFactor => math.abs(math.determinant(rotScale)); + + private readonly float2x2 rotScale; + private readonly float2 translation; + + public TransformFloat(float2x2 rotScale, float2 translation) => (this.rotScale, this.translation) = (rotScale, translation); + private static TransformFloat Translate(float2 offset) => new(float2x2.identity, offset); + private static TransformFloat Scale(float2 scale) => new(new float2x2(scale.x, 0, 0, scale.y), float2.zero); + private static TransformFloat Rotate(float2x2 rotation) => new(rotation, float2.zero); + public static TransformFloat operator *(TransformFloat lhs, TransformFloat rhs) => new( + math.mul(lhs.rotScale, rhs.rotScale), + math.mul(math.inverse(rhs.rotScale), lhs.translation) + rhs.translation + ); + + public TransformFloat Inverse() => new(math.inverse(rotScale), math.mul(rotScale, -translation)); + public float2 Transform(float2 point) => math.mul(rotScale, point + translation); + + public readonly TransformFloat CalculatePCATransformation (NativeArray positions) { + var com = (float2)0; + foreach (var p in positions) { + com += p; + } + com /= positions.Length; + + var cov = float2x2.zero; + for (int i = 0; i < positions.Length; i++) { + var q = positions[i] - com; + cov += Kron(q, q); + } + cov /= positions.Length; + + Eigen(cov, out _, out var rotationMatrix); + + var partialTransform = Rotate(math.transpose(rotationMatrix)) * Translate(-com); + float2 min = float.MaxValue; + float2 max = float.MinValue; + for (int i = 0; i < positions.Length; i++) { + var p = partialTransform.Transform(positions[i]); + min = math.min(p, min); + max = math.max(p, max); + } + + var c = 0.5f * (min + max); + var s = 2f / (max - min); + + return Scale(s) * Translate(-c) * partialTransform; + } + + public readonly TransformFloat CalculateLocalTransformation (NativeArray positions) { + float2 min = float.MaxValue, max = float.MinValue, com = 0; + foreach (var p in positions) { + min = math.min(p, min); + max = math.max(p, max); + com += p; + } + + com /= positions.Length; + var scale = 1 / math.cmax(math.max(math.abs(max - com), math.abs(min - com))); + return Scale(scale) * Translate(-com); + } + + /// + /// Solves eigen problem of the given . + /// + /// Eigen values. + /// Eigen vectors. + private static void Eigen (float2x2 matrix, out float2 eigval, out float2x2 eigvec) { + var a00 = matrix[0][0]; + var a11 = matrix[1][1]; + var a01 = matrix[0][1]; + + var a00a11 = a00 - a11; + var p1 = a00 + a11; + var p2 = (a00a11 >= 0 ? 1 : -1) * math.sqrt(a00a11 * a00a11 + 4 * a01 * a01); + var lambda1 = p1 + p2; + var lambda2 = p1 - p2; + eigval = 0.5f * math.float2(lambda1, lambda2); + + var phi = 0.5f * math.atan2(2 * a01, a00a11); + + eigvec = math.float2x2 + ( + m00: math.cos(phi), m01: -math.sin(phi), + m10: math.sin(phi), m11: math.cos(phi) + ); + } + + /// + /// Returns Kronecer product of and . + /// + private static float2x2 Kron(float2 a, float2 b) => math.float2x2(a * b[0], a * b[1]); + } + + internal readonly struct TransformDouble : ITransform { + public readonly TransformDouble Identity => new(double2x2.identity, double2.zero); + public readonly double AreaScalingFactor => math.abs(math.determinant(rotScale)); + + private readonly double2x2 rotScale; + private readonly double2 translation; + + public TransformDouble(double2x2 rotScale, double2 translation) => (this.rotScale, this.translation) = (rotScale, translation); + private static TransformDouble Translate(double2 offset) => new(double2x2.identity, offset); + private static TransformDouble Scale(double2 scale) => new(new double2x2(scale.x, 0, 0, scale.y), double2.zero); + private static TransformDouble Rotate(double2x2 rotation) => new(rotation, double2.zero); + public static TransformDouble operator *(TransformDouble lhs, TransformDouble rhs) => new( + math.mul(lhs.rotScale, rhs.rotScale), + math.mul(math.inverse(rhs.rotScale), lhs.translation) + rhs.translation + ); + + public TransformDouble Inverse() => new(math.inverse(rotScale), math.mul(rotScale, -translation)); + public double2 Transform(double2 point) => math.mul(rotScale, point + translation); + + public readonly TransformDouble CalculatePCATransformation (NativeArray positions) { + var com = (double2)0; + foreach (var p in positions) { + com += p; + } + com /= positions.Length; + + var cov = double2x2.zero; + for (int i = 0; i < positions.Length; i++) { + var q = positions[i] - com; + cov += Kron(q, q); + } + cov /= positions.Length; + + Eigen(cov, out _, out var rotationMatrix); + + var partialTransform = Rotate(math.transpose(rotationMatrix)) * Translate(-com); + double2 min = double.MaxValue; + double2 max = double.MinValue; + for (int i = 0; i < positions.Length; i++) { + var p = partialTransform.Transform(positions[i]); + min = math.min(p, min); + max = math.max(p, max); + } + + var c = 0.5f * (min + max); + var s = 2f / (max - min); + + return Scale(s) * Translate(-c) * partialTransform; + } + + public readonly TransformDouble CalculateLocalTransformation (NativeArray positions) { + double2 min = double.MaxValue, max = double.MinValue, com = 0; + foreach (var p in positions) { + min = math.min(p, min); + max = math.max(p, max); + com += p; + } + + com /= positions.Length; + var scale = 1 / math.cmax(math.max(math.abs(max - com), math.abs(min - com))); + return Scale(scale) * Translate(-com); + } + + /// + /// Solves eigen problem of the given . + /// + /// Eigen values. + /// Eigen vectors. + private static void Eigen (double2x2 matrix, out double2 eigval, out double2x2 eigvec) { + var a00 = matrix[0][0]; + var a11 = matrix[1][1]; + var a01 = matrix[0][1]; + + var a00a11 = a00 - a11; + var p1 = a00 + a11; + var p2 = (a00a11 >= 0 ? 1 : -1) * math.sqrt(a00a11 * a00a11 + 4 * a01 * a01); + var lambda1 = p1 + p2; + var lambda2 = p1 - p2; + eigval = 0.5f * math.double2(lambda1, lambda2); + + var phi = 0.5f * math.atan2(2 * a01, a00a11); + + eigvec = math.double2x2 + ( + m00: math.cos(phi), m01: -math.sin(phi), + m10: math.sin(phi), m11: math.cos(phi) + ); + } + + /// + /// Returns Kronecer product of and . + /// + private static double2x2 Kron(double2 a, double2 b) => math.double2x2(a * b[0], a * b[1]); + } + + /// + /// Note: translation transformation is only supported for type . + /// + internal readonly struct TransformInt : ITransform { + public readonly TransformInt Identity => new(int2.zero); + public readonly int AreaScalingFactor => 1; + private readonly int2 translation; + public TransformInt(int2 translation) => this.translation = translation; + public TransformInt Inverse() => new(-translation); + public int2 Transform(int2 point) => point + translation; + public readonly TransformInt CalculatePCATransformation(NativeArray positions) => throw new NotImplementedException( + "PCA is not implemented for int2 coordinates!" + ); + + public readonly TransformInt CalculateLocalTransformation (NativeArray positions) { + int2 min = int.MaxValue, max = int.MinValue, com = 0; + foreach (var p in positions) { + min = math.min(p, min); + max = math.max(p, max); + com += p; + } + + return new(-com / positions.Length); + } + } + +#if UNITY_MATHEMATICS_FIXEDPOINT + internal readonly struct TransformFp : ITransform { + // NOTE: fpmath misses determinant and inverse functions. + private static fp det(fp2x2 m) => m[0][0] * m[1][1] - m[0][1] * m[1][0]; + private static fp2x2 inv(fp2x2 m) => fpmath.fp2x2(m[1][1], -m[1][0], -m[0][1], m[0][0]) / det(m); + public readonly TransformFp Identity => new(fp2x2.identity, fp2.zero); + public readonly fp AreaScalingFactor => fpmath.abs(det(rotScale)); + + private readonly fp2x2 rotScale; + private readonly fp2 translation; + + public TransformFp(fp2x2 rotScale, fp2 translation) => (this.rotScale, this.translation) = (rotScale, translation); + private static TransformFp Translate(fp2 offset) => new(fp2x2.identity, offset); + private static TransformFp Scale(fp2 scale) => new(new fp2x2(scale.x, 0, 0, scale.y), fp2.zero); + private static TransformFp Rotate(fp2x2 rotation) => new(rotation, fp2.zero); + public static TransformFp operator *(TransformFp lhs, TransformFp rhs) => new( + fpmath.mul(lhs.rotScale, rhs.rotScale), + fpmath.mul(inv(rhs.rotScale), lhs.translation) + rhs.translation + ); + + public TransformFp Inverse() => new(inv(rotScale), fpmath.mul(rotScale, -translation)); + public fp2 Transform(fp2 point) => fpmath.mul(rotScale, point + translation); + + public readonly TransformFp CalculatePCATransformation (NativeArray positions) { + var com = (fp2)0; + foreach (var p in positions) { + com += p; + } + com /= positions.Length; + + var cov = fp2x2.zero; + for (int i = 0; i < positions.Length; i++) { + var q = positions[i] - com; + cov += Kron(q, q); + } + cov /= positions.Length; + + Eigen(cov, out _, out var rotationMatrix); + + var partialTransform = Rotate(fpmath.transpose(rotationMatrix)) * Translate(-com); + fp2 min = fp.max_value; + fp2 max = fp.min_value; + for (int i = 0; i < positions.Length; i++) { + var p = partialTransform.Transform(positions[i]); + min = fpmath.min(p, min); + max = fpmath.max(p, max); + } + + var c = (min + max) / 2; + var s = (fp)2L / (max - min); + + return Scale(s) * Translate(-c) * partialTransform; + } + + public readonly TransformFp CalculateLocalTransformation (NativeArray positions) { + fp2 min = fp.max_value, max = fp.min_value, com = fp2.zero; + foreach (var p in positions) { + min = fpmath.min(p, min); + max = fpmath.max(p, max); + com += p; + } + + com /= positions.Length; + var scale = 1 / fpmath.cmax(fpmath.max(fpmath.abs(max - com), fpmath.abs(min - com))); + return Scale(scale) * Translate(-com); + } + + /// + /// Solves eigen problem of the given . + /// + /// Eigen values. + /// Eigen vectors. + private static void Eigen (fp2x2 matrix, out fp2 eigval, out fp2x2 eigvec) { + var a00 = matrix[0][0]; + var a11 = matrix[1][1]; + var a01 = matrix[0][1]; + + var a00a11 = a00 - a11; + var p1 = a00 + a11; + var p2 = (a00a11 >= 0 ? 1 : -1) * fpmath.sqrt(a00a11 * a00a11 + 4 * a01 * a01); + var lambda1 = p1 + p2; + var lambda2 = p1 - p2; + eigval = fpmath.fp2(lambda1, lambda2) / 2; + + var phi = fpmath.atan2(2 * a01, a00a11) / 2; + + eigvec = fpmath.fp2x2 + ( + m00: fpmath.cos(phi), m01: -fpmath.sin(phi), + m10: fpmath.sin(phi), m11: fpmath.cos(phi) + ); + } + + /// + /// Returns Kronecer product of and . + /// + private static fp2x2 Kron(fp2 a, fp2 b) => fpmath.fp2x2(a * b[0], a * b[1]); + } +#endif + + /// The raw coordinate type for a single axis. For example or . + /// The 2D coordinate composed of Ts. For example . + /// A value that may have higher precision compared to . Used for squared distances and other products. + internal interface IUtils where T : unmanaged where T2 : unmanaged where TBig : unmanaged { + /// + /// Cast a float to . Note that for integer coordinates, this will be floored. + /// Warning! This operation may cause precision loss, use with caution. + /// + T Cast(TBig v); + T2 CircumCenter(T2 a, T2 b, T2 c); + T Const(float v); + TBig EPSILON(); + bool InCircle(T2 a, T2 b, T2 c, T2 p); + TBig MaxValue(); + T2 MaxValue2(); + T2 MinValue2(); + bool PointInsideTriangle(T2 p, T2 a, T2 b, T2 c); + bool SupportsRefinement(); + T X(T2 v); + T Y(T2 v); + T Zero(); + TBig ZeroTBig(); +#pragma warning disable IDE1006 + T abs(T v); + TBig abs(TBig v); + /// + /// Returns concentric shells segment splitting factor. + /// + /// Concentric shells parameter constant. + /// Segment length squared. + /// alpha in [0, 1] range. + /// + /// Learn more in the paper: + /// + /// J. Ruppert. "A Delaunay Refinement Algorithm for Quality 2-Dimensional Mesh Generation". J. Algorithms 18(3):548-585 (1995) + /// . + /// + T alpha(T concentricShellReferenceRadius, T dSquare); + bool anygreaterthan(T a, T b, T c, T v); + T2 avg(T2 a, T2 b); + T cos(T v); + T diff(T a, T b); + TBig diff(TBig a, TBig b); + T2 diff(T2 a, T2 b); + TBig distancesq(T2 a, T2 b); + T dot(T2 a, T2 b); + bool2 eq(T2 v, T2 w); + bool2 ge(T2 a, T2 b); + bool greater(T a, T b); + bool greater(TBig a, TBig b); + int hashkey(T2 p, T2 c, int hashSize); + bool2 isfinite(T2 v); + bool le(T a, T b); + bool le(TBig a, TBig b); + bool2 le(T2 a, T2 b); + T2 lerp(T2 a, T2 b, T v); + bool less(TBig a, TBig b); + T2 max(T2 v, T2 w); + T2 min(T2 v, T2 w); + TBig mul(T a, T b); + T2 neg(T2 v); + T2 normalizesafe(T2 v); +#pragma warning restore IDE1006 + } + + internal readonly struct FloatUtils : IUtils { + public readonly float Cast(float v) => v; + public readonly float2 CircumCenter (float2 a, float2 b, float2 c) { + var d = b - a; + var e = c - a; + + var bl = math.lengthsq(d); + var cl = math.lengthsq(e); + + var d2 = 0.5f / (d.x * e.y - d.y * e.x); + + return a + d2 * (bl * math.float2(e.y, -e.x) + cl * math.float2(-d.y, d.x)); + } + public readonly float Const(float v) => v; + public readonly float EPSILON() => math.EPSILON; + public readonly bool InCircle (float2 a, float2 b, float2 c, float2 p) { + var dx = a.x - p.x; + var dy = a.y - p.y; + var ex = b.x - p.x; + var ey = b.y - p.y; + var fx = c.x - p.x; + var fy = c.y - p.y; + + var ap = dx * dx + dy * dy; + var bp = ex * ex + ey * ey; + var cp = fx * fx + fy * fy; + + return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; + } + public readonly float MaxValue() => float.MaxValue; + public readonly float2 MaxValue2() => float.MaxValue; + public readonly float2 MinValue2() => float.MinValue; + public readonly bool PointInsideTriangle (float2 p, float2 a, float2 b, float2 c) { + static float cross(float2 a, float2 b) => a.x * b.y - a.y * b.x; + static float3 bar (float2 a, float2 b, float2 c, float2 p) { + var (v0, v1, v2) = (b - a, c - a, p - a); + var denInv = 1 / cross(v0, v1); + var v = denInv * cross(v2, v1); + var w = denInv * cross(v0, v2); + var u = 1.0f - v - w; + return new(u, v, w); + } + // NOTE: use barycentric property. + return math.cmax(-bar(a, b, c, p)) <= 0; + } + public readonly bool SupportsRefinement() => true; + public readonly float X(float2 a) => a.x; + public readonly float Y(float2 a) => a.y; + public readonly float Zero() => 0; + public readonly float ZeroTBig() => 0; + public readonly float abs(float v) => math.abs(v); + public readonly float alpha (float concentricShellReferenceRadius, float edgeLengthSq) { + var d = math.sqrt(edgeLengthSq); + var k = (int)math.round(math.log2(0.5f * d / concentricShellReferenceRadius)); + return concentricShellReferenceRadius / d * (k < 0 ? math.pow(2, k) : 1 << k); + } + public readonly bool anygreaterthan(float a, float b, float c, float v) => math.any(math.float3(a, b, c) > v); + public readonly float2 avg(float2 a, float2 b) => 0.5f * (a + b); + public readonly float cos(float v) => math.cos(v); + public readonly float diff(float a, float b) => a - b; + public readonly float2 diff(float2 a, float2 b) => a - b; + public readonly float distancesq(float2 a, float2 b) => math.distancesq(a, b); + public readonly float dot(float2 a, float2 b) => math.dot(a, b); + public readonly bool2 eq(float2 v, float2 w) => v == w; + public readonly bool2 ge(float2 a, float2 b) => a >= b; + public readonly bool greater(float a, float b) => a > b; + public readonly int hashkey (float2 p, float2 c, int hashSize) { + return (int)math.floor(pseudoAngle(p.x - c.x, p.y - c.y) * hashSize) % hashSize; + + static float pseudoAngle (float dx, float dy) { + var p = dx / (math.abs(dx) + math.abs(dy)); + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] + } + } + public readonly bool2 isfinite(float2 v) => math.isfinite(v); + public readonly bool le(float a, float b) => a <= b; + public readonly bool2 le(float2 a, float2 b) => a <= b; + public readonly float2 lerp(float2 a, float2 b, float v) => math.lerp(a, b, v); + public readonly bool less(float a, float b) => a < b; + public readonly float2 max(float2 v, float2 w) => math.max(v, w); + public readonly float2 min(float2 v, float2 w) => math.min(v, w); + public readonly float mul(float a, float b) => a * b; + public readonly float2 neg(float2 v) => -v; + public readonly float2 normalizesafe(float2 v) => math.normalizesafe(v); + } + + internal readonly struct DoubleUtils : IUtils { + public readonly double Cast(double v) => v; + public readonly double2 CircumCenter (double2 a, double2 b, double2 c) { + var d = b - a; + var e = c - a; + + var bl = math.lengthsq(d); + var cl = math.lengthsq(e); + + var d2 = 0.5 / (d.x * e.y - d.y * e.x); + + return a + d2 * (bl * math.double2(e.y, -e.x) + cl * math.double2(-d.y, d.x)); + } + public readonly double Const(float v) => v; + public readonly double EPSILON() => math.EPSILON_DBL; + public readonly bool InCircle (double2 a, double2 b, double2 c, double2 p) { + var dx = a.x - p.x; + var dy = a.y - p.y; + var ex = b.x - p.x; + var ey = b.y - p.y; + var fx = c.x - p.x; + var fy = c.y - p.y; + + var ap = dx * dx + dy * dy; + var bp = ex * ex + ey * ey; + var cp = fx * fx + fy * fy; + + return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; + } + public readonly double MaxValue() => double.MaxValue; + public readonly double2 MaxValue2() => double.MaxValue; + public readonly double2 MinValue2() => double.MinValue; + public readonly bool PointInsideTriangle (double2 p, double2 a, double2 b, double2 c) { + static double cross(double2 a, double2 b) => a.x * b.y - a.y * b.x; + static double3 bar (double2 a, double2 b, double2 c, double2 p) { + var (v0, v1, v2) = (b - a, c - a, p - a); + var denInv = 1 / cross(v0, v1); + var v = denInv * cross(v2, v1); + var w = denInv * cross(v0, v2); + var u = 1 - v - w; + return new(u, v, w); + } + // NOTE: use barycentric property. + return math.cmax(-bar(a, b, c, p)) <= 0; + } + public readonly bool SupportsRefinement() => true; + public readonly double X(double2 a) => a.x; + public readonly double Y(double2 a) => a.y; + public readonly double Zero() => 0; + public readonly double ZeroTBig() => 0; + public readonly double abs(double v) => math.abs(v); + public readonly double alpha (double concentricShellReferenceRadius, double edgeLengthSq) { + var d = math.sqrt(edgeLengthSq); + var k = (int)math.round(math.log2(0.5 * d / concentricShellReferenceRadius)); + return concentricShellReferenceRadius / d * (k < 0 ? math.pow(2, k) : 1 << k); + } + public readonly bool anygreaterthan(double a, double b, double c, double v) => math.any(math.double3(a, b, c) > v); + public readonly double2 avg(double2 a, double2 b) => 0.5f * (a + b); + public readonly double cos(double v) => math.cos(v); + public readonly double diff(double a, double b) => a - b; + public readonly double2 diff(double2 a, double2 b) => a - b; + public readonly double distancesq(double2 a, double2 b) => math.distancesq(a, b); + public readonly double dot(double2 a, double2 b) => math.dot(a, b); + public readonly bool2 eq(double2 v, double2 w) => v == w; + public readonly bool2 ge(double2 a, double2 b) => a >= b; + public readonly bool greater(double a, double b) => a > b; + public readonly int hashkey (double2 p, double2 c, int hashSize) { + return (int)math.floor(pseudoAngle(p.x - c.x, p.y - c.y) * hashSize) % hashSize; + + static double pseudoAngle (double dx, double dy) { + var p = dx / (math.abs(dx) + math.abs(dy)); + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] + } + } + public readonly bool2 isfinite(double2 v) => math.isfinite(v); + public readonly bool le(double a, double b) => a <= b; + public readonly bool2 le(double2 a, double2 b) => a <= b; + public readonly double2 lerp(double2 a, double2 b, double v) => math.lerp(a, b, v); + public readonly bool less(double a, double b) => a < b; + public readonly double2 max(double2 v, double2 w) => math.max(v, w); + public readonly double2 min(double2 v, double2 w) => math.min(v, w); + public readonly double mul(double a, double b) => a * b; + public readonly double2 neg(double2 v) => -v; + public readonly double2 normalizesafe(double2 v) => math.normalizesafe(v); + } + + internal readonly struct IntUtils : IUtils { + public readonly int Cast(long v) => (int)v; + public readonly int2 CircumCenter (int2 a, int2 b, int2 c) { + var d = b - a; + var e = c - a; + + var bl = (long)d.x * d.x + (long)d.y * d.y; + var cl = (long)e.x * e.x + (long)e.y * e.y; + + var div = (long)d.x * e.y - (long)d.y * e.x; + // NOTE: In a case when div = 0 (i.e. circumcenter is not well defined) we use int.MaxValue to mimic the infinity. + // Doubles can represent all integers up to 2^53 exactly, so they can represent all int32 coordinates, and thus it is safe to cast here. + return div == 0 ? new(int.MaxValue) : (int2)math.round(a + (0.5 / div) * (bl * math.double2(e.y, -e.x) + cl * math.double2(-d.y, d.x))); + } + + public readonly int Const(float v) => (int)v; + public readonly long EPSILON() => 0; + + public readonly bool InCircle (int2 a, int2 b, int2 c, int2 p) { + // Do a coordinate change to check if the origin is inside abc instead. + // Note: Will overflow if the coordinates differ by more than 2^31 (but this is not the limiting factor) + a -= p; + b -= p; + c -= p; + // TODO: Is it better for performance to check if the coordinates are small, + // and if so, do the calculation with 64-bit arithmetic only? + + // Should not overflow since we cast to long + var ap = (long)a.x*a.x + (long)a.y*a.y; + var bp = (long)b.x*b.x + (long)b.y*b.y; + var cp = (long)c.x*c.x + (long)c.y*c.y; + + // This is the calculation we want to do, but it may overflow for large coordinates. + // Therefore we first do 64-bit multiplications, and then the final 3 multiplications with 128-bit arithmetic. + // return a.x * (b.y * cp - bp * c.y) - a.y * (b.x * cp - bp * c.x) + ap * (b.x * c.y - b.y * c.x) < 0; + + // May overflow for coordinates larger than about 2^20. + // Therefore, when verifying coordinates, we ensure that the bounding box is smaller than 2^20. + var det1 = b.y * cp - bp * c.y; + var det2 = b.x * cp - bp * c.x; + var det3 = b.x * (long)c.y - b.y * (long)c.x; + + var res = I128.Multiply(a.x, det1) - I128.Multiply(a.y, det2) + I128.Multiply(ap, det3); + + return res.IsNegative; + } + public readonly long MaxValue() => long.MaxValue; + public readonly int2 MaxValue2() => int.MaxValue; + public readonly int2 MinValue2() => int.MinValue; + public readonly bool PointInsideTriangle (int2 p, int2 a, int2 b, int2 c) { + static long cross(int2 a, int2 b) => (long)a.x * b.y - (long)a.y * b.x; + // NOTE: triangle orientation is guaranteed. + return cross(p - a, b - a) >= 0 && cross(p - b, c - b) >= 0 && cross(p - c, a - c) >= 0; + } + public readonly bool SupportsRefinement() => false; + public readonly int X(int2 a) => a.x; + public readonly int Y(int2 a) => a.y; + public readonly int Zero() => 0; + public readonly long ZeroTBig() => 0; + public readonly int abs(int v) => math.abs(v); + public readonly long abs(long v) => math.abs(v); + public readonly int alpha(int concentricShellReferenceRadius, int edgeLengthSq) => throw new NotImplementedException(); + public readonly bool anygreaterthan(int a, int b, int c, int v) => throw new NotImplementedException(); + public readonly int2 avg(int2 a, int2 b) => (a + b) / 2; + public readonly int cos(int v) => throw new NotImplementedException(); + public readonly int diff(int a, int b) => a - b; + public readonly long diff(long a, long b) => a - b; + public readonly int2 diff(int2 a, int2 b) => a - b; + public readonly long distancesq(int2 a, int2 b) => (long)(a - b).x * (a - b).x + (long)(a - b).y * (a - b).y; + public readonly int dot(int2 a, int2 b) => throw new NotImplementedException(); + public readonly bool2 eq(int2 v, int2 w) => v == w; + public readonly bool2 ge(int2 a, int2 b) => a >= b; + public readonly bool greater(int a, int b) => a > b; + public readonly bool greater(long a, long b) => a > b; + public readonly int hashkey (int2 p, int2 c, int hashSize) { + return (int)math.floor(pseudoAngle(p.x - c.x, p.y - c.y) * hashSize) % hashSize; + + static double pseudoAngle (int dx, int dy) { + var dist = math.abs(dx) + math.abs(dy); + if (dist == 0) return 0; + var p = (double)dx / dist; + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] + } + } + // TODO: Validate really large coordinates with tests. Probably this should include check for v < 2^20. + public readonly bool2 isfinite(int2 v) => true; + public readonly bool le(int a, int b) => a <= b; + public readonly bool le(long a, long b) => a <= b; + public readonly bool2 le(int2 a, int2 b) => a <= b; + public readonly int2 lerp(int2 a, int2 b, int v) => throw new NotImplementedException(); + public readonly bool less(long a, long b) => a < b; + public readonly int2 max(int2 v, int2 w) => math.max(v, w); + public readonly int2 min(int2 v, int2 w) => math.min(v, w); + public readonly long mul(int a, int b) => (long)a * b; + public readonly int2 neg(int2 v) => -v; + public readonly int2 normalizesafe(int2 v) => throw new NotImplementedException(); + } + +#if UNITY_MATHEMATICS_FIXEDPOINT + internal readonly struct FpUtils : IUtils { + public readonly fp Cast(fp v) => v; + public readonly fp2 CircumCenter (fp2 a, fp2 b, fp2 c) { + var d = b - a; + var e = c - a; + + var bl = fpmath.lengthsq(d); + var cl = fpmath.lengthsq(e); + + // NOTE: In a case when div = 0 (i.e. circumcenter is not well defined) we use fp.max_value to mimic the infinity. + var div = d.x * e.y - d.y * e.x; + return div == 0 ? fp.max_value : a + (fp)1L / 2L / div * (bl * fpmath.fp2(e.y, -e.x) + cl * fpmath.fp2(-d.y, d.x)); + } + public readonly fp Const(float v) => (fp)v; + public readonly fp EPSILON() => fp.FromRaw(1L); + public readonly bool InCircle (fp2 a, fp2 b, fp2 c, fp2 p) { + var dx = a.x - p.x; + var dy = a.y - p.y; + var ex = b.x - p.x; + var ey = b.y - p.y; + var fx = c.x - p.x; + var fy = c.y - p.y; + + var ap = dx * dx + dy * dy; + var bp = ex * ex + ey * ey; + var cp = fx * fx + fy * fy; + + return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; + } + public readonly fp MaxValue() => fp.max_value; + public readonly fp2 MaxValue2() => fp.max_value; + public readonly fp2 MinValue2() => fp.min_value; + public readonly bool PointInsideTriangle (fp2 p, fp2 a, fp2 b, fp2 c) { + static fp cross(fp2 a, fp2 b) => a.x * b.y - a.y * b.x; + static fp3 bar (fp2 a, fp2 b, fp2 c, fp2 p) { + var (v0, v1, v2) = (b - a, c - a, p - a); + var denInv = 1 / cross(v0, v1); + var v = denInv * cross(v2, v1); + var w = denInv * cross(v0, v2); + var u = (fp)1L - v - w; + return new(u, v, w); + } + // NOTE: use barycentric property. + return fpmath.cmax(-bar(a, b, c, p)) <= 0; + } + public readonly bool SupportsRefinement() => true; + public readonly fp X(fp2 a) => a.x; + public readonly fp Y(fp2 a) => a.y; + public readonly fp Zero() => 0; + public readonly fp ZeroTBig() => 0; + public readonly fp abs(fp v) => fpmath.abs(v); + public readonly fp alpha (fp D, fp dSquare) { + var d = fpmath.sqrt(dSquare); + var k = (int)fpmath.round(fpmath.log2(d / D / 2L)); + return D / d * (k < 0 ? fpmath.pow(2, k) : 1 << k); + } + public readonly bool anygreaterthan(fp a, fp b, fp c, fp v) => math.any(fpmath.fp3(a, b, c) > v); + public readonly fp2 avg(fp2 a, fp2 b) => (a + b) / 2; + public readonly fp cos(fp v) => fpmath.cos(v); + public readonly fp diff(fp a, fp b) => a - b; + public readonly fp2 diff(fp2 a, fp2 b) => a - b; + public readonly fp distancesq(fp2 a, fp2 b) => fpmath.distancesq(a, b); + public readonly fp dot(fp2 a, fp2 b) => fpmath.dot(a, b); + public readonly bool2 eq(fp2 v, fp2 w) => v == w; + public readonly bool2 ge(fp2 a, fp2 b) => a >= b; + public readonly bool greater(fp a, fp b) => a > b; + public readonly int hashkey (fp2 p, fp2 c, int hashSize) { + return (int)fpmath.floor(pseudoAngle(p.x - c.x, p.y - c.y) * hashSize) % hashSize; + + static fp pseudoAngle (fp dx, fp dy) { + var p = dx / (fpmath.abs(dx) + fpmath.abs(dy)); + return (dy > 0 ? 3 - p : 1 + p) / 4; // [0..1] + } + } + public readonly bool2 isfinite(fp2 v) => fpmath.isfinite(v); + public readonly bool le(fp a, fp b) => a <= b; + public readonly bool2 le(fp2 a, fp2 b) => a <= b; + public readonly fp2 lerp(fp2 a, fp2 b, fp v) => fpmath.lerp(a, b, v); + public readonly bool less(fp a, fp b) => a < b; + public readonly fp2 max(fp2 v, fp2 w) => fpmath.max(v, w); + public readonly fp2 min(fp2 v, fp2 w) => fpmath.min(v, w); + public readonly fp mul(fp a, fp b) => a * b; + public readonly fp2 neg(fp2 v) => -v; + public readonly fp2 normalizesafe(fp2 v) => fpmath.normalizesafe(v); + } +#endif +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs.meta new file mode 100644 index 0000000..9ee0048 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/Triangulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 134af43887144874baba950fcbbd99c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs new file mode 100644 index 0000000..f17f820 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs @@ -0,0 +1,52 @@ +using Unity.Mathematics; + +namespace andywiecko.BurstTriangulator { + /// + /// A signed 128-bit integer. + /// + struct I128 { + ulong hi; + ulong lo; + + public bool IsNegative => (hi & 0x8000000000000000UL) != 0; + + public I128(ulong hi, ulong lo) => (this.hi, this.lo) = (hi, lo); + public static I128 operator + (I128 a, I128 b) { + var lo = a.lo + b.lo; + var hi = a.hi + b.hi + (lo < a.lo ? 1UL : 0); + return new(hi, lo); + } + + public static I128 operator - (I128 a, I128 b) { + var lo = a.lo - b.lo; + var hi = a.hi - b.hi - (lo > a.lo ? 1UL : 0); + return new(hi, lo); + } + + public static I128 operator -(I128 a) => new I128(~a.hi, ~a.lo) + new I128(0, 1); + + /// + /// Multiplies two 64-bit signed integers, without any possibility of overflow. + /// + public static I128 Multiply (long slhs, long srhs) { + // From https://stackoverflow.com/a/58381061 + var negative = (slhs < 0) ^ (srhs < 0); + ulong lhs = (ulong)math.abs(slhs); + ulong rhs = (ulong)math.abs(srhs); + + // First calculate all of the cross products. + ulong lo_lo = (lhs & 0xFFFFFFFFUL) * (rhs & 0xFFFFFFFFUL); + ulong hi_lo = (lhs >> 32) * (rhs & 0xFFFFFFFFUL); + ulong lo_hi = (lhs & 0xFFFFFFFFUL) * (rhs >> 32); + ulong hi_hi = (lhs >> 32) * (rhs >> 32); + + // Now add the products together. These will never overflow. + ulong cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFFUL) + lo_hi; + ulong upper = (hi_lo >> 32) + (cross >> 32) + hi_hi; + + var res = new I128(upper, (cross << 32) | (lo_lo & 0xFFFFFFFFUL)); + res = negative ? -res : res; + return res; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs.meta new file mode 100644 index 0000000..b1dbd92 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/U128.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 612e57e0b7ab4d2469aaa505f5124c1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef new file mode 100644 index 0000000..5f68069 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef @@ -0,0 +1,18 @@ +{ + "name": "andywiecko.BurstTriangulator", + "rootNamespace": "", + "references": [ + "Unity.Burst", + "Unity.Mathematics", + "Unity.Collections" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef.meta new file mode 100644 index 0000000..8365732 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Triangulation/andywiecko.BurstTriangulator.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: db11b4b5d7520bc479416b48c98206cb +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs new file mode 100644 index 0000000..0d6dfe7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs @@ -0,0 +1,11 @@ +using UnityEngine; +using Unity.Mathematics; + +namespace Pathfinding { + public static class Vector2IntExtensions { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static int2 ToInt2 (this Vector2Int v) { + return new int2(v.x, v.y); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs.meta new file mode 100644 index 0000000..e79f2a5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Geometry/Vector2IntExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9425a80c48577a29197319636963481e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs new file mode 100644 index 0000000..1e9d131 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs @@ -0,0 +1,390 @@ +using UnityEngine; + +namespace Pathfinding { + using Pathfinding.Drawing; + + [AddComponentMenu("Pathfinding/GraphUpdateScene")] + /// + /// Helper class for easily updating graphs. + /// + /// To use the GraphUpdateScene component, create a new empty GameObject and add the component to it, it can be found under Components-->Pathfinding-->GraphUpdateScene. + /// + /// The region which the component will affect is defined by creating a polygon in the scene. + /// If you make sure you have the Position tool enabled (top-left corner of the Unity window) you can shift+click in the scene view to add more points to the polygon. + /// You can remove points using shift+alt+click. + /// By clicking on the points you can bring up a positioning tool. You can also open the "points" array in the inspector to set each point's coordinates manually. + /// [Open online documentation to see images] + /// In the inspector there are a number of variables. The first one is named "Convex", it sets if the convex hull of the points should be calculated or if the polygon should be used as-is. + /// Using the convex hull is faster when applying the changes to the graph, but with a non-convex polygon you can specify more complicated areas. + /// The next two variables, called "Apply On Start" and "Apply On Scan" determine when to apply the changes. If the object is in the scene from the beginning, both can be left on, it doesn't + /// matter since the graph is also scanned at start. However if you instantiate it later in the game, you can make it apply it's setting directly, or wait until the next scan (if any). + /// If the graph is rescanned, all GraphUpdateScene components which have the Apply On Scan variable toggled will apply their settings again to the graph since rescanning clears all previous changes. + /// You can also make it apply it's changes using scripting. + /// GetComponent().Apply (); + /// The above code will make it apply its changes to the graph (assuming a GraphUpdateScene component is attached to the same GameObject). + /// + /// Next there is "Modify Walkability" and "Set Walkability" (which appears when "Modify Walkability" is toggled). + /// If Modify Walkability is set, then all nodes inside the area will either be set to walkable or unwalkable depending on the value of the "Set Walkability" variable. + /// + /// Penalty can also be applied to the nodes. A higher penalty (aka weight) makes the nodes harder to traverse so it will try to avoid those areas. + /// + /// The tagging variables can be read more about on this page: tags (view in online documentation for working links) "Working with tags". + /// + /// Note: The Y (up) axis of the transform that this component is attached to should be in the same direction as the up direction of the graph. + /// So if you for example have a grid in the XY plane then the transform should have the rotation (-90,0,0). + /// + [HelpURL("https://arongranberg.com/astar/documentation/stable/graphupdatescene.html")] + public class GraphUpdateScene : GraphModifier { + /// Points which define the region to update + public Vector3[] points; + + /// Private cached convex hull of the + private Vector3[] convexPoints; + + /// + /// Use the convex hull of the points instead of the original polygon. + /// + /// See: https://en.wikipedia.org/wiki/Convex_hull + /// + public bool convex = true; + + /// + /// Minumum height of the bounds of the resulting Graph Update Object. + /// Useful when all points are laid out on a plane but you still need a bounds with a height greater than zero since a + /// zero height graph update object would usually result in no nodes being updated. + /// + public float minBoundsHeight = 1; + + /// + /// Penalty to add to nodes. + /// Usually you need quite large values, at least 1000-10000. A higher penalty means that agents will try to avoid those nodes more. + /// + /// Be careful when setting negative values since if a node gets a negative penalty it will underflow and instead get + /// really large. In most cases a warning will be logged if that happens. + /// + /// See: tags (view in online documentation for working links) for another way of applying penalties. + /// + public int penaltyDelta; + + /// If true, then all affected nodes will be made walkable or unwalkable according to + public bool modifyWalkability; + + /// Nodes will be made walkable or unwalkable according to this value if is true + public bool setWalkability; + + /// Apply this graph update object on start + public bool applyOnStart = true; + + /// Apply this graph update object whenever a graph is rescanned + public bool applyOnScan = true; + + /// + /// Update node's walkability and connectivity using physics functions. + /// For grid graphs, this will update the node's position and walkability exactly like when doing a scan of the graph. + /// If enabled for grid graphs, will be ignored. + /// + /// For Point Graphs, this will recalculate all connections which passes through the bounds of the resulting Graph Update Object + /// using raycasts (if enabled). + /// + public bool updatePhysics; + + /// \copydoc Pathfinding::GraphUpdateObject::resetPenaltyOnPhysics + public bool resetPenaltyOnPhysics = true; + + /// \copydoc Pathfinding::GraphUpdateObject::updateErosion + public bool updateErosion = true; + + /// + /// Should the tags of the nodes be modified. + /// If enabled, set all nodes' tags to + /// + public bool modifyTag; + + /// If is enabled, set all nodes' tags to this value + public PathfindingTag setTag; + + /// Emulates behavior from before version 4.0 + [HideInInspector] + public bool legacyMode = false; + + /// + /// Private cached inversion of . + /// Used for InvertSettings() + /// + private PathfindingTag setTagInvert; + + /// + /// Has apply been called yet. + /// Used to prevent applying twice when both applyOnScan and applyOnStart are enabled + /// + private bool firstApplied; + + /// + /// Use world space for coordinates. + /// If true, the shape will not follow when moving around the transform. + /// + [SerializeField] + [UnityEngine.Serialization.FormerlySerializedAs("useWorldSpace")] + private bool legacyUseWorldSpace; + + [SerializeField] + [UnityEngine.Serialization.FormerlySerializedAs("setTag")] + private int setTagCompatibility = -1; + + /// Do some stuff at start + public void Start () { + if (!Application.isPlaying) return; + + // If firstApplied is true, that means the graph was scanned during Awake. + // So we shouldn't apply it again because then we would end up applying it two times + if (!firstApplied && applyOnStart) { + Apply(); + } + } + + public override void OnPostScan () { + if (applyOnScan) Apply(); + } + + /// + /// Inverts all invertable settings for this GUS. + /// Namely: penalty delta, walkability, tags. + /// + /// Penalty delta will be changed to negative penalty delta. + /// will be inverted. + /// will be stored in a private variable, and the new value will be 0. When calling this function again, the saved + /// value will be the new value. + /// + /// Calling this function an even number of times without changing any settings in between will be identical to no change in settings. + /// + public virtual void InvertSettings () { + setWalkability = !setWalkability; + penaltyDelta = -penaltyDelta; + if (setTagInvert == 0) { + setTagInvert = setTag; + setTag = 0; + } else { + setTag = setTagInvert; + setTagInvert = 0; + } + } + + /// + /// Recalculate convex hull. + /// Will not do anything if is disabled. + /// + public void RecalcConvex () { + convexPoints = convex ? Polygon.ConvexHullXZ(points) : null; + } + + /// + /// Calculates the bounds for this component. + /// This is a relatively expensive operation, it needs to go through all points and + /// run matrix multiplications. + /// + public Bounds GetBounds () { + if (points == null || points.Length == 0) { + Bounds bounds; + var coll = GetComponent(); + var coll2D = GetComponent(); + var rend = GetComponent(); + + if (coll != null) bounds = coll.bounds; + else if (coll2D != null) { + bounds = coll2D.bounds; + bounds.size = new Vector3(bounds.size.x, bounds.size.y, Mathf.Max(bounds.size.z, 1f)); + } else if (rend != null) { + bounds = rend.bounds; + } else { + return new Bounds(Vector3.zero, Vector3.zero); + } + + if (legacyMode && bounds.size.y < minBoundsHeight) bounds.size = new Vector3(bounds.size.x, minBoundsHeight, bounds.size.z); + return bounds; + } else { + if (convexPoints == null) RecalcConvex(); + return GraphUpdateShape.GetBounds(convex ? convexPoints : points, legacyMode && legacyUseWorldSpace ? Matrix4x4.identity : transform.localToWorldMatrix, minBoundsHeight); + } + } + + /// + /// The GraphUpdateObject which would be applied by this component. + /// + /// No graphs are actually updated by this function. Call AstarPath.active.UpdateGraphs and pass this object if you want that. + /// This method is useful if you want to modify the object before passing it to the UpdateGraphs function. + /// + /// See: + /// + public virtual GraphUpdateObject GetGraphUpdate () { + GraphUpdateObject guo; + + if (points == null || points.Length == 0) { + var polygonCollider = GetComponent(); + if (polygonCollider != null) { + var points2D = polygonCollider.points; + Vector3[] pts = new Vector3[points2D.Length]; + for (int i = 0; i < pts.Length; i++) { + var p = points2D[i] + polygonCollider.offset; + pts[i] = new Vector3(p.x, 0, p.y); + } + + var mat = transform.localToWorldMatrix * Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90, 0, 0), Vector3.one); + var shape = new GraphUpdateShape(pts, convex, mat, minBoundsHeight); + guo = new GraphUpdateObject(GetBounds()); + guo.shape = shape; + } else { + var bounds = GetBounds(); + if (bounds.center == Vector3.zero && bounds.size == Vector3.zero) { + Debug.LogError("Cannot apply GraphUpdateScene, no points defined and no renderer or collider attached", this); + return null; + } else if (bounds.size == Vector3.zero) { + Debug.LogWarning("Collider bounding box was empty. Are you trying to apply the GraphUpdateScene before the collider has been enabled or initialized?", this); + // Note: This is technically valid, so we don't return null here + } + + guo = new GraphUpdateObject(bounds); + } + } else { + GraphUpdateShape shape; + if (legacyMode && !legacyUseWorldSpace) { + // Used for compatibility with older versions + var worldPoints = new Vector3[points.Length]; + for (int i = 0; i < points.Length; i++) worldPoints[i] = transform.TransformPoint(points[i]); + shape = new GraphUpdateShape(worldPoints, convex, Matrix4x4.identity, minBoundsHeight); + } else { + shape = new GraphUpdateShape(points, convex, legacyMode && legacyUseWorldSpace ? Matrix4x4.identity : transform.localToWorldMatrix, minBoundsHeight); + } + var bounds = shape.GetBounds(); + guo = new GraphUpdateObject(bounds); + guo.shape = shape; + } + + firstApplied = true; + + guo.modifyWalkability = modifyWalkability; + guo.setWalkability = setWalkability; + guo.addPenalty = penaltyDelta; + guo.updatePhysics = updatePhysics; + guo.updateErosion = updateErosion; + guo.resetPenaltyOnPhysics = resetPenaltyOnPhysics; + + guo.modifyTag = modifyTag; + guo.setTag = setTag; + return guo; + } + + /// + /// Updates graphs with a created GUO. + /// Creates a Pathfinding.GraphUpdateObject with a Pathfinding.GraphUpdateShape + /// representing the polygon of this object and update all graphs using AstarPath.UpdateGraphs. + /// This will not update graphs immediately. See AstarPath.UpdateGraph for more info. + /// + public void Apply () { + if (AstarPath.active == null) { + Debug.LogError("There is no AstarPath object in the scene", this); + return; + } + + var guo = GetGraphUpdate(); + if (guo != null) AstarPath.active.UpdateGraphs(guo); + } + + static readonly Color GizmoColorSelected = new Color(227/255f, 61/255f, 22/255f, 1.0f); + static readonly Color GizmoColorUnselected = new Color(227/255f, 61/255f, 22/255f, 0.9f); + + /// Draws some gizmos + public override void DrawGizmos () { + bool selected = GizmoContext.InActiveSelection(this); + Color c = selected ? GizmoColorSelected : GizmoColorUnselected; + + if (selected) { + var col = Color.Lerp(c, new Color(1, 1, 1, 0.2f), 0.9f); + + Bounds b = GetBounds(); + Draw.SolidBox(b.center, b.size, col); + Draw.WireBox(b.center, b.size, col); + } + + if (points == null) return; + + if (convex) c.a *= 0.5f; + + Matrix4x4 matrix = legacyMode && legacyUseWorldSpace ? Matrix4x4.identity : transform.localToWorldMatrix; + + if (convex) { + c.r -= 0.1f; + c.g -= 0.2f; + c.b -= 0.1f; + } + + using (Draw.WithMatrix(matrix)) { + if (selected || !convex) { + var fadedColor = c; + fadedColor.a *= 0.7f; + Draw.Polyline(points, true, convex ? fadedColor : c); + } + + if (convex) { + if (convexPoints == null) RecalcConvex(); + Draw.Polyline(convexPoints, true, selected ? GizmoColorSelected : GizmoColorUnselected); + } + + // Draw the full 3D shape + var pts = convex ? convexPoints : points; + if (selected && pts != null && pts.Length > 0) { + float miny = pts[0].y, maxy = pts[0].y; + for (int i = 0; i < pts.Length; i++) { + miny = Mathf.Min(miny, pts[i].y); + maxy = Mathf.Max(maxy, pts[i].y); + } + var extraHeight = Mathf.Max(minBoundsHeight - (maxy - miny), 0) * 0.5f; + miny -= extraHeight; + maxy += extraHeight; + + using (Draw.WithColor(new Color(1, 1, 1, 0.2f))) { + for (int i = 0; i < pts.Length; i++) { + var next = (i+1) % pts.Length; + var p1 = pts[i] + Vector3.up*(miny - pts[i].y); + var p2 = pts[i] + Vector3.up*(maxy - pts[i].y); + var p1n = pts[next] + Vector3.up*(miny - pts[next].y); + var p2n = pts[next] + Vector3.up*(maxy - pts[next].y); + Draw.Line(p1, p2); + Draw.Line(p1, p1n); + Draw.Line(p2, p2n); + } + } + } + } + } + + /// + /// Disables legacy mode if it is enabled. + /// Legacy mode is automatically enabled for components when upgrading from an earlier version than 3.8.6. + /// + public void DisableLegacyMode () { + if (legacyMode) { + legacyMode = false; + if (legacyUseWorldSpace) { + legacyUseWorldSpace = false; + for (int i = 0; i < points.Length; i++) { + points[i] = transform.InverseTransformPoint(points[i]); + } + RecalcConvex(); + } + } + } + + protected override void OnUpgradeSerializedData (ref Serialization.Migrations migrations, bool unityThread) { + if (migrations.TryMigrateFromLegacyFormat(out var legacyVersion)) { + if (legacyVersion == 0) { + // Use the old behavior if some points are already set + if (points != null && points.Length > 0) legacyMode = true; + } + if (setTagCompatibility != -1) { + setTag = (uint)setTagCompatibility; + setTagCompatibility = -1; + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs.meta new file mode 100644 index 0000000..fae937e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateScene.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: efee954c69f0d421086729bb8df1137f +timeCreated: 1490044676 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: -221 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs new file mode 100644 index 0000000..ed3f13d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs @@ -0,0 +1,198 @@ +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace Pathfinding { + /// + /// Defines a shape for a Pathfinding.GraphUpdateObject. + /// The shape consists of a number of points which it can either calculate the convex hull of or use as a polygon directly. + /// + /// A shape is essentially a 2D shape however it can be rotated arbitrarily. + /// When a matrix and a list of points is specified in the constructor the matrix decides what direction + /// is the 'up' direction. When checking if a point is contained in the shape, the point will be projected down + /// on a plane where the 'up' direction is the normal and then it will check if the shape contains the point. + /// + /// See: Pathfinding.GraphUpdateObject.shape + /// + public class GraphUpdateShape { + Vector3[] _points; + Vector3[] _convexPoints; + bool _convex; + Vector3 right = Vector3.right; + Vector3 forward = Vector3.forward; + Vector3 up = Vector3.up; + Vector3 origin; + public float minimumHeight; + + /// Shape optimized for burst + public struct BurstShape { + [DeallocateOnJobCompletion] + NativeArray points; + float3 origin, right, forward; + bool containsEverything; + + public BurstShape(GraphUpdateShape scene, Allocator allocator) { + var pts = scene.convex ? scene._convexPoints : scene._points; + + if (pts == null) points = new NativeArray(0, allocator); + else points = new NativeArray(pts, allocator); + + origin = scene.origin; + right = scene.right; + forward = scene.forward; + + // Speeds up calculations below + var mgn = scene.right.sqrMagnitude; + if (mgn > 0) right /= mgn; + mgn = scene.forward.sqrMagnitude; + if (mgn > 0) forward /= mgn; + containsEverything = false; + } + + /// Shape that contains everything + public static BurstShape Everything => new BurstShape { + points = new NativeArray(0, Allocator.Persistent), + origin = float3.zero, + right = float3.zero, + forward = float3.zero, + containsEverything = true, + }; + + public bool Contains (float3 point) { + if (containsEverything) return true; + // Transform to local space (shape in the XZ plane) + point -= origin; + // Point in local space + var p = new float3(math.dot(point, right), 0, math.dot(point, forward)); + + int j = points.Length-1; + bool inside = false; + + for (int i = 0; i < points.Length; j = i++) { + if (((points[i].z <= p.z && p.z < points[j].z) || (points[j].z <= p.z && p.z < points[i].z)) && + (p.x < (points[j].x - points[i].x) * (p.z - points[i].z) / (points[j].z - points[i].z) + points[i].x)) + inside = !inside; + } + return inside; + } + } + + /// + /// Gets or sets the points of the polygon in the shape. + /// These points should be specified in clockwise order. + /// Will automatically calculate the convex hull if is set to true + /// + public Vector3[] points { + get { + return _points; + } + set { + _points = value; + if (convex) CalculateConvexHull(); + } + } + + /// + /// Sets if the convex hull of the points should be calculated. + /// Convex hulls are faster but non-convex hulls can be used to specify more complicated shapes. + /// + public bool convex { + get { + return _convex; + } + set { + if (_convex != value && value) { + CalculateConvexHull(); + } + _convex = value; + } + } + + public GraphUpdateShape () { + } + + /// + /// Construct a shape. + /// See: + /// + /// Contour of the shape in local space with respect to the matrix (i.e the shape should be in the XZ plane, the Y coordinate will only affect the bounds) + /// If true, the convex hull of the points will be calculated. + /// local to world space matrix for the points. The matrix determines the up direction of the shape. + /// If the points would be in the XZ plane only, the shape would not have a height and then it might not + /// include any points inside it (as testing for inclusion is done in 3D space when updating graphs). This ensures + /// that the shape has at least the minimum height (in the up direction that the matrix specifies). + public GraphUpdateShape (Vector3[] points, bool convex, Matrix4x4 matrix, float minimumHeight) { + this.convex = convex; + this.points = points; + origin = matrix.MultiplyPoint3x4(Vector3.zero); + right = matrix.MultiplyPoint3x4(Vector3.right) - origin; + up = matrix.MultiplyPoint3x4(Vector3.up) - origin; + forward = matrix.MultiplyPoint3x4(Vector3.forward) - origin; + this.minimumHeight = minimumHeight; + } + + void CalculateConvexHull () { + _convexPoints = points != null? Polygon.ConvexHullXZ(points) : null; + } + + /// World space bounding box of this shape + public Bounds GetBounds () { + return GetBounds(convex ? _convexPoints : points, right, up, forward, origin, minimumHeight); + } + + public static Bounds GetBounds (Vector3[] points, Matrix4x4 matrix, float minimumHeight) { + var origin = matrix.MultiplyPoint3x4(Vector3.zero); + var right = matrix.MultiplyPoint3x4(Vector3.right) - origin; + var up = matrix.MultiplyPoint3x4(Vector3.up) - origin; + var forward = matrix.MultiplyPoint3x4(Vector3.forward) - origin; + + return GetBounds(points, right, up, forward, origin, minimumHeight); + } + + static Bounds GetBounds (Vector3[] points, Vector3 right, Vector3 up, Vector3 forward, Vector3 origin, float minimumHeight) { + if (points == null || points.Length == 0) return new Bounds(); + float miny = points[0].y, maxy = points[0].y; + for (int i = 0; i < points.Length; i++) { + miny = Mathf.Min(miny, points[i].y); + maxy = Mathf.Max(maxy, points[i].y); + } + var extraHeight = Mathf.Max(minimumHeight - (maxy - miny), 0) * 0.5f; + miny -= extraHeight; + maxy += extraHeight; + + Vector3 min = right * points[0].x + up * points[0].y + forward * points[0].z; + Vector3 max = min; + for (int i = 0; i < points.Length; i++) { + var p = right * points[i].x + forward * points[i].z; + var p1 = p + up * miny; + var p2 = p + up * maxy; + min = Vector3.Min(min, p1); + min = Vector3.Min(min, p2); + max = Vector3.Max(max, p1); + max = Vector3.Max(max, p2); + } + return new Bounds((min+max)*0.5F + origin, max-min); + } + + public bool Contains (GraphNode node) { + return Contains((Vector3)node.position); + } + + public bool Contains (Vector3 point) { + // Transform to local space (shape in the XZ plane) + point -= origin; + var localSpacePoint = new Vector3(Vector3.Dot(point, right)/right.sqrMagnitude, 0, Vector3.Dot(point, forward)/forward.sqrMagnitude); + + if (convex) { + if (_convexPoints == null) return false; + + for (int i = 0, j = _convexPoints.Length-1; i < _convexPoints.Length; j = i, i++) { + if (VectorMath.RightOrColinearXZ(_convexPoints[i], _convexPoints[j], localSpacePoint)) return false; + } + return true; + } else { + return _points != null && Polygon.ContainsPointXZ(_points, localSpacePoint); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs.meta new file mode 100644 index 0000000..7df21dd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/GraphUpdateShape.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c31d3b0be14344e98aa458dc66c3a94 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy.meta new file mode 100644 index 0000000..336abc7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 80f7f222c070f664d9cbdd3b3e2aed66 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs new file mode 100644 index 0000000..33522be --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs @@ -0,0 +1,275 @@ +namespace Pathfinding { + /// + /// Helper proxy for handling compatibility with the old NNConstraint API. + /// + /// Deprecated: Use instead. This class is deprecated and will be removed in a future version. + /// + public class NNConstraintGraphUpdateObjectProxy { + readonly GraphUpdateObject graphUpdateObject; + + public NNConstraintGraphUpdateObjectProxy (GraphUpdateObject graphUpdateObject) { + this.graphUpdateObject = graphUpdateObject; + } + + public GraphMask graphMask { + get => graphUpdateObject.graphMask; + set => graphUpdateObject.graphMask = value; + } + } + + /// + /// Helper proxy for handling compatibility with the old NNConstraint API. + /// This class is used to allow the new NearestNodeConstraint API to be used in the same way as the old NNConstraint API. + /// + /// Deprecated: Use instead. This class is deprecated and will be removed in a future version. + /// + public class NNConstraintPathProxy { + readonly Path path; + readonly int pathID; + + internal NNConstraintPathProxy (Path path) { + this.path = path; + this.pathID = path.pathID; + } + + void Validate () { + if (path.pathID != pathID) { + throw new System.InvalidOperationException("Trying to change traversability constraint for a path that no longer exists. Check out the migration guide for version 5.4 for more info."); + } + } + + public GraphMask graphMask { + get => path.traversalConstraint.graphMask; + set { + Validate(); + path.traversalConstraint.graphMask = value; + } + } + + public int tags { + get => path.traversalConstraint.tags; + set { + Validate(); + path.traversalConstraint.tags = value; + } + } + + public DistanceMetric distanceMetric { + get => path.nearestNodeDistanceMetric; + set { + Validate(); + path.nearestNodeDistanceMetric = value; + } + } + } + + /// + /// Nearest node constraint. Constrains which nodes will be returned by the function. + /// + /// Deprecated: Use instead. This class is deprecated and will be removed in a future version. + /// + [System.Obsolete("Use NearestNodeConstraint struct instead. Check the upgrade guide for version 5.4 for more info.")] + public class NNConstraint { + /// + /// Graphs treated as valid to search on. + /// This is a bitmask meaning that bit 0 specifies whether or not the first graph in the graphs list should be able to be included in the search, + /// bit 1 specifies whether or not the second graph should be included and so on. + /// + /// // Enables the first and third graphs to be included, but not the rest + /// myNNConstraint.graphMask = (1 << 0) | (1 << 2); + /// + /// + /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph"); + /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph"); + /// + /// NearestNodeConstraint nn = NearestNodeConstraint.Walkable; + /// + /// nn.graphMask = mask1 | mask2; + /// + /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph' + /// var info = AstarPath.active.GetNearest(somePoint, nn); + /// + /// + /// Note: This does only affect which nodes are returned from a call, if a valid graph is connected to an invalid graph using a node link then it might be searched anyway. + /// + /// See: + /// See: + /// See: bitmasks (view in online documentation for working links) + /// + public GraphMask graphMask = GraphMask.everything; + + /// Only treat nodes in the area as suitable. Does not affect anything if is less than 0 (zero) + public bool constrainArea; + + /// Area ID to constrain to. Will not affect anything if less than 0 (zero) or if is false + public int area = -1; + + /// + /// Determines how to measure distances to the navmesh. + /// + /// The default is a euclidean distance, which works well for most things. + /// + /// See: + /// + public DistanceMetric distanceMetric; + + /// Constrain the search to only walkable or unwalkable nodes depending on . + public bool constrainWalkability = true; + + /// + /// Only search for walkable or unwalkable nodes if is enabled. + /// If true, only walkable nodes will be searched for, otherwise only unwalkable nodes will be searched for. + /// Does not affect anything if if false. + /// + public bool walkable = true; + + /// + /// if available, do an XZ check instead of checking on all axes. + /// The navmesh/recast graph as well as the grid/layered grid graph supports this. + /// + /// This can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for: + /// [Open online documentation to see images] + /// + /// Deprecated: Use = DistanceMetric.ClosestAsSeenFromAbove() instead + /// + [System.Obsolete("Use distanceMetric = DistanceMetric.ClosestAsSeenFromAbove() instead")] + public bool distanceXZ { + get { + return distanceMetric.isProjectedDistance && distanceMetric.distanceScaleAlongProjectionDirection == 0; + } + set { + if (value) { + distanceMetric = DistanceMetric.ClosestAsSeenFromAbove(); + } else { + distanceMetric = DistanceMetric.Euclidean; + } + } + } + + /// + /// Sets if tags should be constrained. + /// See: + /// + public bool constrainTags = true; + + /// + /// Nodes which have any of these tags set are suitable. + /// This is a bitmask, i.e bit 0 indicates that tag 0 is good, bit 3 indicates tag 3 is good etc. + /// See: + /// See: + /// See: bitmasks (view in online documentation for working links) + /// + public int tags = -1; + + /// + /// Constrain distance to node. + /// Uses distance from . + /// If this is false, it will completely ignore the distance limit. + /// + /// If there are no suitable nodes within the distance limit then the search will terminate with a null node as a result. + /// Note: This value is not used in this class, it is used by the AstarPath.GetNearest function. + /// + public bool constrainDistance = true; + + /// + /// Returns whether or not the graph conforms to this NNConstraint's rules. + /// Note that only the first 31 graphs are considered using this function. + /// If the has bit 31 set (i.e the last graph possible to fit in the mask), all graphs + /// above index 31 will also be considered suitable. + /// + public virtual bool SuitableGraph (int graphIndex, NavGraph graph) { + return graphMask.Contains((uint)graphIndex); + } + + /// Returns whether or not the node conforms to this NNConstraint's rules + public virtual bool Suitable (GraphNode node) { + if (constrainWalkability && node.Walkable != walkable) return false; + + if (constrainArea && area >= 0 && node.Area != area) return false; + + if (constrainTags && ((tags >> (int)node.Tag) & 0x1) == 0) return false; + + return true; + } + + public void UseSettings (PathRequestSettings settings) { + graphMask = settings.graphMask; + constrainTags = true; + tags = settings.traversableTags; + constrainWalkability = true; + walkable = true; + } + + /// + /// The default NNConstraint. + /// Equivalent to new NNConstraint (). + /// This NNConstraint has settings which works for most, it only finds walkable nodes + /// and it constrains distance set by A* Inspector -> Settings -> Max Nearest Node Distance + /// + /// Deprecated: Use instead. It is equivalent, but the name is more descriptive. + /// + [System.Obsolete("Use NNConstraint.Walkable instead. It is equivalent, but the name is more descriptive")] + public static NNConstraint Default { + get { + return new NNConstraint(); + } + } + + /// + /// An NNConstraint which filters out unwalkable nodes. + /// This is the most commonly used NNConstraint. + /// + /// It also constrains the nearest node to be within the distance set by A* Inspector -> Settings -> Max Nearest Node Distance + /// + public static NNConstraint Walkable { + get { + return new NNConstraint(); + } + } + + /// Returns a constraint which does not filter the results + public static NNConstraint None { + get { + return new NNConstraint { + constrainWalkability = false, + constrainArea = false, + constrainTags = false, + constrainDistance = false, + graphMask = GraphMask.everything, + }; + } + } + + /// Default constructor. Equals to the property + public NNConstraint () { + } + + public TraversalConstraint ToTraversalConstraint () { + return new TraversalConstraint { + tags = tags, + graphMask = graphMask, + }; + } + + public NearestNodeConstraint ToNearestNodeConstraint () { + return new NearestNodeConstraint { + filter = null, + distanceMetric = distanceMetric, + tags = constrainTags ? tags : -1, + area = constrainArea ? area : -1, + graphMask = graphMask, + maxDistanceSqr = constrainDistance ? -1 : float.PositiveInfinity, + walkable = constrainWalkability ? (walkable ? NearestNodeConstraint.WalkabilityConstraint.Walkable : NearestNodeConstraint.WalkabilityConstraint.Unwalkable) : NearestNodeConstraint.WalkabilityConstraint.DontCare, + }; + } + } + + /// + /// A special NNConstraint which can use different logic for the start node and end node in a path. + /// A PathNNConstraint can be assigned to the Path.nnConstraint field, the path will first search for the start node, then it will call SetStart and proceed with searching for the end node (nodes in the case of a MultiTargetPath). + /// The default PathNNConstraint will constrain the end point to lie inside the same area as the start point. + /// + [System.Obsolete("Use NearestNodeConstraint instead", true)] + public class PathNNConstraint : NNConstraint { + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs.meta new file mode 100644 index 0000000..108fc0c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Legacy/NNConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aee6feb4dd971c2458046b3aea3d6488 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc.meta new file mode 100644 index 0000000..77e0364 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dfc976d61106d46b6a18ace94ffaea8d diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs new file mode 100644 index 0000000..181daf5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs @@ -0,0 +1,124 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.Drawing; + + [HelpURL("https://arongranberg.com/astar/documentation/stable/animationlink.html")] + public class AnimationLink : NodeLink2 { + public string clip; + public float animSpeed = 1; + public bool reverseAnim = true; + + public GameObject referenceMesh; + public LinkClip[] sequence; + public string boneRoot = "bn_COG_Root"; + + [System.Serializable] + public class LinkClip { + public AnimationClip clip; + public Vector3 velocity; + public int loopCount = 1; + + public string name { + get { + return clip != null ? clip.name : ""; + } + } + } + + static Transform SearchRec (Transform tr, string name) { + int childCount = tr.childCount; + + for (int i = 0; i < childCount; i++) { + Transform ch = tr.GetChild(i); + if (ch.name == name) return ch; + else { + Transform rec = SearchRec(ch, name); + if (rec != null) return rec; + } + } + return null; + } + + public void CalculateOffsets (List trace, out Vector3 endPosition) { + //Vector3 opos = transform.position; + endPosition = transform.position; + if (referenceMesh == null) return; + + GameObject ob = GameObject.Instantiate(referenceMesh, transform.position, transform.rotation) as GameObject; + ob.hideFlags = HideFlags.HideAndDontSave; + + Transform root = SearchRec(ob.transform, boneRoot); + if (root == null) throw new System.Exception("Could not find root transform"); + + Animation anim = ob.GetComponent(); + if (anim == null) anim = ob.AddComponent(); + + for (int i = 0; i < sequence.Length; i++) { + anim.AddClip(sequence[i].clip, sequence[i].clip.name); + } + + Vector3 prevOffset = Vector3.zero; + Vector3 position = transform.position; + Vector3 firstOffset = Vector3.zero; + + for (int i = 0; i < sequence.Length; i++) { + LinkClip c = sequence[i]; + if (c == null) { + endPosition = position; + return; + } + + anim[c.clip.name].enabled = true; + anim[c.clip.name].weight = 1; + + for (int repeat = 0; repeat < c.loopCount; repeat++) { + anim[c.clip.name].normalizedTime = 0; + anim.Sample(); + Vector3 soffset = root.position - transform.position; + + if (i > 0) { + position += prevOffset - soffset; + } else { + firstOffset = soffset; + } + + for (int t = 0; t <= 20; t++) { + float tf = t/20.0f; + anim[c.clip.name].normalizedTime = tf; + anim.Sample(); + Vector3 tmp = position + (root.position-transform.position) + c.velocity*tf*c.clip.length; + trace.Add(tmp); + } + position = position + c.velocity*1*c.clip.length; + + anim[c.clip.name].normalizedTime = 1; + anim.Sample(); + Vector3 eoffset = root.position - transform.position; + prevOffset = eoffset; + } + + anim[c.clip.name].enabled = false; + anim[c.clip.name].weight = 0; + } + + position += prevOffset - firstOffset; + + GameObject.DestroyImmediate(ob); + + endPosition = position; + } + + public override void DrawGizmos () { + base.DrawGizmos(); + List buffer = Pathfinding.Pooling.ListPool.Claim(); + Vector3 endPosition = Vector3.zero; + CalculateOffsets(buffer, out endPosition); + for (int i = 0; i < buffer.Count-1; i++) { + Draw.Line(buffer[i], buffer[i+1], Color.blue); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs.meta new file mode 100644 index 0000000..6c2aca2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AnimationLink.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2e8b1fd6fa484fc29f8a26fb5e8662b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs new file mode 100644 index 0000000..5504b19 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Pathfinding.Util { + internal static class AssemblySearcher { + public static List FindTypesInheritingFrom() { + var result = new List(); + foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) { + // Skip some assemblies which are known to not contain any graph types, for performance + var name = assembly.GetName().Name; + if (name.StartsWith("Unity.") || name.StartsWith("UnityEngine.") || name == "UnityEngine" || name.StartsWith("UnityEditor.") || name == "UnityEditor" || name.StartsWith("Mono.") || name.StartsWith("System.") || name == "System" || name.StartsWith("mscorlib") || name.StartsWith("I18N") || name == "netstandard" || name == "nunit.framework") continue; + + System.Type[] types = null; + try { + types = assembly.GetTypes(); + } catch { + // Ignore type load exceptions and things like that. + // We might not be able to read all assemblies for some reason, but hopefully the relevant types exist in the assemblies that we can read + continue; + } + + foreach (var type in types) { +#if NETFX_CORE && !UNITY_EDITOR + System.Type baseType = type.GetTypeInfo().BaseType; +#else + var baseType = type.BaseType; +#endif + while (baseType != null) { + if (System.Type.Equals(baseType, typeof(T))) { + result.Add(type); + break; + } + +#if NETFX_CORE && !UNITY_EDITOR + baseType = baseType.GetTypeInfo().BaseType; +#else + baseType = baseType.BaseType; +#endif + } + } + } + return result; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs.meta new file mode 100644 index 0000000..4223835 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AssemblySearcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8a85da7519a8240d87c58437c6d6d4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs new file mode 100644 index 0000000..b473a74 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs @@ -0,0 +1,345 @@ +//#define ProfileAstar + +using UnityEngine; +using System.Text; +using Pathfinding.Pooling; + +namespace Pathfinding { + [AddComponentMenu("Pathfinding/Pathfinding Debugger")] + [ExecuteInEditMode] + /// + /// Debugger for the A* Pathfinding Project. + /// This class can be used to profile different parts of the pathfinding system + /// and the whole game as well to some extent. + /// + /// Clarification of the labels shown when enabled. + /// All memory related things profiles the whole game not just the A* Pathfinding System. + /// - Currently allocated: memory the GC (garbage collector) says the application has allocated right now. + /// - Peak allocated: maximum measured value of the above. + /// - Last collect peak: the last peak of 'currently allocated'. + /// - Allocation rate: how much the 'currently allocated' value increases per second. This value is not as reliable as you can think + /// it is often very random probably depending on how the GC thinks this application is using memory. + /// - Collection frequency: how often the GC is called. Again, the GC might decide it is better with many small collections + /// or with a few large collections. So you cannot really trust this variable much. + /// - Last collect fps: FPS during the last garbage collection, the GC will lower the fps a lot. + /// + /// - FPS: current FPS (not updated every frame for readability) + /// - Lowest FPS (last x): As the label says, the lowest fps of the last x frames. + /// + /// - Size: Size of the path pool. + /// - Total created: Number of paths of that type which has been created. Pooled paths are not counted twice. + /// If this value just keeps on growing and growing without an apparent stop, you are are either not pooling any paths + /// or you have missed to pool some path somewhere in your code. + /// + /// See: pooling + /// + /// TODO: Add field showing how many graph updates are being done right now + /// + [HelpURL("https://arongranberg.com/astar/documentation/stable/astardebugger.html")] + public class AstarDebugger : VersionedMonoBehaviour { + public int yOffset = 5; + + public bool show = true; + public bool showInEditor = false; + + public bool showFPS = false; + public bool showPathProfile = false; + public bool showMemProfile = false; + public bool showGraph = false; + + public int graphBufferSize = 200; + + /// + /// Font to use. + /// A monospaced font is the best + /// + public Font font = null; + public int fontSize = 12; + + StringBuilder text = new StringBuilder(); + string cachedText; + float lastUpdate = -999; + + private GraphPoint[] graph; + + struct GraphPoint { + public float fps, memory; + public bool collectEvent; + } + + private float delayedDeltaTime = 1; + private float lastCollect = 0; + private float lastCollectNum = 0; + private float delta = 0; + private float lastDeltaTime = 0; + private int allocRate = 0; + private int lastAllocMemory = 0; + private float lastAllocSet = -9999; + private int allocMem = 0; + private int collectAlloc = 0; + private int peakAlloc = 0; + + private int fpsDropCounterSize = 200; + private float[] fpsDrops; + + private Rect boxRect; + + private GUIStyle style; + + private Camera cam; + + float graphWidth = 100; + float graphHeight = 100; + float graphOffset = 50; + + public void Start () { + useGUILayout = false; + + fpsDrops = new float[fpsDropCounterSize]; + + cam = GetComponent(); + if (cam == null) { + cam = Camera.main; + } + + graph = new GraphPoint[graphBufferSize]; + + if (Time.unscaledDeltaTime > 0) { + for (int i = 0; i < fpsDrops.Length; i++) { + fpsDrops[i] = 1F / Time.unscaledDeltaTime; + } + } + } + + int maxVecPool = 0; + int maxNodePool = 0; + + PathTypeDebug[] debugTypes = new PathTypeDebug[] { + new PathTypeDebug("ABPath", () => PathPool.GetSize(typeof(ABPath)), () => PathPool.GetTotalCreated(typeof(ABPath))) + , + new PathTypeDebug("MultiTargetPath", () => PathPool.GetSize(typeof(MultiTargetPath)), () => PathPool.GetTotalCreated(typeof(MultiTargetPath))), + new PathTypeDebug("RandomPath", () => PathPool.GetSize(typeof(RandomPath)), () => PathPool.GetTotalCreated(typeof(RandomPath))), + new PathTypeDebug("FleePath", () => PathPool.GetSize(typeof(FleePath)), () => PathPool.GetTotalCreated(typeof(FleePath))), + new PathTypeDebug("ConstantPath", () => PathPool.GetSize(typeof(ConstantPath)), () => PathPool.GetTotalCreated(typeof(ConstantPath))), + new PathTypeDebug("FloodPath", () => PathPool.GetSize(typeof(FloodPath)), () => PathPool.GetTotalCreated(typeof(FloodPath))), + new PathTypeDebug("FloodPathTracer", () => PathPool.GetSize(typeof(FloodPathTracer)), () => PathPool.GetTotalCreated(typeof(FloodPathTracer))) + }; + + struct PathTypeDebug { + string name; + System.Func getSize; + System.Func getTotalCreated; + public PathTypeDebug (string name, System.Func getSize, System.Func getTotalCreated) { + this.name = name; + this.getSize = getSize; + this.getTotalCreated = getTotalCreated; + } + + public void Print (StringBuilder text) { + int totCreated = getTotalCreated(); + + if (totCreated > 0) { + text.Append("\n").Append((" " + name).PadRight(25)).Append(getSize()).Append("/").Append(totCreated); + } + } + } + + public void LateUpdate () { + if (!show || (!Application.isPlaying && !showInEditor)) return; + + if (Time.unscaledDeltaTime <= 0.0001f) + return; + + int collCount = System.GC.CollectionCount(0); + + if (lastCollectNum != collCount) { + lastCollectNum = collCount; + delta = Time.realtimeSinceStartup-lastCollect; + lastCollect = Time.realtimeSinceStartup; + lastDeltaTime = Time.unscaledDeltaTime; + collectAlloc = allocMem; + } + + allocMem = (int)System.GC.GetTotalMemory(false); + + bool collectEvent = allocMem < peakAlloc; + peakAlloc = !collectEvent ? allocMem : peakAlloc; + + if (Time.realtimeSinceStartup - lastAllocSet > 0.3F || !Application.isPlaying) { + int diff = allocMem - lastAllocMemory; + lastAllocMemory = allocMem; + lastAllocSet = Time.realtimeSinceStartup; + delayedDeltaTime = Time.unscaledDeltaTime; + + if (diff >= 0) { + allocRate = diff; + } + } + + if (Application.isPlaying) { + fpsDrops[Time.frameCount % fpsDrops.Length] = Time.unscaledDeltaTime > 0.00001f ? 1F / Time.unscaledDeltaTime : 0; + int graphIndex = Time.frameCount % graph.Length; + graph[graphIndex].fps = Time.unscaledDeltaTime < 0.00001f ? 1F / Time.unscaledDeltaTime : 0; + graph[graphIndex].collectEvent = collectEvent; + graph[graphIndex].memory = allocMem; + } + + if (Application.isPlaying && cam != null && showGraph) { + graphWidth = cam.pixelWidth*0.8f; + + + float minMem = float.PositiveInfinity, maxMem = 0, minFPS = float.PositiveInfinity, maxFPS = 0; + for (int i = 0; i < graph.Length; i++) { + minMem = Mathf.Min(graph[i].memory, minMem); + maxMem = Mathf.Max(graph[i].memory, maxMem); + minFPS = Mathf.Min(graph[i].fps, minFPS); + maxFPS = Mathf.Max(graph[i].fps, maxFPS); + } + + int currentGraphIndex = Time.frameCount % graph.Length; + + Matrix4x4 m = Matrix4x4.TRS(new Vector3((cam.pixelWidth - graphWidth)/2f, graphOffset, 1), Quaternion.identity, new Vector3(graphWidth, graphHeight, 1)); + + for (int i = 0; i < graph.Length-1; i++) { + if (i == currentGraphIndex) continue; + + DrawGraphLine(i, m, i/(float)graph.Length, (i+1)/(float)graph.Length, Mathf.InverseLerp(minMem, maxMem, graph[i].memory), Mathf.InverseLerp(minMem, maxMem, graph[i+1].memory), Color.blue); + DrawGraphLine(i, m, i/(float)graph.Length, (i+1)/(float)graph.Length, Mathf.InverseLerp(minFPS, maxFPS, graph[i].fps), Mathf.InverseLerp(minFPS, maxFPS, graph[i+1].fps), Color.green); + } + } + } + + void DrawGraphLine (int index, Matrix4x4 m, float x1, float x2, float y1, float y2, Color color) { + Debug.DrawLine(cam.ScreenToWorldPoint(m.MultiplyPoint3x4(new Vector3(x1, y1))), cam.ScreenToWorldPoint(m.MultiplyPoint3x4(new Vector3(x2, y2))), color); + } + + public void OnGUI () { + if (!show || (!Application.isPlaying && !showInEditor)) return; + + if (style == null) { + style = new GUIStyle(); + style.normal.textColor = Color.white; + style.padding = new RectOffset(5, 5, 5, 5); + } + + if (Time.realtimeSinceStartup - lastUpdate > 0.5f || cachedText == null || !Application.isPlaying) { + lastUpdate = Time.realtimeSinceStartup; + + boxRect = new Rect(5, yOffset, 310, 40); + + text.Length = 0; + text.AppendLine("A* Pathfinding Project Debugger"); + text.Append("A* Version: ").Append(AstarPath.Version.ToString()); + + if (showMemProfile) { + boxRect.height += 200; + + text.AppendLine(); + text.AppendLine(); + text.Append("Currently allocated".PadRight(25)); + text.Append((allocMem/1000000F).ToString("0.0 MB")); + text.AppendLine(); + + text.Append("Peak allocated".PadRight(25)); + text.Append((peakAlloc/1000000F).ToString("0.0 MB")).AppendLine(); + + text.Append("Last collect peak".PadRight(25)); + text.Append((collectAlloc/1000000F).ToString("0.0 MB")).AppendLine(); + + + text.Append("Allocation rate".PadRight(25)); + text.Append((allocRate/1000000F).ToString("0.0 MB")).AppendLine(); + + text.Append("Collection frequency".PadRight(25)); + text.Append(delta.ToString("0.00")); + text.Append("s\n"); + + text.Append("Last collect fps".PadRight(25)); + text.Append((1F/lastDeltaTime).ToString("0.0 fps")); + text.Append(" ("); + text.Append(lastDeltaTime.ToString("0.000 s")); + text.Append(")"); + } + + if (showFPS) { + text.AppendLine(); + text.AppendLine(); + var delayedFPS = delayedDeltaTime > 0.00001f ? 1F/delayedDeltaTime : 0; + text.Append("FPS".PadRight(25)).Append(delayedFPS.ToString("0.0 fps")); + + + float minFps = Mathf.Infinity; + + for (int i = 0; i < fpsDrops.Length; i++) if (fpsDrops[i] < minFps) minFps = fpsDrops[i]; + + text.AppendLine(); + text.Append(("Lowest fps (last " + fpsDrops.Length + ")").PadRight(25)).Append(minFps.ToString("0.0")); + } + + if (showPathProfile) { + AstarPath astar = AstarPath.active; + + text.AppendLine(); + + if (astar == null) { + text.Append("\nNo AstarPath Object In The Scene"); + } else { +#if ProfileAstar + double searchSpeed = (double)AstarPath.TotalSearchedNodes*10000 / (double)AstarPath.TotalSearchTime; + text.Append("\nSearch Speed (nodes/ms) ").Append(searchSpeed.ToString("0")).Append(" ("+AstarPath.TotalSearchedNodes+" / ").Append(((double)AstarPath.TotalSearchTime/10000F).ToString("0")+")"); +#endif + + if (Pathfinding.Pooling.ListPool.GetSize() > maxVecPool) maxVecPool = Pathfinding.Pooling.ListPool.GetSize(); + if (Pathfinding.Pooling.ListPool.GetSize() > maxNodePool) maxNodePool = Pathfinding.Pooling.ListPool.GetSize(); + + text.Append("\nPool Sizes (size/total created)"); + + for (int i = 0; i < debugTypes.Length; i++) { + debugTypes[i].Print(text); + } + } + } + + cachedText = text.ToString(); + } + + + if (font != null) { + style.font = font; + style.fontSize = fontSize; + } + + boxRect.height = style.CalcHeight(new GUIContent(cachedText), boxRect.width); + + GUI.Box(boxRect, ""); + GUI.Label(boxRect, cachedText, style); + + if (showGraph) { + float minMem = float.PositiveInfinity, maxMem = 0, minFPS = float.PositiveInfinity, maxFPS = 0; + for (int i = 0; i < graph.Length; i++) { + minMem = Mathf.Min(graph[i].memory, minMem); + maxMem = Mathf.Max(graph[i].memory, maxMem); + minFPS = Mathf.Min(graph[i].fps, minFPS); + maxFPS = Mathf.Max(graph[i].fps, maxFPS); + } + + float line; + GUI.color = Color.blue; + // Round to nearest x.x MB + line = Mathf.RoundToInt(maxMem/(100.0f*1000)); + GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line*1000*100) - 10, 100, 20), (line/10.0f).ToString("0.0 MB")); + + line = Mathf.Round(minMem/(100.0f*1000)); + GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line*1000*100) - 10, 100, 20), (line/10.0f).ToString("0.0 MB")); + + GUI.color = Color.green; + // Round to nearest x.x MB + line = Mathf.Round(maxFPS); + GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); + + line = Mathf.Round(minFPS); + GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs.meta new file mode 100644 index 0000000..8e8a582 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AstarDebugger.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5103795af2d504ea693528e938005441 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs new file mode 100644 index 0000000..f67bff3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs @@ -0,0 +1,133 @@ +using UnityEngine; +using Unity.Mathematics; + +namespace Pathfinding { + using Pathfinding.Drawing; + + /// + /// Policy for how often to recalculate an agent's path. + /// + /// See: + /// See: + /// + [System.Serializable] + public class AutoRepathPolicy { + /// Policy mode for how often to recalculate an agent's path. + public enum Mode : byte { + /// + /// Never automatically recalculate the path. + /// Paths can be recalculated manually by for example calling or . + /// This mode is useful if you want full control of when the agent calculates its path. + /// + Never, + /// + /// Recalculate the path every seconds. + /// + /// This is primarily included for historical reasons, but might be useful if you want the path recalculations to happen at a very predictable rate. + /// In most cases it is recommended to use the Dynamic mode. + /// + EveryNSeconds, + /// + /// Recalculate the path at least every seconds but more often if the destination moves a lot. + /// This mode is recommended since it allows the agent to quickly respond to new destinations without using up a lot of CPU power to calculate paths + /// when it doesn't have to. + /// + /// More precisely: + /// Let C be a circle centered at the destination for the last calculated path, with a radius equal to the distance to that point divided by . + /// If the new destination is outside that circle the path will be immediately recalculated. + /// Otherwise let F be the 1 - (distance from the circle's center to the new destination divided by the circle's radius). + /// So F will be 1 if the new destination is the same as the old one and 0 if it is at the circle's edge. + /// Recalculate the path if the time since the last path recalculation is greater than multiplied by F. + /// + /// Thus if the destination doesn't change the path will be recalculated every seconds. + /// + Dynamic, + } + + /// + /// Policy to use when recalculating paths. + /// + /// See: for more details. + /// + public Mode mode = Mode.Dynamic; + + /// Number of seconds between each automatic path recalculation for Mode.EveryNSeconds + [UnityEngine.Serialization.FormerlySerializedAs("interval")] + public float period = 0.5f; + + /// + /// How sensitive the agent should be to changes in its destination for Mode.Dynamic. + /// A higher value means the destination has to move less for the path to be recalculated. + /// + /// See: + /// + public float sensitivity = 10.0f; + + /// Maximum number of seconds between each automatic path recalculation for Mode.Dynamic + [UnityEngine.Serialization.FormerlySerializedAs("maximumInterval")] + public float maximumPeriod = 2.0f; + + /// If true the sensitivity will be visualized as a circle in the scene view when the game is playing + public bool visualizeSensitivity = false; + + Vector3 lastDestination = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + float lastRepathTime = float.NegativeInfinity; + + /// + /// True if the path should be recalculated according to the policy + /// + /// The above parameters are relevant only if is . + /// + /// The current position of the agent. + /// The radius of the agent. You may pass 0.0 if the agent doesn't have a radius. + /// The goal of the agent right now + /// The current time in seconds + public virtual bool ShouldRecalculatePath (Vector3 position, float radius, Vector3 destination, float time) { + if (mode == Mode.Never || float.IsPositiveInfinity(destination.x)) return false; + + float timeSinceLast = time - lastRepathTime; + if (mode == Mode.EveryNSeconds) { + return timeSinceLast >= period; + } else { + // cost = change in destination / max(distance to destination, radius) + float squaredCost = (destination - lastDestination).sqrMagnitude / Mathf.Max((position - lastDestination).sqrMagnitude, radius*radius); + float fraction = squaredCost * (sensitivity*sensitivity); + if (float.IsNaN(fraction)) { + // The agent's radius is zero, and the destination is precisely at the agent's position, which is also the destination of the last calculated path + // This is a special case. It happens sometimes for the AILerp component when it reaches its + // destination, as the AILerp component has no radius. + // In this case we just use the maximum period. + fraction = 0; + } + + return timeSinceLast >= maximumPeriod*(1 - Mathf.Sqrt(fraction)); + } + } + + /// Reset the runtime variables so that the policy behaves as if the game just started + public virtual void Reset () { + lastRepathTime = float.NegativeInfinity; + } + + /// Must be called when a path request has been scheduled + public virtual void DidRecalculatePath (Vector3 destination, float time) { + lastRepathTime = time; + lastDestination = destination; + // Randomize the repath time slightly so that all agents don't request a path at the same time + // in the future. This is useful when there are a lot of agents instantiated at exactly the same time. + const float JITTER_AMOUNT = 0.3f; + lastRepathTime -= (UnityEngine.Random.value - 0.5f) * JITTER_AMOUNT * (mode == Mode.Dynamic ? maximumPeriod : period); + } + + public void DrawGizmos (CommandBuilder draw, Vector3 position, float radius, Util.NativeMovementPlane movementPlane) { + if (visualizeSensitivity && !float.IsPositiveInfinity(lastDestination.x)) { + float r = Mathf.Sqrt(Mathf.Max((position - lastDestination).sqrMagnitude, radius*radius)/(sensitivity*sensitivity)); + draw.Circle(lastDestination, movementPlane.ToWorld(float2.zero, 1), r, Color.magenta); + } + } + + public AutoRepathPolicy Clone () { + return MemberwiseClone() as AutoRepathPolicy; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs.meta new file mode 100644 index 0000000..d3535cb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/AutoRepathPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2664ef60fd2811ba280670298a6d312b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs new file mode 100644 index 0000000..17f5828 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs @@ -0,0 +1,13 @@ +using Pathfinding.Serialization; + +namespace Pathfinding { + [JsonOptIn] + /// + /// Base class for all graph editors. + /// Defined here only so non-editor classes can use the field + /// + public class GraphEditorBase { + /// NavGraph this editor is exposing + public NavGraph target; + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs.meta new file mode 100644 index 0000000..1a638c9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphEditorBase.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 704136724bc95455ebe477f42f5c5a84 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs new file mode 100644 index 0000000..b94de4f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs @@ -0,0 +1,252 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; + +namespace Pathfinding { + /// + /// GraphModifier is used for modifying graphs or processing graph data based on events. + /// This class is a simple container for a number of events. + /// + /// \borderlessimage{graph_events.png} + /// + /// Warning: Some events will be called both in play mode and in editor mode (at least the scan events). + /// So make sure your code handles both cases well. You may choose to ignore editor events. + /// See: Application.IsPlaying + /// + /// Warning: Events may be received before Awake and OnEnable has been called on the component. This is because + /// graphs are typically scanned during Awake on the AstarPath component, which may happen before Awake on the graph modifier itself. + /// + [ExecuteInEditMode] + public abstract class GraphModifier : VersionedMonoBehaviour { + /// All active graph modifiers + private static GraphModifier root; + + private GraphModifier prev; + private GraphModifier next; + + /// Unique persistent ID for this component, used for serialization + [SerializeField] + [HideInInspector] + protected ulong uniqueID; + + /// Maps persistent IDs to the component that uses it + protected static Dictionary usedIDs = new Dictionary(); + + protected static List GetModifiersOfType() where T : GraphModifier { + var current = root; + var result = new List(); + + while (current != null) { + var cast = current as T; + if (cast != null) result.Add(cast); + current = current.next; + } + return result; + } + + public static void FindAllModifiers () { + var allModifiers = UnityCompatibility.FindObjectsByTypeSorted(); + + for (int i = 0; i < allModifiers.Length; i++) { + if (allModifiers[i].enabled) { + if (allModifiers[i].next == null) { + // The modifier is not yet registered. Presumably it is enabled, + // but unity hasn't had time to call OnEnable yet. + // Disabling it and enabling it will force unity to call OnEnable immediately. + // We don't want to call it ourselves, because then Unity won't know that it has been called, + // which could cause issues for lifecycle management. + // For example, if we called OnEnable manually (before Unity did), and then the object was destroyed + // before Unity had a chance to call OnEnable, then Unity would not call OnDisable. + allModifiers[i].enabled = false; + allModifiers[i].enabled = true; + } + } + } + } + + /// GraphModifier event type + public enum EventType { + PostScan = 1 << 0, + PreScan = 1 << 1, + LatePostScan = 1 << 2, + PreUpdate = 1 << 3, + PostUpdate = 1 << 4, + PostCacheLoad = 1 << 5, + PostUpdateBeforeAreaRecalculation = 1 << 6, + PostGraphLoad = 1 << 7, + } + + /// Triggers an event for all active graph modifiers + public static void TriggerEvent (GraphModifier.EventType type) { + if (!Application.isPlaying) { + FindAllModifiers(); + } + + try { + GraphModifier c = root; + switch (type) { + case EventType.PreScan: + while (c != null) { c.OnPreScan(); c = c.next; } + break; + case EventType.PostScan: + while (c != null) { c.OnPostScan(); c = c.next; } + break; + case EventType.LatePostScan: + while (c != null) { c.OnLatePostScan(); c = c.next; } + break; + case EventType.PreUpdate: + while (c != null) { c.OnGraphsPreUpdate(); c = c.next; } + break; + case EventType.PostUpdate: + while (c != null) { c.OnGraphsPostUpdate(); c = c.next; } + break; + case EventType.PostUpdateBeforeAreaRecalculation: + while (c != null) { c.OnGraphsPostUpdateBeforeAreaRecalculation(); c = c.next; } + break; + case EventType.PostCacheLoad: + while (c != null) { c.OnPostCacheLoad(); c = c.next; } + break; + case EventType.PostGraphLoad: + while (c != null) { c.OnPostGraphLoad(); c = c.next; } + break; + } + } catch (System.Exception e) { + Debug.LogException(e); + } + } + + /// Adds this modifier to list of active modifiers + protected virtual void OnEnable () { + RemoveFromLinkedList(); + AddToLinkedList(); + ConfigureUniqueID(); + } + + /// Removes this modifier from list of active modifiers + protected virtual void OnDisable () { + RemoveFromLinkedList(); + } + + protected override void Awake () { + base.Awake(); + ConfigureUniqueID(); + } + + void ConfigureUniqueID () { + // Check if any other object is using the same uniqueID + // In that case this object may have been duplicated + GraphModifier usedBy; + + if (usedIDs.TryGetValue(uniqueID, out usedBy) && usedBy != this) { + Reset(); + } + + usedIDs[uniqueID] = this; + } + + void AddToLinkedList () { + if (root == null) { + root = this; + } else { + next = root; + root.prev = this; + root = this; + } + } + + void RemoveFromLinkedList () { + if (root == this) { + root = next; + if (root != null) root.prev = null; + } else { + if (prev != null) prev.next = next; + if (next != null) next.prev = prev; + } + prev = null; + next = null; + } + + protected virtual void OnDestroy () { + usedIDs.Remove(uniqueID); + } + + /// + /// Called right after all graphs have been scanned. + /// + /// Note: Area information (see may not be up to date when this event is sent. + /// This means some methods like may return incorrect results. + /// Use if you need that info to be up to date. + /// + /// See: OnLatePostScan + /// + public virtual void OnPostScan () {} + + /// + /// Called right before graphs are going to be scanned. + /// + /// See: OnLatePostScan + /// + public virtual void OnPreScan () {} + + /// + /// Called at the end of the scanning procedure. + /// This is the absolute last thing done by Scan. + /// + public virtual void OnLatePostScan () {} + + /// + /// Called after cached graphs have been loaded. + /// When using cached startup, this event is analogous to OnLatePostScan and implementing scripts + /// should do roughly the same thing for both events. + /// + public virtual void OnPostCacheLoad () {} + + /// + /// Called after a graph has been deserialized and loaded. + /// Note: The graph may not have had any valid node data, it might just contain the graph settings. + /// + /// This will be called often outside of play mode. Make sure to check Application.isPlaying if appropriate. + /// + public virtual void OnPostGraphLoad () {} + + /// Called before graphs are updated using GraphUpdateObjects + public virtual void OnGraphsPreUpdate () {} + + /// + /// Called after graphs have been updated using GraphUpdateObjects or navmesh cutting. + /// + /// This is among other times called after graphs have been scanned, updated using GraphUpdateObjects, navmesh cuts, or GraphUpdateScene components. + /// + /// Area recalculations (see ) have been done at this stage so things like PathUtilities.IsPathPossible will work. + /// + /// Use instead if you are modifying the graph in any way, especially connections and walkability. + /// This is because if you do this then area recalculations + /// + public virtual void OnGraphsPostUpdate () {} + + /// + /// Called after graphs have been updated. + /// + /// This is among other times called after graphs have been scanned, updated using GraphUpdateObjects, navmesh cuts, or GraphUpdateScene components. + /// + /// Note: Area information (see may not be up to date when this event is sent. + /// This means some methods like may return incorrect results. + /// Use if you need that info to be up to date. + /// + /// Use this if you are modifying any graph connections or walkability. + /// + /// See: + /// + public virtual void OnGraphsPostUpdateBeforeAreaRecalculation () {} + + protected override void Reset () { + base.Reset(); + // Create a new random 64 bit value (62 bit actually because we skip negative numbers, but that's still enough by a huge margin) + var rnd1 = (ulong)Random.Range(0, int.MaxValue); + var rnd2 = ((ulong)Random.Range(0, int.MaxValue) << 32); + + uniqueID = rnd1 | rnd2; + usedIDs[uniqueID] = this; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs.meta new file mode 100644 index 0000000..e8dbf6d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphModifier.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 39897fb482672480a817862c3909a4aa +timeCreated: 1490044676 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: -222 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs new file mode 100644 index 0000000..4b21d17 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using UnityEngine.Profiling; + +namespace Pathfinding.Util { + public interface IGraphSnapshot : System.IDisposable { + /// + /// Restores the graph data to the state it had when the snapshot was taken, in the bounding box that the snapshot captured. + /// + /// You can get the context from the callback provided to the method. + /// + void Restore(IGraphUpdateContext ctx); + } + + /// + /// A snapshot of parts of graphs. + /// + /// See: + /// + public struct GraphSnapshot : IGraphSnapshot { + List inner; + + internal GraphSnapshot (List inner) { + this.inner = inner; + } + + /// \copydocref{IGraphSnapshot.Restore} + public void Restore (IGraphUpdateContext ctx) { + Profiler.BeginSample("Restoring Graph Snapshot"); + for (int i = 0; i < inner.Count; i++) { + inner[i].Restore(ctx); + } + Profiler.EndSample(); + } + + public void Dispose () { + if (inner != null) { + for (int i = 0; i < inner.Count; i++) { + inner[i].Dispose(); + } + inner = null; + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs.meta new file mode 100644 index 0000000..b551fc1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphSnapshot.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c0eba1e12af0b040baa5627d27fe428 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs new file mode 100644 index 0000000..c28cc6a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs @@ -0,0 +1,274 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Pooling; + + /// + /// Contains utility methods for getting useful information out of graph. + /// This class works a lot with the class, a useful function to get nodes is . + /// + /// See: + /// See: + /// See: + /// + public static class GraphUtilities { + /// + /// Convenience method to get a list of all segments of the contours of a graph. + /// Returns: A list of segments. Every 2 elements form a line segment. The first segment is (result[0], result[1]), the second one is (result[2], result[3]) etc. + /// The line segments are oriented so that the navmesh is on the right side of the segments when seen from above. + /// + /// This method works for navmesh, recast, grid graphs and layered grid graphs. For other graph types it will return an empty list. + /// + /// If you need more information about how the contours are connected you can take a look at the other variants of this method. + /// + /// + /// // Get the first graph + /// var navmesh = AstarPath.active.graphs[0]; + /// + /// // Get all contours of the graph (works for grid, navmesh and recast graphs) + /// var segments = GraphUtilities.GetContours(navmesh); + /// + /// // Every 2 elements form a line segment. The first segment is (segments[0], segments[1]), the second one is (segments[2], segments[3]) etc. + /// // The line segments are oriented so that the navmesh is on the right side of the segments when seen from above. + /// for (int i = 0; i < segments.Count; i += 2) { + /// var start = segments[i]; + /// var end = segments[i+1]; + /// Debug.DrawLine(start, end, Color.red, 3); + /// } + /// + /// + /// [Open online documentation to see images] + /// [Open online documentation to see images] + /// + public static List GetContours (NavGraph graph) { + List result = ListPool.Claim(); + + if (graph is NavmeshBase navmesh) { + GetContours(navmesh, (vertices, cycle) => { + for (int j = cycle ? vertices.Count - 1 : 0, i = 0; i < vertices.Count; j = i, i++) { + result.Add((Vector3)vertices[j]); + result.Add((Vector3)vertices[i]); + } + }); +#if !ASTAR_NO_GRID_GRAPH + } else if (graph is GridGraph) { + GetContours(graph as GridGraph, vertices => { + for (int j = vertices.Length - 1, i = 0; i < vertices.Length; j = i, i++) { + result.Add((Vector3)vertices[j]); + result.Add((Vector3)vertices[i]); + } + }, 0); +#endif + } + return result; + } + + /// + /// Traces the contour of a navmesh. + /// + /// [Open online documentation to see images] + /// + /// This image is just used to illustrate the difference between chains and cycles. That it shows a grid graph is not relevant. + /// [Open online documentation to see images] + /// + /// See: + /// + /// The navmesh-like object to trace. This can be a recast or navmesh graph or it could be a single tile in one such graph. + /// Will be called once for each contour with the contour as a parameter as well as a boolean indicating if the contour is a cycle or a chain (see second image). + public static void GetContours (NavmeshBase navmesh, System.Action, bool> results) { + // Assume 3 vertices per node + var uses = new bool[3]; + + var outline = new Dictionary(); + var vertexPositions = new Dictionary(); + var hasInEdge = new HashSet(); + + navmesh.GetNodes(_node => { + var node = _node as TriangleMeshNode; + + uses[0] = uses[1] = uses[2] = false; + + if (node != null) { + // Find out which edges are shared with other nodes + for (int j = 0; j < node.connections.Length; j++) { + var conn = node.connections[j]; + if (conn.isEdgeShared) uses[conn.shapeEdge] = true; + } + + // Loop through all edges on the node + for (int j = 0; j < 3; j++) { + // The edge is not shared with any other node + // I.e it is an exterior edge on the mesh + if (!uses[j]) { + var i1 = j; + var i2 = (j+1) % node.GetVertexCount(); + + outline[node.GetVertexIndex(i1)] = node.GetVertexIndex(i2); + hasInEdge.Add(node.GetVertexIndex(i2)); + vertexPositions[node.GetVertexIndex(i1)] = node.GetVertex(i1); + vertexPositions[node.GetVertexIndex(i2)] = node.GetVertex(i2); + } + } + } + }); + + Polygon.TraceContours(outline, hasInEdge, (chain, cycle) => { + List vertices = ListPool.Claim(); + for (int i = 0; i < chain.Count; i++) vertices.Add(vertexPositions[chain[i]]); + results(vertices, cycle); + }); + } + +#if !ASTAR_NO_GRID_GRAPH + /// + /// Finds all contours of a collection of nodes in a grid graph. + /// + /// + /// var grid = AstarPath.active.data.gridGraph; + /// + /// // Find all contours in the graph and draw them using debug lines + /// GraphUtilities.GetContours(grid, vertices => { + /// for (int i = 0; i < vertices.Length; i++) { + /// Debug.DrawLine(vertices[i], vertices[(i+1)%vertices.Length], Color.red, 4); + /// } + /// }, 0); + /// + /// + /// In the image below you can see the contour of a graph. + /// [Open online documentation to see images] + /// + /// In the image below you can see the contour of just a part of a grid graph (when the nodes parameter is supplied) + /// [Open online documentation to see images] + /// + /// Contour of a hexagon graph + /// [Open online documentation to see images] + /// + /// See: + /// + /// The grid to find the contours of + /// The callback will be called once for every contour that is found with the vertices of the contour. The contour always forms a cycle. + /// Contours will be simplified if the y coordinates for adjacent vertices differ by no more than this value. + /// Only these nodes will be searched. If this parameter is null then all nodes in the grid graph will be searched. + /// Allows you to disable connections between nodes. If null, no additional filtering will be done. The filter must be symmetric, so that f(A,B) == f(B,A). A contour edge will be generated between two adjacent nodes if this function returns false for the pair. + public static void GetContours (GridGraph grid, System.Action callback, float yMergeThreshold, GridNodeBase[] nodes = null, System.Func connectionFilter = null) { + // Set of all allowed nodes or null if all nodes are allowed + HashSet nodeSet = nodes != null ? new HashSet(nodes) : null; + + // Use all nodes if the nodes parameter is null + if (grid is LayerGridGraph lgraph) nodes = nodes ?? lgraph.nodes; + nodes = nodes ?? grid.nodes; + int[] neighbourXOffsets = GridGraph.neighbourXOffsets; + int[] neighbourZOffsets = GridGraph.neighbourZOffsets; + var neighbourIndices = grid.neighbours == NumNeighbours.Six ? GridGraph.hexagonNeighbourIndices : new [] { 0, 1, 2, 3 }; + var offsetMultiplier = grid.neighbours == NumNeighbours.Six ? 1/3f : 0.5f; + + if (nodes != null) { + var trace = ListPool.Claim(); + var seenStates = new HashSet(); + + for (int i = 0; i < nodes.Length; i++) { + var startNode = nodes[i]; + // The third check is a fast check for if the node has connections in all grid directions, if it has then we can skip processing it (unless the nodes parameter was used in which case we have to handle the edge cases) + if (startNode != null && startNode.Walkable && (!startNode.HasConnectionsToAllEightNeighbours || nodeSet != null)) { + for (int startDir = 0; startDir < neighbourIndices.Length; startDir++) { + int startState = ((int)startNode.NodeIndex << 4) | startDir; + + // Check if there is an obstacle in that direction + var startNeighbour = startNode.GetNeighbourAlongDirection(neighbourIndices[startDir]); + if (connectionFilter != null && startNeighbour != null && !connectionFilter(startNode, startNeighbour)) startNeighbour = null; + + if ((startNeighbour == null || (nodeSet != null && !nodeSet.Contains(startNeighbour))) && !seenStates.Contains(startState)) { + // Start tracing a contour here + trace.ClearFast(); + int dir = startDir; + GridNodeBase node = startNode; + + while (true) { + int state = ((int)node.NodeIndex << 4) | dir; + if (state == startState && trace.Count > 0) { + break; + } + + seenStates.Add(state); + + var neighbour = node.GetNeighbourAlongDirection(neighbourIndices[dir]); + if (connectionFilter != null && neighbour != null && !connectionFilter(node, neighbour)) neighbour = null; + + if (neighbour == null || (nodeSet != null && !nodeSet.Contains(neighbour))) { + // Draw edge + var d0 = neighbourIndices[dir]; + dir = (dir + 1) % neighbourIndices.Length; + var d1 = neighbourIndices[dir]; + + // Position in graph space of the vertex + Vector3 graphSpacePos = new Vector3(node.XCoordinateInGrid + 0.5f, 0, node.ZCoordinateInGrid + 0.5f); + // Offset along diagonal to get the correct XZ coordinates + graphSpacePos.x += (neighbourXOffsets[d0] + neighbourXOffsets[d1]) * offsetMultiplier; + graphSpacePos.z += (neighbourZOffsets[d0] + neighbourZOffsets[d1]) * offsetMultiplier; + graphSpacePos.y = grid.transform.InverseTransform((Vector3)node.position).y; + + if (trace.Count >= 2) { + var v0 = trace[trace.Count-2]; + var v1 = trace[trace.Count-1]; + var v1d = v1 - v0; + var v2d = graphSpacePos - v0; + // Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead + if (((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold)) { + trace.Add(graphSpacePos); + } else { + trace[trace.Count-1] = graphSpacePos; + } + } else { + trace.Add(graphSpacePos); + } + } else { +#if UNITY_EDITOR + if (!neighbour.HasConnectionInDirection(GridNodeBase.OppositeConnectionDirection(neighbourIndices[dir]))) { + throw new System.InvalidOperationException("Cannot calculate contour. The graph contains one-way connections. A contour is not well defined if one-way connections exist."); + } +#endif + // Move + node = neighbour; + dir = (dir + neighbourIndices.Length/2 + 1) % neighbourIndices.Length; + } + } + + // Simplify the contour a bit around the start point. + // Otherwise we might return a cycle which was not as simplified as possible and the number of vertices + // would depend on where in the cycle the algorithm started to traverse the contour. + if (trace.Count >= 3) { + var v0 = trace[trace.Count-2]; + var v1 = trace[trace.Count-1]; + var v1d = v1 - v0; + var v2d = trace[0] - v0; + // Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead + if (!(((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold))) { + trace.RemoveAt(trace.Count - 1); + } + } + + if (trace.Count >= 3) { + var v0 = trace[trace.Count-1]; + var v1 = trace[0]; + var v1d = v1 - v0; + var v2d = trace[1] - v0; + // Replace the previous point if it is colinear with the point just before it and just after it (the current point), because that point wouldn't add much information, but it would add CPU overhead + if (!(((Mathf.Abs(v1d.x) > 0.01f || Mathf.Abs(v2d.x) > 0.01f) && (Mathf.Abs(v1d.z) > 0.01f || Mathf.Abs(v2d.z) > 0.01f)) || (Mathf.Abs(v1d.y) > yMergeThreshold || Mathf.Abs(v2d.y) > yMergeThreshold))) { + trace.RemoveAt(0); + } + } + var result = trace.ToArray(); + grid.transform.Transform(result); + callback(result); + } + } + } + } + + ListPool.Release(ref trace); + } + } +#endif + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs.meta new file mode 100644 index 0000000..d0417cc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/GraphUtilities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fd178834bf6c54cdb8fc5f76a039a91c +timeCreated: 1502889881 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs new file mode 100644 index 0000000..88c5a7b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs @@ -0,0 +1,26 @@ +using UnityEngine; +namespace Pathfinding { + using Pathfinding.Util; + + /// Helper for + [HelpURL("https://arongranberg.com/astar/documentation/stable/localspacegraph.html")] + public class LocalSpaceGraph : VersionedMonoBehaviour { + Matrix4x4 originalMatrix; + MutableGraphTransform graphTransform = new MutableGraphTransform(Matrix4x4.identity); + public GraphTransform transformation { get { return graphTransform; } } + + void Start () { + originalMatrix = transform.worldToLocalMatrix; + transform.hasChanged = true; + Refresh(); + } + + public void Refresh () { + // Avoid updating the GraphTransform if the object has not moved + if (transform.hasChanged) { + graphTransform.SetMatrix(transform.localToWorldMatrix * originalMatrix); + transform.hasChanged = false; + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs.meta new file mode 100644 index 0000000..72899ae --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/LocalSpaceGraph.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 402bb27f2effb4bd183ec7f7dd47b078 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs new file mode 100644 index 0000000..f22fd7a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs @@ -0,0 +1,454 @@ +using Pathfinding.Sync; +using Unity.Collections; +using Unity.Burst; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Profiling; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Profiling; + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.RVO; + using Pathfinding.Jobs; + using Pathfinding.Drawing; + using Pathfinding.Collections; + + [BurstCompile] + public class NavmeshEdges { + public RVO.SimulatorBurst.ObstacleData obstacleData; + NativeReference allocationLock; + const int JobRecalculateObstaclesBatchCount = 32; + RWLock rwLock = new RWLock(); + public HierarchicalGraph hierarchicalGraph; + int gizmoVersion = 0; + + public void Dispose () { + // Waits for any jobs to finish + rwLock.WriteSync().Unlock(); + obstacleData.Dispose(); + // Note: The IsCreated check is necessary to not throw an exception in old versions of the collections package. + if (allocationLock.IsCreated) allocationLock.Dispose(); + } + + void Init () { + obstacleData.Init(Allocator.Persistent); + if (!allocationLock.IsCreated) allocationLock = new NativeReference(Allocator.Persistent); + } + + public JobHandle RecalculateObstacles (NativeList dirtyHierarchicalNodes, NativeReference numHierarchicalNodes, JobHandle dependency) { + Init(); + + unsafe { + // Resize the obstacle data arrays if necessary. + // We need to do this in a separate single-threaded job before we branch out to multiple threads. + var writeLock = rwLock.Write(); + var lastJob = new JobResizeObstacles { + numHierarchicalNodes = numHierarchicalNodes, + obstacles = obstacleData.obstacles, + }.Schedule(JobHandle.CombineDependencies(dependency, writeLock.dependency)); + lastJob = new JobCalculateObstacles { + hGraphGC = hierarchicalGraph.gcHandle, + obstacleVertices = obstacleData.obstacleVertices, + obstacleVertexGroups = obstacleData.obstacleVertexGroups, + obstacles = obstacleData.obstacles.AsDeferredJobArray(), + bounds = hierarchicalGraph.bounds.AsDeferredJobArray(), + dirtyHierarchicalNodes = dirtyHierarchicalNodes, + allocationLock = allocationLock, + }.ScheduleBatch(JobRecalculateObstaclesBatchCount, 1, lastJob); + writeLock.UnlockAfter(lastJob); + gizmoVersion++; + return lastJob; + } + } + + public void OnDrawGizmos (DrawingData gizmos, bool renderInGame) { + if (!obstacleData.obstacleVertices.IsCreated) return; + + var hasher = new NodeHasher(AstarPath.active); + hasher.Add(12314127); // Some random constant to avoid hash collisions with other systems + hasher.Add(gizmoVersion); + + if (!gizmos.Draw(hasher)) { + var readLock = rwLock.ReadSync(); + try { + using (var builder = gizmos.GetBuilder(hasher, default, renderInGame)) { + for (int i = 1; i < obstacleData.obstacles.Length; i++) { + var ob = obstacleData.obstacles[i]; + var vertices = obstacleData.obstacleVertices.GetSpan(ob.verticesAllocation); + var groups = obstacleData.obstacleVertexGroups.GetSpan(ob.groupsAllocation); + var vertexOffset = 0; + for (int g = 0; g < groups.Length; g++) { + var group = groups[g]; + builder.PushLineWidth(2f); + for (int j = 0; j < group.vertexCount - 1; j++) { + builder.ArrowRelativeSizeHead(vertices[vertexOffset + j], vertices[vertexOffset + j + 1], new float3(0, 1, 0), 0.05f, Color.black); + } + if (group.type == RVO.ObstacleType.Loop) { + builder.Arrow(vertices[vertexOffset + group.vertexCount - 1], vertices[vertexOffset], new float3(0, 1, 0), 0.05f, Color.black); + } + builder.PopLineWidth(); + vertexOffset += group.vertexCount; + builder.WireBox(0.5f*(group.boundsMn + group.boundsMx), group.boundsMx - group.boundsMn, Color.white); + } + } + } + } finally { + readLock.Unlock(); + } + } + } + + /// + /// Obstacle data for navmesh edges. + /// + /// Can be queried in burst jobs. + /// + public NavmeshBorderData GetNavmeshEdgeData (out RWLock.CombinedReadLockAsync readLock) { + Init(); + var readLock1 = rwLock.Read(); + var hierarchicalNodeData = hierarchicalGraph.GetHierarhicalNodeData(out var readLock2); + readLock = new RWLock.CombinedReadLockAsync(readLock1, readLock2); + return new NavmeshBorderData { + hierarhicalNodeData = hierarchicalNodeData, + obstacleData = obstacleData, + }; + } + + [BurstCompile] + struct JobResizeObstacles : IJob { + public NativeList obstacles; + public NativeReference numHierarchicalNodes; + + public void Execute () { + var prevLength = obstacles.Length; + var newLength = numHierarchicalNodes.Value; + obstacles.Resize(newLength, NativeArrayOptions.UninitializedMemory); + for (int i = prevLength; i < obstacles.Length; i++) obstacles[i] = new RVO.UnmanagedObstacle { verticesAllocation = SlabAllocator.ZeroLengthArray, groupsAllocation = SlabAllocator.ZeroLengthArray }; + // First hierarchical node is always invalid + if (obstacles.Length > 0) obstacles[0] = new RVO.UnmanagedObstacle { verticesAllocation = SlabAllocator.InvalidAllocation, groupsAllocation = SlabAllocator.InvalidAllocation }; + } + } + + struct JobCalculateObstacles : IJobParallelForBatch { + public System.Runtime.InteropServices.GCHandle hGraphGC; + public SlabAllocator obstacleVertices; + public SlabAllocator obstacleVertexGroups; + [NativeDisableParallelForRestriction] + public NativeArray obstacles; + [NativeDisableParallelForRestriction] + public NativeArray bounds; + [ReadOnly] + public NativeList dirtyHierarchicalNodes; + [NativeDisableParallelForRestriction] + public NativeReference allocationLock; + + public void Execute (int startIndex, int count) { + var hGraph = hGraphGC.Target as HierarchicalGraph; + var stepMultiplier = (dirtyHierarchicalNodes.Length + JobRecalculateObstaclesBatchCount - 1) / JobRecalculateObstaclesBatchCount; + startIndex *= stepMultiplier; + count *= stepMultiplier; + var finalIndex = math.min(startIndex + count, dirtyHierarchicalNodes.Length); + var edges = new NativeList(Allocator.Temp); + for (int i = startIndex; i < finalIndex; i++) { + edges.Clear(); + var hNode = dirtyHierarchicalNodes[i]; + UnityEngine.Assertions.Assert.IsTrue(hNode > 0 && hNode < obstacles.Length); + // These tasks are independent, but they benefit a lot from running at the same time + // due to cache locality (they use mostly the same data). + CalculateBoundingBox(hGraph, hNode); + CalculateObstacles(hGraph, hNode, obstacleVertexGroups, obstacleVertices, obstacles, edges); + } + } + + private static readonly ProfilerMarker MarkerBBox = new ProfilerMarker("HierarchicalBBox"); + private static readonly ProfilerMarker MarkerObstacles = new ProfilerMarker("CalculateObstacles"); + private static readonly ProfilerMarker MarkerCollect = new ProfilerMarker("Collect"); + private static readonly ProfilerMarker MarkerTrace = new ProfilerMarker("Trace"); + + void CalculateBoundingBox (HierarchicalGraph hGraph, int hierarchicalNode) { + var nodes = hGraph.children[hierarchicalNode]; + MarkerBBox.Begin(); + var b = new Bounds(); + // We know that all nodes in an hierarchical node only belongs to a single graph, + // so we can branch on the type of the first node, and use optimized code for each node type. + if (nodes.Count == 0) { + // NOOP + } else if (nodes[0] is TriangleMeshNode) { + var mn = new Int3(int.MaxValue, int.MaxValue, int.MaxValue); + var mx = new Int3(int.MinValue, int.MinValue, int.MinValue); + for (int i = 0; i < nodes.Count; i++) { + var node = nodes[i] as TriangleMeshNode; + node.GetVertices(out var v0, out var v1, out var v2); + mn = Int3.Min(Int3.Min(Int3.Min(mn, v0), v1), v2); + mx = Int3.Max(Int3.Max(Int3.Max(mx, v0), v1), v2); + } + b.SetMinMax((Vector3)mn, (Vector3)mx); + } else { + var mn = new Int3(int.MaxValue, int.MaxValue, int.MaxValue); + var mx = new Int3(int.MinValue, int.MinValue, int.MinValue); + for (int i = 0; i < nodes.Count; i++) { + var node = nodes[i]; + mn = Int3.Min(mn, node.position); + mx = Int3.Max(mx, node.position); + } + if (nodes[0] is GridNodeBase) { + float nodeSize; + if (nodes[0] is LevelGridNode) nodeSize = LevelGridNode.GetGridGraph(nodes[0].GraphIndex).nodeSize; + else + nodeSize = GridNode.GetGridGraph(nodes[0].GraphIndex).nodeSize; + // Grid nodes have a surface. We don't know how it is oriented, so we pad conservatively in all directions. + // The surface can extend at most nodeSize*sqrt(2)/2 in any direction. + const float SQRT2_DIV_2 = 0.70710678f; + var padding = nodeSize*SQRT2_DIV_2*Vector3.one; + b.SetMinMax((Vector3)mn - padding, (Vector3)mx + padding); + } else { + // Point node, or other custom node type + b.SetMinMax((Vector3)mn, (Vector3)mx); + } + } + bounds[hierarchicalNode] = b; + MarkerBBox.End(); + } + + void CalculateObstacles (HierarchicalGraph hGraph, int hierarchicalNode, SlabAllocator obstacleVertexGroups, SlabAllocator obstacleVertices, NativeArray obstacles, NativeList edgesScratch) { + MarkerObstacles.Begin(); + MarkerCollect.Begin(); + RVO.RVOObstacleCache.CollectContours(hGraph.children[hierarchicalNode], edgesScratch); + MarkerCollect.End(); + var prev = obstacles[hierarchicalNode]; + unsafe { + ref var allocationLockRef = ref UnsafeUtility.AsRef(allocationLock.GetUnsafePtr()); + if (prev.groupsAllocation != SlabAllocator.ZeroLengthArray) { + unsafe { + allocationLockRef.Lock(); + obstacleVertices.Free(prev.verticesAllocation); + obstacleVertexGroups.Free(prev.groupsAllocation); + allocationLockRef.Unlock(); + } + } + unsafe { + // Find the graph's natural movement plane. + // This is used to simplify almost colinear segments into a single segment. + var children = hGraph.children[hierarchicalNode]; + NativeMovementPlane movementPlane; + bool simplifyObstacles = true; + if (children.Count > 0) { + if (children[0] is GridNodeBase) { + movementPlane = new NativeMovementPlane((children[0].Graph as GridGraph).transform.rotation); + } else if (children[0] is TriangleMeshNode) { + var graph = children[0].Graph as NavmeshBase; + movementPlane = new NativeMovementPlane(graph.transform.rotation); + // If normal recalculation is disabled, the graph may have very a strange shape, like a spherical world. + // In that case we should not simplify the obstacles, as there is no well defined movement plane. + simplifyObstacles = graph.RecalculateNormals; + } else { + movementPlane = new NativeMovementPlane(quaternion.identity); + simplifyObstacles = false; + } + } else { + movementPlane = default; + } + MarkerTrace.Begin(); + var edgesSpan = edgesScratch.AsUnsafeSpan(); + RVO.RVOObstacleCache.TraceContours( + ref edgesSpan, + ref movementPlane, + hierarchicalNode, + (UnmanagedObstacle*)obstacles.GetUnsafePtr(), + ref obstacleVertices, + ref obstacleVertexGroups, + ref allocationLockRef, + simplifyObstacles + ); + MarkerTrace.End(); + } + } + MarkerObstacles.End(); + } + } + + /// + /// Burst-accessible data about borders in the navmesh. + /// + /// Can be queried from burst, and from multiple threads in parallel. + /// + // TODO: Change to a quadtree/kdtree/aabb tree that stored edges as { index: uint10, prev: uint10, next: uint10 }, with a natural max of 1024 vertices per obstacle (hierarchical node). This is fine because hnodes have at most 256 nodes, which cannot create more than 1024 edges. + public struct NavmeshBorderData { + public HierarchicalGraph.HierarhicalNodeData hierarhicalNodeData; + public RVO.SimulatorBurst.ObstacleData obstacleData; + + /// + /// An empty set of edges. + /// + /// Must be disposed using . + /// + public static NavmeshBorderData CreateEmpty (Allocator allocator) { + return new NavmeshBorderData { + hierarhicalNodeData = new HierarchicalGraph.HierarhicalNodeData { + connectionAllocator = default, + connectionAllocations = new NativeList(0, allocator), + bounds = new NativeList(0, allocator), + }, + obstacleData = new RVO.SimulatorBurst.ObstacleData { + obstacleVertexGroups = default, + obstacleVertices = default, + obstacles = new NativeList(0, allocator), + } + }; + } + + public void DisposeEmpty (JobHandle dependsOn) { + if (hierarhicalNodeData.connectionAllocator.IsCreated) throw new System.InvalidOperationException("NavmeshEdgeData was not empty"); + hierarhicalNodeData.connectionAllocations.Dispose(dependsOn); + hierarhicalNodeData.bounds.Dispose(dependsOn); + obstacleData.obstacles.Dispose(dependsOn); + } + + static void GetHierarchicalNodesInRangeRec (int hierarchicalNode, Bounds bounds, SlabAllocator connectionAllocator, [NoAlias] NativeList connectionAllocations, NativeList nodeBounds, [NoAlias] NativeList indices) { + indices.Add(hierarchicalNode); + var conns = connectionAllocator.GetSpan(connectionAllocations[hierarchicalNode]); + for (int i = 0; i < conns.Length; i++) { + var neighbour = conns[i]; + if (nodeBounds[neighbour].Intersects(bounds) && !indices.Contains(neighbour)) { + GetHierarchicalNodesInRangeRec(neighbour, bounds, connectionAllocator, connectionAllocations, nodeBounds, indices); + } + } + } + + static unsafe void ConvertObstaclesToEdges (ref RVO.SimulatorBurst.ObstacleData obstacleData, NativeList obstacleIndices, Bounds localBounds, NativeList edgeBuffer, NativeMovementPlane movementPlane) { + var globalBounds = movementPlane.ToWorld(localBounds); + var worldToMovementPlane = movementPlane.AsWorldToPlaneMatrix(); + var globalMn = (float3)globalBounds.min; + var globalMx = (float3)globalBounds.max; + var localMn = (float3)localBounds.min; + var localMx = (float3)localBounds.max; + int vertexCount = 0; + for (int obstacleIndex = 0; obstacleIndex < obstacleIndices.Length; obstacleIndex++) { + var obstacle = obstacleData.obstacles[obstacleIndices[obstacleIndex]]; + vertexCount += obstacleData.obstacleVertices.GetSpan(obstacle.verticesAllocation).Length; + } + edgeBuffer.ResizeUninitialized(vertexCount*3); + int edgeVertexOffset = 0; + for (int obstacleIndex = 0; obstacleIndex < obstacleIndices.Length; obstacleIndex++) { + var obstacle = obstacleData.obstacles[obstacleIndices[obstacleIndex]]; + if (obstacle.verticesAllocation != SlabAllocator.ZeroLengthArray) { + var vertices = obstacleData.obstacleVertices.GetSpan(obstacle.verticesAllocation); + var groups = obstacleData.obstacleVertexGroups.GetSpan(obstacle.groupsAllocation); + int offset = 0; + for (int i = 0; i < groups.Length; i++) { + var group = groups[i]; + if (!math.all((group.boundsMx >= globalMn) & (group.boundsMn <= globalMx))) { + offset += group.vertexCount; + continue; + } + + var loop = group.type == RVO.ObstacleType.Loop; + for (int a = offset + (loop ? group.vertexCount - 1 : 0), b = offset + (loop ? 0 : 1); b < offset + group.vertexCount; a = b, b++) { + var p1 = vertices[a]; + var p2 = vertices[b]; + var mn = math.min(p1, p2); + var mx = math.max(p1, p2); + // Check for intersection with the global bounds (coarse check) + if (math.all((mx >= globalMn) & (mn <= globalMx))) { + var p1local = worldToMovementPlane.ToXZPlane(p1); + var p2local = worldToMovementPlane.ToXZPlane(p2); + mn = math.min(p1local, p2local); + mx = math.max(p1local, p2local); + // Check for intersection with the local bounds (more accurate) + if (math.all((mx >= localMn) & (mn <= localMx))) { + edgeBuffer[edgeVertexOffset++] = p1local.xz; + edgeBuffer[edgeVertexOffset++] = p2local.xz; + } + } + } + offset += group.vertexCount; + } + } + } + UnityEngine.Assertions.Assert.IsTrue(edgeVertexOffset <= edgeBuffer.Length); + edgeBuffer.Length = edgeVertexOffset; + } + + public GraphHitInfo GetClosestEdge (int hierarchicalNode, float3 position, NativeList scratchBuffer) { + GraphHitInfo hit = default; + hit.origin = position; + hit.point = Vector3.positiveInfinity; + + if (!obstacleData.obstacleVertices.IsCreated) return hit; + + // The hierarchical node is invalid. Maybe it has been removed. + if (hierarchicalNode < 0 || hierarchicalNode >= hierarhicalNodeData.connectionAllocations.Length || hierarhicalNodeData.connectionAllocations[hierarchicalNode] == SlabAllocator.InvalidAllocation) return hit; + + var queue = scratchBuffer; + queue.Clear(); + queue.Add(hierarchicalNode); + int queueIndex = 0; + float closestDistSqr = float.PositiveInfinity; + + while (queueIndex < queue.Length) { + var node = queue[queueIndex++]; + + if (hierarhicalNodeData.bounds[node].SqrDistance(position) > closestDistSqr) continue; + + var obstacle = obstacleData.obstacles[node]; + if (obstacle.verticesAllocation != SlabAllocator.ZeroLengthArray) { + var vertices = obstacleData.obstacleVertices.GetSpan(obstacle.verticesAllocation); + var groups = obstacleData.obstacleVertexGroups.GetSpan(obstacle.groupsAllocation); + int offset = 0; + for (int i = 0; i < groups.Length; i++) { + var group = groups[i]; + var closestInGroupBounds = math.min(math.max(position, group.boundsMn), group.boundsMx); + var groupDistSqr = math.lengthsq(closestInGroupBounds - position); + if (groupDistSqr < closestDistSqr) { + var loop = group.type == RVO.ObstacleType.Loop; + for (int a = offset + (loop ? group.vertexCount - 1 : 0), b = offset + (loop ? 0 : 1); b < offset + group.vertexCount; a = b, b++) { + var p1 = vertices[a]; + var p2 = vertices[b]; + + var t = math.clamp(VectorMath.ClosestPointOnLineFactor(p1, p2, position), 0, 1); + var closestPoint = math.lerp(p1, p2, t); + var distSqr = math.lengthsq(closestPoint - position); + if (distSqr < closestDistSqr) { + closestDistSqr = distSqr; + hit.point = closestPoint; + hit.tangent = p2 - p1; + hit.tangentOrigin = p1; + } + } + } + offset += group.vertexCount; + } + } + + // Enqueue neighbours + var conns = hierarhicalNodeData.connectionAllocator.GetSpan(hierarhicalNodeData.connectionAllocations[node]); + for (int i = 0; i < conns.Length; i++) { + var neighbour = conns[i]; + if (!queue.Contains(neighbour)) { + queue.Add(neighbour); + } + } + } + + return hit; + } + + public void GetObstaclesInRange (int hierarchicalNode, Bounds bounds, NativeList obstacleIndexBuffer) { + obstacleIndexBuffer.Clear(); + if (!obstacleData.obstacleVertices.IsCreated) return; + + // The hierarchical node is invalid. Maybe it has been removed. + if (hierarchicalNode < 0 || hierarchicalNode >= hierarhicalNodeData.connectionAllocations.Length || hierarhicalNodeData.connectionAllocations[hierarchicalNode] == SlabAllocator.InvalidAllocation) return; + + GetHierarchicalNodesInRangeRec(hierarchicalNode, bounds, hierarhicalNodeData.connectionAllocator, hierarhicalNodeData.connectionAllocations, hierarhicalNodeData.bounds, obstacleIndexBuffer); + } + + public void GetEdgesInRange (int hierarchicalNode, Bounds localBounds, NativeList edgeBuffer, NativeList scratchBuffer, NativeMovementPlane movementPlane) { + GetObstaclesInRange(hierarchicalNode, movementPlane.ToWorld(localBounds), scratchBuffer); + ConvertObstaclesToEdges(ref obstacleData, scratchBuffer, localBounds, edgeBuffer, movementPlane); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs.meta new file mode 100644 index 0000000..66750cc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshEdges.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 619ad2f6a5e696d4d849e05e14bc58f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs new file mode 100644 index 0000000..4266054 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs @@ -0,0 +1,492 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Pathfinding { + using Pathfinding.Drawing; + using Pathfinding.Graphs.Navmesh; + using Pathfinding.Jobs; + using Pathfinding.Serialization; + using Pathfinding.Sync; + using Pathfinding.Pooling; + using Unity.Jobs; + + /// + /// Stores a set of navmesh tiles which can be placed on a recast graph. + /// + /// This component is used to store chunks of a to a file and then be able to efficiently load them and place them on an existing recast graph. + /// A typical use case is if you have a procedurally generated level consisting of multiple rooms, and scanning the graph after the level has been generated + /// is too expensive. In this scenario, each room can have its own NavmeshPrefab component which stores the navmesh for just that room, and then when the + /// level is generated all the NavmeshPrefab components will load their tiles and place them on the recast graph, joining them together at the seams. + /// + /// Since this component works on tiles, the size of a NavmeshPrefab must be a multiple of the graph's tile size. + /// The tile size of a recast graph is determined by multiplying the with the tile size in voxels (). + /// When a NavmeshPrefab is placed on a recast graph, it will load the tiles into the closest spot (snapping the position and rotation). + /// The NavmeshPrefab will even resize the graph to make it larger in case you want to place a NavmeshPrefab outside the existing bounds of the graph. + /// + /// Usage + /// + /// - Attach a NavmeshPrefab component to a GameObject (typically a prefab) that you want to store the navmesh for. + /// - Make sure you have a RecastGraph elsewhere in the scene with the same settings that you use for the game. + /// - Adjust the bounding box to fit your game object. The bounding box should be a multiple of the tile size of the recast graph. + /// - In the inspector, click the "Scan" button to scan the graph and store the navmesh as a file, referenced by the NavmeshPrefab component. + /// - Make sure the rendered navmesh looks ok in the scene view. + /// - In your game, instantiate a prefab with the NavmeshComponent. It will automatically load its stored tiles and place them on the first recast graph in the scene. + /// + /// If you have multiple recast graphs you may not want it to always use the first recast graph. + /// In that case you can set the field to false and call the method manually. + /// + /// Accounting for borders + /// + /// When scanning a recast graph (and by extension a NavmeshPrefab), a margin is always added around parts of the graph the agent cannot traverse. + /// This can become problematic when scanning individual chunks separate from the rest of the world, because each one will have a small border of unwalkable space. + /// The result is that when you place them on a recast graph, they will not be able to connect to each other. + /// [Open online documentation to see images] + /// One way to solve this is to scan the prefab together with a mesh that is slightly larger than the prefab, extending the walkable surface enough + /// so that no border is added. In the image below, this mesh is displayed in white. It can be convenient to make this an invisible collider on the prefab + /// that is excluded from physics, but is included in the graph's rasterization layer mask. + /// [Open online documentation to see images] + /// Now that the border has been removed, the chunks can be placed next to each other and be able to connect. + /// [Open online documentation to see images] + /// + /// Loading tiles into a graph + /// + /// If is true, the tiles will be loaded into the first recast graph in the scene when the game starts. + /// If the recast graph is not scanned, it will be initialized with empty tiles and then the tiles will be loaded into it. + /// So if your world is made up entirely of NavmeshPrefabs, you can skip scanning for performance by setting A* Inspector -> Settings -> Scan On Awake to false. + /// + /// You can also apply a NavmeshPrefab to a graph manually by calling the method. + /// + /// Note: A navmesh prefab will fully replace the tiles within its bounding box. You cannot stack multiple navmesh prefabs on top of each other + /// to make e.g. a building with multiple floors. + /// + /// See: + /// See: + /// See: + /// + [AddComponentMenu("Pathfinding/Navmesh Prefab")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/navmeshprefab.html")] + public class NavmeshPrefab : VersionedMonoBehaviour { + /// Reference to the serialized tile data + public TextAsset serializedNavmesh; + + /// + /// If true, the tiles stored in this prefab will be loaded and applied to the first recast graph in the scene when this component is enabled. + /// If false, you will have to call the method manually. + /// + /// If this component is disabled and then enabled again, the tiles will be reloaded. + /// + public bool applyOnStart = true; + + /// + /// If true, the tiles that this prefab loaded into the graph will be removed when this component is disabled or destroyed. + /// If false, the tiles will remain in the graph. + /// + public bool removeTilesWhenDisabled = true; + + /// + /// Bounding box for the navmesh to be stored in this prefab. + /// Should be a multiple of the tile size of the associated recast graph. + /// + /// See: + /// See: + /// + public Bounds bounds = new Bounds(Vector3.zero, new Vector3(10, 10, 10)); + + bool startHasRun = false; + + protected override void Reset () { + base.Reset(); + AstarPath.FindAstarPath(); + if (AstarPath.active != null && AstarPath.active.data.recastGraph != null) { + var graph = AstarPath.active.data.recastGraph; + // Make the default bounds be 1x1 tiles in the graph + bounds = new Bounds(Vector3.zero, new Vector3(graph.TileWorldSizeX, graph.forcedBoundsSize.y, graph.TileWorldSizeZ)); + } + } + +#if UNITY_EDITOR + public override void DrawGizmos () { + using (Draw.WithMatrix(Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one))) { + Draw.WireBox(bounds.center, bounds.size); + } + + if (!Application.isPlaying && serializedNavmesh != null) { + var path = UnityEditor.AssetDatabase.GetAssetPath(serializedNavmesh); + var lastEditTime = System.IO.File.GetLastWriteTimeUtc(Application.dataPath + "/../" + path); + lastEditTime.ToBinary(); + // Hash the metadata to avoid somewhat expensive deserialization and drawing every frame. + var hasher = new Pathfinding.Drawing.DrawingData.Hasher(); + hasher.Add(lastEditTime); + hasher.Add(transform.position); + hasher.Add(transform.rotation); + hasher.Add(bounds); + + // Draw a new mesh if the metadata has changed + if (!Pathfinding.Drawing.DrawingManager.instance.gizmos.Draw(hasher)) { + var builder = Pathfinding.Drawing.DrawingManager.GetBuilder(hasher); + + var tileMeshes = TileMeshes.Deserialize(serializedNavmesh.bytes); + + var center = transform.position + transform.rotation * bounds.center; + var corner = center - transform.rotation*bounds.extents; + var tileWorldSize = tileMeshes.tileWorldSize; + var graphToWorldSpace = Matrix4x4.TRS(corner, transform.rotation, Vector3.one); + + var vertexCount = 0; + var trisCount = 0; + for (int i = 0; i < tileMeshes.tileMeshes.Length; i++) { + vertexCount += tileMeshes.tileMeshes[i].verticesInTileSpace.Length; + trisCount += tileMeshes.tileMeshes[i].triangles.Length; + } + + var colors = ArrayPool.Claim(vertexCount); + var vertices = ArrayPool.Claim(vertexCount); + var triangles = ArrayPool.Claim(trisCount); + vertexCount = 0; + trisCount = 0; + + using (builder.WithColor(AstarColor.SolidColor)) { + for (int z = 0; z < tileMeshes.tileRect.Height; z++) { + for (int x = 0; x < tileMeshes.tileRect.Width; x++) { + var tile = tileMeshes.tileMeshes[x + z*tileMeshes.tileRect.Width]; + + var tileToWorldSpace = graphToWorldSpace * Matrix4x4.Translate(new Vector3(x * tileWorldSize.x, 0, z * tileWorldSize.y)); + var startVertex = vertexCount; + for (int j = 0; j < tile.triangles.Length; trisCount++, j++) { + triangles[trisCount] = tile.triangles[j] + startVertex; + } + for (int j = 0; j < tile.verticesInTileSpace.Length; vertexCount++, j++) { + colors[vertexCount] = AstarColor.SolidColor; + vertices[vertexCount] = tileToWorldSpace.MultiplyPoint3x4((Vector3)tile.verticesInTileSpace[j]); + } + + for (int i = 0; i < tile.triangles.Length; i += 3) { + builder.Line(vertices[startVertex + tile.triangles[i+0]], vertices[startVertex + tile.triangles[i+1]]); + builder.Line(vertices[startVertex + tile.triangles[i+1]], vertices[startVertex + tile.triangles[i+2]]); + builder.Line(vertices[startVertex + tile.triangles[i+2]], vertices[startVertex + tile.triangles[i+0]]); + } + } + } + } + + builder.SolidMesh(vertices, triangles, colors, vertexCount, trisCount); + ArrayPool.Release(ref colors); + ArrayPool.Release(ref vertices); + ArrayPool.Release(ref triangles); + + builder.Dispose(); + } + } + } +#endif + + /// + /// Moves and rotates this object so that it is aligned with tiles in the first recast graph in the scene + /// + /// See: SnapToClosestTileAlignment(RecastGraph) + /// + [ContextMenu("Snap to closest tile alignment")] + public void SnapToClosestTileAlignment () { + AstarPath.FindAstarPath(); + if (AstarPath.active != null && AstarPath.active.data.recastGraph != null) { + SnapToClosestTileAlignment(AstarPath.active.data.recastGraph); + } + } + + /// + /// Applies the navmesh stored in this prefab to the first recast graph in the scene. + /// + /// See: for more details. + /// + [ContextMenu("Apply here")] + public void Apply () { + AstarPath.FindAstarPath(); + if (AstarPath.active != null && AstarPath.active.data.recastGraph != null) { + var graph = AstarPath.active.data.recastGraph; + Apply(graph); + } + } + + /// Moves and rotates this object so that it is aligned with tiles in the given graph + public void SnapToClosestTileAlignment (RecastGraph graph) { + // Calculate a new tile layout, because the graph may not be scanned yet (especially if this code runs outside of play mode) + var tileLayout = TileLayout.FromGraph(graph); + SnapToGraph(tileLayout, transform.position, transform.rotation, bounds, out IntRect tileRect, out int snappedRotation, out float yOffset); + var graphSpaceBounds = tileLayout.GetTileBoundsInGraphSpace(tileRect.xmin, tileRect.ymin, tileRect.Width, tileRect.Height); + var centerInGraphSpace = new Vector3(graphSpaceBounds.center.x, yOffset, graphSpaceBounds.center.z); +#if UNITY_EDITOR + if (!Application.isPlaying) UnityEditor.Undo.RecordObject(transform, "Snap to closest tile alignment"); +#endif + transform.rotation = Quaternion.Euler(graph.rotation) * Quaternion.Euler(0, snappedRotation * 90, 0); + transform.position = tileLayout.transform.Transform(centerInGraphSpace) + transform.rotation*(-bounds.center + new Vector3(0, bounds.extents.y, 0)); + +#if UNITY_EDITOR + if (!Application.isPlaying) UnityEditor.EditorUtility.SetDirty(transform); +#endif + } + + /// + /// Rounds the size of the to the closest multiple of the tile size in the graph, ensuring that the bounds cover at least 1x1 tiles. + /// The new bounds has the same center and size along the y-axis. + /// + public void SnapSizeToClosestTileMultiple (RecastGraph graph) { + this.bounds = SnapSizeToClosestTileMultiple(graph, this.bounds); + } + + /// Start is called before the first frame update + void Start () { + startHasRun = true; + if (applyOnStart && serializedNavmesh != null && AstarPath.active != null && AstarPath.active.data.recastGraph != null) Apply(AstarPath.active.data.recastGraph); + } + + void OnEnable () { + if (startHasRun && applyOnStart && serializedNavmesh != null && AstarPath.active != null && AstarPath.active.data.recastGraph != null) Apply(AstarPath.active.data.recastGraph); + } + + void OnDisable () { + if (removeTilesWhenDisabled && serializedNavmesh != null && AstarPath.active != null) { + var pos = transform.position; + var rot = transform.rotation; + AstarPath.active.AddWorkItem(ctx => { + var graph = AstarPath.active.data.recastGraph; + if (graph != null) { + SnapToGraph(TileLayout.FromGraph(graph), pos, rot, bounds, out IntRect tileRect, out int rotation, out float yOffset); + graph.ClearTiles(tileRect); + } + }); + } + } + + /// + /// Rounds the size of the bounds to the closest multiple of the tile size in the graph, ensuring that the bounds cover at least 1x1 tiles. + /// The returned bounds has the same center and size along the y-axis as the input. + /// + public static Bounds SnapSizeToClosestTileMultiple (RecastGraph graph, Bounds bounds) { + var tileSize = Mathf.Max(graph.editorTileSize * graph.cellSize, 0.001f); + var tiles = new Vector2(bounds.size.x / tileSize, bounds.size.z / tileSize); + var roundedTiles = new Vector2Int(Mathf.Max(1, Mathf.RoundToInt(tiles.x)), Mathf.Max(1, Mathf.RoundToInt(tiles.y))); + return new Bounds( + bounds.center, + new Vector3( + roundedTiles.x * tileSize, + bounds.size.y, + roundedTiles.y * tileSize + ) + ); + } + + public static void SnapToGraph (TileLayout tileLayout, Vector3 position, Quaternion rotation, Bounds bounds, out IntRect tileRect, out int snappedRotation, out float yOffset) { + var rotInGraphSpace = tileLayout.transform.InverseTransformVector(rotation * Vector3.right); + // Snap to increments of 90 degrees + snappedRotation = -Mathf.RoundToInt(Mathf.Atan2(rotInGraphSpace.z, rotInGraphSpace.x) / (0.5f*Mathf.PI)); + var snappedRotationQ = Quaternion.Euler(0, snappedRotation * 90, 0); + var localToGraph = tileLayout.transform.inverseMatrix * Matrix4x4.TRS(position + snappedRotationQ * bounds.center, snappedRotationQ, Vector3.one); + var cornerInGraphSpace1 = localToGraph.MultiplyPoint3x4(-bounds.extents); + var cornerInGraphSpace2 = localToGraph.MultiplyPoint3x4(bounds.extents); + var minInGraphSpace = Vector3.Min(cornerInGraphSpace1, cornerInGraphSpace2); + var tileCoordinatesF = Vector3.Scale(minInGraphSpace, new Vector3(1.0f/tileLayout.TileWorldSizeX, 1, 1.0f/tileLayout.TileWorldSizeZ)); + var tileCoordinates = new Vector2Int(Mathf.RoundToInt(tileCoordinatesF.x), Mathf.RoundToInt(tileCoordinatesF.z)); + var boundsSizeInGraphSpace = new Vector2(bounds.size.x, bounds.size.z); + if (((snappedRotation % 2) + 2) % 2 == 1) Util.Memory.Swap(ref boundsSizeInGraphSpace.x, ref boundsSizeInGraphSpace.y); + var w = Mathf.Max(1, Mathf.RoundToInt(boundsSizeInGraphSpace.x / tileLayout.TileWorldSizeX)); + var h = Mathf.Max(1, Mathf.RoundToInt(boundsSizeInGraphSpace.y / tileLayout.TileWorldSizeZ)); + tileRect = new IntRect( + tileCoordinates.x, + tileCoordinates.y, + tileCoordinates.x + w - 1, + tileCoordinates.y + h - 1 + ); + + yOffset = minInGraphSpace.y; + } + + /// + /// Applies the navmesh stored in this prefab to the given graph. + /// The loaded tiles will be placed at the closest valid spot to this object's current position. + /// Some rounding may occur because the tiles need to be aligned to the graph's tile boundaries. + /// + /// If the recast graph is not scanned, it will be initialized with empty tiles and then the tiles in this prefab will be loaded into it. + /// + /// If the recast graph is too small and the tiles would have been loaded out of bounds, the graph will first be resized to fit. + /// If you have a large graph, this resizing can be a somewhat expensive operation. + /// + /// See: + /// + public void Apply (RecastGraph graph) { + if (serializedNavmesh == null) throw new System.InvalidOperationException("Cannot Apply NavmeshPrefab because no serialized data has been set"); + + AstarPath.active.AddWorkItem(() => { + UnityEngine.Profiling.Profiler.BeginSample("NavmeshPrefab.Apply"); + SnapToGraph(TileLayout.FromGraph(graph), transform.position, transform.rotation, bounds, out IntRect tileRect, out int rotation, out float yOffset); + + var tileMeshes = TileMeshes.Deserialize(serializedNavmesh.bytes); + tileMeshes.Rotate(rotation); + if (tileMeshes.tileRect.Width != tileRect.Width || tileMeshes.tileRect.Height != tileRect.Height) { + throw new System.Exception("NavmeshPrefab has been scanned with a different size than it is right now (or with a different graph). Expected to find " + tileRect.Width + "x" + tileRect.Height + " tiles, but found " + tileMeshes.tileRect.Width + "x" + tileMeshes.tileRect.Height); + } + tileMeshes.tileRect = tileRect; + graph.ReplaceTiles(tileMeshes, yOffset); + UnityEngine.Profiling.Profiler.EndSample(); + }); + } + + /// Scans the navmesh using the first recast graph in the scene, and returns a serialized byte representation + public byte[] Scan () { + // Make sure this method works even when called in the editor outside of play mode. + AstarPath.FindAstarPath(); + if (AstarPath.active == null || AstarPath.active.data.recastGraph == null) throw new System.InvalidOperationException("There's no recast graph in the scene. Add one if you want to scan this navmesh prefab."); + return Scan(AstarPath.active.data.recastGraph); + } + + /// Scans the navmesh and returns a serialized byte representation + public byte[] Scan (RecastGraph graph) { + // Schedule the jobs asynchronously, but immediately wait for them to finish + var result = ScanAsync(graph).Complete(); + var data = result.data; + // Dispose of all the unmanaged memory + result.Dispose(); + return data; + } + + /// + /// Scans the navmesh asynchronously and returns a promise of a byte representation. + /// + /// TODO: Maybe change this method to return a object instead? + /// + public Promise ScanAsync (RecastGraph graph) { + var arena = new DisposeArena(); + + // First configure the rasterization settings by copying them from the recast graph, + // but changing which region we are interested in. + var tileLayout = new TileLayout( + new Bounds(transform.position + transform.rotation * bounds.center, bounds.size), + transform.rotation, + graph.cellSize, + graph.editorTileSize, + graph.useTiles + ); + // Disable cropping to the graph's exact bounds, since this can lead to a 1 voxel border on the +X and +Z edges of the prefab, + // due to the cropping being conservative, to ensure the nodes are strictly inside the bounds. + tileLayout.graphSpaceSize.x = float.PositiveInfinity; + tileLayout.graphSpaceSize.z = float.PositiveInfinity; + var buildSettings = RecastBuilder.BuildTileMeshes(graph, tileLayout, new IntRect(0, 0, tileLayout.tileCount.x - 1, tileLayout.tileCount.y - 1)); + var scene = this.gameObject.scene; + buildSettings.collectionSettings.physicsScene = scene.GetPhysicsScene(); + buildSettings.collectionSettings.physicsScene2D = scene.GetPhysicsScene2D(); + + // Schedule the jobs asynchronously + var tileMeshesPromise = buildSettings.Schedule(arena); + var output = new SerializedOutput { + promise = tileMeshesPromise, + arena = arena, + }; + var serializeJob = new SerializeJob { + tileMeshesPromise = tileMeshesPromise, + output = output, + }.ScheduleManaged(tileMeshesPromise.handle); + + return new Promise(serializeJob, output); + } + + public class SerializedOutput : IProgress, System.IDisposable { + public Promise promise; + public byte[] data; + public DisposeArena arena; + + public float Progress => promise.Progress; + + public void Dispose () { + // Dispose of all the unmanaged memory + promise.Dispose(); + arena.DisposeAll(); + } + } + + struct SerializeJob : IJob { + public Promise tileMeshesPromise; + public SerializedOutput output; + + public void Execute () { + // Note: Assumes that the tileMeshesPromise has already completed + var tileMeshes = tileMeshesPromise.GetValue(); + // Serialize the data to a byte array + output.data = tileMeshes.tileMeshes.ToManaged().Serialize(); + } + } + +#if UNITY_EDITOR + /// + /// Saves the given data to the field, or creates a new file if none exists. + /// + /// A new file will be created if is null. + /// If this object is part of a prefab, the file name will be based on the prefab's name. + /// + /// Warning: This method is only available in the editor. + /// + /// Warning: You should only pass valid serialized tile data to this function. + /// + /// See: + /// See: + /// + public void SaveToFile (byte[] data) { + string path; + if (serializedNavmesh != null) { + // If we already have a file, just overwrite it + path = UnityEditor.AssetDatabase.GetAssetPath(serializedNavmesh); + } else { + // Otherwise create a new file. + // If this is a prefab, base the name on the prefab's name. + System.IO.Directory.CreateDirectory(Application.dataPath + "/Tiles"); + var name = "tiles"; + var prefabPath = UnityEditor.PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(this); + if (prefabPath != null && prefabPath != "") { + name = System.IO.Path.GetFileNameWithoutExtension(prefabPath); + } + name = name.Replace("/", "_").Replace("\\", "_").Replace(".", "_").Replace("__", "_"); + path = UnityEditor.AssetDatabase.GenerateUniqueAssetPath("Assets/Tiles/" + name + ".bytes"); + } + var fullPath = Application.dataPath + "/../" + path; + WriteFileSomewhatAtomic(fullPath, data); + + UnityEditor.AssetDatabase.Refresh(); + serializedNavmesh = UnityEditor.AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset)) as TextAsset; + // Required if we do this in edit mode + UnityEditor.EditorUtility.SetDirty(this); + } + + static void WriteFileSomewhatAtomic (string path, byte[] data) { + // Ensure the temp path is likely on the same disk as the final path. + // This is not necessarily true for the system wide "get temp path" function. + var tempPath = Application.dataPath + "/../Temp/tmp_" + System.Guid.NewGuid().ToString() + ".bytes"; + System.IO.File.WriteAllBytes(tempPath, data); + try { + // Unfortunately the Move + overwrite operation doesn't exist until .net core 3.0. + // So we have to delete the target file before moving to it. + if (System.IO.File.Exists(path)) System.IO.File.Delete(path); + System.IO.File.Move(tempPath, path); + } catch { + if (System.IO.File.Exists(tempPath)) System.IO.File.Delete(tempPath); + } + } + + /// + /// Scans the navmesh and saves it to the field. + /// A new file will be created if is null. + /// If this object is part of a prefab, the file name will be based on the prefab's name. + /// + /// Note: This method is only available in the editor. + /// + public void ScanAndSaveToFile () { + SaveToFile(Scan()); + } +#endif + + protected override void OnUpgradeSerializedData (ref Migrations migrations, bool unityThread) { + migrations.TryMigrateFromLegacyFormat(out var _); + if (migrations.AddAndMaybeRunMigration(1 << 0)) { + removeTilesWhenDisabled = false; + } + base.OnUpgradeSerializedData(ref migrations, unityThread); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs.meta new file mode 100644 index 0000000..fb977d5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NavmeshPrefab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c8f31ae577c3d7af956114ba0f28148 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs new file mode 100644 index 0000000..2387ef0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs @@ -0,0 +1,151 @@ +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.Drawing; + + /// + /// Connects two nodes with a direct connection. + /// It is not possible to detect this link when following a path (which may be good or bad), for that you can use NodeLink2. + /// + /// See: editing-graphs (view in online documentation for working links) + /// + [AddComponentMenu("Pathfinding/Link")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/nodelink.html")] + public class NodeLink : GraphModifier { + /// End position of the link + public Transform end; + + /// + /// The connection will be this times harder/slower to traverse. + /// Note that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should + /// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether. + /// + public float costFactor = 1.0f; + + /// Make a one-way connection + public bool oneWay = false; + + /// Delete existing connection instead of adding one + public bool deleteConnection = false; + + public Transform Start { + get { return transform; } + } + + public Transform End { + get { return end; } + } + + public override void OnGraphsPostUpdateBeforeAreaRecalculation () { + Apply(); + } + + public static void DrawArch (Vector3 a, Vector3 b, Vector3 up, Color color) { + Vector3 dir = b - a; + + if (dir == Vector3.zero) return; + + var normal = Vector3.Cross(up, dir); + var normalUp = Vector3.Cross(dir, normal).normalized * dir.magnitude * 0.1f; + + Draw.Bezier(a, a + normalUp, b + normalUp, b, color); + } + + /// + /// Connects the start and end points using a link or refreshes the existing link. + /// + /// If you have moved the link or otherwise modified it you need to call this method. + /// + /// Warning: This must only be done when it is safe to update the graph structure. + /// The easiest is to do it inside a work item. See . + /// + public virtual void Apply () { + if (Start == null || End == null || AstarPath.active == null) return; + + GraphNode startNode = AstarPath.active.GetNearest(Start.position).node; + GraphNode endNode = AstarPath.active.GetNearest(End.position).node; + + if (startNode == null || endNode == null) return; + + + if (deleteConnection) { + GraphNode.Disconnect(startNode, endNode); + } else { + uint cost = (uint)System.Math.Round((startNode.position-endNode.position).costMagnitude*costFactor); + + GraphNode.Connect(startNode, endNode, cost, oneWay ? OffMeshLinks.Directionality.OneWay : OffMeshLinks.Directionality.TwoWay); + } + } + + public override void DrawGizmos () { + if (Start == null || End == null) return; + + NodeLink.DrawArch(Start.position, End.position, Vector3.up, deleteConnection ? Color.red : Color.green); + } + +#if UNITY_EDITOR + [UnityEditor.MenuItem("Edit/Pathfinding/Link Pair %&l")] + public static void LinkObjects () { + Transform[] tfs = Selection.transforms; + if (tfs.Length == 2) { + LinkObjects(tfs[0], tfs[1], false); + } + SceneView.RepaintAll(); + } + + [UnityEditor.MenuItem("Edit/Pathfinding/Unlink Pair %&u")] + public static void UnlinkObjects () { + Transform[] tfs = Selection.transforms; + if (tfs.Length == 2) { + LinkObjects(tfs[0], tfs[1], true); + } + SceneView.RepaintAll(); + } + + [UnityEditor.MenuItem("Edit/Pathfinding/Delete Links on Selected %&b")] + public static void DeleteLinks () { + Transform[] tfs = Selection.transforms; + for (int i = 0; i < tfs.Length; i++) { + NodeLink[] conns = tfs[i].GetComponents(); + for (int j = 0; j < conns.Length; j++) DestroyImmediate(conns[j]); + } + SceneView.RepaintAll(); + } + + public static void LinkObjects (Transform a, Transform b, bool removeConnection) { + NodeLink connecting = null; + + NodeLink[] conns = a.GetComponents(); + for (int i = 0; i < conns.Length; i++) { + if (conns[i].end == b) { + connecting = conns[i]; + break; + } + } + + conns = b.GetComponents(); + for (int i = 0; i < conns.Length; i++) { + if (conns[i].end == a) { + connecting = conns[i]; + break; + } + } + + if (removeConnection) { + if (connecting != null) DestroyImmediate(connecting); + } else { + if (connecting == null) { + connecting = a.gameObject.AddComponent(); + connecting.end = b; + } else { + connecting.deleteConnection = !connecting.deleteConnection; + } + } + } +#endif + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs.meta new file mode 100644 index 0000000..b4282dc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ca327ce4e754a4597a70fb963758f8bd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs new file mode 100644 index 0000000..a45efe3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs @@ -0,0 +1,406 @@ +using UnityEngine; + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.Drawing; + using Pathfinding.Pooling; + + /// + /// Interface for handling off-mesh links. + /// + /// If a component implements this interface, and is attached to the same GameObject as a NodeLink2 component, + /// then the OnTraverseOffMeshLink method will be called when an agent traverses the off-mesh link. + /// + /// This only works with the component. + /// + /// Note: overrides this callback, if set. + /// + /// The component implements this interface, and allows a small state-machine to run when the agent traverses the link. + /// + /// See: + /// See: offmeshlinks (view in online documentation for working links) + /// + public interface IOffMeshLinkHandler { + /// + /// Name of the handler. + /// This is used to identify the handler in the inspector. + /// + public string name => null; + +#if MODULE_ENTITIES + /// + /// Called when an agent starts traversing an off-mesh link. + /// + /// This can be used to perform any setup that is necessary for the traversal. + /// + /// Returns: An object which will be used to control the agent for the full duration of the link traversal. + /// + /// For simple cases, you can often implement and in the same class, and then + /// just make this method return this. + /// For more complex cases, you may want to keep track of the agent's identity, and thus want to return a new object for each agent that traverses the link. + /// + /// Context for the traversal. Provides information about the link and the agent, as well as some helper methods for movement. + /// This context is only valid for this method call. Do not store it and use it later. + public IOffMeshLinkStateMachine GetOffMeshLinkStateMachine(ECS.AgentOffMeshLinkTraversalContext context); +#endif + } + + public interface IOffMeshLinkStateMachine { +#if MODULE_ENTITIES + /// + /// Called when an agent traverses an off-mesh link. + /// This method should be a coroutine (i.e return an IEnumerable) which will be iterated over until it finishes, or the agent is destroyed. + /// The coroutine should yield null every frame until the agent has finished traversing the link. + /// + /// When the coroutine completes, the agent will be assumed to have reached the end of the link and control + /// will be returned to the normal movement code. + /// + /// The coroutine typically moves the agent to the end of the link over some time, and perform any other actions that are necessary. + /// For example, it could play an animation, or move the agent in a specific way. + /// + /// Context for the traversal. Provides information about the link and the agent, as well as some helper methods for movement. + /// This context is only valid when this coroutine steps forward. Do not store it and use it elsewhere. + System.Collections.IEnumerable OnTraverseOffMeshLink(ECS.AgentOffMeshLinkTraversalContext context) => ECS.JobStartOffMeshLinkTransition.DefaultOnTraverseOffMeshLink(context); + + /// + /// Called when an agent finishes traversing an off-mesh link. + /// + /// This can be used to perform any cleanup that is necessary after the traversal. + /// + /// Either or will be called, but not both. + /// + /// Context for the traversal. Provides information about the link and the agent, as well as some helper methods for movement. + /// This context is only valid for this method call. Do not store it and use it later. + void OnFinishTraversingOffMeshLink (ECS.AgentOffMeshLinkTraversalContext context) {} +#endif + + /// + /// Called when an agent fails to finish traversing an off-mesh link. + /// + /// This can be used to perform any cleanup that is necessary after the traversal. + /// + /// An abort can happen if the agent was destroyed while it was traversing the link. It can also happen if the agent was teleported somewhere else while traversing the link. + /// + /// Either or will be called, but not both. + /// + /// Warning: When this is called, the agent may already be destroyed. The handler component itself could also be destroyed at this point. + /// + void OnAbortTraversingOffMeshLink () {} + } + + /// + /// Connects two nodes using an off-mesh link. + /// In contrast to the component, this link type will not connect the nodes directly + /// instead it will create two link nodes at the start and end position of this link and connect + /// through those nodes. + /// + /// If the closest node to this object is called A and the closest node to the end transform is called + /// D, then it will create one link node at this object's position (call it B) and one link node at + /// the position of the end transform (call it C), it will then connect A to B, B to C and C to D. + /// + /// This link type is possible to detect while following since it has these special link nodes in the middle. + /// The link corresponding to one of those intermediate nodes can be retrieved using the method + /// which can be of great use if you want to, for example, play a link specific animation when reaching the link. + /// + /// \inspectorField{NodeLink2.end; End} + /// \inspectorField{NodeLink2.costFactor; Cost Factor} + /// \inspectorField{NodeLink2.oneWay; One Way} + /// \inspectorField{NodeLink2.pathfindingTag; Pathfinding Tag} + /// \inspectorField{NodeLink2.graphMask; Graph Mask} + /// + /// See: offmeshlinks (view in online documentation for working links) + /// See: The example scene RecastExample2 contains a few links which you can take a look at to see how they are used. + /// + /// Note: If you make any modifications to the node link's settings after it has been created, you need to call the method in order to apply the changes to the graph. + /// + [AddComponentMenu("Pathfinding/Link2")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/nodelink2.html")] + public class NodeLink2 : GraphModifier { + /// End position of the link + public Transform end; + + /// + /// The connection will be this times harder/slower to traverse. + /// + /// A cost factor of 1 means that the link is equally expensive as moving the same distance on the normal navmesh. But a cost factor greater than 1 means that it is proportionally more expensive. + /// + /// You should not use a cost factor less than 1 unless you also change the field (A* Inspector -> Settings -> Pathfinding) to at most the minimum cost factor that you use anywhere in the scene (or disable the heuristic altogether). + /// This is because the pathfinding algorithm assumes that paths are at least as costly as walking just the straight line distance to the target, and if you use a cost factor less than 1, that assumption is no longer true. + /// What then happens is that the pathfinding search may ignore some links because it doesn't even think to search in that direction, even if they would have lead to a lower path cost. + /// + /// Warning: Reducing the heuristic scale or disabling the heuristic can significantly increase the cpu cost for pathfinding, especially for large graphs. + /// + /// Read more about this at https://en.wikipedia.org/wiki/Admissible_heuristic. + /// + public float costFactor = 1.0f; + + /// Make a one-way connection + public bool oneWay = false; + + /// + /// The tag to apply to the link. + /// + /// This can be used to exclude certain agents from using the link, or make it more expensive to use. + /// + /// See: tags (view in online documentation for working links) + /// + public PathfindingTag pathfindingTag = 0; + + /// + /// Which graphs this link is allowed to connect. + /// + /// The link will always connect the nodes closest to the start and end points on the graphs that it is allowed to connect. + /// + public GraphMask graphMask = GraphMask.everything; + + public Transform StartTransform => transform; + + public Transform EndTransform => end; + + protected OffMeshLinks.OffMeshLinkSource linkSource; + + /// + /// Returns the link component associated with the specified node. + /// Returns: The link associated with the node or null if the node is not a link node, or it is not associated with a component. + /// + /// The node to get the link for. + public static NodeLink2 GetNodeLink(GraphNode node) => node is LinkNode linkNode ? linkNode.linkSource.component as NodeLink2 : null; + + /// + /// True if the link is connected to the graph. + /// + /// This will be true if the link has been successfully connected to the graph, and false if it either has failed, or if the component/gameobject is disabled. + /// + /// When the component is enabled, the link will be scheduled to be added to the graph, it will not take effect immediately. + /// This means that this property will return false until the next time graph updates are processed (usually later this frame, or next frame). + /// To ensure the link is refreshed immediately, you can call . + /// + internal bool isActive => linkSource != null && (linkSource.status & OffMeshLinks.OffMeshLinkStatus.Active) != 0; + + IOffMeshLinkHandler onTraverseOffMeshLinkHandler; + + /// + /// Callback to be called when an agent starts traversing an off-mesh link. + /// + /// The handler will be called when the agent starts traversing an off-mesh link. + /// It allows you to to control the agent for the full duration of the link traversal. + /// + /// Use the passed context struct to get information about the link and to control the agent. + /// + /// + /// using UnityEngine; + /// using Pathfinding; + /// using System.Collections; + /// using Pathfinding.ECS; + /// + /// namespace Pathfinding.Examples { + /// public class FollowerJumpLink : MonoBehaviour, IOffMeshLinkHandler, IOffMeshLinkStateMachine { + /// // Register this class as the handler for off-mesh links when the component is enabled + /// void OnEnable() => GetComponent().onTraverseOffMeshLink = this; + /// void OnDisable() => GetComponent().onTraverseOffMeshLink = null; + /// + /// IOffMeshLinkStateMachine IOffMeshLinkHandler.GetOffMeshLinkStateMachine(AgentOffMeshLinkTraversalContext context) => this; + /// + /// void IOffMeshLinkStateMachine.OnFinishTraversingOffMeshLink (AgentOffMeshLinkTraversalContext context) { + /// Debug.Log("An agent finished traversing an off-mesh link"); + /// } + /// + /// void IOffMeshLinkStateMachine.OnAbortTraversingOffMeshLink () { + /// Debug.Log("An agent aborted traversing an off-mesh link"); + /// } + /// + /// IEnumerable IOffMeshLinkStateMachine.OnTraverseOffMeshLink (AgentOffMeshLinkTraversalContext ctx) { + /// var start = (Vector3)ctx.link.relativeStart; + /// var end = (Vector3)ctx.link.relativeEnd; + /// var dir = end - start; + /// + /// // Disable local avoidance while traversing the off-mesh link. + /// // If it was enabled, it will be automatically re-enabled when the agent finishes traversing the link. + /// ctx.DisableLocalAvoidance(); + /// + /// // Move and rotate the agent to face the other side of the link. + /// // When reaching the off-mesh link, the agent may be facing the wrong direction. + /// while (!ctx.MoveTowards( + /// position: start, + /// rotation: Quaternion.LookRotation(dir, ctx.movementPlane.up), + /// gravity: true, + /// slowdown: true).reached) { + /// yield return null; + /// } + /// + /// var bezierP0 = start; + /// var bezierP1 = start + Vector3.up*5; + /// var bezierP2 = end + Vector3.up*5; + /// var bezierP3 = end; + /// var jumpDuration = 1.0f; + /// + /// // Animate the AI to jump from the start to the end of the link + /// for (float t = 0; t < jumpDuration; t += ctx.deltaTime) { + /// ctx.transform.Position = AstarSplines.CubicBezier(bezierP0, bezierP1, bezierP2, bezierP3, Mathf.SmoothStep(0, 1, t / jumpDuration)); + /// yield return null; + /// } + /// } + /// } + /// } + /// + /// + /// Warning: Off-mesh links can be destroyed or disabled at any moment. The built-in code will attempt to make the agent continue following the link even if it is destroyed, + /// but if you write your own traversal code, you should be aware of this. + /// + /// You can alternatively set the corresponding property property on the agent () to specify a callback for a all off-mesh links. + /// + /// Note: The agent's off-mesh link handler takes precedence over the link's off-mesh link handler, if both are set. + /// + /// Warning: This property only works with the component. Use if you are using the movement script. + /// + /// See: offmeshlinks (view in online documentation for working links) for more details and example code + /// + public IOffMeshLinkHandler onTraverseOffMeshLink { + get => onTraverseOffMeshLinkHandler; + set { + onTraverseOffMeshLinkHandler = value; + if (linkSource != null) linkSource.handler = value; + } + } + + public override void OnPostScan () { + TryAddLink(); + } + + protected override void OnEnable () { + base.OnEnable(); + if (Application.isPlaying && !BatchedEvents.Has(this)) BatchedEvents.Add(this, BatchedEvents.Event.Update, OnUpdate); + TryAddLink(); + } + + static void OnUpdate (NodeLink2[] components, int count) { + // Only check for moved links every N frames, for performance + if ((Time.frameCount % 16) != 0) return; + + for (int i = 0; i < count; i++) { + var comp = components[i]; + var start = comp.StartTransform; + var end = comp.EndTransform; + var added = comp.linkSource != null; + if ((start != null && end != null) != added || (added && (start.hasChanged || end.hasChanged))) { + if (start != null) start.hasChanged = false; + if (end != null) end.hasChanged = false; + comp.RemoveLink(); + comp.TryAddLink(); + } + } + } + + void TryAddLink () { + // In case the AstarPath component has been destroyed (destroying the link). + // But do not clear it if the link is inactive because it failed to become enabled + if (linkSource != null && (linkSource.status == OffMeshLinks.OffMeshLinkStatus.Inactive || (linkSource.status & OffMeshLinks.OffMeshLinkStatus.PendingRemoval) != 0)) linkSource = null; + + if (linkSource == null && AstarPath.active != null && EndTransform != null) { + StartTransform.hasChanged = false; + EndTransform.hasChanged = false; + + linkSource = new OffMeshLinks.OffMeshLinkSource { + start = new OffMeshLinks.Anchor { + center = StartTransform.position, + rotation = StartTransform.rotation, + width = 0f, + }, + end = new OffMeshLinks.Anchor { + center = EndTransform.position, + rotation = EndTransform.rotation, + width = 0f, + }, + directionality = oneWay ? OffMeshLinks.Directionality.OneWay : OffMeshLinks.Directionality.TwoWay, + tag = pathfindingTag, + costFactor = costFactor, + graphMask = graphMask, + maxSnappingDistance = 1, // TODO + component = this, + handler = onTraverseOffMeshLink, + }; + AstarPath.active.offMeshLinks.Add(linkSource); + } + } + + void RemoveLink () { + if (AstarPath.active != null && linkSource != null) AstarPath.active.offMeshLinks.Remove(linkSource); + linkSource = null; + } + + protected override void OnDisable () { + base.OnDisable(); + BatchedEvents.Remove(this); + RemoveLink(); + } + + [ContextMenu("Recalculate neighbours")] + void ContextApplyForce () { + Apply(); + } + + /// + /// Disconnects and then reconnects the link to the graph. + /// + /// If you have moved the link or otherwise modified it you need to call this method to apply those changes. + /// + public virtual void Apply () { + RemoveLink(); + TryAddLink(); + } + + private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f); + private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f); + + public override void DrawGizmos () { + if (StartTransform == null || EndTransform == null) return; + + var startPos = StartTransform.position; + var endPos = EndTransform.position; + if (linkSource != null && (Time.renderedFrameCount % 16) == 0 && Application.isEditor) { + // Check if the link has moved + // During runtime, this will be done by the OnUpdate method instead + if (linkSource.start.center != startPos || linkSource.end.center != endPos || linkSource.directionality != (oneWay ? OffMeshLinks.Directionality.OneWay : OffMeshLinks.Directionality.TwoWay) || linkSource.costFactor != costFactor || linkSource.graphMask != graphMask || linkSource.tag != pathfindingTag) { + Apply(); + } + } + + bool selected = GizmoContext.InActiveSelection(this); + var graphs = linkSource != null && AstarPath.active != null? AstarPath.active.offMeshLinks.ConnectedGraphs(linkSource) : null; + var up = Vector3.up; + + // Find the natural up direction of the connected graphs, so that we can orient the gizmos appropriately + if (graphs != null) { + for (int i = 0; i < graphs.Count; i++) { + var graph = graphs[i]; + if (graph != null) { + if (graph is NavmeshBase navmesh) { + up = navmesh.transform.WorldUpAtGraphPosition(Vector3.zero); + break; + } else if (graph is GridGraph grid) { + up = grid.transform.WorldUpAtGraphPosition(Vector3.zero); + break; + } + } + } + ListPool.Release(ref graphs); + } + + var active = linkSource != null && linkSource.status == OffMeshLinks.OffMeshLinkStatus.Active; + Color color = selected ? GizmosColorSelected : GizmosColor; + if (active) color = Color.green; + + Draw.Circle(startPos, up, 0.4f, linkSource != null && linkSource.status.HasFlag(OffMeshLinks.OffMeshLinkStatus.FailedToConnectStart) ? Color.red : color); + Draw.Circle(endPos, up, 0.4f, linkSource != null && linkSource.status.HasFlag(OffMeshLinks.OffMeshLinkStatus.FailedToConnectEnd) ? Color.red : color); + + NodeLink.DrawArch(startPos, endPos, up, color); + if (selected) { + Vector3 cross = Vector3.Cross(up, endPos-startPos).normalized; + using (Draw.WithLineWidth(2)) { + NodeLink.DrawArch(startPos+cross*0.0f, endPos+cross*0.0f, up, color); + } + // NodeLink.DrawArch(startPos-cross*0.1f, endPos-cross*0.1f, color); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs.meta new file mode 100644 index 0000000..a8151fb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd2cfff5dfa8c4244aa00fea9675adb2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: eb30e238fe6211b4fa5f441a73bd01ef, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs new file mode 100644 index 0000000..f61969c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs @@ -0,0 +1,294 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Drawing; + + public class NodeLink3Node : PointNode { + public NodeLink3 link; + public Vector3 portalA; + public Vector3 portalB; + + public NodeLink3Node (AstarPath astar) { + astar.InitializeNode(this); + } + + public override bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) { + left = portalA; + right = portalB; + if (this.connections.Length < 2) return false; + + if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length); + + return true; + } + + public GraphNode GetOther (GraphNode a) { + if (this.connections.Length < 2) return null; + if (this.connections.Length != 2) throw new System.Exception("Invalid NodeLink3Node. Expected 2 connections, found " + this.connections.Length); + + return a == connections[0].node ? (connections[1].node as NodeLink3Node).GetOtherInternal(this) : (connections[0].node as NodeLink3Node).GetOtherInternal(this); + } + + GraphNode GetOtherInternal (GraphNode a) { + if (this.connections.Length < 2) return null; + return a == connections[0].node ? connections[1].node : connections[0].node; + } + } + + /// + /// Connects two TriangleMeshNodes (recast/navmesh graphs) as if they had shared an edge. + /// Note: Usually you do not want to use this type of link, you want to use NodeLink2 or NodeLink (sorry for the not so descriptive names). + /// + [AddComponentMenu("Pathfinding/Link3")] + [HelpURL("https://arongranberg.com/astar/documentation/stable/nodelink3.html")] + public class NodeLink3 : GraphModifier { + protected static Dictionary reference = new Dictionary(); + public static NodeLink3 GetNodeLink (GraphNode node) { + reference.TryGetValue(node, out NodeLink3 v); + return v; + } + + /// End position of the link + public Transform end; + + /// + /// The connection will be this times harder/slower to traverse. + /// Note that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should + /// lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether. + /// + public float costFactor = 1.0f; + + public Transform StartTransform { + get { return transform; } + } + + public Transform EndTransform { + get { return end; } + } + + NodeLink3Node startNode; + NodeLink3Node endNode; + MeshNode connectedNode1, connectedNode2; + Vector3 clamped1, clamped2; + bool postScanCalled = false; + + public GraphNode StartNode { + get { return startNode; } + } + + public GraphNode EndNode { + get { return endNode; } + } + + public override void OnPostScan () { + if (AstarPath.active.isScanning) { + InternalOnPostScan(); + } else { + AstarPath.active.AddWorkItem(new AstarWorkItem(_ => { + InternalOnPostScan(); + return true; + })); + } + } + + public void InternalOnPostScan () { +#if !ASTAR_NO_POINT_GRAPH + if (AstarPath.active.data.pointGraph == null) { + AstarPath.active.data.AddGraph(typeof(PointGraph)); + } + + //Get nearest nodes from the first point graph, assuming both start and end transforms are nodes + startNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)StartTransform.position); + startNode.link = this; + endNode = AstarPath.active.data.pointGraph.AddNode(new NodeLink3Node(AstarPath.active), (Int3)EndTransform.position); + endNode.link = this; +#else + throw new System.Exception("Point graphs are not included. Check your A* Optimization settings."); +#endif + connectedNode1 = null; + connectedNode2 = null; + + if (startNode == null || endNode == null) { + startNode = null; + endNode = null; + return; + } + + postScanCalled = true; + reference[startNode] = this; + reference[endNode] = this; + Apply(true); + } + + public override void OnGraphsPostUpdateBeforeAreaRecalculation () { + if (!AstarPath.active.isScanning) { + if (connectedNode1 != null && connectedNode1.Destroyed) { + connectedNode1 = null; + } + if (connectedNode2 != null && connectedNode2.Destroyed) { + connectedNode2 = null; + } + + if (!postScanCalled) { + OnPostScan(); + } else { + //OnPostScan will also call this method + Apply(false); + } + } + } + + protected override void OnEnable () { + base.OnEnable(); + +#if !ASTAR_NO_POINT_GRAPH + if (Application.isPlaying && AstarPath.active != null && AstarPath.active.data != null && AstarPath.active.data.pointGraph != null) { + OnGraphsPostUpdate(); + } +#endif + } + + protected override void OnDisable () { + base.OnDisable(); + + postScanCalled = false; + + if (startNode != null) reference.Remove(startNode); + if (endNode != null) reference.Remove(endNode); + + if (startNode != null && endNode != null) { + startNode.RemovePartialConnection(endNode); + endNode.RemovePartialConnection(startNode); + + if (connectedNode1 != null && connectedNode2 != null) { + startNode.RemovePartialConnection(connectedNode1); + connectedNode1.RemovePartialConnection(startNode); + + endNode.RemovePartialConnection(connectedNode2); + connectedNode2.RemovePartialConnection(endNode); + } + } + } + + void RemoveConnections (GraphNode node) { + //TODO, might be better to replace connection + node.ClearConnections(true); + } + + [ContextMenu("Recalculate neighbours")] + void ContextApplyForce () { + if (Application.isPlaying) { + Apply(true); + } + } + + public void Apply (bool forceNewCheck) { + //TODO + //This function assumes that connections from the n1,n2 nodes never need to be removed in the future (e.g because the nodes move or something) + var nn = NearestNodeConstraint.None; + + nn.distanceMetric = DistanceMetric.ClosestAsSeenFromAboveSoft(); + + //Search all graphs but the one which start and end nodes are on + nn.graphMask = ~GraphMask.FromGraphIndex(startNode.GraphIndex); + + bool same = true; + + { + var info = AstarPath.active.GetNearest(StartTransform.position, nn); + same &= info.node == connectedNode1 && info.node != null; + connectedNode1 = info.node as MeshNode; + clamped1 = info.position; + if (connectedNode1 != null) Debug.DrawRay((Vector3)connectedNode1.position, Vector3.up*5, Color.red); + } + + { + var info = AstarPath.active.GetNearest(EndTransform.position, nn); + same &= info.node == connectedNode2 && info.node != null; + connectedNode2 = info.node as MeshNode; + clamped2 = info.position; + if (connectedNode2 != null) Debug.DrawRay((Vector3)connectedNode2.position, Vector3.up*5, Color.cyan); + } + + if (connectedNode2 == null || connectedNode1 == null) return; + + startNode.position = (Int3)StartTransform.position; + endNode.position = (Int3)EndTransform.position; + + if (same && !forceNewCheck) return; + + RemoveConnections(startNode); + RemoveConnections(endNode); + + uint cost = (uint)Mathf.RoundToInt(((Int3)(StartTransform.position-EndTransform.position)).costMagnitude*costFactor); + GraphNode.Connect(startNode, endNode, cost); + + Int3 dir = connectedNode2.position - connectedNode1.position; + + for (int a = 0; a < connectedNode1.GetVertexCount(); a++) { + Int3 va1 = connectedNode1.GetVertex(a); + Int3 va2 = connectedNode1.GetVertex((a+1) % connectedNode1.GetVertexCount()); + + if (Int3.DotLong((va2-va1).Normal2D(), dir) > 0) continue; + + for (int b = 0; b < connectedNode2.GetVertexCount(); b++) { + Int3 vb1 = connectedNode2.GetVertex(b); + Int3 vb2 = connectedNode2.GetVertex((b+1) % connectedNode2.GetVertexCount()); + + if (Int3.DotLong((vb2-vb1).Normal2D(), dir) < 0) continue; + + if (Int3.Angle((vb2-vb1), (va2-va1)) > (170.0/360.0f)*Mathf.PI*2) { + float t1 = 0; + float t2 = 1; + + t2 = System.Math.Min(t2, VectorMath.ClosestPointOnLineFactor(va1, va2, vb1)); + t1 = System.Math.Max(t1, VectorMath.ClosestPointOnLineFactor(va1, va2, vb2)); + + if (t2 < t1) { + Debug.LogError("Something went wrong! " + t1 + " " + t2 + " " + va1 + " " + va2 + " " + vb1 + " " + vb2+"\nTODO, how can this happen?"); + } else { + Vector3 pa = (Vector3)(va2-va1)*t1 + (Vector3)va1; + Vector3 pb = (Vector3)(va2-va1)*t2 + (Vector3)va1; + + startNode.portalA = pa; + startNode.portalB = pb; + + endNode.portalA = pb; + endNode.portalB = pa; + + //Add connections between nodes, or replace old connections if existing + GraphNode.Connect(connectedNode1, startNode, (uint)Mathf.RoundToInt(((Int3)(clamped1 - StartTransform.position)).costMagnitude*costFactor)); + GraphNode.Connect(endNode, connectedNode2, (uint)Mathf.RoundToInt(((Int3)(clamped2 - EndTransform.position)).costMagnitude*costFactor)); + return; + } + } + } + } + } + + private readonly static Color GizmosColor = new Color(206.0f/255.0f, 136.0f/255.0f, 48.0f/255.0f, 0.5f); + private readonly static Color GizmosColorSelected = new Color(235.0f/255.0f, 123.0f/255.0f, 32.0f/255.0f, 1.0f); + + public override void DrawGizmos () { + bool selected = GizmoContext.InActiveSelection(this); + Color color = selected ? GizmosColorSelected : GizmosColor; + + if (StartTransform != null) { + Draw.xz.Circle(StartTransform.position, 0.4f, color); + } + if (EndTransform != null) { + Draw.xz.Circle(EndTransform.position, 0.4f, color); + } + + if (StartTransform != null && EndTransform != null) { + NodeLink.DrawArch(StartTransform.position, EndTransform.position, Vector3.up, color); + if (selected) { + Vector3 cross = Vector3.Cross(Vector3.up, (EndTransform.position-StartTransform.position)).normalized; + NodeLink.DrawArch(StartTransform.position+cross*0.1f, EndTransform.position+cross*0.1f, Vector3.up, color); + NodeLink.DrawArch(StartTransform.position-cross*0.1f, EndTransform.position-cross*0.1f, Vector3.up, color); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs.meta new file mode 100644 index 0000000..d8b4f52 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/NodeLink3.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3850d0dc2bead45568e6b5bbcc011606 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs new file mode 100644 index 0000000..68238be --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs @@ -0,0 +1,635 @@ +using System.Collections.Generic; +using Unity.Collections; +using UnityEngine; +using UnityEngine.Profiling; +using Pathfinding.Pooling; +using Pathfinding.Collections; +using System; +using UnityEngine.Assertions; + +namespace Pathfinding { + /// + /// Manager for off-mesh links. + /// + /// This manager tracks all active off-mesh links in the scene and recalculates them when needed. + /// If an off-mesh link is activated, a will also be added to the graph list to store the special nodes necessary for the links to work. + /// + /// Whenever a graph update happens, the method should be called with the bounds of the updated area. + /// This will cause the links touching that bounding box to be recalculated at the end of the graph update step. + /// + /// Typically you will not need to interact with this class yourself, instead you can use the pre-built components like . + /// + public class OffMeshLinks { + AABBTree tree = new AABBTree(); + List pendingAdd = new List(); + bool updateScheduled; + AstarPath astar; + + public OffMeshLinks(AstarPath astar) { + this.astar = astar; + } + + /// + /// The start or end point of an off-mesh link. + /// + /// See: + /// + public struct Anchor { + /// Where the link connects to the navmesh + public Vector3 center; + /// Rotation that the character should align itself with when traversing the link + public Quaternion rotation; + /// + /// Width of the link. + /// + /// Note: No values other than 0 are currently supported. + /// + public float width; + + /// First point on the segment that makes up this anchor + public readonly Vector3 point1 => center + rotation * new Vector3(-0.5f * width, 0, 0); + + /// Second point on the segment that makes up this anchor + public readonly Vector3 point2 => center + rotation * new Vector3(0.5f * width, 0, 0); + + public static bool operator ==(Anchor a, Anchor b) => a.center == b.center && a.rotation == b.rotation && a.width == b.width; + public static bool operator !=(Anchor a, Anchor b) => a.center != b.center || a.rotation != b.rotation || a.width != b.width; + + public override bool Equals(object obj) => obj is Anchor && this == (Anchor)obj; + public override int GetHashCode() => (center.GetHashCode() * 23 ^ rotation.GetHashCode()) * 23 ^ width.GetHashCode(); + } + + /// Determines how a link is connected in the graph + public enum Directionality { + /// Movement is only allowed from the start point to the end point + OneWay, + /// Movement is allowed in both directions + TwoWay, + } + + [System.Flags] + public enum OffMeshLinkStatus { + Inactive = 1 << 0, + Pending = 1 << 1, + Active = 1 << 2, + FailedToConnectStart = Inactive | 1 << 3, + FailedToConnectEnd = Inactive | 1 << 4, + PendingRemoval = 1 << 5, + } + + /// + /// Information about an off-mesh link. + /// + /// Off-mesh links connect two points on the navmesh which are not necessarily connected by a normal navmesh connection. + /// + /// See: + /// See: + /// + public readonly struct OffMeshLinkTracer { + public OffMeshLinkTracer(OffMeshLinkConcrete link, bool reversed) { + this.link = link; + this.relativeStart = reversed ? link.end.center : link.start.center; + this.relativeEnd = reversed ? link.start.center : link.end.center; + this.isReverse = reversed; + } + + + public OffMeshLinkTracer(OffMeshLinkConcrete link, Vector3 relativeStart, Vector3 relativeEnd, bool isReverse) { + this.link = link; + this.relativeStart = relativeStart; + this.relativeEnd = relativeEnd; + this.isReverse = isReverse; + } + + /// + /// The off-mesh link that the agent is traversing. + /// + /// Note: If the off-mesh link is destroyed while the agent is traversing it, properties like , may refer to a destroyed gameObject. + /// + public readonly OffMeshLinkConcrete link; + + /// + /// The start point of the off-mesh link from the agent's perspective. + /// + /// This is the point where the agent starts traversing the off-mesh link, regardless of if the link is traversed from the start to end or from end to start. + /// + public readonly Vector3 relativeStart; + + /// + /// The end point of the off-mesh link from the agent's perspective. + /// + /// This is the point where the agent will finish traversing the off-mesh link, regardless of if the link is traversed from start to end or from end to start. + /// + public readonly Vector3 relativeEnd; + + /// + /// True if the agent is traversing the off-mesh link from original link's end to its start point. + /// + /// Note: The and fields are always set from the agent's perspective. So the agent always moves from to . + /// + public readonly bool isReverse; + + /// \copydocref{OffMeshLinkSource.component} + public Component component => link.component; + /// \copydocref{OffMeshLinkSource.gameObject} + public GameObject gameObject => link.gameObject; + } + + public class OffMeshLinkSource { + /// The start of the link + public Anchor start; + + /// The end of the link + public Anchor end; + public Directionality directionality; + + /// + /// Tag to apply to this link. + /// + /// See: tags (view in online documentation for working links) + /// + public PathfindingTag tag; + + /// Multiplies the cost of traversing this link by this amount + public float costFactor; // TODO: Add constant cost? + + /// + /// Maximum distance from the start/end points to the navmesh. + /// + /// If the distance is greater than this, the link will not be connected to the navmesh. + /// + public float maxSnappingDistance; + + /// + /// Graph mask for which graphs the link is allowed to connect to. + /// + /// The link's endpoints will be connected to the closest valid node on any graph that matches the mask. + /// + public GraphMask graphMask; + + public IOffMeshLinkHandler handler; + + /// + /// The Component associated with this link. + /// + /// Typically this will be a component. But users can also create their own components and fill out this field as appropriate. + /// + /// This field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the component associated with a link. + /// + /// Warning: If the link has been destroyed, this may return a destroyed component. + /// A link may be destroyed even while a character is traversing it. + /// + public Component component; + + /// + /// The GameObject associated with this link. + /// + /// This field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the GameObject associated with a link. + /// + /// Warning: If the link has been destroyed, this may return a destroyed GameObject. + /// A link may be destroyed even while a character is traversing it. + /// + public GameObject gameObject => component != null ? component.gameObject : null; + + internal AABBTree.Key treeKey; + + public OffMeshLinkStatus status { get; internal set; } = OffMeshLinkStatus.Inactive; + + /// + /// Bounding box which encapsulates the link and any position on the navmesh it could possibly be connected to. + /// + /// This is used to determine which links need to be recalculated when a graph update happens. + /// + public Bounds bounds { + get { + var b = new Bounds(); + b.SetMinMax(start.point1, start.point2); + b.Encapsulate(end.point1); + b.Encapsulate(end.point2); + b.Expand(maxSnappingDistance*2); + return b; + } + } + } + + internal class OffMeshLinkCombined { + public OffMeshLinkSource source; + public OffMeshLinkConcrete concrete; + } + + public class OffMeshLinkConcrete { + /// \copydocref{OffMeshLinkSource.start} + public Anchor start; + /// \copydocref{OffMeshLinkSource.end} + public Anchor end; + public GraphNode[] startNodes; + public GraphNode[] endNodes; + public LinkNode startLinkNode; + public LinkNode endLinkNode; + /// \copydocref{OffMeshLinkSource.directionality} + public Directionality directionality; + /// \copydocref{OffMeshLinkSource.tag} + public PathfindingTag tag; + public float costFactor; + internal bool staleConnections; + internal OffMeshLinkSource source; + + /// \copydocref{OffMeshLinkSource.handler} + public IOffMeshLinkHandler handler => source.handler; + + /// \copydocref{OffMeshLinkSource.component} + public Component component => source.component; + + /// \copydocref{OffMeshLinkSource.gameObject} + public GameObject gameObject => source.component != null ? source.component.gameObject : null; + + // public Bounds bounds { + // get { + // var b = new Bounds(); + // b.SetMinMax(start.point1, start.point2); + // b.Encapsulate(end.point1); + // b.Encapsulate(end.point2); + // return b; + // } + // } + + public bool Equivalent (OffMeshLinkConcrete other) { + if (start != other.start) return false; + if (end != other.end) return false; + if (startNodes.Length != other.startNodes.Length || endNodes.Length != other.endNodes.Length) return false; + if (directionality != other.directionality || tag != other.tag || costFactor != other.costFactor) return false; + + for (int i = 0; i < startNodes.Length; i++) { + if (startNodes[i] != other.startNodes[i]) return false; + } + for (int i = 0; i < endNodes.Length; i++) { + if (endNodes[i] != other.endNodes[i]) return false; + } + return true; + } + + public void Disconnect () { + if (startLinkNode == null) { + Assert.IsNull(endLinkNode); + } else if (startLinkNode.Destroyed) { + Assert.IsTrue(endLinkNode.Destroyed); + } else { + Assert.IsFalse(endLinkNode.Destroyed); + var linkGraph = startLinkNode.Graph as LinkGraph; + linkGraph.RemoveNode(startLinkNode); + linkGraph.RemoveNode(endLinkNode); + } + startLinkNode = null; + endLinkNode = null; + } + + public void Connect (LinkGraph linkGraph, OffMeshLinkSource source) { + Assert.IsNull(startLinkNode); + Assert.IsNull(endLinkNode); + startLinkNode = linkGraph.AddNode(); + startLinkNode.linkSource = source; + startLinkNode.linkConcrete = this; + startLinkNode.position = (Int3)start.center; + startLinkNode.Tag = tag; + + endLinkNode = linkGraph.AddNode(); + endLinkNode.position = (Int3)end.center; + endLinkNode.linkSource = source; + endLinkNode.linkConcrete = this; + endLinkNode.Tag = tag; + + for (int i = 0; i < startNodes.Length; i++) { + var dist = (VectorMath.ClosestPointOnSegment(start.point1, start.point2, (Vector3)startNodes[i].position) - (Vector3)startNodes[i].position).magnitude; + var cost = (uint)(Int3.Precision * dist); + GraphNode.Connect(startNodes[i], startLinkNode, cost, directionality); + } + for (int i = 0; i < endNodes.Length; i++) { + var dist = (VectorMath.ClosestPointOnSegment(end.point1, end.point2, (Vector3)endNodes[i].position) - (Vector3)endNodes[i].position).magnitude; + var cost = (uint)(Int3.Precision * dist); + GraphNode.Connect(endLinkNode, endNodes[i], cost, directionality); + } + var middleCost = (uint)(Int3.Precision * costFactor * (end.center - start.center).magnitude); + GraphNode.Connect(startLinkNode, endLinkNode, middleCost, directionality); + staleConnections = false; + } + + public OffMeshLinkTracer GetTracer (LinkNode firstNode) { + Assert.IsTrue(firstNode == startLinkNode || firstNode == endLinkNode); + return new OffMeshLinkTracer(this, firstNode == endLinkNode); + } + } + + /// + /// Get all graphs that this link is connected to. + /// + /// Returns: A list of all graphs that this link is connected to. This does not include the link graph. + /// An empty list will be returned if the link is not connected to any graphs. + /// + /// Note: For lower GC pressure, the returned list should be pooled after you are done with it. See: pooling (view in online documentation for working links) + /// + /// The link to get connected graphs for. + public List ConnectedGraphs (OffMeshLinkSource link) { + var graphs = ListPool.Claim(); + if (link.status != OffMeshLinkStatus.Active) return graphs; + Assert.IsTrue(link.treeKey.isValid); + var combined = tree[link.treeKey]; + Assert.IsNotNull(combined.concrete); + var concrete = combined.concrete; + for (int i = 0; i < concrete.startNodes.Length; i++) { + var graph = concrete.startNodes[i].Graph; + if (!graphs.Contains(graph)) graphs.Add(graph); + } + for (int i = 0; i < concrete.endNodes.Length; i++) { + var graph = concrete.endNodes[i].Graph; + if (!graphs.Contains(graph)) graphs.Add(graph); + } + return graphs; + } + + /// + /// Adds a new off-mesh link. + /// + /// If any graphs change in the future, the link will automatically be updated to connect to the updated graphs. + /// + /// Note: The link will not be added immediately, it will be added at the end of the current graph update step. + /// Or, if no graph update is currently running, a graph update will be scheduled, and the link will be added at the end of that update. + /// This is to avoid modifying the graph during a graph update. + /// + /// See: + /// + /// The link to add. + public void Add (OffMeshLinkSource link) { + if (link == null) throw new ArgumentNullException("link"); + if (link.status != OffMeshLinkStatus.Inactive) throw new System.ArgumentException("Link is already added"); + pendingAdd.Add(link); + link.status = OffMeshLinkStatus.Pending; + ScheduleUpdate(); + } + + internal void OnDisable () { + var ls = new List(); + tree.Query(new Bounds(Vector3.zero, Vector3.positiveInfinity), ls); + for (int i = 0; i < ls.Count; i++) { + ls[i].source.status = OffMeshLinkStatus.Inactive; + ls[i].source.treeKey = default; + } + tree.Clear(); + for (int i = 0; i < pendingAdd.Count; i++) { + pendingAdd[i].status = OffMeshLinkStatus.Inactive; + pendingAdd[i].treeKey = default; + } + pendingAdd.Clear(); + } + + /// + /// Removes an existing off-mesh link. + /// + /// Note: The link will not be removed immediately, it will be removed at the end of the current graph update step. + /// Or, if no graph update is currently running, a graph update will be scheduled, and the link will be removed at the end of that update. + /// This is to avoid modifying the graph during a graph update. + /// + /// See: + /// + /// The link to remove. If the link is already removed, nothing will be done. + public void Remove (OffMeshLinkSource link) { + if (link == null) throw new ArgumentNullException("link"); + if (link.status == OffMeshLinkStatus.Inactive || (link.status & OffMeshLinkStatus.PendingRemoval) != 0) { + return; + } else if (link.status == OffMeshLinkStatus.Pending) { + link.status = OffMeshLinkStatus.Inactive; + pendingAdd.Remove(link); + } else { + link.status |= OffMeshLinkStatus.Pending | OffMeshLinkStatus.PendingRemoval; + tree.Tag(link.treeKey); + } + + Assert.IsTrue(link.status == OffMeshLinkStatus.Inactive || (link.status & OffMeshLinkStatus.PendingRemoval) != 0); + ScheduleUpdate(); + } + + bool ClampSegment (Anchor anchor, GraphMask graphMask, float maxSnappingDistance, out Anchor result, List nodes) { + var nn = NearestNodeConstraint.Walkable; + nn.distanceMetric = DistanceMetric.Euclidean; + nn.graphMask = graphMask; + Profiler.BeginSample("GetNearest"); + var nearest = astar.GetNearest(0.5f*(anchor.point1 + anchor.point2), nn); + Profiler.EndSample(); + + if (nearest.distanceCostSqr > maxSnappingDistance*maxSnappingDistance) nearest = default; + + if (nearest.node == null) { + result = default; + return false; + } + + if (anchor.width > 0 && nearest.node.Graph is IRaycastableGraph rayGraph) { + var offset = 0.5f * (anchor.point2 - anchor.point1); + rayGraph.Linecast(nearest.position, nearest.position - offset, nearest.node, out var hit1, nodes); + rayGraph.Linecast(nearest.position, nearest.position + offset, nearest.node, out var hit2, nodes); + result = new Anchor { + center = (hit1.point + hit2.point) * 0.5f, + rotation = anchor.rotation, + width = (hit1.point - hit2.point).magnitude, + }; + + // Sort and deduplicate + nodes.Sort((a, b) => a.NodeIndex.CompareTo(b.NodeIndex)); + for (int j = nodes.Count - 1; j >= 0; j--) { + var n = nodes[j]; + for (int k = j - 1; k >= 0; k--) { + if (nodes[k] == n) { + nodes.RemoveAtSwapBack(j); + break; + } + } + } + } else { + result = new Anchor { + center = nearest.position, + rotation = anchor.rotation, + width = 0f, + }; + nodes.Add(nearest.node); + } + return true; + } + + /// + /// Mark links touching the given bounds as dirty. + /// + /// The bounds should contain the surface of all nodes that have been changed. + /// + /// This will cause the links to be recalculated as soon as possible. + /// + /// Note: Since graphs should only be modified during graph updates, this method should also only be called during a graph update. + /// + public void DirtyBounds (Bounds bounds) { + Profiler.BeginSample("DirtyBounds"); + tree.Tag(bounds); + Profiler.EndSample(); + // Note: We don't have to call ScheduleUpdate here, because DirtyBounds will only be called during a work item/graph update + } + + /// + /// Mark a link as dirty. + /// + /// This will cause the link to be recalculated as soon as possible. + /// + public void Dirty (OffMeshLinkSource link) { + DirtyNoSchedule(link); + ScheduleUpdate(); + } + + internal void DirtyNoSchedule (OffMeshLinkSource link) { + tree.Tag(link.treeKey); + } + + void ScheduleUpdate () { + if (!updateScheduled && !astar.isScanning && !astar.IsAnyWorkItemInProgress) { + updateScheduled = true; + astar.AddWorkItem(() => {}); + } + } + + /// + /// Get the nearest link to a point. + /// + /// Returns: The nearest link to the point or a default if no link was found. + /// The returned struct contains both the link and information about which side of the link is closest to the point. + /// If the end is closer than the start, then a reversed will be returned. + /// + /// Point to search around. + /// Maximum distance to search. Use a small distance for better performance. + public OffMeshLinkTracer GetNearest (Vector3 point, float maxDistance) { + if (maxDistance < 0) return default; + if (!float.IsFinite(maxDistance)) throw new System.ArgumentOutOfRangeException("maxDistance"); + + var ls = ListPool.Claim(); + tree.Query(new Bounds(point, new Vector3(2*maxDistance, 2*maxDistance, 2*maxDistance)), ls); + OffMeshLinkConcrete nearest = null; + bool reversed = false; + float nearestDist = maxDistance*maxDistance; + for (int i = 0; i < ls.Count; i++) { + var link = ls[i].concrete; + var dist = VectorMath.SqrDistancePointSegment(link.start.point1, link.start.point2, point); + if (dist < nearestDist) { + nearestDist = dist; + nearest = link; + reversed = false; + } + dist = VectorMath.SqrDistancePointSegment(link.end.point1, link.end.point2, point); + if (dist < nearestDist) { + nearestDist = dist; + nearest = link; + reversed = true; + } + } + ListPool.Release(ref ls); + return nearest != null ? new OffMeshLinkTracer(nearest, reversed) : default; + } + + internal void Refresh () { + Profiler.BeginSample("Refresh Off-mesh links"); + updateScheduled = false; + + var pendingUpdate = ListPool.Claim(); + // Find all links that require updates + // These have previously been tagged using the DirtyBounds method + tree.QueryTagged(pendingUpdate, true); + + // Add all links to the tree which are pending insertion + for (int i = 0; i < pendingAdd.Count; i++) { + var link = pendingAdd[i]; + Assert.IsTrue(link.status == OffMeshLinkStatus.Pending); + var combined = new OffMeshLinkCombined { + source = link, + concrete = null, + }; + link.treeKey = tree.Add(link.bounds, combined); + pendingUpdate.Add(combined); + } + pendingAdd.Clear(); + + List startNodes = ListPool.Claim(); + List endNodes = ListPool.Claim(); + + for (int i = 0; i < pendingUpdate.Count; i++) { + for (int j = 0; j < i; j++) { + if (pendingUpdate[i].source == pendingUpdate[j].source) throw new System.Exception("Duplicate link"); + } + var source = pendingUpdate[i].source; + + var combined = tree[source.treeKey]; + var prevConcrete = combined.concrete; + + if ((source.status & OffMeshLinkStatus.PendingRemoval) != 0) { + if (prevConcrete != null) { + prevConcrete.Disconnect(); + combined.concrete = null; + } + tree.Remove(source.treeKey); + source.treeKey = default; + source.status = OffMeshLinkStatus.Inactive; + continue; + } + + startNodes.Clear(); + if (!ClampSegment(source.start, source.graphMask, source.maxSnappingDistance, out var concreteStart, startNodes)) { + if (prevConcrete != null) { + prevConcrete.Disconnect(); + combined.concrete = null; + } + source.status = OffMeshLinkStatus.FailedToConnectStart; + continue; + } + endNodes.Clear(); + if (!ClampSegment(source.end, source.graphMask, source.maxSnappingDistance, out var concreteEnd, endNodes)) { + if (prevConcrete != null) { + prevConcrete.Disconnect(); + combined.concrete = null; + } + source.status = OffMeshLinkStatus.FailedToConnectEnd; + continue; + } + + var concrete = new OffMeshLinkConcrete { + start = concreteStart, + end = concreteEnd, + startNodes = startNodes.ToArrayFromPool(), + endNodes = endNodes.ToArrayFromPool(), + source = source, + directionality = source.directionality, + tag = source.tag, + costFactor = source.costFactor, + }; + + if (prevConcrete != null && !prevConcrete.staleConnections && prevConcrete.Equivalent(concrete)) { + // Nothing to do. The link is already connected like it should be. + source.status &= ~OffMeshLinkStatus.Pending; + Assert.AreNotEqual(OffMeshLinkStatus.Inactive, source.status); + } else { + // Remove previous connections + if (prevConcrete != null) { + prevConcrete.Disconnect(); + ArrayPool.Release(ref prevConcrete.startNodes); + ArrayPool.Release(ref prevConcrete.endNodes); + } + + // Add new connections + if (astar.data.linkGraph == null) { + var graph = astar.data.AddGraph(); + graph.name = "Internal graph for off-mesh links"; + } + concrete.Connect(astar.data.linkGraph, source); + combined.concrete = concrete; + source.status = OffMeshLinkStatus.Active; + } + } + + ListPool.Release(ref pendingUpdate); + ListPool.Release(ref startNodes); + ListPool.Release(ref endNodes); + Profiler.EndSample(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs.meta new file mode 100644 index 0000000..96fffdd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/OffMeshLinks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ad699a60828e7e4e98c493913084bb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs new file mode 100644 index 0000000..88fb499 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs @@ -0,0 +1,619 @@ +using UnityEngine; +using System.Collections.Generic; +using Unity.Mathematics; + +namespace Pathfinding.Util { + /// Interpolates along a sequence of points + public class PathInterpolator { + /// + /// Represents a single point on the polyline represented by the . + /// The cursor is a lightweight structure which can be used to move backwards and forwards along a . + /// + /// If the changes (e.g. has its path swapped out), then this cursor is invalidated and cannot be used anymore. + /// + public struct Cursor { + private PathInterpolator interpolator; + private int version; + private float currentDistance; + private float distanceToSegmentStart; + private float currentSegmentLength; + + /// + /// Current segment. + /// The start and end points of the segment are path[value] and path[value+1]. + /// + int segmentIndex { get; set; } + + public int segmentCount { + get { + AssertValid(); + return interpolator.path.Count - 1; + } + } + + /// Last point in the path + public Vector3 endPoint { + get { + AssertValid(); + return interpolator.path[interpolator.path.Count-1]; + } + } + + /// + /// Fraction of the way along the current segment. + /// 0 is at the start of the segment, 1 is at the end of the segment. + /// + public float fractionAlongCurrentSegment { + get { + return currentSegmentLength > 0 ? (currentDistance - distanceToSegmentStart) / currentSegmentLength : 1f; + } + set { + currentDistance = distanceToSegmentStart + Mathf.Clamp01(value) * currentSegmentLength; + } + } + + /// A cursor at the start of the polyline represented by the interpolator + public static Cursor StartOfPath (PathInterpolator interpolator) { + if (!interpolator.valid) throw new System.InvalidOperationException("PathInterpolator has no path set"); + return new Cursor { + interpolator = interpolator, + version = interpolator.version, + segmentIndex = 0, + currentDistance = 0, + distanceToSegmentStart = 0, + currentSegmentLength = (interpolator.path[1] - interpolator.path[0]).magnitude, + }; + } + + /// + /// True if this instance has a path set. + /// See: SetPath + /// + public bool valid { + get { + return interpolator != null && interpolator.version == version; + } + } + + /// + /// Tangent of the curve at the current position. + /// Not necessarily normalized. + /// + public Vector3 tangent { + get { + AssertValid(); + return interpolator.path[segmentIndex+1] - interpolator.path[segmentIndex]; + } + } + + /// Remaining distance until the end of the path + public float remainingDistance { + get { + AssertValid(); + return interpolator.totalDistance - distance; + } + set { + AssertValid(); + distance = interpolator.totalDistance - value; + } + } + + /// Traversed distance from the start of the path + public float distance { + get { + return currentDistance; + } + set { + AssertValid(); + currentDistance = value; + + while (currentDistance < distanceToSegmentStart && segmentIndex > 0) PrevSegment(); + while (currentDistance > distanceToSegmentStart + currentSegmentLength && segmentIndex < interpolator.path.Count - 2) NextSegment(); + } + } + + /// Current position + public Vector3 position { + get { + AssertValid(); + float t = currentSegmentLength > 0.0001f ? (currentDistance - distanceToSegmentStart) / currentSegmentLength : 0f; + return Vector3.Lerp(interpolator.path[segmentIndex], interpolator.path[segmentIndex+1], t); + } + } + + /// Appends the remaining path between and to buffer + public void GetRemainingPath (List buffer) { + AssertValid(); + buffer.Add(position); + for (int i = segmentIndex+1; i < interpolator.path.Count; i++) { + buffer.Add(interpolator.path[i]); + } + } + + void AssertValid () { + if (!this.valid) throw new System.InvalidOperationException("The cursor has been invalidated because SetPath has been called on the interpolator. Please create a new cursor."); + } + + /// + /// The tangent(s) of the curve at the current position. + /// Not necessarily normalized. + /// + /// Will output t1=, t2= if on a straight line segment. + /// Will output the previous and next tangents for the adjacent line segments when on a corner. + /// + /// This is similar to but can output two tangents instead of one when on a corner. + /// + public void GetTangents (out Vector3 t1, out Vector3 t2) { + AssertValid(); + var nearStart = currentDistance <= distanceToSegmentStart + 0.001f; + var nearEnd = currentDistance >= distanceToSegmentStart + currentSegmentLength - 0.001f; + if (nearStart || nearEnd) { + int s1, s2; + if (nearStart) { + s1 = segmentIndex > 0 ? segmentIndex - 1 : segmentIndex; + s2 = segmentIndex; + } else { + s1 = segmentIndex; + s2 = segmentIndex < interpolator.path.Count - 2 ? segmentIndex + 1 : segmentIndex; + } + t1 = interpolator.path[s1+1] - interpolator.path[s1]; + t2 = interpolator.path[s2+1] - interpolator.path[s2]; + } else { + t1 = tangent; + t2 = t1; + } + } + + /// + /// A vector parallel to the local curvature. + /// + /// This will be zero on straight line segments, and in the same direction as the rotation axis when on a corner. + /// + /// Since this interpolator follows a polyline, the curvature is always either 0 or infinite. + /// Therefore the magnitude of this vector has no meaning when non-zero. Only the direction matters. + /// + public Vector3 curvatureDirection { + get { + GetTangents(out var t1, out var t2); + var up = Vector3.Cross(t1, t2); + return up.sqrMagnitude <= 0.000001f ? Vector3.zero : up; + } + } + + /// + /// Moves the cursor to the next geometric corner in the path. + /// + /// This is the next geometric corner. + /// If the original path contained any zero-length segments, they will be skipped over. + /// + public void MoveToNextCorner () { + AssertValid(); + var path = interpolator.path; + // Skip zero-length segments + while (currentDistance >= this.distanceToSegmentStart + this.currentSegmentLength && segmentIndex < path.Count - 2) NextSegment(); + // Skip parallel segements + while (segmentIndex < path.Count - 2 && VectorMath.IsColinear(path[segmentIndex], path[segmentIndex+1], path[segmentIndex+2])) NextSegment(); + // Move to end of current segment + currentDistance = distanceToSegmentStart + currentSegmentLength; + } + + /// + /// Moves to the closest intersection of the line segment (origin + direction*range.x, origin + direction*range.y). + /// The closest intersection as measured by the distance along the path is returned. + /// + /// If no intersection is found, false will be returned and the cursor remains unchanged. + /// + /// The intersection is calculated in XZ space. + /// + /// A point on the line + /// The direction of the line. Need not be normalized. + /// The range of the line segment along the line. The segment is (origin + direction*range.x, origin + direction*range.y). May be (-inf, +inf) to consider an infinite line. + public bool MoveToClosestIntersectionWithLineSegment (Vector3 origin, Vector3 direction, Vector2 range) { + AssertValid(); + var closestIntersection = float.PositiveInfinity; + var closestDist = float.PositiveInfinity; + var d = 0f; + for (int i = 0; i < interpolator.path.Count - 1; i++) { + var p1 = interpolator.path[i]; + var p2 = interpolator.path[i+1]; + var segmentLength = (p2 - p1).magnitude; + if ( + VectorMath.LineLineIntersectionFactors(((float3)p1).xz, ((float3)(p2 - p1)).xz, ((float3)origin).xz, ((float3)direction).xz, out float t1, out float t2) + && t1 >= 0.0f && t1 <= 1.0f + && t2 >= range.x && t2 <= range.y + ) { + var intersection = d + t1 * segmentLength; + var dist = Mathf.Abs(intersection - this.currentDistance); + if (dist < closestDist) { + closestIntersection = intersection; + closestDist = dist; + } + } + d += segmentLength; + } + if (closestDist != float.PositiveInfinity) { + this.distance = closestIntersection; + return true; + } + return false; + } + + /// Move to the specified segment and move a fraction of the way to the next segment + void MoveToSegment (int index, float fractionAlongSegment) { + AssertValid(); + if (index < 0 || index >= interpolator.path.Count - 1) throw new System.ArgumentOutOfRangeException("index"); + while (segmentIndex > index) PrevSegment(); + while (segmentIndex < index) NextSegment(); + currentDistance = distanceToSegmentStart + Mathf.Clamp01(fractionAlongSegment) * currentSegmentLength; + } + + /// Move as close as possible to the specified point + public void MoveToClosestPoint (Vector3 point) { + AssertValid(); + + float bestDist = float.PositiveInfinity; + float bestFactor = 0f; + int bestIndex = 0; + + var path = interpolator.path; + + for (int i = 0; i < path.Count-1; i++) { + float factor = VectorMath.ClosestPointOnLineFactor(path[i], path[i+1], point); + Vector3 closest = Vector3.Lerp(path[i], path[i+1], factor); + float dist = (point - closest).sqrMagnitude; + + if (dist < bestDist) { + bestDist = dist; + bestFactor = factor; + bestIndex = i; + } + } + + MoveToSegment(bestIndex, bestFactor); + } + + public void MoveToLocallyClosestPoint (Vector3 point, bool allowForwards = true, bool allowBackwards = true) { + AssertValid(); + + var path = interpolator.path; + + segmentIndex = Mathf.Min(segmentIndex, path.Count - 2); + while (true) { + int currentSegment = segmentIndex; + var factor = VectorMath.ClosestPointOnLineFactor(path[currentSegment], path[currentSegment+1], point); + if (factor > 1.0f && allowForwards && segmentIndex < path.Count - 2) { + NextSegment(); + allowBackwards = false; + } else if (factor < 0.0f && allowBackwards && segmentIndex > 0) { + PrevSegment(); + allowForwards = false; + } else { + if (factor > 0.5f && segmentIndex < path.Count - 2) { + NextSegment(); + } + break; + } + } + + // Check the distances to the two segments extending from the vertex path[segmentIndex] + // and pick the position on those segments that is closest to the #point parameter. + float factor1 = 0, d1 = float.PositiveInfinity; + + if (segmentIndex > 0) { + var s1 = segmentIndex - 1; + factor1 = VectorMath.ClosestPointOnLineFactor(path[s1], path[s1+1], point); + d1 = (Vector3.Lerp(path[s1], path[s1+1], factor1) - point).sqrMagnitude; + } + + var factor2 = VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], point); + var d2 = (Vector3.Lerp(path[segmentIndex], path[segmentIndex+1], factor2) - point).sqrMagnitude; + + if (d1 < d2) MoveToSegment(segmentIndex - 1, factor1); + else MoveToSegment(segmentIndex, factor2); + } + + public void MoveToCircleIntersection2D(Vector3 circleCenter3D, float radius, T transform) where T : IMovementPlane { + AssertValid(); + + var path = interpolator.path; + + // Move forwards as long as we are getting closer to circleCenter3D + while (segmentIndex < path.Count - 2 && VectorMath.ClosestPointOnLineFactor(path[segmentIndex], path[segmentIndex+1], circleCenter3D) > 1) { + NextSegment(); + } + + var circleCenter = transform.ToPlane(circleCenter3D); + + // Move forwards as long as the current segment endpoint is within the circle + while (segmentIndex < path.Count - 2 && (transform.ToPlane(path[segmentIndex+1]) - circleCenter).sqrMagnitude <= radius*radius) { + NextSegment(); + } + + // Calculate the intersection with the circle. This involves some math. + var factor = VectorMath.LineCircleIntersectionFactor(circleCenter, transform.ToPlane(path[segmentIndex]), transform.ToPlane(path[segmentIndex+1]), radius); + + // Move to the intersection point + MoveToSegment(segmentIndex, factor); + } + + /// + /// Integrates exp(-|x|/smoothingDistance)/(2*smoothingDistance) from a to b. + /// The integral from -inf to +inf is 1. + /// + static float IntegrateSmoothingKernel (float a, float b, float smoothingDistance) { + if (smoothingDistance <= 0) return a <= 0 && b > 0 ? 1 : 0; + var iA = a < 0 ? Mathf.Exp(a / smoothingDistance) : 2.0f - Mathf.Exp(-a / smoothingDistance); + var iB = b < 0 ? Mathf.Exp(b / smoothingDistance) : 2.0f - Mathf.Exp(-b / smoothingDistance); + return 0.5f * (iB - iA); + } + + /// Integrates (x - a)*exp(-x/smoothingDistance)/(2*smoothingDistance) from a to b. + static float IntegrateSmoothingKernel2 (float a, float b, float smoothingDistance) { + if (smoothingDistance <= 0) return 0f; + var iA = -Mathf.Exp(-a / smoothingDistance) * (smoothingDistance); + var iB = -Mathf.Exp(-b / smoothingDistance) * (smoothingDistance + b - a); + return 0.5f * (iB - iA); + } + + static Vector3 IntegrateSmoothTangent (Vector3 p1, Vector3 p2, ref Vector3 tangent, ref float distance, float expectedRadius, float smoothingDistance) { + var segment = p2 - p1; + var segmentLength = segment.magnitude; + if (segmentLength <= 0.00001f) return Vector3.zero; + var nextTangent = segment * (1.0f / segmentLength); + var deltaAngle = Vector3.Angle(tangent, nextTangent) * Mathf.Deg2Rad; + var arcLength = expectedRadius*Mathf.Abs(deltaAngle); + // We try to approximate + // integrate kernel(x) * tangent(x); where * denotes convolution + + var integratedTangent = Vector3.zero; + if (arcLength > float.Epsilon) { + // Arc + // integrate kernel(x) * (tangent + (nextTangent - tangent) * x/arcLength) dx + var convolution = tangent * IntegrateSmoothingKernel(distance, distance + arcLength, smoothingDistance) + + (nextTangent - tangent) * IntegrateSmoothingKernel2(distance, distance + arcLength, smoothingDistance) / arcLength; + integratedTangent += convolution; + distance += arcLength; + } + + // Straight line + // integrate kernel(x) * nextTangent dx = nextTangent * integrate kernel(x) dx + integratedTangent += nextTangent * IntegrateSmoothingKernel(distance, distance + segmentLength, smoothingDistance); + tangent = nextTangent; + distance += segmentLength; + return integratedTangent; + } + + public Vector3 EstimateSmoothTangent (Vector3 normalizedTangent, float smoothingDistance, float expectedRadius, Vector3 beforePathStartContribution, bool forward = true, bool backward = true) { + AssertValid(); + if (expectedRadius <= float.Epsilon || smoothingDistance <= 0f) return normalizedTangent; + + var path = interpolator.path; + var estimatedTangent = Vector3.zero; + // Avoid zero-length segments at the start + while (currentDistance >= this.distanceToSegmentStart + this.currentSegmentLength && segmentIndex < interpolator.path.Count - 2) NextSegment(); + if (forward) { + var d = 0f; + var prev = position; + var prevTangent = normalizedTangent; + for (int i = segmentIndex + 1; i < path.Count; i++) { + estimatedTangent += IntegrateSmoothTangent(prev, path[i], ref prevTangent, ref d, expectedRadius, smoothingDistance); + prev = path[i]; + } + } + if (backward) { + var d = 0f; + var prevTangent = -normalizedTangent; + var prev = position; + for (int i = segmentIndex; i >= 0; i--) { + estimatedTangent -= IntegrateSmoothTangent(prev, path[i], ref prevTangent, ref d, expectedRadius, smoothingDistance); + prev = path[i]; + } + estimatedTangent += beforePathStartContribution * IntegrateSmoothingKernel(float.NegativeInfinity, -currentDistance, smoothingDistance); + } + + return estimatedTangent; + } + + public Vector3 EstimateSmoothCurvature (Vector3 tangent, float smoothingDistance, float expectedRadius) { + AssertValid(); + if (expectedRadius <= float.Epsilon) return Vector3.zero; + + var path = interpolator.path; + tangent = tangent.normalized; + var curvature = Vector3.zero; + // Avoid zero-length segments at the start + while (currentDistance >= this.distanceToSegmentStart + this.currentSegmentLength && segmentIndex < interpolator.path.Count - 2) NextSegment(); + var d = 0f; + var prev = position; + var currentTangent = tangent.normalized; + for (int i = segmentIndex + 1; i < path.Count; i++) { + var segment = path[i] - prev; + var t = segment.normalized; + var deltaAngle = Vector3.Angle(currentTangent, t) * Mathf.Deg2Rad; + var c = Vector3.Cross(currentTangent, t).normalized; + var angleDerivative = 1.0f / expectedRadius; + var arcLength = expectedRadius*Mathf.Abs(deltaAngle); + // d/dx(f * angle(x)) = f * angle'(x); where * denotes convolution + var convolutionDerivative = angleDerivative * IntegrateSmoothingKernel(d, d + arcLength, smoothingDistance); + curvature -= convolutionDerivative * c; + currentTangent = t; + d += arcLength; + d += segment.magnitude; + prev = path[i]; + } + // Do another integral in the backwards direction. + // Ensures that if smoothingDistance is 0, the smoothing kernel will only be sampled once at x=0. + d = float.Epsilon; + currentTangent = -tangent.normalized; + prev = position; + for (int i = segmentIndex; i >= 0; i--) { + var segment = path[i] - prev; + if (segment == Vector3.zero) continue; + + var t = segment.normalized; + var deltaAngle = Vector3.Angle(currentTangent, t) * Mathf.Deg2Rad; + var c = Vector3.Cross(currentTangent, t).normalized; + var angleDerivative = 1.0f / expectedRadius; + var arcLength = expectedRadius*Mathf.Abs(deltaAngle); + // d/dx(f * angle(x)) = f * angle'(x); where * denotes convolution + var convolutionDerivative = angleDerivative * IntegrateSmoothingKernel(d, d + arcLength, smoothingDistance); + curvature += convolutionDerivative * c; + currentTangent = t; + d += arcLength; + d += segment.magnitude; + prev = path[i]; + } + return curvature; + } + + /// + /// Moves the agent along the path, stopping to rotate on the spot when the path changes direction. + /// + /// Note: The cursor state does not include the rotation of the agent. So if an agent stops in the middle of a rotation, the final state of this struct will be as if the agent completed its rotation. + /// If you want to preserve the rotation state as well, keep track of the output tangent, and pass it along to the next call to this function. + /// + /// The number of seconds to move forwards or backwards (if negative). + /// Speed in meters/second. + /// Turning speed in radians/second. + /// The current forwards direction of the agent. May be set to the #tangent property if you have no other needs. + /// If set to something other than #tangent, the agent will start by rotating to face the #tangent direction. + /// This will be replaced with the forwards direction of the agent after moving. + /// It will be smoothly interpolated as the agent rotates from one segment to the next. + /// It is more precise than the #tangent property after this call, which does not take rotation into account. + /// This value is not necessarily normalized. + public void MoveWithTurningSpeed (float time, float speed, float turningSpeed, ref Vector3 tangent) { + if (turningSpeed <= 0) throw new System.ArgumentException("turningSpeed must be greater than zero"); + if (speed <= 0) throw new System.ArgumentException("speed must be greater than zero"); + AssertValid(); + var radiansToMeters = speed / turningSpeed; + var remainingOffset = time * speed; + int its = 0; + // Make sure we don't start by rotating unnecessarily + while (remainingOffset > 0 && currentDistance >= this.distanceToSegmentStart + this.currentSegmentLength && segmentIndex < interpolator.path.Count - 2) NextSegment(); + while (remainingOffset < 0 && currentDistance <= this.distanceToSegmentStart && segmentIndex > 0) PrevSegment(); + while (remainingOffset != 0f) { + its++; + if (its > 100) throw new System.Exception("Infinite Loop " + remainingOffset + " " + time); + var desiredTangent = this.tangent; + if (tangent != desiredTangent && currentSegmentLength > 0) { + // Rotate to face the desired tangent + var angle = Vector3.Angle(tangent, desiredTangent) * Mathf.Deg2Rad; + var arcLength = angle * radiansToMeters; + if (Mathf.Abs(remainingOffset) > arcLength) { + remainingOffset -= arcLength * Mathf.Sign(remainingOffset); + tangent = desiredTangent; + } else { + tangent = Vector3.Slerp(tangent, desiredTangent, Mathf.Abs(remainingOffset) / arcLength); + return; + } + } + + if (remainingOffset > 0) { + // Move forward along the segment + var remainingOnCurrentSegment = this.currentSegmentLength - (this.currentDistance - this.distanceToSegmentStart); + if (remainingOffset >= remainingOnCurrentSegment) { + remainingOffset -= remainingOnCurrentSegment; + if (segmentIndex + 1 >= this.interpolator.path.Count - 1) { + MoveToSegment(segmentIndex, 1.0f); + return; + } else { + MoveToSegment(segmentIndex + 1, 0.0f); + } + } else { + this.currentDistance += remainingOffset; + return; + } + } else { + // Move backward along the segment + var remainingOnCurrentSegment = this.currentDistance - this.distanceToSegmentStart; + if (-remainingOffset > remainingOnCurrentSegment) { + remainingOffset += remainingOnCurrentSegment; + if (segmentIndex - 1 < 0) { + MoveToSegment(segmentIndex, 0.0f); + return; + } else { + MoveToSegment(segmentIndex - 1, 1.0f); + } + } else { + this.currentDistance += remainingOffset; + return; + } + } + } + } + + void PrevSegment () { + segmentIndex--; + currentSegmentLength = (interpolator.path[segmentIndex+1] - interpolator.path[segmentIndex]).magnitude; + distanceToSegmentStart -= currentSegmentLength; + } + + void NextSegment () { + segmentIndex++; + distanceToSegmentStart += currentSegmentLength; + currentSegmentLength = (interpolator.path[segmentIndex+1] - interpolator.path[segmentIndex]).magnitude; + } + } + + List path; + int version = 1; + float totalDistance; + + /// + /// True if this instance has a path set. + /// See: SetPath + /// + public bool valid { + get { + return path != null; + } + } + + public Cursor start { + get { + return Cursor.StartOfPath(this); + } + } + + public Cursor AtDistanceFromStart (float distance) { + var cursor = start; + + cursor.distance = distance; + return cursor; + } + + /// + /// Set the path to interpolate along. + /// This will invalidate all existing cursors. + /// + public void SetPath (List path) { + this.version++; + if (this.path == null) this.path = new List(); + this.path.Clear(); + + if (path == null) { + totalDistance = float.PositiveInfinity; + return; + } + + if (path.Count < 2) throw new System.ArgumentException("Path must have a length of at least 2"); + + var prev = path[0]; + + totalDistance = 0; + this.path.Capacity = Mathf.Max(this.path.Capacity, path.Count); + this.path.Add(path[0]); + for (int i = 1; i < path.Count; i++) { + var current = path[i]; + // Avoid degenerate segments + if (current != prev) { + totalDistance += (current - prev).magnitude; + this.path.Add(current); + prev = current; + } + } + if (this.path.Count < 2) this.path.Add(path[0]); + if (float.IsNaN(totalDistance)) throw new System.ArgumentException("Path contains NaN values"); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs.meta new file mode 100644 index 0000000..ed9f0c3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathInterpolator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9c0392dbc5e744ee28e7b9ee81aea1e2 +timeCreated: 1490125383 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs new file mode 100644 index 0000000..d09535c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs @@ -0,0 +1,32 @@ +namespace Pathfinding.Util { + /// + /// Represents a part of a path, with optional link information. + /// + /// A path is divided up into parts, where each part is either a sequence of nodes or an off-mesh link. + /// If your agent never traverses off-mesh links, the path will always consist of only a single part which is a sequence of nodes. + /// + public struct PathPartWithLinkInfo { + public PathPartWithLinkInfo(int startIndex, int endIndex, OffMeshLinks.OffMeshLinkTracer linkInfo = default) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.linkInfo = linkInfo; + } + + /// + /// Index of the first point in the path that this part represents. + /// + /// For off-mesh links, this will refer to the last point in the part before the off-mesh link. + /// + public int startIndex; + /// + /// Index of the last point in the path that this part represents. + /// + /// For off-mesh links, this will refer to the first point in the part after the off-mesh link. + /// + public int endIndex; + /// The off-mesh link that this part represents. Will contain a null link if this part is not an off-mesh link + public OffMeshLinks.OffMeshLinkTracer linkInfo; + /// Specifies if this is a sequence of nodes, or an off-mesh link + public Funnel.PartType type => linkInfo.link != null ? Funnel.PartType.OffMeshLink : Funnel.PartType.NodeSequence; + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs.meta new file mode 100644 index 0000000..ac0fcdb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathPartWithLinkInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef250ec5543ec034cbf24245e48384bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs new file mode 100644 index 0000000..3ecb98f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs @@ -0,0 +1,157 @@ +namespace Pathfinding { + /// + /// Settings for how an agent searches for paths. + /// + /// This struct contains information about which graphs the agent can use, which nodes it can traverse, and if any nodes should be easier or harder to traverse. + /// + /// See: + /// See: + /// + [System.Serializable] +#if UNITY_2023_1_OR_NEWER + [Unity.Properties.GeneratePropertyBag] +#endif + public struct PathRequestSettings : System.IEquatable { + /// + /// Graphs that this agent can use. + /// This field determines which graphs will be considered when searching for the start and end nodes of a path. + /// It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units, or one graph for people and one graph for ships. + /// + /// This is a bitmask so if you for example want to make the agent only use graph index 3 then you can set this to: + /// settings.graphMask = GraphMask.FromGraphIndex(3); + /// + /// See: bitmasks (view in online documentation for working links) + /// + /// Note that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering + /// then this mask may no longer be correct. + /// + /// If you know the name of the graph you can use the method: + /// + /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph"); + /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph"); + /// + /// NearestNodeConstraint nn = NearestNodeConstraint.Walkable; + /// + /// nn.graphMask = mask1 | mask2; + /// + /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph' + /// var info = AstarPath.active.GetNearest(somePoint, nn); + /// + /// + /// See: multiple-agent-types (view in online documentation for working links) + /// + public GraphMask graphMask; + + /// + /// The penalty for each tag. + /// + /// If null, all penalties will be treated as zero. Otherwise, the array should always have a length of exactly 32. + /// + /// [Open online documentation to see images] + /// + /// See: tags (view in online documentation for working links) + /// Deprecated: Use or instead. + /// + [System.Obsolete("Use tagEntryCosts or tagCostMultipliers instead")] + public uint[] tagPenalties { + get => tagEntryCosts; + set => tagEntryCosts = value; + } + + /// + /// The tags which this agent can traverse. + /// + /// This is a bitmask. Each bit indicates that the agent can traverse nodes with the corresponding tag. + /// If a bit is not set, the agent will treat it as if it is not traversable. + /// + /// The default value is -1, which sets all bits, and indicates that the agent can traverse all tags. + /// + /// [Open online documentation to see images] + /// + /// See: bitmasks (view in online documentation for working links) + /// See: tags (view in online documentation for working links) + /// + public int traversableTags; + + /// \copydocref{TraversalCosts.tagCostMultipliers} + public float[] tagCostMultipliers; + + /// \copydocref{TraversalCosts.tagEntryCosts} + [UnityEngine.Serialization.FormerlySerializedAs("tagPenalties")] + public uint[] tagEntryCosts; + + /// + /// Filters which nodes the agent can traverse, and can also add penalties to each traversed node. + /// + /// In most common situations, this is left as null (which implies the default traversal provider: ). + /// But if you need custom pathfinding behavior which cannot be done using the , and , then setting an is a great option. + /// It provides you a lot more control over how the pathfinding works. + /// + /// + /// followerEntity.pathfindingSettings.traversalProvider = new MyCustomTraversalProvider(); + /// + /// + /// See: traversal_provider (view in online documentation for working links) + /// + public ITraversalProvider traversalProvider; + + /// A PathRequestSettings instance with default values for all fields + public static PathRequestSettings Default { + get { + var res = new PathRequestSettings { + graphMask = GraphMask.everything, + tagCostMultipliers = new float[32], + tagEntryCosts = new uint[32], + traversableTags = -1, + traversalProvider = null, + }; + for (int i = 0; i < res.tagCostMultipliers.Length; i++) res.tagCostMultipliers[i] = 1f; + return res; + } + } + + /// + /// Converts this struct to a which can be used for nearest node queries. + /// + /// See: + /// See: + /// + public NearestNodeConstraint ToNearestNodeConstraint () { + return ToTraversalConstraint().ToNearestNodeConstraint(); + } + + /// + /// Converts this struct to a which can be used for pathfinding or linecasts. + /// + /// See: + /// + public TraversalConstraint ToTraversalConstraint () { + return new TraversalConstraint { + tags = traversableTags, + traversalProvider = traversalProvider, + graphMask = graphMask, + }; + } + + /// + /// Converts this struct to a which can be used for pathfinding. + /// + /// See: + /// + public TraversalCosts ToTraversalCosts () { + return new TraversalCosts { + tagEntryCosts = tagEntryCosts, + tagCostMultipliers = tagCostMultipliers, + traversalProvider = traversalProvider, + }; + } + + public bool Equals (PathRequestSettings other) { + return graphMask == other.graphMask && + Util.Memory.SequenceEqual(tagCostMultipliers, other.tagCostMultipliers) && + Util.Memory.SequenceEqual(tagEntryCosts, other.tagEntryCosts) && + traversableTags == other.traversableTags && + traversalProvider == other.traversalProvider; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs.meta new file mode 100644 index 0000000..9264f08 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathRequestSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 159402c3a3fd6434cad55fd1055058e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs new file mode 100644 index 0000000..c42f236 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs @@ -0,0 +1,56 @@ +namespace Pathfinding { + /// + /// Represents a single pathfinding tag. + /// + /// Values of this type will be automatically displayed in the unity inspector as a dropdown list of all the pathfinding tags. + /// + /// Note: The tag refers to a pathfinding tag, not a unity tag that is applied to GameObjects, or any other kind of tag. + /// + /// See: tags (view in online documentation for working links) + /// + [System.Serializable] + public struct PathfindingTag { + /// + /// Underlaying tag value. + /// Should always be between 0 and (inclusive). + /// + public uint value; + + public PathfindingTag(uint value) { + this.value = value; + } + + public static implicit operator uint (PathfindingTag tag) { + return tag.value; + } + + public static implicit operator PathfindingTag(uint tag) { + return new PathfindingTag(tag); + } + + /// Get the value of the PathfindingTag with the given name + public static PathfindingTag FromName (string tagName) { + AstarPath.FindAstarPath(); + if (AstarPath.active == null) throw new System.InvalidOperationException("There's no AstarPath component in the scene. Cannot get tag names."); + + var tagNames = AstarPath.active.GetTagNames(); + var tag = System.Array.IndexOf(tagNames, tagName); + if (tag == -1) throw new System.ArgumentException("There's no pathfinding tag with the name '" + tagName + "'"); + + return new PathfindingTag((uint)tag); + } + + /// + /// Converts the tag value to a bitmask of tags. + /// + /// This can, for example, be used to set . + /// + public int ToMask () { + return 1 << (int)value; + } + + public override string ToString () { + return value.ToString(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs.meta new file mode 100644 index 0000000..4deed42 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/PathfindingTag.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2eeb70ede92586d3e9834dd8f17a8097 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs new file mode 100644 index 0000000..2b6a421 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs @@ -0,0 +1,313 @@ +using UnityEngine; +using Unity.Collections; +using Unity.Mathematics; +using Unity.Burst; + +namespace Pathfinding.RVO { + /// + /// Controls if the agent slows down to a stop if the area around the destination is crowded. + /// The main idea for this script is to + /// - Reduce the local avoidance priority for agents that have reached their destination once. + /// - Make agents stop if there is a high density of units around its destination. + /// + /// 'High density' is defined as: + /// Take the circle with the center at the AI's destination and a radius such that the AI's current position + /// is touching its border. Let 'A' be the area of that circle. Further let 'a' be the total area of all + /// individual agents inside that circle. + /// The agent should stop if a > A*0.6 or something like that. I.e if the agents inside the circle cover + /// over 60% of the surface of the circle. The 60% figure can be modified (see . + /// + /// This script was inspired by how Starcraft 2 does its local avoidance. + /// + /// See: + /// + [System.Serializable] + public struct RVODestinationCrowdedBehavior { + /// Enables or disables this module + public bool enabled; + + /// + /// The threshold for when to stop. + /// See the class description for more info. + /// + [Range(0, 1)] + public float densityThreshold; + + /// + /// If true, the agent will start to move to the destination again if it determines that it is now less crowded. + /// If false and the destination becomes less crowded (or if the agent is pushed away from the destination in some way), then the agent will still stay put. + /// + public bool returnAfterBeingPushedAway; + + public float progressAverage; + bool wasEnabled; + float timer1; + float shouldStopDelayTimer; + bool lastShouldStopResult; + Vector3 lastShouldStopDestination; + Vector3 reachedDestinationPoint; + public bool lastJobDensityResult; + + /// See https://en.wikipedia.org/wiki/Circle_packing + const float MaximumCirclePackingDensity = 0.9069f; + + [BurstCompile(CompileSynchronously = false, FloatMode = FloatMode.Fast)] + public struct JobDensityCheck : Pathfinding.Jobs.IJobParallelForBatched { + [ReadOnly] + RVOQuadtreeBurst quadtree; + [ReadOnly] + public NativeArray data; + [ReadOnly] + public NativeArray agentPosition; + [ReadOnly] + NativeArray agentTargetPoint; + [ReadOnly] + NativeArray agentRadius; + [ReadOnly] + NativeArray agentDesiredSpeed; + [ReadOnly] + NativeArray agentOutputTargetPoint; + [ReadOnly] + NativeArray agentOutputSpeed; + [WriteOnly] + public NativeArray outThresholdResult; + public NativeArray progressAverage; + + public float deltaTime; + + public bool allowBoundsChecks => false; + + public struct QueryData { + public float3 agentDestination; + public int agentIndex; + public float densityThreshold; + } + + public JobDensityCheck(int size, float deltaTime, SimulatorBurst simulator) { + agentPosition = simulator.simulationData.position; + agentTargetPoint = simulator.simulationData.targetPoint; + agentRadius = simulator.simulationData.radius; + agentDesiredSpeed = simulator.simulationData.desiredSpeed; + agentOutputTargetPoint = simulator.outputData.targetPoint; + agentOutputSpeed = simulator.outputData.speed; + quadtree = simulator.quadtree; + data = new NativeArray(size, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + outThresholdResult = new NativeArray(size, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + progressAverage = new NativeArray(size, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + this.deltaTime = deltaTime; + } + + public void Dispose () { + data.Dispose(); + outThresholdResult.Dispose(); + progressAverage.Dispose(); + } + + public void Set (int index, int rvoAgentIndex, float3 destination, float densityThreshold, float progressAverage) { + data[index] = new QueryData { + agentDestination = destination, + densityThreshold = densityThreshold, + agentIndex = rvoAgentIndex, + }; + this.progressAverage[index] = progressAverage; + } + + void Pathfinding.Jobs.IJobParallelForBatched.Execute (int start, int count) { + for (int i = start; i < start + count; i++) { + Execute(i); + } + } + + float AgentDensityInCircle (float3 position, float radius) { + return quadtree.QueryArea(position, radius) / (radius * radius * math.PI); + } + + void Execute (int i) { + var query = data[i]; + + var position = agentPosition[query.agentIndex]; + var radius = agentRadius[query.agentIndex]; + + var desiredDirection = math.normalizesafe(agentTargetPoint[query.agentIndex] - position); + float delta; + + if (agentDesiredSpeed[query.agentIndex] > 0.01f) { + // How quickly the agent can move + var speedTowardsTarget = math.dot(desiredDirection, math.normalizesafe(agentOutputTargetPoint[query.agentIndex] - position) * agentOutputSpeed[query.agentIndex]); + // Make it relative to how quickly it wants to move + // So 0.0 means it is stuck + // 1.0 means it is moving as quickly as it wants + // Cap the desired speed by the agent's radius. This avoids making agents that want to move very quickly + // but are slowed down to a still reasonable speed get a very low progressAverage. + delta = speedTowardsTarget / math.max(0.001f, math.min(agentDesiredSpeed[query.agentIndex], agentRadius[query.agentIndex])); + // Clamp between -1 and 1 + delta = math.clamp(delta, -1.0f, 1.0f); + } else { + // If the agent doesn't want to move anywhere then it has 100% progress + delta = 1.0f; + } + + // Exponentially decaying average of the deltas (in seconds^-1) + const float FilterConvergenceSpeed = 2.0f; + progressAverage[i] = math.lerp(progressAverage[i], delta, FilterConvergenceSpeed * deltaTime); + + // If no destination has been set, then always stop + if (math.any(math.isinf(query.agentDestination))) { + outThresholdResult[i] = true; + return; + } + + var checkRadius = math.length(query.agentDestination - position); + var checkRadius2 = radius*5; + + if (checkRadius > checkRadius2) { + // TODO: Change center to be slightly biased towards agent position + // If the agent is far away from the destination then do a faster check around the destination first. + if (AgentDensityInCircle(query.agentDestination, checkRadius2) < MaximumCirclePackingDensity*query.densityThreshold) { + outThresholdResult[i] = false; + return; + } + } + + outThresholdResult[i] = AgentDensityInCircle(query.agentDestination, checkRadius) > MaximumCirclePackingDensity*query.densityThreshold; + } + } + + public void ReadJobResult (ref JobDensityCheck jobResult, int index) { + bool shouldStop = jobResult.outThresholdResult[index]; + + progressAverage = jobResult.progressAverage[index]; + + lastJobDensityResult = shouldStop; + shouldStopDelayTimer = Mathf.Lerp(shouldStopDelayTimer, shouldStop ? 1 : 0, Time.deltaTime); + shouldStop = shouldStop && shouldStopDelayTimer > 0.1f; + lastShouldStopResult = shouldStop; + lastShouldStopDestination = jobResult.data[index].agentDestination; + } + + public RVODestinationCrowdedBehavior (bool enabled, float densityFraction, bool returnAfterBeingPushedAway) { + this.enabled = wasEnabled = enabled; + this.densityThreshold = densityFraction; + this.returnAfterBeingPushedAway = returnAfterBeingPushedAway; + this.lastJobDensityResult = false; + this.progressAverage = 0; + this.wasStopped = false; + this.lastShouldStopDestination = new Vector3(float.NaN, float.NaN, float.NaN); + this.reachedDestinationPoint = new Vector3(float.NaN, float.NaN, float.NaN); + timer1 = 0; + shouldStopDelayTimer = 0; + reachedDestination = false; + lastShouldStopResult = false; + } + + /// + /// Marks the destination as no longer being reached. + /// + /// If the agent had stopped because the destination was crowded, this will make it immediately try again + /// to move forwards if it can. If the destination is still crowded it will soon stop again. + /// + /// This is useful to call when a user gave an agent an explicit order to ensure it doesn't + /// just stay in the same location without even trying to move forwards. + /// + public void ClearDestinationReached () { + wasStopped = false; + progressAverage = 1.0f; + reachedDestination = false; + } + + public void OnDestinationChanged (Vector3 newDestination, bool reachedDestination) { + timer1 = float.PositiveInfinity; + // TODO: Check previous ShouldStop result. Check how much the circles overlap. + // With significant overlap we may want to keep reachedCurrentDestination = true + this.reachedDestination = reachedDestination; // (ideal: || ShouldStop(ai, rvo)) + } + + /// + /// True if the agent has reached its destination. + /// If the agents destination changes this may return false until the next frame. + /// Note that changing the destination every frame may cause this value to never return true. + /// + /// True will be returned if the agent has stopped due to being close enough to the destination. + /// This may be quite some distance away if there are many other agents around the destination. + /// + /// See: + /// + public bool reachedDestination { get; private set; } + + bool wasStopped; + + const float DefaultPriority = 1.0f; + const float StoppedPriority = 0.1f; + const float MoveBackPriority = 0.5f; + + public void Update (bool rvoControllerEnabled, bool reachedDestination, ref bool isStopped, ref float rvoPriorityMultiplier, ref float rvoFlowFollowingStrength, Vector3 agentPosition) { + if (!(enabled && rvoControllerEnabled)) { + if (wasEnabled) { + wasEnabled = false; + // Reset to default values + rvoPriorityMultiplier = DefaultPriority; + rvoFlowFollowingStrength = 0; + timer1 = float.PositiveInfinity; + progressAverage = 1.0f; + } + return; + } + wasEnabled = true; + + if (reachedDestination) { + var validRange = (agentPosition - this.reachedDestinationPoint).sqrMagnitude; + if ((lastShouldStopDestination - this.reachedDestinationPoint).sqrMagnitude > validRange) { + // The reachedDestination bool is no longer valid. + // The destination has moved significantly from the last point where we detected that it was crowded. + // It may end up being set to true immediately afterwards though if + // the parameter reachedDestination (not this.reachedDestination) is true. + this.reachedDestination = false; + } + } + + if (reachedDestination || lastShouldStopResult) { + // We have reached the destination the destination is crowded enough that we should stop here anyway + timer1 = 0f; + this.reachedDestination = true; + this.reachedDestinationPoint = this.lastShouldStopDestination; + rvoPriorityMultiplier = Mathf.Lerp(rvoPriorityMultiplier, StoppedPriority, Time.deltaTime * 2); + rvoFlowFollowingStrength = Mathf.Lerp(rvoFlowFollowingStrength, 1.0f, Time.deltaTime * 4); + wasStopped |= math.abs(progressAverage) < 0.1f; + isStopped |= wasStopped; // false && rvoPriorityMultiplier > 0.9f; + } else if (isStopped) { + // We have not reached the destination, but a separate script is telling is to stop + timer1 = 0f; + this.reachedDestination = false; + rvoPriorityMultiplier = Mathf.Lerp(rvoPriorityMultiplier, StoppedPriority, Time.deltaTime * 2); + rvoFlowFollowingStrength = Mathf.Lerp(rvoFlowFollowingStrength, 1.0f, Time.deltaTime * 4); + wasStopped |= math.abs(progressAverage) < 0.1f; + } else { + // Check if we had reached the current destination previously (but it is not reached any longer) + // TODO: Rename variable, confusing + if (this.reachedDestination) { + timer1 += Time.deltaTime; + if (timer1 > 3 && returnAfterBeingPushedAway) { + // Make the agent try to move back to the destination + // Use a slightly higher priority than agents that are just standing still, but lower than regular agents + rvoPriorityMultiplier = Mathf.Lerp(rvoPriorityMultiplier, MoveBackPriority, Time.deltaTime * 2); + rvoFlowFollowingStrength = 0; + isStopped = false; + wasStopped = false; + } else { + rvoPriorityMultiplier = Mathf.Lerp(rvoPriorityMultiplier, StoppedPriority, Time.deltaTime * 2); + rvoFlowFollowingStrength = Mathf.Lerp(rvoFlowFollowingStrength, 1.0f, Time.deltaTime * 4); + wasStopped |= math.abs(progressAverage) < 0.1f; + isStopped = wasStopped; + //isStopped = false && rvoPriorityMultiplier > 0.9f; + } + } else { + // This is the common case: the agent is just on its way to the destination + rvoPriorityMultiplier = Mathf.Lerp(rvoPriorityMultiplier, DefaultPriority, Time.deltaTime * 4); + rvoFlowFollowingStrength = 0f; + isStopped = false; + wasStopped = false; + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs.meta new file mode 100644 index 0000000..1957e7c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/RVODestinationCrowdedBehavior.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3346d0269f43f49adbf879577f758cd0 +timeCreated: 1503053987 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs new file mode 100644 index 0000000..6dae6d7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs @@ -0,0 +1,45 @@ +using UnityEngine; + +namespace Pathfinding.Util { + /// Compatibility class for Unity APIs that are not available in all Unity versions + public static class UnityCompatibility { + public static T[] FindObjectsByTypeSorted() where T : Object { +#if UNITY_2021_3_OR_NEWER && !(UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER) + return Object.FindObjectsByType(FindObjectsSortMode.InstanceID); +#else + return Object.FindObjectsOfType(); +#endif + } + + public static T[] FindObjectsByTypeUnsorted() where T : Object { +#if UNITY_2021_3_OR_NEWER && !(UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER) + return Object.FindObjectsByType(FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(); +#endif + } + + public static T[] FindObjectsByTypeUnsortedWithInactive() where T : Object { +#if UNITY_2021_3_OR_NEWER && !(UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER) + return Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(true); +#endif + } + + public static T FindAnyObjectByType() where T : Object { +#if UNITY_2021_3_OR_NEWER && !(UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER) + return Object.FindAnyObjectByType(); +#else + return Object.FindObjectOfType(); +#endif + } + } +} + +#if !UNITY_2022_3_OR_NEWER +namespace Pathfinding { + public class IgnoredByDeepProfilerAttribute : System.Attribute { + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs.meta new file mode 100644 index 0000000..abff2d5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/UnityCompatibility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e62cc47f530e57d4294dbb1baba8ffc5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs new file mode 100644 index 0000000..fabfbae --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs @@ -0,0 +1,140 @@ +#if NETFX_CORE +using System.Threading; +using System.Threading.Tasks; +using System.Reflection; +using System.IO; +using TP = System.Reflection.TypeInfo; +#else +using TP = System.Type; +#endif + +namespace Pathfinding.WindowsStore { + public static class WindowsStoreCompatibility { + public static TP GetTypeInfo (System.Type type) { +#if NETFX_CORE + return type.GetTypeInfo(); +#else + return type; +#endif + } + +#if NETFX_CORE + public static void Close (this BinaryWriter stream) { + stream.Dispose(); + } + + public static void Close (this BinaryReader stream) { + stream.Dispose(); + } + + public static void Close (this StreamWriter stream) { + stream.Dispose(); + } +#endif + } + +#if NETFX_CORE + public delegate void ParameterizedThreadStart(System.Object ob); + public delegate void ThreadStart(); + + public class Thread { + // + // Fields + // + private Pathfinding.WindowsStore.ParameterizedThreadStart _paramThreadStart; + + private CancellationTokenSource _taskCancellationTokenSource; + + private Task _task = null; + + private Pathfinding.WindowsStore.ThreadStart _threadStart; + + private static ManualResetEvent SleepEvent = new ManualResetEvent(false); + + // + // Properties + // + public bool IsAlive { + get { + return this._task != null && !this._task.IsCompleted; + } + set { + throw new System.NotImplementedException(); + } + } + + public bool IsBackground { + get { + return false; + } + set { + } + } + + public string Name { + get; + set; + } + + // + // Constructors + // + public Thread (Pathfinding.WindowsStore.ParameterizedThreadStart start) { + this._taskCancellationTokenSource = new CancellationTokenSource(); + this._paramThreadStart = start; + } + + public Thread (Pathfinding.WindowsStore.ThreadStart start) { + this._taskCancellationTokenSource = new CancellationTokenSource(); + this._threadStart = start; + } + + // + // Static Methods + // + public static void Sleep (int ms) { + SleepEvent.WaitOne(ms); + } + + // + // Methods + // + public void Abort () { + if (this._taskCancellationTokenSource != null) { + this._taskCancellationTokenSource.Cancel(); + } + } + + private void EnsureTask (object paramThreadStartParam = null) { + if (this._task == null) { + if (this._paramThreadStart != null) { + this._task = new Task(delegate { + this._paramThreadStart(paramThreadStartParam); + }, this._taskCancellationTokenSource.Token); + } else { + if (this._threadStart != null) { + this._task = new Task(delegate { + this._threadStart(); + }, this._taskCancellationTokenSource.Token); + } + } + } + } + + public bool Join (int ms) { + this.EnsureTask(); + return this._task.Wait(ms, this._taskCancellationTokenSource.Token); + } + + public void Start () { + this.EnsureTask(); + this._task.Start(TaskScheduler.Default); + } + + public void Start (object param) { + this.EnsureTask(param); + this._task.Start(TaskScheduler.Default); + } + } +#endif +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs.meta new file mode 100644 index 0000000..d99c0c7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WindowsStoreCompatibility.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 810a4e97f5ccb4b5184c4c3206492974 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs new file mode 100644 index 0000000..94774c7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs @@ -0,0 +1,514 @@ +using UnityEngine; +using UnityEngine.Profiling; +using Unity.Jobs; +using UnityEngine.Assertions; +using System.Collections.Generic; +using Pathfinding.Drawing; +using Unity.Mathematics; + +namespace Pathfinding { + /// + /// An item of work that can be executed when graphs are safe to update. + /// See: + /// See: + /// + public struct AstarWorkItem { + /// + /// Init function. + /// May be null if no initialization is needed. + /// Will be called once, right before the first call to or . + /// + public System.Action init; + + /// + /// Init function. + /// May be null if no initialization is needed. + /// Will be called once, right before the first call to or . + /// + /// A context object is sent as a parameter. This can be used + /// to for example queue a flood fill that will be executed either + /// when a work item calls EnsureValidFloodFill or all work items have + /// been completed. If multiple work items are updating nodes + /// so that they need a flood fill afterwards, using the QueueFloodFill + /// method is preferred since then only a single flood fill needs + /// to be performed for all of the work items instead of one + /// per work item. + /// + public System.Action initWithContext; + + /// + /// Update function, called once per frame when the work item executes. + /// Takes a param force. If that is true, the work item should try to complete the whole item in one go instead + /// of spreading it out over multiple frames. + /// + /// Warning: If you make modifications to the graphs, they must only be made during the last time the method is called. + /// Earlier invocations, as well as the / mehods, are only for pre-calculating information required for the update. + /// + /// Returns: True when the work item is completed. + /// + public System.Func update; + + /// + /// Update function, called once per frame when the work item executes. + /// Takes a param force. If that is true, the work item should try to complete the whole item in one go instead + /// of spreading it out over multiple frames. + /// Returns: True when the work item is completed. + /// + /// Warning: If you make modifications to the graphs, they must only be made during the last time the method is called. + /// Earlier invocations, as well as the / mehods, are only for pre-calculating information required for the update. + /// + /// A context object is sent as a parameter. This can be used + /// to for example queue a flood fill that will be executed either + /// when a work item calls EnsureValidFloodFill or all work items have + /// been completed. If multiple work items are updating nodes + /// so that they need a flood fill afterwards, using the QueueFloodFill + /// method is preferred since then only a single flood fill needs + /// to be performed for all of the work items instead of one + /// per work item. + /// + public System.Func updateWithContext; + + /// Creates a work item which will call the specified functions when executed. + /// Will be called once per frame when the work item executes. See #update for details. + public AstarWorkItem (System.Func update) { + this.init = null; + this.initWithContext = null; + this.updateWithContext = null; + this.update = update; + } + + /// Creates a work item which will call the specified functions when executed. + /// Will be called once per frame when the work item executes. See #updateWithContext for details. + public AstarWorkItem (System.Func update) { + this.init = null; + this.initWithContext = null; + this.updateWithContext = update; + this.update = null; + } + + /// Creates a work item which will call the specified functions when executed. + /// Will be called once, right before the first call to update. See #init for details. + /// Will be called once per frame when the work item executes. See #update for details. + public AstarWorkItem (System.Action init, System.Func update = null) { + this.init = init; + this.initWithContext = null; + this.update = update; + this.updateWithContext = null; + } + + /// Creates a work item which will call the specified functions when executed. + /// Will be called once, right before the first call to update. See #initWithContext for details. + /// Will be called once per frame when the work item executes. See #updateWithContext for details. + public AstarWorkItem (System.Action init, System.Func update = null) { + this.init = null; + this.initWithContext = init; + this.update = null; + this.updateWithContext = update; + } + } + + /// Interface to expose a subset of the WorkItemProcessor functionality + public interface IWorkItemContext : IGraphUpdateContext { + /// + /// Call during work items to queue a flood fill. + /// An instant flood fill can be done via FloodFill() + /// but this method can be used to batch several updates into one + /// to increase performance. + /// WorkItems which require a valid Flood Fill in their execution can call EnsureValidFloodFill + /// to ensure that a flood fill is done if any earlier work items queued one. + /// + /// Once a flood fill is queued it will be done after all WorkItems have been executed. + /// + /// Deprecated: You no longer need to call this method. Connectivity data is automatically kept up-to-date. + /// + [System.Obsolete("You no longer need to call this method. Connectivity data is automatically kept up-to-date.")] + void QueueFloodFill(); + + /// + /// If a WorkItem needs to have a valid area information during execution, call this method to ensure there are no pending flood fills. + /// If you are using the property or the method in your work items, then you may want to call this method before you use them, + /// to ensure that the data is up to date. + /// + /// See: + /// + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem((IWorkItemContext ctx) => { + /// // Update the graph in some way + /// // ... + /// + /// // Ensure that connectivity information is up to date. + /// // This will also automatically run after all work items have been executed. + /// ctx.EnsureValidFloodFill(); + /// + /// // Use connectivity information + /// if (PathUtilities.IsPathPossible(someNode, someOtherNode)) { + /// // Do something + /// } + /// })); + /// + /// + void EnsureValidFloodFill(); + + /// + /// Call to send a GraphModifier.EventType.PreUpdate event to all graph modifiers. + /// The difference between this and GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate) is that using this method + /// ensures that multiple PreUpdate events will not be issued during a single update. + /// + /// Once an event has been sent no more events will be sent until all work items are complete and a PostUpdate or PostScan event is sent. + /// + /// When scanning a graph PreUpdate events are never sent. However a PreScan event is always sent before a scan begins. + /// + void PreUpdate(); + + /// + /// Trigger a graph modification event. + /// This will cause a event to be issued after all graph updates have finished. + /// Some scripts listen for this event. For example off-mesh links listen to it and will recalculate which nodes they are connected to when it it sent. + /// If a graph is dirtied multiple times, or even if multiple graphs are dirtied, the event will only be sent once. + /// + // TODO: Deprecate? + void SetGraphDirty(NavGraph graph); + + /// + /// Add a visualization for showing the original bounds of a graph update. + /// + /// For example, each bounding box for a navmesh cut will be sent to this method to allow visualizing it in the scene view. + /// + /// This has no effect on gameplay, it is only for debugging purposes. + /// + /// See: + /// See: + /// + void VisualizeOriginalGraphUpdateBounds(Bounds bounds); + } + + class WorkItemProcessor : IWorkItemContext { + public event System.Action OnGraphsUpdated; + + /// Used to prevent waiting for work items to complete inside other work items as that will cause the program to hang + public bool workItemsInProgressRightNow { get; private set; } + + readonly AstarPath astar; + readonly IndexedQueue workItems = new IndexedQueue(); + readonly List boundsVisualizations = new List(); + + + /// True if any work items are queued right now + public bool anyQueued { + get { return workItems.Count > 0; } + } + + bool anyGraphsDirty = true; + bool preUpdateEventSent = false; + + /// + /// True while a batch of work items are being processed. + /// Set to true when a work item is started to be processed, reset to false when all work items are complete. + /// + /// Work item updates are often spread out over several frames, this flag will be true during the whole time the + /// updates are in progress. + /// + public bool workItemsInProgress { get; private set; } + + struct BoundsVisualization { + public Bounds bounds; + public float createdTime; + public int createdFrame; + public BoundsVisualizationType type; + + public enum BoundsVisualizationType { + OriginalBounds, + AffectedBounds + } + + public BoundsVisualization (Bounds bounds, BoundsVisualizationType type) { + this.bounds = bounds; + this.type = type; + createdTime = Time.realtimeSinceStartup; + createdFrame = Time.frameCount; + } + } + + /// Similar to Queue but allows random access + // TODO: Replace with CircularBuffer? + class IndexedQueue { + T[] buffer = new T[4]; + int start; + + public T this[int index] { + get { + if (index < 0 || index >= Count) throw new System.IndexOutOfRangeException(); + return buffer[(start + index) % buffer.Length]; + } + set { + if (index < 0 || index >= Count) throw new System.IndexOutOfRangeException(); + buffer[(start + index) % buffer.Length] = value; + } + } + + public int Count { get; private set; } + + public void Enqueue (T item) { + if (Count == buffer.Length) { + var newBuffer = new T[buffer.Length*2]; + for (int i = 0; i < Count; i++) { + newBuffer[i] = this[i]; + } + buffer = newBuffer; + start = 0; + } + + buffer[(start + Count) % buffer.Length] = item; + Count++; + } + + public T Dequeue () { + if (Count == 0) throw new System.InvalidOperationException(); + var item = buffer[start]; + start = (start + 1) % buffer.Length; + Count--; + return item; + } + } + + public void VisualizeOriginalGraphUpdateBounds (Bounds bounds) { + if ((astar.graphUpdateDebugMode & GraphUpdateDebugMode.VisualizeOriginalBounds) != 0) boundsVisualizations.Add(new BoundsVisualization(bounds, BoundsVisualization.BoundsVisualizationType.OriginalBounds)); + } + + /// + /// Call during work items to queue a flood fill. + /// An instant flood fill can be done via FloodFill() + /// but this method can be used to batch several updates into one + /// to increase performance. + /// WorkItems which require a valid Flood Fill in their execution can call EnsureValidFloodFill + /// to ensure that a flood fill is done if any earlier work items queued one. + /// + /// Once a flood fill is queued it will be done after all WorkItems have been executed. + /// + /// Deprecated: This method no longer does anything. + /// + void IWorkItemContext.QueueFloodFill () { + } + + void IWorkItemContext.PreUpdate () { + if (!preUpdateEventSent && !astar.isScanning) { + preUpdateEventSent = true; + GraphModifier.TriggerEvent(GraphModifier.EventType.PreUpdate); + } + } + + // This will also call DirtyGraphs + void IWorkItemContext.SetGraphDirty(NavGraph graph) => astar.DirtyBounds(graph.bounds); + + void IGraphUpdateContext.DirtyBounds (Bounds bounds) { + // This will also call the other overload of DirtyBounds + astar.DirtyBounds(bounds); + } + + internal void DirtyBounds (Bounds bounds) { + if ((astar.graphUpdateDebugMode & GraphUpdateDebugMode.VisualizeAffectedBounds) != 0) boundsVisualizations.Add(new BoundsVisualization(bounds, BoundsVisualization.BoundsVisualizationType.AffectedBounds)); + anyGraphsDirty = true; + } + + /// If a WorkItem needs to have a valid area information during execution, call this method to ensure there are no pending flood fills + public void EnsureValidFloodFill () { + astar.hierarchicalGraph.RecalculateIfNecessary(); + } + + public WorkItemProcessor (AstarPath astar) { + this.astar = astar; + } + + /// + /// Add a work item to be processed when pathfinding is paused. + /// + /// See: ProcessWorkItems + /// + public void AddWorkItem (AstarWorkItem item) { + workItems.Enqueue(item); + } + + bool ProcessWorkItems (bool force, bool sendEvents) { + if (workItemsInProgressRightNow) throw new System.Exception("Processing work items recursively. Please do not wait for other work items to be completed inside work items. " + + "If you think this is not caused by any of your scripts, this might be a bug."); + + // Work items may update graph data arbitrarily + // So we need to hold a write lock here so that for example + // ECS jobs don't try to read the graph data while it is being updated + var lockObj = astar.LockGraphDataForWritingSync(); + astar.data.LockGraphStructure(true); + + // Make sure the physics engine data is up to date. + // Graph updates may use physics methods and it is very confusing if they + // do not always pick up the latest changes made to the scene. + UnityEngine.Physics.SyncTransforms(); + UnityEngine.Physics2D.SyncTransforms(); + + workItemsInProgressRightNow = true; + + try { + bool workRemaining = false; + bool anyFinished = false; + while (workItems.Count > 0) { + // Working on a new batch + if (!workItemsInProgress) { + workItemsInProgress = true; + } + + // Peek at first item in the queue + AstarWorkItem itm = workItems[0]; + bool status; + + try { + // Call init the first time the item is seen + if (itm.init != null) { + itm.init(); + itm.init = null; + } + + if (itm.initWithContext != null) { + itm.initWithContext(this); + itm.initWithContext = null; + } + + // Make sure the item in the queue is up to date + workItems[0] = itm; + + if (itm.update != null) { + status = itm.update(force); + } else if (itm.updateWithContext != null) { + status = itm.updateWithContext(this, force); + } else { + status = true; + } + } catch { + workItems.Dequeue(); + throw; + } + + if (!status) { + if (force) { + Debug.LogError("Misbehaving WorkItem. 'force'=true but the work item did not complete.\nIf force=true is passed to a WorkItem it should always return true."); + } + + // There's more work to do on this work item + workRemaining = true; + break; + } else { + workItems.Dequeue(); + anyFinished = true; + } + } + + if (sendEvents && anyFinished) { + if (anyGraphsDirty) { + Profiler.BeginSample("PostUpdateBeforeAreaRecalculation"); + GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdateBeforeAreaRecalculation); + Profiler.EndSample(); + } + + astar.offMeshLinks.Refresh(); + EnsureValidFloodFill(); + + if (anyGraphsDirty) { + Profiler.BeginSample("PostUpdate"); + GraphModifier.TriggerEvent(GraphModifier.EventType.PostUpdate); + if (OnGraphsUpdated != null) OnGraphsUpdated(); + Profiler.EndSample(); + } + } + if (workRemaining) return false; + } finally { + lockObj.Unlock(); + astar.data.UnlockGraphStructure(); + workItemsInProgressRightNow = false; + } + + // Reset flags at the end + anyGraphsDirty = false; + preUpdateEventSent = false; + + workItemsInProgress = false; + return true; + } + + /// + /// Process graph updating work items. + /// Process all queued work items, e.g graph updates and the likes. + /// + /// Returns: + /// - false if there are still items to be processed. + /// - true if the last work items was processed and pathfinding threads are ready to be resumed. + /// + /// This will not call in contrast to . + /// + /// See: + /// + public bool ProcessWorkItemsForScan (bool force) { + return ProcessWorkItems(force, false); + } + + /// + /// Process graph updating work items. + /// Process all queued work items, e.g graph updates and the likes. + /// + /// Returns: + /// - false if there are still items to be processed. + /// - true if the last work items was processed and pathfinding threads are ready to be resumed. + /// + /// See: + /// + /// This method also calls GraphModifier.TriggerEvent(PostUpdate) if any graphs were dirtied. + /// It also calls after the work items are done + /// + public bool ProcessWorkItemsForUpdate (bool force) { + return ProcessWorkItems(force, true); + } + + public void DrawGizmos () { + if (!Application.isEditor && !astar.showGraphsInStandalonePlayer) { + boundsVisualizations.Clear(); + return; + } + + var frame = Time.frameCount; + var time = Time.realtimeSinceStartup; + var draw = Application.isEditor ? Draw.editor : Draw.ingame; + var removeCount = 0; + var mode = astar.graphUpdateDebugMode; + + using (draw.WithLineWidth(2)) { + for (int i = 0; i < boundsVisualizations.Count; i++) { + var boundsVis = boundsVisualizations[i]; + var first = boundsVis.createdFrame == frame; + + Color color; + bool show; + if (boundsVis.type == BoundsVisualization.BoundsVisualizationType.OriginalBounds) { + color = Palette.Colorbrewer.Set1.Yellow; + show = (mode & GraphUpdateDebugMode.VisualizeOriginalBounds) != 0; + } else if (boundsVis.type == BoundsVisualization.BoundsVisualizationType.AffectedBounds) { + color = Palette.Colorbrewer.Set1.Orange; + show = (mode & GraphUpdateDebugMode.VisualizeAffectedBounds) != 0; + } else { + throw new System.ArgumentOutOfRangeException(); + } + + if (!first) color.a *= 0.5f * math.exp(-2f * (time - boundsVis.createdTime)); + + if (color.a < 0.01f) { + removeCount = i + 1; + } + + if (show) draw.WireBox(boundsVis.bounds, color); + } + } + + if ((mode & GraphUpdateDebugMode.VisualizeOverTime) == 0 || !Application.isPlaying) { + boundsVisualizations.Clear(); + } else if (removeCount > 0) { + boundsVisualizations.RemoveRange(0, removeCount); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs.meta new file mode 100644 index 0000000..e7cf323 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Misc/WorkItemProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4236764d0fc1041abaac13b858be5118 +timeCreated: 1443114816 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes.meta new file mode 100644 index 0000000..76b5eb8 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b41391edefb794969b06b1e03993abf4 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs new file mode 100644 index 0000000..55cdb6a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs @@ -0,0 +1,946 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Serialization; +using Unity.Profiling; + +namespace Pathfinding { + using Pathfinding.Pooling; + + /// Represents a connection to another node + public struct Connection { + /// Node which this connection goes to + public GraphNode node; + + /// + /// Cost of moving along this connection. + /// A cost of 1000 corresponds approximately to the cost of moving one world unit. + /// + public uint cost; + + /// + /// Various metadata about the connection, such as the side of the node shape which this connection uses. + /// + /// - Bits 0..1 represent . + /// - Bits 2..3 represent . + /// - Bit 4 represents . + /// - Bit 5 represents . + /// - Bit 6 represents . + /// + /// Note: Due to alignment, the and fields use 12 bytes which will be padded + /// to 16 bytes when used in an array even if this field would be removed. + /// So this field does not contribute to increased memory usage. We could even expand it to 32-bits if we need to in the future. + /// + public byte shapeEdgeInfo; + + /// + /// The edge of the shape which this connection uses. + /// This is an index into the shape's vertices. + /// + /// A value of 0 corresponds to using the side for vertex 0 and vertex 1 on the node. 1 corresponds to vertex 1 and 2, etc. + /// A value of 3 is invalid, and this will be the value if is false. + /// + /// See: + /// See: + /// + public int shapeEdge => shapeEdgeInfo & 0b11; + + /// + /// The edge of the shape in the other node, which this connection represents. + /// + /// See: + /// + public int adjacentShapeEdge => (shapeEdgeInfo >> 2) & 0b11; + + /// + /// True if the two nodes share an identical edge. + /// + /// This is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents. + /// + /// In contrast to , this is true only if the triangle edge is identical (but reversed) in the other node. + /// + public bool edgesAreIdentical => (shapeEdgeInfo & IdenticalEdge) != 0; + + /// + /// True if the two nodes share an edge. + /// + /// This is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents. + /// Note that the edge does not need to be perfectly identical for this to be true, it is enough if the edge is very similar. + /// + public bool isEdgeShared => (shapeEdgeInfo & NoSharedEdge) != NoSharedEdge; + + /// + /// True if the connection allows movement from this node to the other node. + /// + /// A connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. + /// + public bool isOutgoing => (shapeEdgeInfo & OutgoingConnection) != 0; + + /// + /// True if the connection allows movement from the other node to this node. + /// + /// A connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. + /// + public bool isIncoming => (shapeEdgeInfo & IncomingConnection) != 0; + + + public const byte NoSharedEdge = 0b1111; + public const byte IncomingConnection = 1 << 4; + public const byte OutgoingConnection = 1 << 5; + public const byte IdenticalEdge = 1 << 6; + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public Connection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) { + this.node = node; + this.cost = cost; + this.shapeEdgeInfo = PackShapeEdgeInfo(isOutgoing, isIncoming); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static byte PackShapeEdgeInfo(bool isOutgoing, bool isIncoming) => (byte)(NoSharedEdge | (isIncoming ? IncomingConnection : 0) | (isOutgoing ? OutgoingConnection : 0)); + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static byte PackShapeEdgeInfo (byte shapeEdge, byte adjacentShapeEdge, bool areEdgesIdentical, bool isOutgoing, bool isIncoming) { +#if UNITY_EDITOR + if (shapeEdge > 3) throw new System.ArgumentException("shapeEdge must be at most 3"); + if (adjacentShapeEdge > 3) throw new System.ArgumentException("adjacentShapeEdge must be at most 3"); +#endif + return (byte)(shapeEdge | (adjacentShapeEdge << 2) | (areEdgesIdentical ? IdenticalEdge : 0) | (isOutgoing ? OutgoingConnection : 0) | (isIncoming ? IncomingConnection : 0)); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public Connection (GraphNode node, uint cost, byte shapeEdgeInfo) { + this.node = node; + this.cost = cost; + this.shapeEdgeInfo = shapeEdgeInfo; + } + + public override int GetHashCode () { + return node.GetHashCode() ^ (int)cost; + } + + public override bool Equals (object obj) { + if (!(obj is Connection)) return false; + var conn = (Connection)obj; + return conn.node == node && conn.cost == cost && conn.shapeEdgeInfo == shapeEdgeInfo; + } + } + + /// Base class for all nodes + public abstract class GraphNode { + /// Internal unique index. Also stores some bitpacked values such as and . + private int nodeIndex; + + /// + /// Bitpacked field holding several pieces of data. + /// See: Walkable + /// See: Area + /// See: GraphIndex + /// See: Tag + /// + protected uint flags; + +#if !ASTAR_NO_PENALTY + /// + /// Penalty cost for walking on this node. + /// This can be used to make it harder/slower to walk over certain nodes. + /// + /// A penalty of 1000 (Int3.Precision) corresponds to the cost of walking one world unit. + /// + /// See: graph-updates (view in online documentation for working links) + /// + private uint penalty; +#endif + + /// + /// Graph which this node belongs to. + /// + /// If you know the node belongs to a particular graph type, you can cast it to that type: + /// + /// GraphNode node = ...; + /// GridGraph graph = node.Graph as GridGraph; + /// + /// + /// Will return null if the node has been destroyed. + /// + public NavGraph Graph => AstarData.GetGraph(this); + + /// + /// Destroys the node. + /// Cleans up any temporary pathfinding data used for this node. + /// The graph is responsible for calling this method on nodes when they are destroyed, including when the whole graph is destoyed. + /// Otherwise memory leaks might present themselves. + /// + /// Once called the property will return true and subsequent calls to this method will not do anything. + /// + /// Note: Assumes the current active AstarPath instance is the same one that created this node. + /// + /// Warning: Should only be called by graph classes on their own nodes. If you want to remove nodes from a point graph, use instead. + /// + public void Destroy () { + if (Destroyed) return; + + ClearConnections(true); + + if (AstarPath.active != null) { + AstarPath.active.DestroyNode(this); + } + NodeIndex = DestroyedNodeIndex; + } + + public bool Destroyed { + [IgnoredByDeepProfiler] + get { + return NodeIndex == DestroyedNodeIndex; + } + } + + // If anyone creates more than about 200 million nodes then things will not go so well, however at that point one will certainly have more pressing problems, such as having run out of RAM + const uint NodeIndexMask = 0xFFFFFFF; + public const uint DestroyedNodeIndex = NodeIndexMask - 1; + public const int InvalidNodeIndex = 0; + const int TemporaryFlag1Mask = 0x10000000; + const int TemporaryFlag2Mask = 0x20000000; + + /// + /// Internal unique index. + /// Every node will get a unique index. + /// This index is not necessarily correlated with e.g the position of the node in the graph. + /// + public uint NodeIndex { + [IgnoredByDeepProfiler] + get { return (uint)nodeIndex & NodeIndexMask; } + [IgnoredByDeepProfiler] + internal set { nodeIndex = (int)(((uint)nodeIndex & ~NodeIndexMask) | value); } + } + + /// + /// How many path node variants should be created for each node. + /// + /// This should be a constant for each node type. + /// + /// Typically this is 1, but for example the triangle mesh node type has 3 variants, one for each edge. + /// + /// See: + /// + internal virtual int PathNodeVariants => 1; + + /// + /// Temporary flag for internal purposes. + /// May only be used in the Unity thread. Must be reset to false after every use. + /// + internal bool TemporaryFlag1 { get { return (nodeIndex & TemporaryFlag1Mask) != 0; } set { nodeIndex = (nodeIndex & ~TemporaryFlag1Mask) | (value ? TemporaryFlag1Mask : 0); } } + + /// + /// Temporary flag for internal purposes. + /// May only be used in the Unity thread. Must be reset to false after every use. + /// + internal bool TemporaryFlag2 { get { return (nodeIndex & TemporaryFlag2Mask) != 0; } set { nodeIndex = (nodeIndex & ~TemporaryFlag2Mask) | (value ? TemporaryFlag2Mask : 0); } } + + /// + /// Position of the node in world space. + /// Note: The position is stored as an Int3, not a Vector3. + /// You can convert an Int3 to a Vector3 using an explicit conversion. + /// var v3 = (Vector3)node.position; + /// + public Int3 position; + + #region Constants + /// Position of the walkable bit. See: + const int FlagsWalkableOffset = 0; + /// Mask of the walkable bit. See: + const uint FlagsWalkableMask = 1 << FlagsWalkableOffset; + + /// Start of hierarchical node index bits. See: + const int FlagsHierarchicalIndexOffset = 1; + /// Mask of hierarchical node index bits. See: + const uint HierarchicalIndexMask = (131072-1) << FlagsHierarchicalIndexOffset; + + /// Start of bits. See: + const int HierarchicalDirtyOffset = 18; + + /// Mask of the bit. See: + const uint HierarchicalDirtyMask = 1 << HierarchicalDirtyOffset; + + /// Start of graph index bits. See: + const int FlagsGraphOffset = 24; + /// Mask of graph index bits. See: + const uint FlagsGraphMask = (256u-1) << FlagsGraphOffset; + + public const uint MaxHierarchicalNodeIndex = HierarchicalIndexMask >> FlagsHierarchicalIndexOffset; + + /// Max number of graphs-1 + public const uint MaxGraphIndex = (FlagsGraphMask-1) >> FlagsGraphOffset; + public const uint InvalidGraphIndex = (FlagsGraphMask) >> FlagsGraphOffset; + + /// Start of tag bits. See: + const int FlagsTagOffset = 19; + /// Max number of tags - 1. Always a power of 2 minus one + public const int MaxTagIndex = 32 - 1; + /// Mask of tag bits. See: + const uint FlagsTagMask = MaxTagIndex << FlagsTagOffset; + + #endregion + + #region Properties + + /// + /// Holds various bitpacked variables. + /// + /// Bit 0: + /// Bits 1 through 17: + /// Bit 18: + /// Bits 19 through 23: + /// Bits 24 through 31: + /// + /// Warning: You should pretty much never modify this property directly. Use the other properties instead. + /// + public uint Flags { + get => flags; + set => flags = value; + } + + /// + /// Additional cost for entering this node. + /// This can be used to make it harder/slower to walk over specific nodes. + /// A cost of 1000 () corresponds to the cost of moving 1 world unit. + /// + /// This cost does not scale with the distance traversed across the node. + /// For recast/navmesh graphs, it is recommended to use costs that scale with the distance instead. + /// For example . + /// + /// See: graph-updates (view in online documentation for working links) + /// + public uint Penalty { +#if !ASTAR_NO_PENALTY + get => penalty; + set { + if (value > 0xFFFFFF) + Debug.LogWarning("Very high penalty applied. Are you sure negative values haven't underflowed?\n" + + "Penalty values this high could with long paths cause overflows and in some cases infinite loops because of that.\n" + + "Penalty value applied: "+value); + penalty = value; + } +#else + get => 0U; + set {} +#endif + } + + /// + /// True if the node is traversable. + /// + /// See: graph-updates (view in online documentation for working links) + /// + public bool Walkable { + [IgnoredByDeepProfiler] + get => (flags & FlagsWalkableMask) != 0; + [IgnoredByDeepProfiler] + set { + flags = flags & ~FlagsWalkableMask | (value ? 1U : 0U) << FlagsWalkableOffset; + AstarPath.active.hierarchicalGraph.AddDirtyNode(this); + } + } + + /// + /// Hierarchical Node that contains this node. + /// The graph is divided into clusters of small hierarchical nodes in which there is a path from every node to every other node. + /// This structure is used to speed up connected component calculations which is used to quickly determine if a node is reachable from another node. + /// + /// See: + /// + /// Warning: This is an internal property and you should most likely not change it. + /// + /// Warning: This is only guaranteed to be valid outside of graph updates, and only for walkable nodes. + /// + internal int HierarchicalNodeIndex { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + get => (int)((flags & HierarchicalIndexMask) >> FlagsHierarchicalIndexOffset); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + set => flags = (flags & ~HierarchicalIndexMask) | (uint)(value << FlagsHierarchicalIndexOffset); + } + + /// Some internal bookkeeping + internal bool IsHierarchicalNodeDirty { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + get => (flags & HierarchicalDirtyMask) != 0; + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + set => flags = flags & ~HierarchicalDirtyMask | (value ? 1U : 0U) << HierarchicalDirtyOffset; + } + + /// + /// Connected component that contains the node. + /// This is visualized in the scene view as differently colored nodes (if the graph coloring mode is set to 'Areas'). + /// Each area represents a set of nodes such that there is no valid path between nodes of different colors. + /// + /// See: https://en.wikipedia.org/wiki/Connected_component_(graph_theory) + /// See: + /// + public uint Area => AstarPath.active.hierarchicalGraph.GetConnectedComponent(HierarchicalNodeIndex); + + /// + /// Graph which contains this node. + /// See: + /// See: + /// + public uint GraphIndex { + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)][IgnoredByDeepProfiler] + get => (flags & FlagsGraphMask) >> FlagsGraphOffset; + set => flags = flags & ~FlagsGraphMask | value << FlagsGraphOffset; + } + + /// + /// Node tag. + /// See: tags (view in online documentation for working links) + /// See: graph-updates (view in online documentation for working links) + /// + public uint Tag { + get => (flags & FlagsTagMask) >> FlagsTagOffset; + set => flags = flags & ~FlagsTagMask | ((value << FlagsTagOffset) & FlagsTagMask); + } + + #endregion + + /// + /// Inform the system that the node's connectivity has changed. + /// This is used for recalculating the connected components of the graph. + /// + /// See: + /// + /// You must call this method if you change the connectivity or walkability of the node without going through the high level methods + /// such as the property or the method. For example if your manually change the array you need to call this method. + /// + public void SetConnectivityDirty () { + AstarPath.active.hierarchicalGraph.AddDirtyNode(this); + } + + /// + /// Calls the delegate with all connections from this node. + /// + /// * + /// + /// node.GetConnections(connectedTo => { + /// Debug.DrawLine((Vector3)node.position, (Vector3)connectedTo.position, Color.red); + /// }); + /// + /// + /// You can add all connected nodes to a list like this + /// + /// var connections = new List(); + /// node.GetConnections(connections.Add); + /// + /// + /// The delegate which will be called once for every connection. + /// A bitmask of which connection types will be included. You may pass any combination of \reflink{Connection.OutgoingConnection} and \reflink{Connection.IncomingConnection}. + /// Defaults to only outgoing connections. Unless one-way links are added to a graph, all connections will typically be bidirectional. + public virtual void GetConnections (System.Action action, int connectionFilter = Connection.OutgoingConnection) { + GetConnections((GraphNode node, ref System.Action action) => action(node), ref action, connectionFilter); + } + + /// + /// Calls the delegate with all connections from or to this node, and passes a custom data value to the delegate. + /// + /// + /// node.GetConnections(connectedTo => { + /// Debug.DrawLine((Vector3)node.position, (Vector3)connectedTo.position, Color.red); + /// }); + /// + /// + /// You can add all connected nodes to a list like this + /// + /// var connections = new List(); + /// node.GetConnections(connections.Add); + /// + /// + /// The delegate which will be called once for every connection. + /// The first parameter to the delegate is the connection and the second parameter is the custom data passed to this method. + /// Custom data which will be passed to the delegate. + /// A bitmask of which connection types will be included. A connection can either be incoming, outgoing, or both (bidirectional). You may pass any combination of \reflink{Connection.OutgoingConnection} and \reflink{Connection.IncomingConnection}. + /// Defaults to only outgoing connections. Unless one-way links are added to a graph, all connections will typically be bidirectional. + public abstract void GetConnections(NodeActionWithData action, ref T data, int connectionFilter = Connection.OutgoingConnection); + + public delegate void NodeActionWithData(GraphNode node, ref T data); + + /// + /// Adds a connection between two nodes. + /// + /// If the nodes already have a connection to each other, that connection will be updated with the new cost. + /// + /// Note that some graphs have a special representation for some connections which is more efficient. + /// For example grid graphs can represent connections to its 8 neighbours more efficiently. + /// But to use that efficient representation you'll need to call instead of this method. + /// + /// This is different from an off-mesh link. An off-mesh link contains more metadata about the connection and is in many cases preferable to use instead of this method. + /// This is a much lower-level method which is used by off-mesh links internally. + /// + /// Movement scripts such as the or may get confused if they try to follow a connection made using this method + /// as it does not contain any information about how to traverse the connection. + /// + /// Internally, both nodes keep track of the connection to the other node, even for a one-way connection. + /// This is done to make sure the connection can always be removed later on, if for example one of the nodes is destroyed. + /// + /// + /// // Connect two nodes + /// var node1 = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.None).node; + /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NearestNodeConstraint.None).node; + /// var cost = (uint)(node2.position - node1.position).costMagnitude; + /// + /// GraphNode.Connect(node1, node2, cost, OffMeshLinks.Directionality.TwoWay); + /// + /// + /// See: + /// See: which is a lower level method. But if you use it, you need to handle invariants yourself. + /// + /// First node to connect. + /// Second node to connect + /// Cost of the connection. A cost of 1000 corresponds approximately to the cost of moving one world unit. See \reflink{Int3.Precision}. + /// Determines if both lhs->rhs and rhs->lhs connections will be created, or if only a connection from lhs->rhs should be created. + public static void Connect (GraphNode lhs, GraphNode rhs, uint cost, OffMeshLinks.Directionality directionality = OffMeshLinks.Directionality.TwoWay) { + if (lhs.Destroyed || rhs.Destroyed) throw new System.ArgumentException("Cannot connect destroyed nodes"); + lhs.AddPartialConnection(rhs, cost, true, directionality == OffMeshLinks.Directionality.TwoWay); + rhs.AddPartialConnection(lhs, cost, directionality == OffMeshLinks.Directionality.TwoWay, true); + } + + /// + /// Removes the connection between two nodes. + /// + /// If no connection exists between the nodes, nothing will be done. + /// + /// This will also handle special connections representations that some node types use. For example grid graphs represent + /// the connections to their 8 grid neighbours differently from other connections. + /// + /// See: + /// + public static void Disconnect (GraphNode lhs, GraphNode rhs) { + lhs.RemovePartialConnection(rhs); + rhs.RemovePartialConnection(lhs); + } + + /// + /// Adds a connection to the given node. + /// + /// Deprecated: Use the static method instead, or if you really need to. + /// + [System.Obsolete("Use the static Connect method instead, or AddPartialConnection if you really need to")] + public void AddConnection(GraphNode node, uint cost) => AddPartialConnection(node, cost, true, true); + + /// + /// Removes a connection to the given node. + /// + /// Deprecated: Use the static method instead, or if you really need to. + /// + + [System.Obsolete("Use the static Disconnect method instead, or RemovePartialConnection if you really need to")] + public void RemoveConnection(GraphNode node) => RemovePartialConnection(node); + + /// + /// Add a connection from this node to the specified node. + /// If the connection already exists, the cost will simply be updated and + /// no extra connection added. + /// + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { + /// // Connect two nodes + /// var node1 = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.None).node; + /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NearestNodeConstraint.None).node; + /// var cost = (uint)(node2.position - node1.position).costMagnitude; + /// node1.AddPartialConnection(node2, cost, true, true); + /// node2.AddPartialConnection(node1, cost, true, true); + /// + /// node1.ContainsOutgoingConnection(node2); // True + /// + /// node1.RemovePartialConnection(node2); + /// node2.RemovePartialConnection(node1); + /// })); + /// + /// + /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. + /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. + /// + /// Some graphs have a special representation for some connections which is more efficient. + /// For example grid graphs can represent connections to its 8 neighbours more efficiently. + /// But to use that efficient representation you'll need to call instead of this method. + /// + public abstract void AddPartialConnection(GraphNode node, uint cost, bool isOutgoing, bool isIncoming); + + /// + /// Removes any connection from this node to the specified node. + /// If no such connection exists, nothing will be done. + /// + /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. + /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. + /// Graphs sometimes use this method directly to improve performance in some situations. + /// + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { + /// // Connect two nodes + /// var node1 = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.None).node; + /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NearestNodeConstraint.None).node; + /// var cost = (uint)(node2.position - node1.position).costMagnitude; + /// node1.AddPartialConnection(node2, cost, true, true); + /// node2.AddPartialConnection(node1, cost, true, true); + /// + /// node1.ContainsOutgoingConnection(node2); // True + /// + /// node1.RemovePartialConnection(node2); + /// node2.RemovePartialConnection(node1); + /// })); + /// + /// + public abstract void RemovePartialConnection(GraphNode node); + + /// + /// Remove all connections between this node and other nodes. + /// + /// Warning: If you pass false to the alsoReverse parameter, you must ensure that you preserve the required invariants of connections. See . + /// + /// if true, neighbours will be requested to remove connections to this node. + public abstract void ClearConnections(bool alsoReverse = true); + + /// + /// True if this node contains a connection to the given node. + /// + /// Deprecated: Use instead + /// + [System.Obsolete("Use ContainsOutgoingConnection instead")] + public bool ContainsConnection(GraphNode node) => ContainsOutgoingConnection(node); + + /// + /// True if this node contains a connection to the given node. + /// + /// This will not return true if another node has a one-way connection to this node. + /// + /// + /// AstarPath.active.AddWorkItem(new AstarWorkItem(ctx => { + /// // Connect two nodes + /// var node1 = AstarPath.active.GetNearest(transform.position, NearestNodeConstraint.None).node; + /// var node2 = AstarPath.active.GetNearest(transform.position + Vector3.right, NearestNodeConstraint.None).node; + /// var cost = (uint)(node2.position - node1.position).costMagnitude; + /// node1.AddPartialConnection(node2, cost, true, true); + /// node2.AddPartialConnection(node1, cost, true, true); + /// + /// node1.ContainsOutgoingConnection(node2); // True + /// + /// node1.RemovePartialConnection(node2); + /// node2.RemovePartialConnection(node1); + /// })); + /// + /// + public virtual bool ContainsOutgoingConnection (GraphNode node) { + // Simple but slow default implementation + bool contains = false; + + GetConnections((GraphNode neighbour, ref bool contains) => { + contains |= neighbour == node; + }, ref contains); + return contains; + } + + /// + /// Add a portal from this node to the specified node. + /// This function should add a portal to the left and right lists which is connecting the two nodes (this and other). + /// + /// Returns: True if the call was deemed successful. False if some unknown case was encountered and no portal could be added. + /// If both calls to node1.GetPortal (node2,...) and node2.GetPortal (node1,...) return false, the funnel modifier will fall back to adding to the path + /// the positions of the node. + /// + /// The default implementation simply returns false. + /// + /// This function may add more than one portal if necessary. + /// + /// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html + /// + /// Deprecated: Use GetPortal(GraphNode, out Vector3, out Vector3) instead + /// + /// The node which is on the other side of the portal (strictly speaking it does not actually have to be on the other side of the portal though). + /// List of portal points on the left side of the funnel + /// List of portal points on the right side of the funnel + /// If this is true, the call was made on a node with the other node as the node before this one in the path. + /// In this case you may choose to do nothing since a similar call will be made to the other node with this node referenced as other (but then with backwards = true). + /// You do not have to care about switching the left and right lists, that is done for you already. + [System.Obsolete("Use GetPortal(GraphNode, out Vector3, out Vector3) instead")] + public bool GetPortal (GraphNode other, List left, List right, bool backwards) { + if (!backwards && GetPortal(other, out var lp, out var rp)) { + if (left != null) { + left.Add(lp); + right.Add(rp); + } + return true; + } else { + return false; + } + } + + /// + /// Add a portal from this node to the specified node. + /// This function returns a portal which connects this node to the given adjacenet node. + /// + /// Returns: True if the call was deemed successful. False if some unknown case was encountered and no portal could be added. + /// If both calls to node1.GetPortal (node2,...) and node2.GetPortal (node1,...) return false, the funnel modifier will fall back to adding to the path + /// the positions of the node. + /// + /// The default implementation simply returns false. + /// + /// See: http://digestingduck.blogspot.se/2010/03/simple-stupid-funnel-algorithm.html + /// + /// The node which is on the other side of the portal. + /// Output left side of the portal. + /// Output right side of the portal. + public virtual bool GetPortal (GraphNode other, out Vector3 left, out Vector3 right) { + left = Vector3.zero; + right = Vector3.zero; + return false; + } + + /// + /// Open the node. + /// Used internally by the A* algorithm. + /// + public abstract void Open(ref Path.SearchContext ctx, uint pathNodeIndex, uint gScore); + + /// + /// Open the node at a specific point. + /// + /// Used internally by the A* algorithm. + /// + /// Used when a path starts inside a node, or when an off-mesh link is used to move to a point inside this node. + /// + public abstract void OpenAtPoint(ref Path.SearchContext ctx, uint pathNodeIndex, Int3 position, uint gScore); + + /// + /// The position of the path node during the search. + /// + /// When an A* search on triangle nodes is carried out, each edge of the node is a separate path node variant. + /// The search will additionally decide where on that edge the path node is located. + /// This is encoded by the fractionAlongEdge variable. + /// This function decodes the position of the path node. + /// + /// Note: Most node types only have a single path node variant and does not use the fractionAlongEdge field. + /// In those cases this function only returns the node unchanged. + /// + public virtual Int3 DecodeVariantPosition(uint pathNodeIndex, uint fractionAlongEdge) => position; + + /// The surface area of the node in square world units + public virtual float SurfaceArea() => 0; + + /// + /// A random point on the surface of the node. + /// For point nodes and other nodes which do not have a surface, this will always return the position of the node. + /// + public virtual Vector3 RandomPointOnSurface () { + return (Vector3)position; + } + + /// Closest point on the surface of this node to the point p + public abstract Vector3 ClosestPointOnNode(Vector3 p); + + /// Checks if point is inside the node when seen from above + public virtual bool ContainsPoint (Int3 point) { + return ContainsPoint((Vector3)point); + } + + /// Checks if point is inside the node when seen from above. + public abstract bool ContainsPoint(Vector3 point); + + /// Checks if point is inside the node in graph space + public abstract bool ContainsPointInGraphSpace(Int3 point); + + /// + /// Hash code used for checking if the gizmos need to be updated. + /// Will change when the gizmos for the node might change. + /// + public virtual int GetGizmoHashCode () { + // Some hashing, the constants are just some arbitrary prime numbers. #flags contains the info for #Tag and #Walkable + return position.GetHashCode() ^ (19 * (int)Penalty) ^ (41 * (int)(flags & ~(HierarchicalIndexMask | HierarchicalDirtyMask))); + } + + /// Serialized the node data to a byte array + public virtual void SerializeNode (GraphSerializationContext ctx) { + // Write basic node data. + ctx.writer.Write(Penalty); + // Save all flags except the hierarchical node index and the dirty bit + ctx.writer.Write(Flags & ~(HierarchicalIndexMask | HierarchicalDirtyMask)); + } + + /// Deserializes the node data from a byte array + public virtual void DeserializeNode (GraphSerializationContext ctx) { + Penalty = ctx.reader.ReadUInt32(); + // Load all flags except the hierarchical node index and the dirty bit (they aren't saved in newer versions and older data should just be cleared) + // Note that the dirty bit needs to be preserved here because it may already be set (due to the node being created) + Flags = (ctx.reader.ReadUInt32() & ~(HierarchicalIndexMask | HierarchicalDirtyMask)) | (Flags & (HierarchicalIndexMask | HierarchicalDirtyMask)); + + // Set the correct graph index (which might have changed, e.g if loading additively) + GraphIndex = ctx.graphIndex; + } + + /// + /// Used to serialize references to other nodes e.g connections. + /// Use the GraphSerializationContext.GetNodeIdentifier and + /// GraphSerializationContext.GetNodeFromIdentifier methods + /// for serialization and deserialization respectively. + /// + /// Nodes must override this method and serialize their connections. + /// Graph generators do not need to call this method, it will be called automatically on all + /// nodes at the correct time by the serializer. + /// + public virtual void SerializeReferences (GraphSerializationContext ctx) { + } + + /// + /// Used to deserialize references to other nodes e.g connections. + /// Use the GraphSerializationContext.GetNodeIdentifier and + /// GraphSerializationContext.GetNodeFromIdentifier methods + /// for serialization and deserialization respectively. + /// + /// Nodes must override this method and serialize their connections. + /// Graph generators do not need to call this method, it will be called automatically on all + /// nodes at the correct time by the serializer. + /// + public virtual void DeserializeReferences (GraphSerializationContext ctx) { + } + } + + public abstract class MeshNode : GraphNode { + /// + /// All connections from this node. + /// See: + /// See: + /// + /// Note: If you modify this array or the contents of it you must call . + /// + /// May be null if the node has no connections. + /// + public Connection[] connections; + + /// Get a vertex of this node. + /// vertex index. Must be between 0 and #GetVertexCount (exclusive). Typically between 0 and 3. + public abstract Int3 GetVertex(int i); + + /// + /// Number of corner vertices that this node has. + /// For example for a triangle node this will return 3. + /// + public abstract int GetVertexCount(); + + /// + /// Closest point on the surface of this node when seen from above. + /// This is usually very similar to but when the node is in a slope this can be significantly different. + /// [Open online documentation to see images] + /// When the blue point in the above image is used as an argument this method call will return the green point while the method will return the red point. + /// + public abstract Vector3 ClosestPointOnNodeXZ(Vector3 p); + + public override void ClearConnections (bool alsoReverse = true) { + // Remove all connections to this node from our neighbours + if (alsoReverse && connections != null) { + for (int i = 0; i < connections.Length; i++) { + connections[i].node.RemovePartialConnection(this); + } + } + + ArrayPool.Release(ref connections, true); + AstarPath.active.hierarchicalGraph.AddDirtyNode(this); + } + + public override void GetConnections(NodeActionWithData action, ref T data, int connectionFilter = Connection.OutgoingConnection) { + if (connections == null) return; + for (int i = 0; i < connections.Length; i++) if ((connections[i].shapeEdgeInfo & connectionFilter) != 0) action(connections[i].node, ref data); + } + + public override bool ContainsOutgoingConnection (GraphNode node) { + if (connections != null) for (int i = 0; i < connections.Length; i++) if (connections[i].node == node && connections[i].isOutgoing) return true; + return false; + } + + public override void AddPartialConnection (GraphNode node, uint cost, bool isOutgoing, bool isIncoming) { + AddPartialConnection(node, cost, Connection.PackShapeEdgeInfo(isOutgoing, isIncoming)); + } + + /// + /// Add a connection from this node to the specified node. + /// + /// If the connection already exists, the cost will simply be updated and + /// no extra connection added. + /// + /// Warning: In almost all cases, you should be using the method instead. If you use this method, you must ensure that you preserve the required invariants of connections. + /// Notably: If a connection exists from A to B, then there must also exist a connection from B to A. And their outgoing and incoming connection flags must be set symmetrically. + /// + /// Node to add a connection to + /// Cost of traversing the connection. A cost of 1000 corresponds approximately to the cost of moving 1 world unit. + /// Info about how the edge is which edge on the shape of this node to use or \reflink{Connection.NoSharedEdge} if no edge is used. See \reflink{Connection.PackShapeEdgeInfo(byte,byte,bool,bool,bool)}. + public void AddPartialConnection (GraphNode node, uint cost, byte shapeEdgeInfo) { + if (node == null) throw new System.ArgumentNullException(); + + // Check if we already have a connection to the node + if (connections != null) { + for (int i = 0; i < connections.Length; i++) { + if (connections[i].node == node) { + // Just update the cost for the existing connection + connections[i].cost = cost; + connections[i].shapeEdgeInfo = shapeEdgeInfo; + return; + } + } + } + + // Create new arrays which include the new connection + int connLength = connections != null ? connections.Length : 0; + + var newconns = ArrayPool.ClaimWithExactLength(connLength+1); + for (int i = 0; i < connLength; i++) { + newconns[i] = connections[i]; + } + + newconns[connLength] = new Connection(node, cost, shapeEdgeInfo); + + if (connections != null) { + ArrayPool.Release(ref connections, true); + } + + connections = newconns; + AstarPath.active.hierarchicalGraph.AddDirtyNode(this); + } + + public override void RemovePartialConnection (GraphNode node) { + if (connections == null) return; + + // Iterate through all connections and check if there are any to the node + for (int i = 0; i < connections.Length; i++) { + if (connections[i].node == node) { + // Create new arrays which have the specified node removed + int connLength = connections.Length; + + var newconns = ArrayPool.ClaimWithExactLength(connLength-1); + for (int j = 0; j < i; j++) { + newconns[j] = connections[j]; + } + for (int j = i+1; j < connLength; j++) { + newconns[j-1] = connections[j]; + } + + if (connections != null) { + ArrayPool.Release(ref connections, true); + } + + connections = newconns; + AstarPath.active.hierarchicalGraph.AddDirtyNode(this); + return; + } + } + } + + public override int GetGizmoHashCode () { + var hash = base.GetGizmoHashCode(); + + if (connections != null) { + for (int i = 0; i < connections.Length; i++) { + hash ^= 17 * connections[i].GetHashCode(); + } + } + return hash; + } + + public override void SerializeReferences(GraphSerializationContext ctx) => ctx.SerializeConnections(connections, true); + + public override void DeserializeReferences(GraphSerializationContext ctx) => connections = ctx.DeserializeConnections(true); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs.meta new file mode 100644 index 0000000..0ecafaf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Nodes/GraphNode.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a660e0d66fb3b49d19d438de51ac6744 +timeCreated: 1486987492 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding.meta new file mode 100644 index 0000000..ddf8b4f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbaf930b256c7634db0b555f62098d79 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs.meta new file mode 100644 index 0000000..cad1e5f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/BlockableChannel.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6a8344fd0d1c453cbaf5c5eb8f55ca5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs new file mode 100644 index 0000000..bf98aee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs @@ -0,0 +1,320 @@ +using Unity.Collections; +using UnityEngine; +using Unity.Mathematics; +using Unity.Jobs; +using UnityEngine.Profiling; +using Pathfinding.Collections; + +namespace Pathfinding { + using Pathfinding.Util; + using Pathfinding.Jobs; + using UnityEngine.Assertions; + + internal class GlobalNodeStorage { + readonly AstarPath astar; + Unity.Jobs.JobHandle lastAllocationJob; + + /// + /// Holds the next node index which has not been used by any previous node. + /// See: + /// + public uint nextNodeIndex = 1; + + /// + /// The number of nodes for which path node data has been reserved. + /// Will be at least as high as + /// + public uint reservedPathNodeData = 0; + + /// Number of nodes that have been destroyed in total + public uint destroyedNodesVersion { get; private set; } + + const int InitialTemporaryNodes = 256; + + int temporaryNodeCount = InitialTemporaryNodes; + + /// + /// Holds indices for nodes that have been destroyed. + /// To avoid trashing a lot of memory structures when nodes are + /// frequently deleted and created, node indices are reused. + /// + /// There's one pool for each possible number of node variants (1, 2 and 3). + /// + readonly IndexedStack[] nodeIndexPools = new [] { + new IndexedStack(), + new IndexedStack(), + new IndexedStack(), + }; + + public PathfindingThreadData[] pathfindingThreadData = new PathfindingThreadData[0]; + + /// Maps from NodeIndex to node + GraphNode[] nodes = new GraphNode[0]; + + public GlobalNodeStorage (AstarPath astar) { + this.astar = astar; + } + + public GraphNode GetNode(uint nodeIndex) => nodes[nodeIndex]; + +#if UNITY_EDITOR + public struct DebugPathNode { + public uint g; + public uint h; + public uint parentIndex; + public ushort pathID; + public byte fractionAlongEdge; + } +#endif + + public struct PathfindingThreadData { + public UnsafeSpan pathNodes; +#if UNITY_EDITOR + public UnsafeSpan debugPathNodes; +#endif + } + + class IndexedStack { + T[] buffer = new T[4]; + + public int Count { get; private set; } + + public void Push (T v) { + if (Count == buffer.Length) { + Util.Memory.Realloc(ref buffer, buffer.Length * 2); + } + + buffer[Count] = v; + Count++; + } + + public void Clear () { + Count = 0; + } + + public T Pop () { + Count--; + return buffer[Count]; + } + + /// Pop the last N elements and store them in the buffer. The items will be in insertion order. + public void PopMany (T[] resultBuffer, int popCount) { + if (popCount > Count) throw new System.IndexOutOfRangeException(); + System.Array.Copy(buffer, Count - popCount, resultBuffer, 0, popCount); + Count -= popCount; + } + } + + void DisposeThreadData () { + if (pathfindingThreadData.Length > 0) { + for (int i = 0; i < pathfindingThreadData.Length; i++) { + unsafe { + pathfindingThreadData[i].pathNodes.Free(Allocator.Persistent); +#if UNITY_EDITOR + pathfindingThreadData[i].debugPathNodes.Free(Allocator.Persistent); +#endif + } + } + pathfindingThreadData = new PathfindingThreadData[0]; + } + } + + public void SetThreadCount (int threadCount) { + if (pathfindingThreadData.Length != threadCount) { + DisposeThreadData(); + pathfindingThreadData = new PathfindingThreadData[threadCount]; + + for (int i = 0; i < pathfindingThreadData.Length; i++) { + // Allocate per-thread data. + // We allocate using UnsafeSpans because this code may run inside jobs, and Unity does not allow us to allocate NativeArray memory + // using the persistent allocator inside jobs. + pathfindingThreadData[i].pathNodes = new UnsafeSpan(Allocator.Persistent, (int)reservedPathNodeData + temporaryNodeCount); +#if UNITY_EDITOR + pathfindingThreadData[i].debugPathNodes = new UnsafeSpan(Allocator.Persistent, (int)reservedPathNodeData); + pathfindingThreadData[i].debugPathNodes.FillZeros(); +#endif + var pnodes = pathfindingThreadData[i].pathNodes; + pnodes.Fill(PathNode.Default); + } + } + } + + /// + /// Grows temporary node storage for the given thread. + /// + /// This can happen if a path traverses a lot of off-mesh links, or if it is a multi-target path with a lot of targets. + /// + /// If enough nodes are created that we have a to grow the regular node storage, then the number of temporary nodes will grow to the same value on all threads. + /// + public void GrowTemporaryNodeStorage (int threadID) { + var threadData = pathfindingThreadData[threadID]; + int currentTempNodeCount = threadData.pathNodes.Length - (int)reservedPathNodeData; + Assert.IsTrue(currentTempNodeCount >= 0 && currentTempNodeCount <= temporaryNodeCount); + // We don't want to grow this often, since we will have to re-allocate the storage for *ALL* nodes, + // not just the temporary nodes. So we use a high growth factor. + temporaryNodeCount = System.Math.Max(temporaryNodeCount, currentTempNodeCount * 8); + var prevLength = threadData.pathNodes.Length; + threadData.pathNodes = threadData.pathNodes.Reallocate(Allocator.Persistent, (int)reservedPathNodeData + temporaryNodeCount); + // Fill the new nodes with default values + threadData.pathNodes.Slice(prevLength).Fill(PathNode.Default); + pathfindingThreadData[threadID] = threadData; + } + + /// + /// Initializes temporary path data for a node. + /// Warning: This method should not be called directly. + /// + /// See: + /// + public void InitializeNode (GraphNode node) { + var variants = node.PathNodeVariants; + + // Graphs may initialize nodes from different threads, + // so we need to lock. + // Luckily, uncontested locks are really really cheap in C# + lock (this) { + if (nodeIndexPools[variants-1].Count > 0) { + node.NodeIndex = nodeIndexPools[variants-1].Pop(); + } else { + // Highest node index in the new list of nodes + node.NodeIndex = nextNodeIndex; + nextNodeIndex += (uint)variants; + ReserveNodeIndices(nextNodeIndex); + } + + for (int i = 0; i < variants; i++) { + nodes[node.NodeIndex + i] = node; + } + + astar.hierarchicalGraph.OnCreatedNode(node); + } + } + + /// + /// Reserves space for global node data. + /// + /// Warning: Must be called only when a lock is held on this object. + /// + void ReserveNodeIndices (uint nextNodeIndex) { + if (nextNodeIndex <= reservedPathNodeData) return; + + reservedPathNodeData = math.ceilpow2(nextNodeIndex); + + // Allocate more internal pathfinding data for the new nodes + astar.hierarchicalGraph.ReserveNodeIndices(reservedPathNodeData); + var threadCount = pathfindingThreadData.Length; + DisposeThreadData(); + SetThreadCount(threadCount); + Memory.Realloc(ref nodes, (int)reservedPathNodeData); + } + + /// + /// Destroyes the given node. + /// This is to be called after the node has been disconnected from the graph so that it cannot be reached from any other nodes. + /// It should only be called during graph updates, that is when the pathfinding threads are either not running or paused. + /// + /// Warning: This method should not be called by user code. It is used internally by the system. + /// + public void DestroyNode (GraphNode node) { + var nodeIndex = node.NodeIndex; + if (nodeIndex == GraphNode.DestroyedNodeIndex) return; + + destroyedNodesVersion++; + int variants = node.PathNodeVariants; + nodeIndexPools[variants - 1].Push(nodeIndex); + for (int i = 0; i < variants; i++) { + nodes[nodeIndex + i] = null; + } + + for (int t = 0; t < pathfindingThreadData.Length; t++) { + var threadData = pathfindingThreadData[t]; + for (uint i = 0; i < variants; i++) { + // This is not required for pathfinding, but not clearing it may confuse gizmo drawing for a fraction of a second. + // Especially when 'Show Search Tree' is enabled + threadData.pathNodes[nodeIndex + i].pathID = 0; +#if UNITY_EDITOR + threadData.debugPathNodes[nodeIndex + i].pathID = 0; +#endif + } + } + + astar.hierarchicalGraph.OnDestroyedNode(node); + } + + public void OnDisable () { + lastAllocationJob.Complete(); + nextNodeIndex = 1; + reservedPathNodeData = 0; + for (int i = 0; i < nodeIndexPools.Length; i++) nodeIndexPools[i].Clear(); + nodes = new GraphNode[0]; + DisposeThreadData(); + } + + struct JobAllocateNodes : IJob where T : GraphNode { + public T[] result; + public int count; + public GlobalNodeStorage nodeStorage; + public uint variantsPerNode; + public System.Func createNode; + + public bool allowBoundsChecks => false; + + public void Execute () { + Profiler.BeginSample("Allocating nodes"); + var hierarchicalGraph = nodeStorage.astar.hierarchicalGraph; + + lock (nodeStorage) { + var pool = nodeStorage.nodeIndexPools[variantsPerNode-1]; + uint nextNodeIndex = nodeStorage.nextNodeIndex; + + // Allocate the actual nodes + for (uint i = 0; i < count; i++) { + var node = result[i] = createNode(); + + // Get a new node index. Re-use one from a previously destroyed node if possible + if (pool.Count > 0) { + node.NodeIndex = pool.Pop(); + } else { + node.NodeIndex = nextNodeIndex; + nextNodeIndex += variantsPerNode; + } + } + + // Allocate more internal pathfinding data for the new nodes + nodeStorage.ReserveNodeIndices(nextNodeIndex); + + // Mark the node indices as used + nodeStorage.nextNodeIndex = nextNodeIndex; + + for (int i = 0; i < count; i++) { + var node = result[i]; + hierarchicalGraph.AddDirtyNode(node); + nodeStorage.nodes[node.NodeIndex] = node; + } + } + Profiler.EndSample(); + } + } + + public Unity.Jobs.JobHandle AllocateNodesJob(T[] result, int count, System.Func createNode, uint variantsPerNode) where T : GraphNode { + // Get all node indices that we are going to recycle and store them in a new buffer. + // It's best to store them in a new buffer to avoid multithreading issues. + UnityEngine.Assertions.Assert.IsTrue(variantsPerNode > 0 && variantsPerNode <= 3); + + // It may be tempting to use a parallel job for this + // but it seems like allocation (new) in C# uses some kind of locking. + // Therefore it is not faster (it may even be slower) to try to allocate the nodes in multiple threads in parallel. + // The job will use locking internally for safety, but it's still nice to set appropriate dependencies, to avoid lots of worker threads + // just stalling because they are waiting for a lock, in case this method is called multiple times in parallel. + lastAllocationJob = new JobAllocateNodes { + result = result, + count = count, + nodeStorage = this, + variantsPerNode = variantsPerNode, + createNode = createNode, + }.ScheduleManaged(lastAllocationJob); + + return lastAllocationJob; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs.meta new file mode 100644 index 0000000..c348e27 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GlobalNodeStorage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a659ff4e5955406479ba3f895ac8fca5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs new file mode 100644 index 0000000..bc1c42a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs @@ -0,0 +1,292 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Pathfinding { + using Pathfinding.Jobs; + using Pathfinding.Pooling; + using Unity.Jobs; + using Unity.Profiling; + using UnityEngine.Assertions; + using UnityEngine.Profiling; + + /// + /// Promise representing a graph update. + /// + /// This is used internally by the system to represent a graph update. + /// Generally you shouldn't need to care about it, unless you are implementing your own graph type. + /// + public interface IGraphUpdatePromise { + /// + /// Returns the progress of the update. + /// + /// This should be a value between 0 and 1. + /// + float Progress => 0.0f; + + /// + /// Coroutine to prepare an update asynchronously. + /// + /// If a JobHandle is returned, it will be awaited before the coroutine is ticked again, and before the method is called. + /// + /// After this coroutine has finished, the method will be called. + /// + /// Note: No changes must be made to the graph in this method. Those should only be done in the method. + /// + /// May return null if no async work is required. + /// + IEnumerator Prepare() => null; + + /// + /// Applies the update in a single atomic update. + /// + /// It is done as a single atomic update (from the main thread's perspective) to ensure + /// that even if one does an async scan or update, the graph will always be in a valid state. + /// This guarantees that things like GetNearest will still work during an async scan. + /// + /// Warning: Must only be called after the method has finished. + /// + // TODO: Pass in a JobHandle and allow returning a JobHandle? + void Apply(IGraphUpdateContext context); + } + + /// + /// Helper functions for graph updates. + /// + /// A context is passed to graphs when they are updated, and to work items when they are executed. + /// The interface inherits from this interface. + /// + public interface IGraphUpdateContext { + /// + /// Mark a particular region of the world as having been changed. + /// + /// This should be used whenever graphs are changed. + /// + /// This is used to recalculate off-mesh links that touch these bounds, and it will also ensure events are callled. + /// + /// The bounding box should cover the surface of all nodes that have been updated. + /// It is fine to use a larger bounding box than necessary (even an infinite one), though this may be slower, since more off-mesh links need to be recalculated. + /// You can even use an infinitely large bounding box if you don't want to bother calculating a more accurate one. + /// You can also call this multiple times to dirty multiple bounding boxes. + /// + void DirtyBounds(Bounds bounds); + } + + public class GraphUpdateProcessor { + /// Holds graphs that can be updated + readonly AstarPath astar; + + /// Used for IsAnyGraphUpdateInProgress + bool anyGraphUpdateInProgress; + + /// + /// Queue containing all waiting graph update queries. Add to this queue by using \link AddToQueue \endlink. + /// See: AddToQueue + /// + readonly Queue graphUpdateQueue = new Queue(); + readonly List<(IGraphUpdatePromise, IEnumerator)> pendingPromises = new List<(IGraphUpdatePromise, IEnumerator)>(); + readonly List pendingGraphUpdates = new List(); + + /// Returns if any graph updates are waiting to be applied + public bool IsAnyGraphUpdateQueued { get { return graphUpdateQueue.Count > 0; } } + + /// Returns if any graph updates are in progress + public bool IsAnyGraphUpdateInProgress { get { return anyGraphUpdateInProgress; } } + + public GraphUpdateProcessor (AstarPath astar) { + this.astar = astar; + } + + /// Work item which can be used to apply all queued updates + public AstarWorkItem GetWorkItem () { + return new AstarWorkItem(QueueGraphUpdatesInternal, ProcessGraphUpdates); + } + + /// + /// Update all graphs using the GraphUpdateObject. + /// This can be used to, e.g make all nodes in an area unwalkable, or set them to a higher penalty. + /// The graphs will be updated as soon as possible (with respect to AstarPath.batchGraphUpdates) + /// + /// See: FlushGraphUpdates + /// + public void AddToQueue (GraphUpdateObject ob) { + // Put the GUO in the queue + graphUpdateQueue.Enqueue(ob); + } + + /// + /// Discards all queued graph updates. + /// + /// Graph updates that are already in progress will not be discarded. + /// + public void DiscardQueued () { + while (graphUpdateQueue.Count > 0) { + graphUpdateQueue.Dequeue().internalStage = GraphUpdateObject.STAGE_ABORTED; + } + } + + /// Schedules graph updates internally + void QueueGraphUpdatesInternal (IWorkItemContext context) { + Assert.IsTrue(pendingGraphUpdates.Count == 0); + while (graphUpdateQueue.Count > 0) { + var ob = graphUpdateQueue.Dequeue(); + pendingGraphUpdates.Add(ob); + context.VisualizeOriginalGraphUpdateBounds(ob.bounds); + if (ob.internalStage != GraphUpdateObject.STAGE_PENDING) { + Debug.LogError("Expected remaining graph update to be pending"); + continue; + } + } + + foreach (IUpdatableGraph g in astar.data.GetUpdateableGraphs()) { + NavGraph gr = g as NavGraph; + var updates = ListPool.Claim(); + for (int i = 0; i < pendingGraphUpdates.Count; i++) { + GraphUpdateObject ob = pendingGraphUpdates[i]; + if (ob.graphMask.Contains(gr)) { + updates.Add(ob); + } + } + if (updates.Count > 0) { + var promise = g.ScheduleGraphUpdates(updates); + if (promise != null) { + var it = promise.Prepare(); + pendingPromises.Add((promise, it)); + } else { + ListPool.Release(ref updates); + } + } else { + ListPool.Release(ref updates); + } + } + + context.PreUpdate(); + anyGraphUpdateInProgress = true; + } + + static readonly ProfilerMarker MarkerSleep = new ProfilerMarker(ProfilerCategory.Loading, "Sleep"); + static readonly ProfilerMarker MarkerCalculate = new ProfilerMarker("Calculating Graph Update"); + static readonly ProfilerMarker MarkerApply = new ProfilerMarker("Applying Graph Update"); + + /// + /// Updates graphs. + /// Will do some graph updates, possibly signal another thread to do them. + /// Will only process graph updates added by QueueGraphUpdatesInternal + /// + /// Returns: True if all graph updates have been done and pathfinding (or other tasks) may resume. + /// False if there are still graph updates being processed or waiting in the queue. + /// + /// Helper methods for the work items. + /// If true, all graph updates will be processed before this function returns. The return value + /// will be True. + bool ProcessGraphUpdates (IWorkItemContext context, bool force) { + Assert.IsTrue(anyGraphUpdateInProgress); + + if (pendingPromises.Count > 0) { + try { + if (ProcessGraphUpdatePromises(pendingPromises, context, force ? TimeSlice.Infinite : TimeSlice.MillisFromNow(2)) != -1) { + return false; + } + } catch (System.Exception e) { + Debug.LogError(new System.Exception("Error while updating graphs", e)); + return false; + } + + pendingPromises.Clear(); + } + + anyGraphUpdateInProgress = false; + for (int i = 0; i < pendingGraphUpdates.Count; i++) { + pendingGraphUpdates[i].internalStage = GraphUpdateObject.STAGE_APPLIED; + } + pendingGraphUpdates.Clear(); + return true; + } + + public static int ProcessGraphUpdatePromises (List<(IGraphUpdatePromise, IEnumerator)> promises, IGraphUpdateContext context, TimeSlice timeSlice) { + var r = PrepareGraphUpdatePromises(promises, timeSlice); + if (r == -1) { + ApplyGraphUpdatePromises(promises, context); + } + return r; + } + + public static int PrepareGraphUpdatePromises (List<(IGraphUpdatePromise, IEnumerator)> promises, TimeSlice timeSlice) { + TimeSlice idleTimeSlice = default; + bool async = !timeSlice.isInfinite; + + while (true) { + int firstNonFinished = -1; + bool anyMainThreadProgress = false; + for (int i = 0; i < promises.Count; i++) { + var(promise, it) = promises[i]; + if (it == null) continue; + if (async) { + if (it.Current.IsCompleted) { + // If the job completed (maybe because a real job completed, or because the iterator returned a dummy JobHandle), then it must be doing some work on the main thread. + // In that case, we shouldn't sleep or yield while waiting. + anyMainThreadProgress = true; + it.Current.Complete(); + } else { + if (firstNonFinished == -1) firstNonFinished = i; + continue; + } + } else { + it.Current.Complete(); + } + + MarkerCalculate.Begin(); + try { + if (it.MoveNext()) { + if (firstNonFinished == -1) firstNonFinished = i; + } else promises[i] = (promise, null); + } catch { + MarkerCalculate.End(); + promises[i] = (null, null); + throw; + } + MarkerCalculate.End(); + } + + if (firstNonFinished == -1) { + break; + } else if (async) { + if (timeSlice.expired) { + return firstNonFinished; + } else if (anyMainThreadProgress) { + // Reset the idle time slice if we got something done on the main thread. + // This allows us to wait on more very short jobs. + idleTimeSlice = TimeSlice.MillisFromNow(0.1f); + } else if (idleTimeSlice.expired) { + return firstNonFinished; + } else { + // Allow waiting for a short amount of time to allow very short running + // jobs in graph updates to complete without having to wait until the next frame. + // While waiting we release our thread's time slice to make sure other threads get priority. + if (!anyMainThreadProgress) { + MarkerSleep.Begin(); + System.Threading.Thread.Yield(); + MarkerSleep.End(); + } + } + } + } + + return -1; + } + + public static void ApplyGraphUpdatePromises (List<(IGraphUpdatePromise, IEnumerator)> promises, IGraphUpdateContext context) { + for (int i = 0; i < promises.Count; i++) { + var(promise, it) = promises[i]; + Assert.IsNull(it); + if (promise != null) { + MarkerApply.Begin(); + try { + promise.Apply(context); + } finally { + MarkerApply.End(); + } + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs.meta new file mode 100644 index 0000000..bd24da6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/GraphUpdateProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b1798d8e7c7d54972ae8522558cbd27c +timeCreated: 1443114816 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs new file mode 100644 index 0000000..79f4a7f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs @@ -0,0 +1,102 @@ +using Unity.Mathematics; +using Unity.Burst; +using Pathfinding.Util; +using Pathfinding.Graphs.Util; +using Pathfinding.Collections; + +namespace Pathfinding { + /// + /// Calculates an estimated cost from the specified point to the target. + /// + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + [BurstCompile] + public readonly struct HeuristicObjective { + readonly int3 mn; + readonly int3 mx; + readonly Heuristic heuristic; + readonly float heuristicScale; + readonly UnsafeSpan euclideanEmbeddingCosts; + readonly uint euclideanEmbeddingPivots; + readonly uint targetNodeIndex; + + public bool hasHeuristic => heuristic != Heuristic.None; + + public HeuristicObjective (int3 point, Heuristic heuristic, float heuristicScale) { + this.mn = this.mx = point; + this.heuristic = heuristic; + this.heuristicScale = heuristicScale; + this.euclideanEmbeddingCosts = default; + this.euclideanEmbeddingPivots = 0; + this.targetNodeIndex = 0; + } + + public HeuristicObjective (int3 point, Heuristic heuristic, float heuristicScale, uint targetNodeIndex, EuclideanEmbedding euclideanEmbedding) { + this.mn = this.mx = point; + this.heuristic = heuristic; + this.heuristicScale = heuristicScale; + // The euclidean embedding costs are guaranteed to be valid for the duration of the pathfinding request. + // We cannot perform checks here, because we may be running in another thread, and Unity does not like that. + this.euclideanEmbeddingCosts = euclideanEmbedding != null? euclideanEmbedding.costs.AsUnsafeSpanNoChecks() : default; + this.euclideanEmbeddingPivots = euclideanEmbedding != null ? (uint)euclideanEmbedding.pivotCount : 0; + this.targetNodeIndex = targetNodeIndex; + } + + public HeuristicObjective (int3 mn, int3 mx, Heuristic heuristic, float heuristicScale, uint targetNodeIndex, EuclideanEmbedding euclideanEmbedding) { + this.mn = mn; + this.mx = mx; + this.heuristic = heuristic; + this.heuristicScale = heuristicScale; + // The euclidean embedding costs are guaranteed to be valid for the duration of the pathfinding request. + // We cannot perform checks here, because we may be running in another thread, and Unity does not like that. + this.euclideanEmbeddingCosts = euclideanEmbedding != null? euclideanEmbedding.costs.AsUnsafeSpanNoChecks() : default; + this.euclideanEmbeddingPivots = euclideanEmbedding != null ? (uint)euclideanEmbedding.pivotCount : 0; + this.targetNodeIndex = targetNodeIndex; + } + + public int Calculate (int3 point, uint nodeIndex) { + return Calculate(in this, ref point, nodeIndex); + } + + [BurstCompile] + public static int Calculate (in HeuristicObjective objective, ref int3 point, uint nodeIndex) { + var closest = math.clamp(point, objective.mn, objective.mx); + var diff = point - closest; + + int h; + switch (objective.heuristic) { + case Heuristic.Euclidean: + h = (int)(math.length((float3)diff) * objective.heuristicScale); + break; + case Heuristic.Manhattan: + h = (int)(math.csum(math.abs(diff)) * objective.heuristicScale); + break; + case Heuristic.DiagonalManhattan: + // Octile distance extended to 3D + diff = math.abs(diff); + var a = diff.x; + var b = diff.y; + var c = diff.z; + // Sort the values so that a <= b <= c + if (a > b) Memory.Swap(ref a, ref b); + if (b > c) Memory.Swap(ref b, ref c); + if (a > b) Memory.Swap(ref a, ref b); + + // This is the same as the Manhattan distance, but with a different weight for the diagonal moves. + const float SQRT_3 = 1.7321f; + const float SQRT_2 = 1.4142f; + h = (int)(objective.heuristicScale * (SQRT_3 * a + SQRT_2 * (b-a) + (c-b-a))); + break; + case Heuristic.None: + default: + h = 0; + break; + } + + if (objective.euclideanEmbeddingPivots > 0) { + h = math.max(h, (int)EuclideanEmbedding.GetHeuristic(objective.euclideanEmbeddingCosts, objective.euclideanEmbeddingPivots, nodeIndex, objective.targetNodeIndex)); + } + return h; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs.meta new file mode 100644 index 0000000..df7cd07 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HeuristicObjective.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f7f83de4e1bae824c801f70be70d1d5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs new file mode 100644 index 0000000..91adfe9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs @@ -0,0 +1,626 @@ +// #define CHECK_INVARIANTS +using System.Collections.Generic; +using Pathfinding.Util; +using UnityEngine; +using Unity.Jobs; +using UnityEngine.Profiling; +using UnityEngine.Assertions; + +namespace Pathfinding { + using System.Runtime.InteropServices; + using Pathfinding.Drawing; + using Pathfinding.Jobs; + using Pathfinding.Collections; + using Pathfinding.Sync; + using Pathfinding.Pooling; + using Unity.Collections; + using Unity.Collections.LowLevel.Unsafe; + + /// + /// Holds a hierarchical graph to speed up certain pathfinding queries. + /// + /// A common type of query that needs to be very fast is on the form 'is this node reachable from this other node'. + /// This is for example used when picking the end node of a path. The end node is determined as the closest node to the end point + /// that can be reached from the start node. + /// + /// This data structure's primary purpose is to keep track of which connected component each node is contained in, in order to make such queries fast. + /// + /// See: https://en.wikipedia.org/wiki/Connected_component_(graph_theory) + /// + /// A connected component is a set of nodes such that there is a valid path between every pair of nodes in that set. + /// Thus the query above can simply be answered by checking if they are in the same connected component. + /// The connected component is exposed on nodes as the property and on this class using the method. + /// + /// Note: This class does not calculate strictly connected components. In case of one-way connections, it will still consider the nodes to be in the same connected component. + /// + /// In the image below (showing a 200x200 grid graph) each connected component is colored using a separate color. + /// The actual color doesn't signify anything in particular however, only that they are different. + /// [Open online documentation to see images] + /// + /// Prior to version 4.2 the connected components were just a number stored on each node, and when a graph was updated + /// the connected components were completely recalculated. This can be done relatively efficiently using a flood filling + /// algorithm (see https://en.wikipedia.org/wiki/Flood_fill) however it still requires a pass through every single node + /// which can be quite costly on larger graphs. + /// + /// This class instead builds a much smaller graph that still respects the same connectivity as the original graph. + /// Each node in this hierarchical graph represents a larger number of real nodes that are one single connected component. + /// Take a look at the image below for an example. In the image each color is a separate hierarchical node, and the black connections go between the center of each hierarchical node. + /// + /// [Open online documentation to see images] + /// + /// With the hierarchical graph, the connected components can be calculated by flood filling the hierarchical graph instead of the real graph. + /// Then when we need to know which connected component a node belongs to, we look up the connected component of the hierarchical node the node belongs to. + /// + /// The benefit is not immediately obvious. The above is just a bit more complicated way to accomplish the same thing. However the real benefit comes when updating the graph. + /// When the graph is updated, all hierarchical nodes which contain any node that was affected by the update is removed completely and then once all have been removed new hierarchical nodes are recalculated in their place. + /// Once this is done the connected components of the whole graph can be updated by flood filling only the hierarchical graph. Since the hierarchical graph is vastly smaller than the real graph, this is significantly faster. + /// + /// [Open online documentation to see videos] + /// + /// So finally using all of this, the connected components of the graph can be recalculated very quickly as the graph is updated. + /// The effect of this grows larger the larger the graph is, and the smaller the graph update is. Making a small update to a 1000x1000 grid graph is on the order of 40 times faster with these optimizations. + /// When scanning a graph or making updates to the whole graph at the same time there is however no speed boost. In fact due to the extra complexity it is a bit slower, however after profiling the extra time seems to be mostly insignificant compared to the rest of the cost of scanning the graph. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// See: + /// See: + /// + public class HierarchicalGraph { + const int Tiling = 16; + const int MaxChildrenPerNode = Tiling * Tiling; + const int MinChildrenPerNode = MaxChildrenPerNode/2; + + GlobalNodeStorage nodeStorage; + internal List[] children; + internal NativeList connectionAllocations; + internal SlabAllocator connectionAllocator; + NativeList dirtiedHierarchicalNodes; + int[] areas; + byte[] dirty; + int[] versions; + bool anyNodesDestroyed = false; + internal NativeList bounds; + /// Holds areas.Length as a burst-accessible reference + NativeReference numHierarchicalNodes; + internal GCHandle gcHandle; + + public int version { get; private set; } + public NavmeshEdges navmeshEdges; + + Queue temporaryQueue = new Queue(); + List currentConnections = new List(); + Stack temporaryStack = new Stack(); + + HierarchicalBitset dirtyNodes; + + CircularBuffer freeNodeIndices; + + int gizmoVersion = 0; + + RWLock rwLock = new RWLock(); + + /// + /// Disposes of all unmanaged data and clears managed data. + /// + /// If you want to use this instance again, you must call . + /// + internal void OnDisable () { + rwLock.WriteSync().Unlock(); + navmeshEdges.Dispose(); + if (gcHandle.IsAllocated) gcHandle.Free(); + if (connectionAllocator.IsCreated) { + numHierarchicalNodes.Dispose(); + connectionAllocator.Dispose(); + connectionAllocations.Dispose(); + bounds.Dispose(); + dirtiedHierarchicalNodes.Dispose(); + dirtyNodes.Dispose(); + + children = null; + areas = null; + dirty = null; + versions = null; + freeNodeIndices.Clear(); + } + } + + // Make methods internal + public int GetHierarchicalNodeVersion (int index) { + return (index * 71237) ^ versions[index]; + } + + /// Burst-accessible data about the hierarhical nodes + public struct HierarhicalNodeData { + [Unity.Collections.ReadOnly] + public SlabAllocator connectionAllocator; + [Unity.Collections.ReadOnly] + public NativeList connectionAllocations; + [Unity.Collections.ReadOnly] + public NativeList bounds; + } + + /// + /// Data about the hierarhical nodes. + /// + /// Can be accessed in burst jobs. + /// + public HierarhicalNodeData GetHierarhicalNodeData (out RWLock.ReadLockAsync readLock) { + readLock = rwLock.Read(); + return new HierarhicalNodeData { + connectionAllocator = connectionAllocator, + connectionAllocations = connectionAllocations, + bounds = bounds, + }; + } + + internal HierarchicalGraph (GlobalNodeStorage nodeStorage) { + this.nodeStorage = nodeStorage; + navmeshEdges = new NavmeshEdges(); + navmeshEdges.hierarchicalGraph = this; + } + + /// + /// Initializes the HierarchicalGraph data. + /// It is safe to call this multiple times even if it has already been enabled before. + /// + public void OnEnable () { + if (!connectionAllocator.IsCreated) { + gcHandle = GCHandle.Alloc(this); + + connectionAllocator = new SlabAllocator(1024, Allocator.Persistent); + connectionAllocations = new NativeList(0, Allocator.Persistent); + bounds = new NativeList(0, Allocator.Persistent); + numHierarchicalNodes = new NativeReference(0, Allocator.Persistent); + dirtiedHierarchicalNodes = new NativeList(0, Allocator.Persistent); + dirtyNodes = new HierarchicalBitset(1024, Allocator.Persistent); + + children = new List[1] { new List() }; + areas = new int[1]; + dirty = new byte[1]; + versions = new int[1]; + freeNodeIndices.Clear(); + } + } + + internal void OnCreatedNode (GraphNode node) { + AddDirtyNode(node); + } + + internal void OnDestroyedNode (GraphNode node) { + dirty[node.HierarchicalNodeIndex] = 1; + dirtyNodes.Reset((int)node.NodeIndex); + node.IsHierarchicalNodeDirty = false; + anyNodesDestroyed = true; + } + + /// + /// Marks this node as dirty because it's connectivity or walkability has changed. + /// This must be called by node classes after any connectivity/walkability changes have been made to them. + /// + /// See: + /// + public void AddDirtyNode (GraphNode node) { + if (!node.IsHierarchicalNodeDirty) { + // We may be calling this when shutting down + if (!dirtyNodes.IsCreated || node.Destroyed) return; + + dirtyNodes.Set((int)node.NodeIndex); + + // Mark the associated hierarchical node as dirty to ensure it is recalculated or removed later. + // Nodes that have been unwalkable since the last update, will have HierarchicalNodeIndex=0, which is a dummy hierarchical node that is never used. + dirty[node.HierarchicalNodeIndex] = 1; + node.IsHierarchicalNodeDirty = true; + } + } + + public void ReserveNodeIndices (uint nodeIndexCount) { + dirtyNodes.Capacity = Mathf.Max(dirtyNodes.Capacity, (int)nodeIndexCount); + } + + public int NumConnectedComponents { get; private set; } + + /// Get the connected component index of a hierarchical node + public uint GetConnectedComponent (int hierarchicalNodeIndex) { + return (uint)areas[hierarchicalNodeIndex]; + } + + struct JobRecalculateComponents : IJob { + public System.Runtime.InteropServices.GCHandle hGraphGC; + public NativeList connectionAllocations; + public NativeList bounds; + public NativeList dirtiedHierarchicalNodes; + public NativeReference numHierarchicalNodes; + bool warnedAboutTooManyHierarchicalNodes; + + void Grow (HierarchicalGraph graph) { + var newChildren = new List[System.Math.Min((int)GraphNode.MaxHierarchicalNodeIndex + 1, System.Math.Max(64, graph.children.Length*2))]; + var newAreas = new int[newChildren.Length]; + var newDirty = new byte[newChildren.Length]; + var newVersions = new int[newChildren.Length]; + numHierarchicalNodes.Value = newChildren.Length; + + graph.children.CopyTo(newChildren, 0); + graph.areas.CopyTo(newAreas, 0); + graph.dirty.CopyTo(newDirty, 0); + graph.versions.CopyTo(newVersions, 0); + bounds.Resize(newChildren.Length, NativeArrayOptions.UninitializedMemory); + connectionAllocations.Resize(newChildren.Length, NativeArrayOptions.ClearMemory); + + // Create all necessary lists for the new nodes + // Iterate in reverse so that when popping from the freeNodeIndices + // stack we get numbers from smallest to largest (this is not + // necessary, but it makes the starting colors be a bit nicer when + // visualized in the scene view). + for (int i = newChildren.Length - 1; i >= graph.children.Length; i--) { + newChildren[i] = ListPool.Claim(MaxChildrenPerNode); + connectionAllocations[i] = SlabAllocator.InvalidAllocation; + if (i > 0) graph.freeNodeIndices.PushEnd(i); + } + connectionAllocations[0] = SlabAllocator.InvalidAllocation; + + graph.children = newChildren; + graph.areas = newAreas; + graph.dirty = newDirty; + graph.versions = newVersions; + } + + int GetHierarchicalNodeIndex (HierarchicalGraph graph) { + if (graph.freeNodeIndices.Length == 0) { + if (graph.children.Length == (int)GraphNode.MaxHierarchicalNodeIndex + 1) { + if (!warnedAboutTooManyHierarchicalNodes) { + Debug.LogError("Max number of hierarchical nodes in the graphs reached. Your graphs must be extremely fragmented. Pathfinding may not work correctly.\nYour graphs likely have too many connected components. They are visualized as differently colored regions when using A* Inspector -> Settings -> Graph Coloring = Areas"); + warnedAboutTooManyHierarchicalNodes = true; + } + return 0; + } + Grow(graph); + } + return graph.freeNodeIndices.PopEnd(); + } + + void RemoveHierarchicalNode (HierarchicalGraph hGraph, int hierarchicalNode, bool removeAdjacentSmallNodes) { + Assert.AreNotEqual(hierarchicalNode, 0); + hGraph.freeNodeIndices.PushEnd(hierarchicalNode); + hGraph.versions[hierarchicalNode]++; + var connAllocation = connectionAllocations[hierarchicalNode]; + var conns = hGraph.connectionAllocator.GetSpan(connAllocation); + + for (int i = 0; i < conns.Length; i++) { + var adjacentHierarchicalNode = conns[i]; + // If dirty, this hierarchical node will be removed later anyway, so don't bother doing anything with it. + if (hGraph.dirty[adjacentHierarchicalNode] != 0) continue; + + if (removeAdjacentSmallNodes && hGraph.children[adjacentHierarchicalNode].Count < MinChildrenPerNode) { + hGraph.dirty[adjacentHierarchicalNode] = 2; + RemoveHierarchicalNode(hGraph, adjacentHierarchicalNode, false); + + // The connection list may have been reallocated, so we need to get it again + conns = hGraph.connectionAllocator.GetSpan(connAllocation); + } else { + // Remove the connection from the other node to this node as we are removing this node. + var otherConnections = hGraph.connectionAllocator.GetList(connectionAllocations[adjacentHierarchicalNode]); + otherConnections.Remove(hierarchicalNode); + // Update the allocation index of the list, in case it was reallocated + connectionAllocations[adjacentHierarchicalNode] = otherConnections.allocationIndex; + } + } + Assert.AreEqual(connectionAllocations[hierarchicalNode], connAllocation); + + hGraph.connectionAllocator.Free(connAllocation); + connectionAllocations[hierarchicalNode] = SlabAllocator.InvalidAllocation; + + var nodeChildren = hGraph.children[hierarchicalNode]; + + // Ensure all children of dirty hierarchical nodes are included in the recalculation + var preDirty = hGraph.dirty[hierarchicalNode]; + for (int i = 0; i < nodeChildren.Count; i++) { + if (!nodeChildren[i].Destroyed) hGraph.AddDirtyNode(nodeChildren[i]); + } + // Put the dirty flag back to what it was before, as it might have been set to 1 by the AddDirtyNode call + hGraph.dirty[hierarchicalNode] = preDirty; + + nodeChildren.ClearFast(); + } + + [System.Diagnostics.Conditional("CHECK_INVARIANTS")] + void CheckConnectionInvariants () { + var hGraph = (HierarchicalGraph)hGraphGC.Target; + if (connectionAllocations.Length > 0) Assert.AreEqual(connectionAllocations[0], SlabAllocator.InvalidAllocation); + for (int i = 0; i < connectionAllocations.Length; i++) { + if (connectionAllocations[i] != SlabAllocator.InvalidAllocation) { + var conns = hGraph.connectionAllocator.GetSpan(connectionAllocations[i]); + for (int j = 0; j < conns.Length; j++) { + Assert.IsFalse(connectionAllocations[conns[j]] == SlabAllocator.InvalidAllocation, "Invalid connection allocation"); + var otherConns = hGraph.connectionAllocator.GetSpan(connectionAllocations[conns[j]]); + if (!otherConns.Contains(i)) { + throw new System.Exception("Connections are not bidirectional"); + } + } + } + } + } + + [System.Diagnostics.Conditional("CHECK_INVARIANTS")] + void CheckPreUpdateInvariants () { + var hGraph = (HierarchicalGraph)hGraphGC.Target; + + if (connectionAllocations.Length > 0) Assert.AreEqual(connectionAllocations[0], SlabAllocator.InvalidAllocation); + for (int i = 0; i < connectionAllocations.Length; i++) { + if (connectionAllocations[i] != SlabAllocator.InvalidAllocation) { + var children = hGraph.children[i]; + for (int j = 0; j < children.Count; j++) { + if (!children[j].Destroyed) { + Assert.AreEqual(children[j].HierarchicalNodeIndex, i); + } + } + } + } + } + + [System.Diagnostics.Conditional("CHECK_INVARIANTS")] + void CheckChildInvariants () { + var hGraph = (HierarchicalGraph)hGraphGC.Target; + + if (connectionAllocations.Length > 0) Assert.AreEqual(connectionAllocations[0], SlabAllocator.InvalidAllocation); + for (int i = 0; i < connectionAllocations.Length; i++) { + if (connectionAllocations[i] != SlabAllocator.InvalidAllocation) { + var children = hGraph.children[i]; + for (int j = 0; j < children.Count; j++) { + Assert.IsFalse(children[j].Destroyed); + Assert.AreEqual(children[j].HierarchicalNodeIndex, i); + } + } + } + } + + struct Context { + public List children; + public int hierarchicalNodeIndex; + public List connections; + public uint graphindex; + public Queue queue; + } + + + /// Run a BFS out from a start node and assign up to MaxChildrenPerNode nodes to the specified hierarchical node which are not already assigned to another hierarchical node + void FindHierarchicalNodeChildren (HierarchicalGraph hGraph, int hierarchicalNode, GraphNode startNode) { + Assert.AreNotEqual(hierarchicalNode, 0); + Assert.AreEqual(hGraph.children[hierarchicalNode].Count, 0); + hGraph.versions[hierarchicalNode]++; + + // We create a context and pass that by reference to the GetConnections method. + // This allows us to pass a non-capturing delegate, which does not require a heap allocation. + var queue = hGraph.temporaryQueue; + var context = new Context { + children = hGraph.children[hierarchicalNode], + hierarchicalNodeIndex = hierarchicalNode, + connections = hGraph.currentConnections, + graphindex = startNode.GraphIndex, + queue = queue, + }; + context.connections.Clear(); + context.children.Add(startNode); + context.queue.Enqueue(startNode); + startNode.HierarchicalNodeIndex = hierarchicalNode; + + GraphNode.NodeActionWithData visitConnection = (GraphNode neighbour, ref Context context) => { + if (neighbour.Destroyed) { + throw new System.InvalidOperationException("A node in a " + AstarPath.active.graphs[context.graphindex].GetType().Name + " contained a connection to a destroyed " + neighbour.GetType().Name + "."); + } + var hIndex = neighbour.HierarchicalNodeIndex; + if (hIndex == 0) { + if (context.children.Count < MaxChildrenPerNode && neighbour.Walkable && neighbour.GraphIndex == context.graphindex /* && (((GridNode)currentChildren[0]).XCoordinateInGrid/Tiling == ((GridNode)neighbour).XCoordinateInGrid/Tiling) && (((GridNode)currentChildren[0]).ZCoordinateInGrid/Tiling == ((GridNode)neighbour).ZCoordinateInGrid/Tiling)*/) { + neighbour.HierarchicalNodeIndex = context.hierarchicalNodeIndex; + context.queue.Enqueue(neighbour); + context.children.Add(neighbour); + } + } else if (hIndex != context.hierarchicalNodeIndex && !context.connections.Contains(hIndex)) { + // The Contains call can in theory be very slow as an hierarchical node may be adjacent to an arbitrary number of nodes. + // However in practice due to how the hierarchical nodes are constructed they will only be adjacent to a smallish (≈4-6) number of other nodes. + // So a Contains call will be much faster than say a Set lookup. + context.connections.Add(hIndex); + } + }; + + while (queue.Count > 0) queue.Dequeue().GetConnections(visitConnection, ref context, Connection.IncomingConnection | Connection.OutgoingConnection); + + if (hGraph.currentConnections.Count > SlabAllocator.MaxAllocationSize) { + throw new System.Exception("Too many connections for a single hierarchical node. Do you have thousands of off-mesh links in a single location?"); + } + + for (int i = 0; i < hGraph.currentConnections.Count; i++) { + var otherHierarchicalNode = hGraph.currentConnections[i]; + Assert.AreNotEqual(otherHierarchicalNode, 0); + var otherAllocationIndex = connectionAllocations[otherHierarchicalNode]; + Assert.AreNotEqual(otherAllocationIndex, SlabAllocator.InvalidAllocation); + var otherConnections = hGraph.connectionAllocator.GetList(otherAllocationIndex); + otherConnections.Add(hierarchicalNode); + // Update the allocation index in case the list was reallocated + connectionAllocations[otherHierarchicalNode] = otherConnections.allocationIndex; + } + + connectionAllocations[hierarchicalNode] = hGraph.connectionAllocator.Allocate(hGraph.currentConnections); + queue.Clear(); + } + + /// Flood fills the graph of hierarchical nodes and assigns the same area ID to all hierarchical nodes that are in the same connected component + void FloodFill (HierarchicalGraph hGraph) { + var areas = hGraph.areas; + for (int i = 0; i < areas.Length; i++) areas[i] = 0; + + Stack stack = hGraph.temporaryStack; + int currentArea = 0; + for (int i = 1; i < areas.Length; i++) { + // Already taken care of, or does not exist + if (areas[i] != 0 || connectionAllocations[i] == SlabAllocator.InvalidAllocation) continue; + + currentArea++; + areas[i] = currentArea; + stack.Push(i); + while (stack.Count > 0) { + int node = stack.Pop(); + var conns = hGraph.connectionAllocator.GetSpan(connectionAllocations[node]); + for (int j = conns.Length - 1; j >= 0; j--) { + var otherNode = conns[j]; + // Note: slightly important that this is != currentArea and not != 0 in case there are some connected, but not stongly connected components in the graph (this will happen in only veeery few types of games) + if (areas[otherNode] != currentArea) { + areas[otherNode] = currentArea; + stack.Push(otherNode); + } + } + } + } + + hGraph.NumConnectedComponents = System.Math.Max(1, currentArea + 1); + hGraph.version++; + } + + public void Execute () { + var hGraph = hGraphGC.Target as HierarchicalGraph; + CheckPreUpdateInvariants(); + Profiler.BeginSample("Recalculate Connected Components"); + var dirty = hGraph.dirty; + CheckConnectionInvariants(); + + Profiler.BeginSample("Remove"); + // Remove all hierarchical nodes and then build new hierarchical nodes in their place + // which take into account the new graph data. + var initialFreeLength = hGraph.freeNodeIndices.Length; + for (int i = 1; i < dirty.Length; i++) { + if (dirty[i] == 1) RemoveHierarchicalNode(hGraph, i, true); + } + + // Reset the dirty flag on all hierarchical nodes + for (int i = 1; i < dirty.Length; i++) dirty[i] = 0; + + // Reset the dirty flag on all nodes, and make sure they don't refer to their new destroyed hierarchical nodes + var buffer = new NativeArray(512, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + var nodeStorage = hGraph.nodeStorage; + foreach (var span in hGraph.dirtyNodes.GetIterator(buffer.AsUnsafeSpan())) { + for (int i = 0; i < span.Length; i++) { + var node = nodeStorage.GetNode((uint)span[i]); + node.IsHierarchicalNodeDirty = false; + node.HierarchicalNodeIndex = 0; + } + } + + Profiler.EndSample(); + CheckConnectionInvariants(); + + Profiler.BeginSample("Find"); + dirtiedHierarchicalNodes.Clear(); + foreach (var span in hGraph.dirtyNodes.GetIterator(buffer.AsUnsafeSpan())) { + for (int i = 0; i < span.Length; i++) { + var node = nodeStorage.GetNode((uint)span[i]); + + Assert.IsFalse(node.Destroyed); + if (!node.Destroyed && node.HierarchicalNodeIndex == 0 && node.Walkable) { + var hNode = GetHierarchicalNodeIndex(hGraph); + if (hNode == 0) continue; + + Profiler.BeginSample("FindChildren"); + FindHierarchicalNodeChildren(hGraph, hNode, node); + Profiler.EndSample(); + dirtiedHierarchicalNodes.Add(hNode); + } + } + } + + // These are hierarchical node indices that were pushed to the free id stack, and we did not immediately reuse them. + // This means that they have been destroyed, and we should notify the NavmeshEdges class about this. + for (int i = initialFreeLength; i < hGraph.freeNodeIndices.Length; i++) { + dirtiedHierarchicalNodes.Add(hGraph.freeNodeIndices[i]); + } + + hGraph.dirtyNodes.Clear(); + hGraph.anyNodesDestroyed = false; + + // Recalculate the connected components of the hierarchical nodes + // This is usually very quick compared to the code above + FloodFill(hGraph); + Profiler.EndSample(); + hGraph.gizmoVersion++; + CheckConnectionInvariants(); + CheckChildInvariants(); + } + } + + /// Recalculate the hierarchical graph and the connected components if any nodes have been marked as dirty + public void RecalculateIfNecessary () { + // We need to complete both jobs here, because after this method + // the graph may change in arbitrary ways. The RecalculateObstacles job reads from the graph. + JobRecalculateIfNecessary().Complete(); + } + + /// + /// Schedule a job to recalculate the hierarchical graph and the connected components if any nodes have been marked as dirty. + /// Returns dependsOn if nothing has to be done. + /// + /// Note: Assumes the graph is unchanged until the returned dependency is completed. + /// + public JobHandle JobRecalculateIfNecessary (JobHandle dependsOn = default) { + if (!connectionAllocator.IsCreated) throw new System.InvalidOperationException("The hierarchical graph has not been initialized. Please call OnEnable before using it."); + + if (!dirtyNodes.IsEmpty || anyNodesDestroyed) { + var writeLock = rwLock.Write(); + var lastJob = new JobRecalculateComponents { + hGraphGC = gcHandle, + connectionAllocations = connectionAllocations, + bounds = bounds, + dirtiedHierarchicalNodes = dirtiedHierarchicalNodes, + numHierarchicalNodes = numHierarchicalNodes, + }.Schedule(JobHandle.CombineDependencies(writeLock.dependency, dependsOn)); + // We need to output both jobs as dependencies. + // Firstly they use some internal data (e.g. dirtiedHierarchicalNodes), so we need to set lastJob. + // Secondly, they read from the graph. And the graph data is only read-only until this returned dependency is completed. + lastJob = navmeshEdges.RecalculateObstacles(dirtiedHierarchicalNodes, numHierarchicalNodes, lastJob); + writeLock.UnlockAfter(lastJob); + return lastJob; + } else { + return dependsOn; + } + } + + /// + /// Recalculate everything from scratch. + /// This is primarily to be used for legacy code for compatibility reasons, not for any new code. + /// + /// See: + /// + public void RecalculateAll () { + var writeLock = rwLock.WriteSync(); + AstarPath.active.data.GetNodes(AddDirtyNode); + writeLock.Unlock(); + RecalculateIfNecessary(); + } + + public void OnDrawGizmos (DrawingData gizmos, bool renderInGame) { + var hasher = new NodeHasher(AstarPath.active); + + hasher.Add(gizmoVersion); + + if (!gizmos.Draw(hasher)) { + var readLock = rwLock.ReadSync(); + try { + using (var builder = gizmos.GetBuilder(hasher, default, renderInGame)) { + for (int i = 0; i < areas.Length; i++) { + if (children[i].Count > 0) { + builder.WireBox(bounds[i].center, bounds[i].size); + var conns = connectionAllocator.GetSpan(connectionAllocations[i]); + for (int j = 0; j < conns.Length; j++) { + if (conns[j] > i) { + builder.Line(bounds[i].center, bounds[conns[j]].center, Color.black); + } + } + } + } + } + } finally { + readLock.Unlock(); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs.meta new file mode 100644 index 0000000..e8db5b3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/HierarchicalGraph.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aa5d6e4a6adb04d4ca9b7e735addcbd5 +timeCreated: 1535050640 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs new file mode 100644 index 0000000..daa93b9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs @@ -0,0 +1,261 @@ +using System.Runtime.CompilerServices; +using UnityEngine; +namespace Pathfinding { + /// + /// Provides additional traversal information to a path request. + /// + /// The ITraversalProvider interface is something you can implement to be able to control exactly which nodes are traversable, how costly it is to traverse them, and how costly it is to traverse connections between nodes. + /// This enables a lot of flexibility in how you can control pathfinding, and is useful for many different scenarios. + /// + /// For example, you can use it to: + /// - Make some agents prefer to walk on roads, while others prefer grass. + /// - Create restricted doors, which only some agents can pass through. + /// - In a turn based game, add additional cost for moving across a river between two tiles. + /// - Etc. + /// + /// Example implementation: + /// + /// public class MyCustomTraversalProvider : ITraversalProvider { + /// /** [CanTraverseImpl] */ + /// public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode node) { + /// // If, for example, your agents are afraid of heights, prevent your agents from going above 50 world units + /// return ((Vector3)node.position).y < 50; + /// } + /// /** [CanTraverseImpl] */ + /// + /// /** [CanTraverseFromToImpl] */ + /// public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode from, GraphNode to) { + /// // Connections between nodes can be filtered too. + /// // If you don't have any special rules for this, just forward the call to checking if the 'to' node is traversable. + /// // (or just skip this method, as the default implementation will do the same) + /// + /// // We can, for example, prevent the agent from going directly from a node with tag 2 to a node with tag 3 + /// if (from.Tag == 2 && to.Tag == 3) return false; + /// + /// return CanTraverse(ref traversalConstraint, to); + /// } + /// /** [CanTraverseFromToImpl] */ + /// + /// /** [GetTraversalCostMultiplierImpl] */ + /// public float GetTraversalCostMultiplier (ref TraversalCosts traversalCosts, GraphNode node) { + /// // For example, if your agent is afraid of heights, you can make nodes high up be 10 times more expensive to traverse + /// if (((Vector3)node.position).y > 30) return 10.0f; + /// + /// return DefaultITraversalProvider.GetTraversalCostMultiplier(ref traversalCosts, node); + /// } + /// /** [GetTraversalCostMultiplierImpl] */ + /// + /// /** [GetConnectionCostImpl] */ + /// public uint GetConnectionCost (ref TraversalCosts traversalCosts, GraphNode from, GraphNode to) { + /// // The traversal cost is, by default, the sum of the penalty of the node's tag and the node's penalty + /// return traversalCosts.GetTagEntryCost(to.Tag) + to.Penalty; + /// // alternatively: + /// // return DefaultITraversalProvider.GetConnectionCost(ref traversalCosts, from, to); + /// } + /// /** [GetConnectionCostImpl] */ + /// } + /// + /// + /// You can use it with a : + /// + /// seeker.traversalProvider = new MyCustomTraversalProvider(); + /// + /// + /// Or with a + /// + /// followerEntity.pathfindingSettings.traversalProvider = new MyCustomTraversalProvider(); + /// + /// + /// Or with a path instance: + /// + /// var path = ABPath.Construct(Vector3.zero, Vector3.one); + /// var traversalProvider = new MyCustomTraversalProvider(); + /// + /// // Use the same provider for both traversability and costs + /// path.traversalConstraint.traversalProvider = traversalProvider; + /// path.traversalCosts.traversalProvider = traversalProvider; + /// + /// + /// Warning: Your implementation of this interface may be called from separate threads if pathfinding multithreading is enabled, or if you are using the component. + /// You must ensure that your implementation is thread-safe. It is recommended to make each ITraversalProvider instance immutable, so that it can be shared between threads without any issues. + /// + /// Note: The ITraversalProvider may continue to be used by the movement script after the path has been calculated, in order to repair or simplify its path. + /// + /// See: traversal_provider (view in online documentation for working links) + /// See: tags (view in online documentation for working links) + /// + public interface ITraversalProvider { + /// + /// Filter diagonal connections using for effects applied by this ITraversalProvider. + /// This includes tags and other effects that this ITraversalProvider controls. + /// + /// This only has an effect if is set to false and your grid has set to Eight. + /// + /// Take this example, the grid is completely walkable, but an ITraversalProvider is used to make the nodes marked with '#' + /// as unwalkable. The agent 'S' is in the middle. + /// + /// + /// .......... + /// ....#..... + /// ...#.... + /// ....#..... + /// .......... + /// + /// + /// If filterDiagonalGridConnections is false the agent will be free to use the diagonal connections to move away from that spot. + /// However, if filterDiagonalGridConnections is true (the default) then the diagonal connections will be disabled and the agent will be stuck. + /// + /// Typically, there are a few common use cases: + /// - If your ITraversalProvider makes walls and obstacles and you want it to behave identically to obstacles included in the original grid graph scan, then this should be true. + /// - If your ITraversalProvider is used for agent to agent avoidance and you want them to be able to move around each other more freely, then this should be false. + /// + /// See: + /// + bool filterDiagonalGridConnections => true; + + /// + /// True if node should be able to be traversed by the path. + /// + /// If you are implementing a custom ITraversalProvider, you should override this method and do whatever checks you need to do to determine if the node is traversable. + /// + /// When used, the ITraversalProvider will pretty much always be wrapped in a struct. + /// The method will then do its own checks first, and call this method to do additional filtering. + /// This means that the is always at least as strict as the . + /// + /// Note: This also means that returning true from this method does not necessarily mean that the node is traversable by a path. + /// The TraversalConstraint may have additional filters, and in particular it always checks that the node is . + /// + /// + /// public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode node) { + /// // If, for example, your agents are afraid of heights, prevent your agents from going above 50 world units + /// return ((Vector3)node.position).y < 50; + /// } + /// + /// + /// See: + /// + /// Full constraint that this traversal provider is part of. You can use this to check additional nodes using \reflink{TraversalConstraint.CanTraverseSkipUserFilter(GraphNode)}. + /// It is passed by reference to avoid the performance cost of copying it, since this method is quite hot. + /// The node to check. Should not be null. + bool CanTraverse(ref TraversalConstraint traversalConstraint, GraphNode node) => DefaultITraversalProvider.CanTraverse(ref traversalConstraint, node); + + /// + /// True if the path can traverse a connection between from and to, and if to can be traversed itself. + /// + /// This can be used to block movement between specific nodes. + /// + /// If this method returns true then a call to CanTraverse(traversalConstraint,to) must also return true. + /// Thus this method is a more flexible version of . + /// + /// The default implementation will just call + /// + /// + /// public bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode from, GraphNode to) { + /// // Connections between nodes can be filtered too. + /// // If you don't have any special rules for this, just forward the call to checking if the 'to' node is traversable. + /// // (or just skip this method, as the default implementation will do the same) + /// + /// // We can, for example, prevent the agent from going directly from a node with tag 2 to a node with tag 3 + /// if (from.Tag == 2 && to.Tag == 3) return false; + /// + /// return CanTraverse(ref traversalConstraint, to); + /// } + /// + /// + /// See: + /// + bool CanTraverse(ref TraversalConstraint traversalConstraint, GraphNode from, GraphNode to) => CanTraverse(ref traversalConstraint, to); + + /// + /// Cost of entering a given node from another node. + /// Should return the additional cost for moving between from and to. By default, if no tags or penalties + /// are used, then the connection cost is zero. A cost of 1000 corresponds roughly to the cost of moving 1 world unit. + /// + /// This cost is not scaled by the distance between the two nodes. + /// This cost is also not affected by the . + /// + /// The default implementation returns 0, and all cost comes from the method instead. + /// + /// + /// public uint GetConnectionCost (ref TraversalCosts traversalCosts, GraphNode from, GraphNode to) { + /// // The traversal cost is, by default, the sum of the penalty of the node's tag and the node's penalty + /// return traversalCosts.GetTagEntryCost(to.Tag) + to.Penalty; + /// // alternatively: + /// // return DefaultITraversalProvider.GetConnectionCost(ref traversalCosts, from, to); + /// } + /// + /// + /// See: + /// + uint GetConnectionCost(ref TraversalCosts traversalCosts, GraphNode from, GraphNode to) => DefaultITraversalProvider.GetConnectionCost(ref traversalCosts, from, to); + + /// + /// Multiplier for the cost of traversing some distance across the given node. + /// + /// Returns a float representing how costly it is to move across the node, with 1.0 being the default, and, for example, 2.0 being twice as costly. + /// + /// The value will be multiplied by the traversed distance in millimeters (see ) to get the final cost. + /// For the default multiplier of 1.0, this means that the cost will be the same as the distance in millimeters. + /// + /// Note: Prefer to make traversal more costly (cost multiplier >1), rather than making it cheaper than the default (cost multiplier <1). + /// If you return any cost multiplier less than 1, you'll also need to reduce to the lowest cost multiplier that you use in the project, + /// otherwise the pathfinding algorithm may not find the optimal path. Alternatively you could compensate with a higher connection cost value, so that the + /// agent can never move to the target with a lower cost than the default. See https://en.wikipedia.org/wiki/Admissible_heuristic. + /// + /// + /// public float GetTraversalCostMultiplier (ref TraversalCosts traversalCosts, GraphNode node) { + /// // For example, if your agent is afraid of heights, you can make nodes high up be 10 times more expensive to traverse + /// if (((Vector3)node.position).y > 30) return 10.0f; + /// + /// return DefaultITraversalProvider.GetTraversalCostMultiplier(ref traversalCosts, node); + /// } + /// + /// + /// See: + /// + float GetTraversalCostMultiplier(ref TraversalCosts traversalCosts, GraphNode node) => DefaultITraversalProvider.GetTraversalCostMultiplier(ref traversalCosts, node); + + /// + /// Can the agent traverse the connection between two nodes. + /// + /// Deprecated: Use instead. + /// + [System.Obsolete("Use CanTraverse(ref TraversalConstraint, GraphNode, GraphNode) instead")] + bool CanTraverse(Path path, GraphNode from, GraphNode to) => throw new System.NotImplementedException(); + + /// + /// Can the agent traverse the given node. + /// + /// Deprecated: Use instead. + /// + [System.Obsolete("Use CanTraverse(ref TraversalConstraint, GraphNode) instead")] + bool CanTraverse(Pathfinding.Path path, GraphNode node) => throw new System.NotImplementedException(); + + /// + /// Cost of traversing the given node. + /// + /// Deprecated: Use instead. + /// + [System.Obsolete("Use GetTraversalCostMultiplier(ref TraversalCosts, GraphNode) instead")] + uint GetTraversalCost(Path path, GraphNode node) => throw new System.NotImplementedException(); + } + + /// Convenience class to access the default implementation of the ITraversalProvider + public static class DefaultITraversalProvider { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanTraverse (ref TraversalConstraint traversalConstraint, GraphNode node) { + // The traversalConstraint will have done its filtering already, so we can just return true + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float GetTraversalCostMultiplier (ref TraversalCosts traversalCosts, GraphNode node) { + return traversalCosts.GetDefaultTraversalCostMultiplier(node); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetConnectionCost (ref TraversalCosts traversalCosts, GraphNode from, GraphNode to) { + return traversalCosts.GetDefaultConnectionCost(from, to); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs.meta new file mode 100644 index 0000000..c85b200 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/ITraversalProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb11e2e7634f01646a8d62ad94c75291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs new file mode 100644 index 0000000..a02eb0b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs @@ -0,0 +1,183 @@ +using System.Runtime.CompilerServices; + +namespace Pathfinding { + /// + /// Constraint for the method. + /// This is used to filter out nodes you are not interested in. + /// + /// For example, you can use this to only find walkable nodes, or only nodes with a specific tag. + /// + /// See: + /// + /// Why is this struct passed around with the ref keyword all the time? + /// + /// Structs are passed by value in C#, and copying them can be expensive when they are large (like this one at around 44 bytes). + /// Using the ref keyword allows it to be passed by reference instead, which is much faster. + /// + /// But then why is it not a class? + /// + /// Firstly, because classes involve the garbage collector, and since this struct is created very often (often many times per frame), we want to avoid that. + /// Garbage collections are very expensive operations, and can cause stuttering in the game. + /// + public struct NearestNodeConstraint { + /// + /// Determines how to measure distances to the navmesh. + /// + /// The default is a euclidean distance, which works well for most things. + /// + /// See: + /// + public DistanceMetric distanceMetric; + + /// + /// Area ID to constrain to. + /// Will not affect anything if negative. + /// + /// See: + /// + public int area; + + /// + /// Squared . + /// Negative value means to use the default A* limit (). + /// Positive value means to use a custom limit. + /// + /// Typically you'll modify instead of this. + /// + /// See: + /// + public float maxDistanceSqr; + + /// Sub-filter with more constraints + internal TraversalConstraint traversal; + + /// + /// Constraint to only search only walkable nodes, only unwalkable nodes, or both. + /// See: + /// + public WalkabilityConstraint walkable; + + /// \copydocref{TraversalConstraint::tags} + public int tags { + get => traversal.tags; + set => traversal.tags = value; + } + + /// \copydocref{TraversalConstraint::graphMask} + public GraphMask graphMask { + get => traversal.graphMask; + set => traversal.graphMask = value; + } + + /// + /// Allow only nodes which the given filter function returns true for. + /// + /// The other constraints on this struct will still apply, in addition to the filter function. + /// + /// Note: Either an ITraversalProvider ( or a filter function can be set, but not both. + /// Setting this will clear , and vice versa. + /// + /// + /// var nearestNodeConstraint = NearestNodeConstraint.None; + /// + /// // Set a custom filter. This can be an arbitrary function which takes a GraphNode and returns true or false. + /// nearestNodeConstraint.filter = (GraphNode node) => ((Vector3)node.position).y > 50; + /// + /// // Find the closest node to our position which is above y=50 + /// var nearest = AstarPath.active.GetNearest(transform.position, nearestNodeConstraint); + /// + /// + /// See: + /// + public System.Func filter { + get => traversal.filter; + set => traversal.filter = value; + } + + /// \copydocref{TraversalConstraint::traversalProvider} + public ITraversalProvider traversalProvider { + get => traversal.traversalProvider; + set => traversal.traversalProvider = value; + } + + /// + /// True if all nodes are suitable with respect to the constraints set in this struct. + /// The method will return true for all nodes if this is true. + /// + public bool allNodesAreSuitable => area < 0 && tags == ~0 && graphMask.containsAllGraphs && walkable == WalkabilityConstraint.DontCare && traversal.filterType == TraversalConstraint.FilterType.None; + + /// + /// The maximum distance to the navmesh to search before the search fails. + /// This is the maximum distance from the query point to the closest point on the navmesh. + /// If null (the default), the default limit set on the component will be used (). + /// + /// Positive infinity is a valid value, meaning it will search for the closest node regardless of distance. + /// 0 is also a valid value, but it usually only makes sense with a different distance metric than the default. + /// + /// Note: The distance depends on the distance metric used. + /// + /// See: + /// See: + /// + public float? maxDistance { + get => maxDistanceSqr < 0 ? null : UnityEngine.Mathf.Sqrt(maxDistanceSqr); + set => maxDistanceSqr = value.HasValue ? value.Value * value.Value : -1; + } + + public enum WalkabilityConstraint : byte { + Walkable, + Unwalkable, + DontCare, + } + + internal float maxDistanceSqrOrDefault (AstarPath astar) { + return maxDistanceSqr < 0 ? astar.maxNearestNodeDistanceSqr : maxDistanceSqr; + } + + /// True if the node is ok with respect to the constraints set in this struct + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Suitable (GraphNode node) { + if (area >= 0 && node.Area != area) return false; + if (node.Walkable) { + if (walkable == WalkabilityConstraint.Unwalkable) return false; + } else { + if (walkable == WalkabilityConstraint.Walkable) return false; + } + + // The following part is the same as traversal.CanTraverse(node), + // but it doesn't require that the node is walkable. + // When searching for the nearest node, we allow that check to be customized (see #walkable). + + if ((traversal.tags >> (int)node.Tag & 0x1) == 0) return false; + + switch (traversal.filterType) { + case TraversalConstraint.FilterType.TraversalProvider: + // This is a hot method, so we want to avoid all checks that we possibly can. + // This cast is guaranteed to succeed, so we can skip the check using an unsafe cast. + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref traversal.filterObj).CanTraverse(ref traversal, node); + case TraversalConstraint.FilterType.Func: + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As >(ref traversal.filterObj)(node); + default: + return true; + } + } + + /// Constraint which filters out all unwalkable nodes + public static readonly NearestNodeConstraint Walkable = new NearestNodeConstraint { + traversal = TraversalConstraint.None, + distanceMetric = DistanceMetric.Euclidean, + area = -1, + maxDistanceSqr = -1, + walkable = WalkabilityConstraint.Walkable, + }; + + /// Constraint which allows all nodes + public static readonly NearestNodeConstraint None = new NearestNodeConstraint { + traversal = TraversalConstraint.None, + distanceMetric = DistanceMetric.Euclidean, + area = -1, + maxDistanceSqr = -1, + walkable = WalkabilityConstraint.DontCare, + }; + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs.meta new file mode 100644 index 0000000..fa5e8d9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/NearestNodeConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e37879b73b51a4f4c94c5ba56db2c3d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs new file mode 100644 index 0000000..8366ff6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs @@ -0,0 +1,1216 @@ +//#define ASTAR_POOL_DEBUG // Enables debugging of path pooling. Will log warnings and info messages about paths not beeing pooled correctly. + +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using Unity.Mathematics; +using System.Runtime.CompilerServices; +using Pathfinding.Collections; +using Pathfinding.Pooling; +using Unity.Burst; + +namespace Pathfinding { + /// Base class for all path types + [BurstCompile] + public abstract class Path : IPathInternals { +#if ASTAR_POOL_DEBUG + private string pathTraceInfo = ""; + private string[] claimInfo = new string[2]; + ~Path() { + Debug.Log("Destroying " + GetType().Name + " instance"); + if (claimCount > 0) { + Debug.LogWarning("Pool Is Leaking. See list of claims:\n" + + "Each message below will list what objects are currently claiming the path." + + " These objects have removed their reference to the path object but has not called .Release on it (which is bad).\n" + pathTraceInfo+"\n"); + for (int i = 0; i < claimCount; i++) { + Debug.LogWarning("- Claim "+ (i+1) + " is by a " + claimed[i].GetType().Name + "\n"+claimInfo[i]); + } + } else { + Debug.Log("Some scripts are not using pooling.\n" + pathTraceInfo + "\n"); + } + } +#endif + + /// + /// Callback to call when the path is complete. + /// This is usually sent to the Seeker component which post processes the path and then calls a callback to the script which requested the path + /// + public OnPathDelegate callback; + + /// + /// Immediate callback to call when the path is complete. + /// Warning: This may be called from a separate thread. Usually you do not want to use this one. + /// + /// See: callback + /// + public OnPathDelegate immediateCallback; + + /// Returns the state of the path in the pathfinding pipeline + public PathState PipelineState { get; private set; } + + /// + /// Provides additional traversal information to a path request. + /// See: traversal_provider (view in online documentation for working links) + /// Deprecated: Set path.traversalConstraint.traversalProvider and path.traversalCosts.traversalProvider instead + /// + [System.Obsolete("Use path.traversalConstraint.traversalProvider and path.traversalCosts.traversalProvider instead")] + public ITraversalProvider traversalProvider { + get => traversalConstraint.traversalProvider; + set { + traversalConstraint.traversalProvider = value; + traversalCosts.traversalProvider = value; + } + } + + + /// Backing field for + protected PathCompleteState completeState; + + /// + /// Current state of the path. + /// Bug: This may currently be set to Complete before the path has actually been fully calculated. In particular the vectorPath and path lists may not have been fully constructed. + /// This can lead to race conditions when using multithreading. Try to avoid using this method to check for if the path is calculated right now, use instead. + /// + public PathCompleteState CompleteState { + get { return completeState; } + protected set { + // Locking is used to avoid multithreading race conditions in which, for example, + // the error state is set on the main thread to cancel the path, + // and then a pathfinding thread marks the path as completed, + // which would replace the error state (if a lock and check would not have been used). + // We lock on the path object itself. Users should rarely have to use the path object + // themselves for anything before the path is calculated, much less take a lock on it. + lock (this) { + // Once the path is put in the error state, it cannot be set to any other state + if (completeState != PathCompleteState.Error) completeState = value; + } + } + } + + /// + /// If the path failed, this is true. + /// + /// This typically happens if there's no valid node close enough to the start point of the path, + /// or if there's no node close enough to the target point that is reachable from the start point. + /// The will have more information about what happened. + /// + /// See: + /// See: error-messages (view in online documentation for working links) + /// See: This is equivalent to checking path.CompleteState == PathCompleteState.Error + /// + public bool error => CompleteState == PathCompleteState.Error; + + /// + /// Additional info on why a path failed. + /// See: + /// See: error-messages (view in online documentation for working links) + /// + public string errorLog { get; private set; } + + /// + /// Holds the path as a list. + /// + /// These are all nodes that the path traversed, as calculated by the pathfinding algorithm. + /// This may not be the same nodes as the post processed path traverses. + /// + /// See: + /// + public List path; + + /// + /// Holds the (possibly post-processed) path as a Vector3 list. + /// + /// This list may be modified by path modifiers to be smoother or simpler compared to the raw path generated by the pathfinding algorithm. + /// + /// See: modifiers (view in online documentation for working links) + /// See: + /// + public List vectorPath; + + /// How long it took to calculate this path in milliseconds + public float duration { get; internal set; } + + /// Number of nodes this path has searched + public int searchedNodes { get; protected set; } + + /// + /// True if the path is currently pooled. + /// Do not set this value. Only read. It is used internally. + /// + /// See: + /// + bool IPathInternals.Pooled { get; set; } + + /// + /// True if the Reset function has been called. + /// Used to alert users when they are doing something wrong. + /// + protected bool hasBeenReset; + + /// + /// Constraint for how to search for nodes. + /// Deprecated: Use instead. + /// + [System.Obsolete("Use the traversalConstraint field instead. Check the migration guide for version 5.4 for more information.")] + public NNConstraintPathProxy nnConstraint => new NNConstraintPathProxy(this); + + /// + /// Specifies the cost of traversing different nodes. + /// + /// By default, nodes have no special costs associated with them. + /// But you can modify this to, for example, make nodes with a certain tag more expensive to traverse, or supply completely custom rules by using an . + /// + /// See: + /// See: + /// + public TraversalCosts traversalCosts; + + /// + /// Constrains which nodes the path can traverse. + /// + /// By default, all walkable nodes are traversable. + /// But you can modify this to, for example, only allow nodes with a certain tag to be traversed, or supply completely custom rules by using an . + /// + /// See: + /// See: + /// + public TraversalConstraint traversalConstraint = TraversalConstraint.None; + + /// + /// Distance metric to use when searching for the start and end nodes in the graph. + /// + /// By default, the euclidean distance metric is used. This allows the path to, for example, find the closest start node to the start point, as measured using "normal" distance. + /// However, you can change this to instead measure distance by ignoring the Y coordinate. This is quite useful for characters, as it will find the node right below the agents feet, + /// even if the agent is slightly above the ground on a slope. + /// + /// See: + /// See: + /// + public DistanceMetric nearestNodeDistanceMetric; + + /// Determines which heuristic to use + public Heuristic heuristic; + + /// + /// Scale of the heuristic values. + /// See: AstarPath.heuristicScale + /// + public float heuristicScale = 1F; + + /// ID of this path. Used to distinguish between different paths + public ushort pathID { get; private set; } + + /// + /// Which graph tags are traversable. + /// This is a bitmask so -1 = all bits set = all tags traversable. + /// For example, to set bit 5 to true, you would do + /// myPath.enabledTags |= 1 << 5; + /// To set it to false, you would do + /// myPath.enabledTags &= ~(1 << 5); + /// + /// The Seeker has a popup field where you can set which tags to use. + /// Note: If you are using a Seeker. The Seeker will set this value to what is set in the inspector field on StartPath. + /// So you need to change the Seeker value via script, not set this value if you want to change it via script. + /// + /// See: + /// See: bitmasks (view in online documentation for working links) + /// Deprecated: Use traversalConstraint.tags instead + /// + [System.Obsolete("Use traversalConstraint.tags instead")] + public int enabledTags { + get => traversalConstraint.tags; + set => traversalConstraint.tags = value; + } + + /// + /// Penalties for each tag. + /// Tag 0 which is the default tag, will get a penalty of tagPenalties[0]. + /// These should only be non-negative values since the A* algorithm cannot handle negative penalties. + /// + /// When assigning an array to this property it must have a length of 32. + /// + /// Note: Setting this to null will make all tag penalties be treated as if they are zero. + /// + /// Note: If you are using a Seeker. The Seeker will set this value to what is set in the inspector field when you call seeker.StartPath. + /// So you need to change the Seeker's value via script, not set this value. + /// + /// See: + /// Deprecated: Use instead + /// + [System.Obsolete("Use traversalCosts.tagEntryCosts instead")] + public uint[] tagPenalties { + get => traversalCosts.tagEntryCosts; + set => traversalCosts.tagEntryCosts = value; + } + + /// + /// Constraint for how to search for nodes that are traversable by this path. + /// + /// This constraint is what the path uses to search for closest nodes to the start and end points. + /// + /// This is derived from the and the . + /// + /// See: + /// See: + /// See: + /// + public NearestNodeConstraint nearestNodeConstraint { + get { + var nn = traversalConstraint.ToNearestNodeConstraint(); + nn.distanceMetric = nearestNodeDistanceMetric; + return nn; + } + } + + /// Copies the given settings into this path + public void UseSettings (PathRequestSettings settings) { + traversalConstraint = settings.ToTraversalConstraint(); + traversalCosts = settings.ToTraversalCosts(); + } + + /// + /// Total Length of the path. + /// Calculates the total length of the . + /// Cache this rather than call this function every time since it will calculate the length every time, not just return a cached value. + /// Returns: Total length of , if is null positive infinity is returned. + /// + public float GetTotalLength () { + if (vectorPath == null) return float.PositiveInfinity; + float tot = 0; + for (int i = 0; i < vectorPath.Count-1; i++) tot += Vector3.Distance(vectorPath[i], vectorPath[i+1]); + return tot; + } + + /// + /// Waits until this path has been calculated and returned. + /// Allows for very easy scripting. + /// + /// + /// IEnumerator Start () { + /// // Get the seeker component attached to this GameObject + /// var seeker = GetComponent(); + /// + /// var path = seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, null); + /// // Wait... This may take a frame or two depending on how complex the path is + /// // The rest of the game will continue to run while we wait + /// yield return StartCoroutine(path.WaitForPath()); + /// // The path is calculated now + /// + /// // Draw the path in the scene view for 10 seconds + /// for (int i = 0; i < path.vectorPath.Count - 1; i++) { + /// Debug.DrawLine(path.vectorPath[i], path.vectorPath[i+1], Color.red, 10); + /// } + /// } + /// + /// + /// Note: Do not confuse this with AstarPath.BlockUntilCalculated. This one will wait using yield until it has been calculated + /// while AstarPath.BlockUntilCalculated will halt all operations until the path has been calculated. + /// + /// Throws: System.InvalidOperationException if the path is not started. Send the path to or before calling this function. + /// + /// See: + /// See: https://docs.unity3d.com/Manual/Coroutines.html + /// + public IEnumerator WaitForPath () { + if (PipelineState == PathState.Created) throw new System.InvalidOperationException("This path has not been started yet"); + + while (PipelineState != PathState.Returned) yield return null; + } + + /// + /// Blocks until this path has been calculated and returned. + /// Normally it takes a few frames for a path to be calculated and returned. + /// This function will ensure that the path will be calculated when this function returns + /// and that the callback for that path has been called. + /// + /// Use this function only if you really need to. + /// There is a point to spreading path calculations out over several frames. + /// It smoothes out the framerate and makes sure requesting a large + /// number of paths at the same time does not cause lag. + /// + /// Note: Graph updates and other callbacks might get called during the execution of this function. + /// + /// + /// var path = seeker.StartPath(transform.position, transform.position + Vector3.forward * 10, OnPathComplete); + /// path.BlockUntilCalculated(); + /// + /// // The path is calculated now, and the OnPathComplete callback has been called + /// + /// + /// See: This is equivalent to calling + /// See: + /// + public void BlockUntilCalculated () { + AstarPath.BlockUntilCalculated(this); + } + + public static readonly Unity.Profiling.ProfilerMarker MarkerOpenCandidateConnectionsToEnd = new Unity.Profiling.ProfilerMarker("OpenCandidateConnectionsToEnd"); + public static readonly Unity.Profiling.ProfilerMarker MarkerTrace = new Unity.Profiling.ProfilerMarker("Trace"); + + /// + /// Temporary data for a single path used during the pathfinding process. + /// It is used internally by the pathfinding algorithm to keep track of the current state of the pathfinding process, and provides various helper functions. + /// + /// See: + /// + [BurstCompile] + public struct SearchContext { + public Path path; + public PathHandler pathHandler; + /// + /// Target to use for H score calculations. + /// See: https://en.wikipedia.org/wiki/Admissible_heuristic + /// + public HeuristicObjective heuristicObjective; + public TraversalConstraint traversalConstraint; + public TraversalCosts traversalCosts; + public ushort pathID; + + /// + /// True if this path node might be worth exploring. + /// + /// This is used during a search to filter out nodes which have already been fully searched. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ShouldConsiderPathNode (uint pathNodeIndex) { + var node = pathHandler.pathNodes[pathNodeIndex]; + return node.pathID != pathID || node.heapIndex != BinaryHeap.NotInHeap; + } + + /// + /// Paths use this to skip adding nodes to the search heap. + /// + /// This is used by triangle nodes if they find an edge which is identical (but reversed) to an edge in an adjacent node. + /// This means that it cannot be better to visit the adjacent node's edge from any other way than what we are currently considering. + /// Therefore, instead of adding the node to the heap, only to pop it in the next iteration, we can skip that step and save some processing time. + /// + /// After calling this function, the skipped node should be immediately opened, so that it can be searched. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SkipOverNode (ref SearchContext ctx, uint pathNodeIndex, uint parentNodeIndex, uint fractionAlongEdge, uint hScore, uint gScore) { + ref var otherPathNode = ref pathHandler.pathNodes[pathNodeIndex]; + otherPathNode.pathID = pathID; + otherPathNode.heapIndex = BinaryHeap.NotInHeap; + otherPathNode.parentIndex = parentNodeIndex; + otherPathNode.fractionAlongEdge = fractionAlongEdge; + // Make sure the path gets information about us having visited this in-between node, + // even if we never add it to the heap + path.OnVisitNode(ref ctx, pathNodeIndex, hScore, gScore); + pathHandler.LogVisitedNode(pathNodeIndex, hScore, gScore); + } + + /// + /// Open a connection to the temporary end node if necessary. + /// + /// The start and end nodes are temporary nodes and are not included in the graph itself. + /// This means that we need to handle connections to and from those nodes as a special case. + /// This function will open a connection from the given node to the end node, if such a connection exists. + /// + /// It is called from the function. + /// + /// Position of the path node that is being opened. This may be different from the node's position if \reflink{PathNode.fractionAlongEdge} is being used. + /// Index of the path node that is being opened. This is often the same as parentNodeIndex, but may be different if the node has multiple path node variants. + /// Index of the node that is being opened. + /// G score of the parent node. The cost to reach the parent node from the start of the path. + /// Factor to multiply the cost of traversing the distance to the end node. This is used to account for different traversal costs for different nodes. + public void OpenCandidateConnectionsToEndNode (Int3 position, uint parentPathNode, uint parentNodeIndex, uint parentG, float traversalCostFactor) { + // True iff this node has a connection to one or more temporary nodes + if (pathHandler.pathNodes[parentNodeIndex].flag1) { + MarkerOpenCandidateConnectionsToEnd.Begin(); + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + if (node.type == TemporaryNodeType.End && node.associatedNode == parentNodeIndex) { + var cost = (uint)System.Math.Round((position - node.position).magnitude * traversalCostFactor); + OpenCandidateConnection(parentPathNode, nodeIndex, parentG + cost, 0, node.position); + } + } + MarkerOpenCandidateConnectionsToEnd.End(); + } + } + + /// + /// Opens a connection between two nodes during the A* search. + /// + /// When a node is "opened" (i.e. searched by the A* algorithm), it will open connections to all its neighbours. + /// This function checks those connections to see if passing through the node to its neighbour is the best way to reach the neighbour that we have seen so far, + /// and if so, it will push the neighbour onto the search heap. + /// + /// The node that is being opened. + /// A neighbour of the parent that is being considered. + /// The G value of the target node. This is the cost to reach the target node from the start of the path. Typically the parent's G score plus the cost to traverse the edge between the parent and target node. + /// Internal value used by the TriangleMeshNode to store where on the shared edge between the nodes we say we cross over. + /// The position of the target node. This is used by the heuristic to estimate the cost to reach the end node. + public void OpenCandidateConnection (uint parentPathNode, uint targetPathNode, uint targetG, uint fractionAlongEdge, Int3 targetNodePosition) { + if (!ShouldConsiderPathNode(targetPathNode)) { + // We have seen this node before, but it is not in the heap. + // This means we have already processed it and it must have had a better F score than this node (or the heuristic was not admissable). + // We can safely discard this connection. + return; + } + + var pars = new OpenCandidateParams { + pathID = pathID, + parentPathNode = parentPathNode, + targetPathNode = targetPathNode, + targetNodeIndex = pathHandler.IsTemporaryNode(targetPathNode) ? 0 : pathHandler.GetNode(targetPathNode).NodeIndex, + targetG = targetG, + fractionAlongEdge = fractionAlongEdge, + targetNodePosition = (int3)targetNodePosition, + pathNodes = pathHandler.pathNodes, + }; + OpenCandidateConnectionBurst(ref pars, + ref pathHandler.heap, ref heuristicObjective + ); + } + + /// + /// Parameters to OpenCandidateConnectionBurst. + /// Using a struct instead of passing the parameters as separate arguments is significantly faster. + /// + public struct OpenCandidateParams { + public UnsafeSpan pathNodes; + public uint parentPathNode; + public uint targetPathNode; + public uint targetNodeIndex; + public uint targetG; + public uint fractionAlongEdge; + public int3 targetNodePosition; + public ushort pathID; + } + + /// + /// Burst-compiled internal implementation of OpenCandidateConnection. + /// Compiling it using burst provides a decent 25% speedup. + /// The function itself is much faster, but the overhead of calling it from C# is quite significant. + /// + [BurstCompile] + public static void OpenCandidateConnectionBurst (ref OpenCandidateParams pars, ref BinaryHeap heap, ref HeuristicObjective heuristicObjective) { + var pathID = pars.pathID; + var parentPathNode = pars.parentPathNode; + var targetPathNode = pars.targetPathNode; + var targetG = pars.targetG; + var fractionAlongEdge = pars.fractionAlongEdge; + var targetNodePosition = pars.targetNodePosition; + var pathNodes = pars.pathNodes; + ref var target = ref pathNodes[targetPathNode]; + if (target.pathID != pathID) { + // This is the first time we have seen this node. This connection must be optimal. + target.fractionAlongEdge = fractionAlongEdge; + target.pathID = pathID; + target.parentIndex = parentPathNode; + var candidateH = (uint)heuristicObjective.Calculate(targetNodePosition, pars.targetNodeIndex); + heap.Add(pathNodes, targetPathNode, targetG, candidateH); + } else { + // Note: Before this method is called, a check is done for the case target.pathID==pathID && heapIndex == NotInHeap, + // so we know target.heapIndex != NotInHeap here. + + // We have seen this node before and it is in the heap. + // Now we check if this path to the target node is better than the previous one. + // The previous F score of the node + var previousF = heap.GetF(target.heapIndex); + var previousH = heap.GetH(target.heapIndex); + uint targetH; + + if (target.fractionAlongEdge != fractionAlongEdge) { + // Different fractionAlongEdge, this means that targetNodePosition may have changed + // and therefore the heuristic may also have changed. + targetH = (uint)heuristicObjective.Calculate(targetNodePosition, pars.targetNodeIndex); + } else { + // If fractionAlongEdge has not changed, then we assume the heuristic is also the same. + // This saves us from having to calculate it again. + targetH = previousH; + } + + var targetF = targetG + targetH; + if (targetF < previousF) { + // This connection is better than the previous one. + target.fractionAlongEdge = fractionAlongEdge; + target.parentIndex = parentPathNode; + heap.Add(pathNodes, targetPathNode, targetG, targetH); + } else { + // This connection is not better than the previous one. + // We can safely discard this connection. + } + } + } + + public readonly int3 FirstTemporaryEndNode () { + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + if (node.type == TemporaryNodeType.End) { + return (int3)node.position; + } + } + throw new System.InvalidOperationException("There are no end nodes in the path"); + } + + public readonly void TemporaryEndNodesBoundingBox (out int3 mn, out int3 mx) { + // These represent a bounding box containing all valid end points. + // Typically there's only one end point, but in some cases there can be more. + mn = (int3)int.MaxValue; + mx = (int3)int.MinValue; + + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + if (node.type == TemporaryNodeType.End) { + mn = math.min(mn, (int3)node.position); + mx = math.max(mx, (int3)node.position); + } + } + } + + public void MarkNodesAdjacentToTemporaryEndNodes () { + var pathNodes = pathHandler.pathNodes; + + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + if (node.type == TemporaryNodeType.End) { + // Mark node with flag1 to mark it as a node connected to an end node + var associatedNode = pathHandler.GetNode(node.associatedNode); + for (uint v = 0; v < associatedNode.PathNodeVariants; v++) { + pathNodes[node.associatedNode + v].flag1 = true; + } + } + } + } + + public void AddStartNodesToHeap () { + var pathNodes = pathHandler.pathNodes; + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + if (node.type == TemporaryNodeType.Start) { + // Note: Setting F score to 0 is technically incorrect, but it doesn't + // matter since we will open the start nodes first anyway. + pathHandler.heap.Add(pathNodes, nodeIndex, 0, 0); + } + } + } + } + + /// + /// Returns penalty for the given tag. + /// Deprecated: Use instead. + /// + /// A value between 0 (inclusive) and 32 (exclusive). + [System.Obsolete("Use traversalCosts instead")] + public uint GetTagPenalty (int tag) { + return traversalCosts.GetTagEntryCost((uint)tag); + } + + /// + /// Returns if the node can be traversed. + /// This by default equals to if the node is walkable and if the node's tag is included in . + /// + /// See: + /// Deprecated: Use instead. + /// + [System.Obsolete("Use traversalConstraint.CanTraverse instead")] + public bool CanTraverse (GraphNode node) { + return traversalConstraint.CanTraverse(node); + } + + + /// + /// Returns if the path can traverse a link between from and to and if to can be traversed itself. + /// This by default equals to if the to is walkable and if the to's tag is included in . + /// + /// See: + /// Deprecated: Use instead. + /// + [System.Obsolete("Use traversalConstraint.CanTraverse instead")] + public bool CanTraverse (GraphNode from, GraphNode to) { + return traversalConstraint.CanTraverse(from, to); + } + + /// + /// Returns the cost of traversing the given node. + /// + /// Deprecated: Use instead. + /// + [System.Obsolete("Use traversalCosts instead", true)] + public uint GetTraversalCost (GraphNode node) { + return 0; + } + + /// + /// True if this path is done calculating. + /// + /// Note: The callback for the path might not have been called yet. + /// + /// See: which also takes into account if the path callback has been called and had modifiers applied. + /// + public bool IsDone () { + return PipelineState > PathState.Processing; + } + + /// Threadsafe increment of the state + void IPathInternals.AdvanceState (PathState s) { + lock (this) { + PipelineState = (PathState)System.Math.Max((int)PipelineState, (int)s); + } + } + + /// Causes the path to fail and sets to msg + public void FailWithError (string msg) { + Error(); + if (errorLog != "") errorLog += "\n" + msg; + else errorLog = msg; + } + + /// + /// Aborts the path because of an error. + /// Sets to true. + /// This function is called when an error has occurred (e.g a valid path could not be found). + /// See: + /// + public void Error () { + CompleteState = PathCompleteState.Error; + } + + /// + /// Performs some error checking. + /// Makes sure the user isn't using old code paths and that no major errors have been made. + /// + /// Causes the path to fail if any errors are found. + /// + private void ErrorCheck () { + if (!hasBeenReset) FailWithError("Please use the static Construct function for creating paths, do not use the normal constructors."); + if (((IPathInternals)this).Pooled) FailWithError("The path is currently in a path pool. Are you sending the path for calculation twice?"); + if (PipelineState > PathState.Processing) FailWithError("This path has already been processed. Do not request a path with the same path object twice."); + CheckTraversalProviderCompatibility(); + } + +#if UNITY_EDITOR + static HashSet checkedTraversalProviders = new HashSet(); +#endif + + /// + /// Checks the traversal provider for compatibility with the current version of the A* Pathfinding Project. + /// + /// This is only done in the editor, and only once per traversal provider type. + /// If the traversal provider does not implement the latest version of the ITraversalProvider interface, an error will be logged. + /// + /// Note: This is not called in builds, so it will not affect performance in production. + /// + void CheckTraversalProviderCompatibility () { +#if UNITY_EDITOR + var tp = traversalConstraint.traversalProvider; + if (tp != null && checkedTraversalProviders.Add(tp.GetType())) { + #pragma warning disable CS0618 + bool fail = false; + + try { + tp.CanTraverse(null, null); + fail = true; + } catch (System.NotImplementedException) { + // Ok + } + catch { + fail = true; + } + + try { + tp.CanTraverse(null, null, null); + fail = true; + } catch (System.NotImplementedException) { + // Ok + } + catch { + fail = true; + } + + try { + tp.GetTraversalCost(null, null); + fail = true; + } catch (System.NotImplementedException) { + // Ok + } + catch { + fail = true; + } + + if (fail) { + FailWithError("The traversal provider " + tp.GetType().Name + " implements an old version of the ITraversalProvider interface. Please check the migration guide for version 5.4 for how to upgrade it."); + } + #pragma warning restore CS0618 + } +#endif + } + + /// + /// Called when the path enters the pool. + /// This method should release e.g pooled lists and other pooled resources + /// The base version of this method releases vectorPath and path lists. + /// Reset() will be called after this function, not before. + /// Warning: Do not call this function manually. + /// + protected virtual void OnEnterPool () { + if (vectorPath != null) Pathfinding.Pooling.ListPool.Release(ref vectorPath); + if (path != null) Pathfinding.Pooling.ListPool.Release(ref path); + // Clear the callback to remove a potential memory leak + // while the path is in the pool (which it could be for a long time). + callback = null; + immediateCallback = null; + traversalConstraint = TraversalConstraint.None; + traversalCosts = default; + UnityEngine.Assertions.Assert.IsTrue(claimCount == 0); + } + + /// + /// Reset all values to their default values. + /// + /// Note: All inheriting path types (e.g ConstantPath, RandomPath, etc.) which declare their own variables need to + /// override this function, resetting ALL their variables to enable pooling of paths. + /// If this is not done, trying to use that path type for pooling could result in weird behaviour. + /// The best way is to reset to default values the variables declared in the extended path type and then + /// call the base function in inheriting types with base.Reset(). + /// + protected virtual void Reset () { +#if ASTAR_POOL_DEBUG + pathTraceInfo = "This path was got from the pool or created from here (stacktrace):\n"; + pathTraceInfo += System.Environment.StackTrace; +#endif + + if (System.Object.ReferenceEquals(AstarPath.active, null)) + throw new System.NullReferenceException("No AstarPath object found in the scene. " + + "Make sure there is one or do not create paths in Awake"); + + hasBeenReset = true; + PipelineState = (int)PathState.Created; + releasedNotSilent = false; + + callback = null; + immediateCallback = null; + errorLog = ""; + completeState = PathCompleteState.NotCalculated; + + path = Pathfinding.Pooling.ListPool.Claim(); + vectorPath = Pathfinding.Pooling.ListPool.Claim(); + + duration = 0; + searchedNodes = 0; + + heuristic = AstarPath.active.heuristic; + heuristicScale = AstarPath.active.heuristicScale; + + pathID = AstarPath.active.GetNextPathID(); + + traversalConstraint = TraversalConstraint.None; + traversalCosts = new TraversalCosts(); + nearestNodeDistanceMetric = DistanceMetric.Euclidean; + } + + /// + /// List of claims on this path with reference objects. + /// We use an array instead of List to avoid an indirection and reduce memory usage a bit (actually has a measureable impact). + /// + private System.Object[] claimed = new System.Object[2]; + private int claimCount = 0; + + /// + /// True if the path has been released with a non-silent call yet. + /// + /// See: Release + /// See: Claim + /// + private bool releasedNotSilent; + + /// + /// Increase the reference count on this path by 1 (for pooling). + /// A claim on a path will ensure that it is not pooled. + /// If you are using a path, you will want to claim it when you first get it and then release it when you will not + /// use it anymore. When there are no claims on the path, it will be reset and put in a pool. + /// + /// This is essentially just reference counting. + /// + /// The object passed to this method is merely used as a way to more easily detect when pooling is not done correctly. + /// It can be any object, when used from a movement script you can just pass "this". This class will throw an exception + /// if you try to call Claim on the same path twice with the same object (which is usually not what you want) or + /// if you try to call Release with an object that has not been used in a Claim call for that path. + /// The object passed to the Claim method needs to be the same as the one you pass to this method. + /// + /// See: Release + /// See: Pool + /// See: pooling (view in online documentation for working links) + /// See: https://en.wikipedia.org/wiki/Reference_counting + /// + public void Claim (System.Object o) { + if (System.Object.ReferenceEquals(o, null)) throw new System.ArgumentNullException("o"); + + for (int i = 0; i < claimCount; i++) { + // Need to use ReferenceEquals because it might be called from another thread + if (System.Object.ReferenceEquals(claimed[i], o)) + throw new System.ArgumentException("You have already claimed the path with that object ("+o+"). Are you claiming the path with the same object twice?"); + } + + if (claimCount == claimed.Length) { + Util.Memory.Realloc(ref claimed, claimCount*2); +#if ASTAR_POOL_DEBUG + Util.Memory.Realloc(ref claimInfo, claimCount*2); +#endif + } + + claimed[claimCount] = o; +#if ASTAR_POOL_DEBUG + claimInfo[claimCount] = o.ToString() + "\n\nClaimed from:\n" + System.Environment.StackTrace; +#endif + claimCount++; + } + + /// + /// Reduces the reference count on the path by 1 (pooling). + /// Removes the claim on the path by the specified object. + /// When the reference count reaches zero, the path will be pooled, all variables will be cleared and the path will be put in a pool to be used again. + /// This is great for performance since fewer allocations are made. + /// + /// If the silent parameter is true, this method will remove the claim by the specified object + /// but the path will not be pooled if the claim count reches zero unless a non-silent Release call has been made earlier. + /// This is used by the internal pathfinding components such as Seeker and AstarPath so that they will not cause paths to be pooled. + /// This enables users to skip the claim/release calls if they want without the path being pooled by the Seeker or AstarPath and + /// thus causing strange bugs. + /// + /// See: Claim + /// See: PathPool + /// + public void Release (System.Object o, bool silent = false) { + for (int i = 0; i < claimCount; i++) { + // Need to use ReferenceEquals because it might be called from another thread + if (System.Object.ReferenceEquals(claimed[i], o)) { + claimCount--; + claimed[i] = claimed[claimCount]; + claimed[claimCount] = null; +#if ASTAR_POOL_DEBUG + claimInfo[i] = claimInfo[claimCount]; + claimInfo[claimCount] = null; +#endif + if (!silent) { + releasedNotSilent = true; + } + + if (claimCount == 0 && releasedNotSilent) { + PathPool.Pool(this); + } + return; + } + } + + if (o == null) throw new System.ArgumentNullException("o"); + + if (claimCount == 0) { + throw new System.ArgumentException("You are releasing a path which is not claimed at all (most likely it has been pooled already). " + + "Are you releasing the path with the same object ("+o+") twice?" + + "\nCheck out the documentation on path pooling for help."); + } + throw new System.ArgumentException("You are releasing a path which has not been claimed with this object ("+o+"). " + + "Are you releasing the path with the same object twice?\n" + + "Check out the documentation on path pooling for help."); + } + + /// + /// Traces the calculated path from the end node to the start. + /// This will build an array ( of the nodes this path will pass through and also set the array to the arrays positions. + /// Assumes the and are empty and not null (which will be the case for a correctly initialized path). + /// + protected virtual void Trace (ref SearchContext ctx, uint fromPathNodeIndex) { + Trace(ref ctx, fromPathNodeIndex, true); + } + + protected void Trace (ref SearchContext ctx, uint fromPathNodeIndex, bool reverse) { + MarkerTrace.Begin(); + // Current node we are processing + var c = fromPathNodeIndex; + int count = 0; + var pathHandler = ctx.pathHandler; + var pathNodes = pathHandler.pathNodes; + + while (c != 0) { + c = pathNodes[c].parentIndex; + count++; + if (count > 16384) { + Debug.LogWarning("Infinite loop? >16384 node path. Remove this message if you really have that long paths (Path.cs, Trace method)"); + break; + } + } + + // Ensure the lists have enough capacity + if (path.Capacity < count) path.Capacity = count; + UnityEngine.Assertions.Assert.AreEqual(0, path.Count); + + c = fromPathNodeIndex; + + GraphNode lastNode = null; + for (int i = 0; i < count; i++) { + GraphNode node; + if (pathHandler.IsTemporaryNode(c)) { + node = pathHandler.GetNode(pathHandler.GetTemporaryNode(c).associatedNode); + } else { + node = pathHandler.GetNode(c); + } + // If a node has multiple variants (like the triangle mesh node), then we may visit + // the same node multiple times in a sequence (but different variants of it). + // In the final path we don't want the duplicates. + if (node != lastNode) { + path.Add(node); + lastNode = node; + } + c = pathNodes[c].parentIndex; + } + + // Reverse to make the path go from the start to the end. + // Note: List.Reverse is way faster than a for loop, even for very small lists. + // It seems to call out to a native function. + if (reverse) path.Reverse(); + + count = path.Count; + if (vectorPath.Capacity < count) vectorPath.Capacity = count; + for (int i = 0; i < count; i++) { + vectorPath.Add((Vector3)path[i].position); + } + MarkerTrace.End(); + } + + /// + /// Writes text shared for all overrides of DebugString to the string builder. + /// See: DebugString + /// + protected void DebugStringPrefix (PathLog logMode, System.Text.StringBuilder text) { + text.Append(error ? "Path Failed : " : "Path Completed : "); + text.Append("Computation Time "); + text.Append(duration.ToString(logMode == PathLog.Heavy ? "0.000 ms " : "0.00 ms ")); + + text.Append("Searched Nodes ").Append(searchedNodes); + + if (!error) { + text.Append(" Path Length "); + text.Append(path == null ? "Null" : path.Count.ToString()); + } + } + + /// + /// Writes text shared for all overrides of DebugString to the string builder. + /// See: DebugString + /// + protected void DebugStringSuffix (PathLog logMode, System.Text.StringBuilder text) { + if (error) { + text.Append("\nError: ").Append(errorLog); + } + + // Can only print this from the Unity thread + // since otherwise an exception might be thrown + if (logMode == PathLog.Heavy && !AstarPath.active.IsUsingMultithreading) { + text.Append("\nCallback references "); + if (callback != null) text.Append(callback.Target.GetType().FullName).AppendLine(); + else text.AppendLine("NULL"); + } + + text.Append("\nUnique Path ID: ").Append(pathID); + } + + /// + /// Returns a string with information about it. + /// More information is emitted when logMode == Heavy. + /// An empty string is returned if logMode == None + /// or logMode == OnlyErrors and this path did not fail. + /// + protected virtual void DebugString (System.Text.StringBuilder builder, PathLog logMode) { + if (logMode == PathLog.None || (!error && logMode == PathLog.OnlyErrors)) { + return; + } + + DebugStringPrefix(logMode, builder); + DebugStringSuffix(logMode, builder); + } + + /// Calls callback to return the calculated path. See: + protected virtual void ReturnPath () { + if (callback != null) { + callback(this); + } + } + + /// + /// Prepares low level path variables for calculation. + /// Called before a path search will take place. + /// Always called before the Prepare, Initialize and CalculateStep functions + /// + protected void PrepareBase (PathHandler pathHandler) { + // Assign relevant path data to the pathHandler + pathHandler.InitializeForPath(this); + + try { + ErrorCheck(); + } catch (System.Exception e) { + FailWithError(e.Message); + } + } + + /// + /// Called before the path is started. + /// Called right before Initialize + /// + protected abstract void Prepare(ref SearchContext ctx); + + /// + /// Always called after the path has been calculated. + /// Guaranteed to be called before other paths have been calculated on + /// the same thread. + /// Use for cleaning up things like node tagging and similar. + /// + protected virtual void Cleanup (ref SearchContext ctx) { + // Cleanup any flags set by temporary nodes + var pathHandler = ctx.pathHandler; + var pathNodes = pathHandler.pathNodes; + for (uint i = 0; i < pathHandler.numTemporaryNodes; i++) { + var nodeIndex = pathHandler.temporaryNodeStartIndex + i; + ref var node = ref pathHandler.GetTemporaryNode(nodeIndex); + + var associatedNode = pathHandler.GetNode(node.associatedNode); + for (uint v = 0; v < associatedNode.PathNodeVariants; v++) { + pathNodes[node.associatedNode + v].flag1 = false; + pathNodes[node.associatedNode + v].flag2 = false; + } + } + } + + /// + /// Called when there are no more nodes to search. + /// + /// This may be used to calculate a partial path as a fallback. + /// + protected abstract void OnHeapExhausted(ref SearchContext ctx); + + /// + /// Called when a valid node has been found for the end of the path. + /// + /// This function should trace the path back to the start node, and set CompleteState to Complete. + /// If CompleteState is unchanged, the search will continue. + /// + protected abstract void OnFoundEndNode(ref SearchContext ctx, uint pathNode, uint hScore, uint gScore); + + /// + /// Called for every node that the path visits. + /// + /// This is used by path types to check if the target node has been reached, to log debug data, etc. + /// + public virtual void OnVisitNode (ref SearchContext ctx, uint pathNode, uint hScore, uint gScore) {} + + /// + /// Calculates the path until completed or until the time has passed targetTick. + /// Usually a check is only done every 500 nodes if the time has passed targetTick. + /// Time/Ticks are got from System.DateTime.UtcNow.Ticks. + /// + /// Basic outline of what the function does for the standard path (Pathfinding.ABPath). + /// + /// while the end has not been found and no error has occurred + /// pop the next node of the heap and set it as current + /// check if we have reached the end + /// if so, exit and return the path + /// + /// open the current node, i.e loop through its neighbours, mark them as visited and put them on a heap + /// + /// check if there are still nodes left to process (or have we searched the whole graph) + /// if there are none, flag error and exit + /// + /// check if the function has exceeded the time limit + /// if so, return and wait for the function to get called again + /// + /// + protected virtual void CalculateStep (ref Path.SearchContext ctx, long targetTick) { + int counter = 0; + var temporaryNodeStartIndex = ctx.pathHandler.temporaryNodeStartIndex; + + var pathHandler = ctx.pathHandler; + // Continue to search as long as we haven't encountered an error and we haven't found the target + while (CompleteState == PathCompleteState.NotCalculated) { + searchedNodes++; + + // Any nodes left to search? + if (pathHandler.heap.isEmpty) { + OnHeapExhausted(ref ctx); + return; + } + + // Select the node with the lowest F score and remove it from the open list + var currentPathNodeIndex = pathHandler.heap.Remove(pathHandler.pathNodes, out uint currentNodeG, out uint currentNodeH); + + if (currentPathNodeIndex >= temporaryNodeStartIndex) { + // This is a special node + var node = pathHandler.GetTemporaryNode(currentPathNodeIndex); + if (node.type == TemporaryNodeType.Start) { + // A start node. We should open the associated node at this point + pathHandler.GetNode(node.associatedNode).OpenAtPoint(ref ctx, currentPathNodeIndex, node.position, currentNodeG); + } else if (node.type == TemporaryNodeType.End) { + // An end node. Yay! We found the path we wanted. + // Now we can just trace the path back to the start and return that. + // However, some path types may choose to continue the search to find more end points (e.g. the multi target path). + { + // Make sure we visit the node associated with the end node. + // This is usually redundant, but it can matter in some cases. + // In particular, triangle mesh nodes can be opened in such a way that the temporary end node + // gets a lower F score than the individual sides of the triangle. This means that the temporary end + // node will be searched before the triangle sides are searched and that might complete the path. + // This would lead to us never actually calling LogVisitedNode for the triangle node, if we didn't have this code. + pathHandler.LogVisitedNode(node.associatedNode, currentNodeH, currentNodeG); + } + OnFoundEndNode(ref ctx, currentPathNodeIndex, currentNodeH, currentNodeG); + if (CompleteState == PathCompleteState.Complete) { + return; + } + } + } else { + pathHandler.LogVisitedNode(currentPathNodeIndex, currentNodeH, currentNodeG); + + OnVisitNode(ref ctx, currentPathNodeIndex, currentNodeH, currentNodeG); + + // Loop through all walkable neighbours of the node and add them to the open list. + var node = pathHandler.GetNode(currentPathNodeIndex); + node.Open(ref ctx, currentPathNodeIndex, currentNodeG); + } + + // Check for time every 500 nodes, roughly every 0.5 ms usually + if (counter > 500) { + // Have we exceded the maxFrameTime, if so we should wait one frame before continuing the search since we don't want the game to lag + if (System.DateTime.UtcNow.Ticks >= targetTick) { + return; + } + counter = 0; + + // Mostly for development + if (searchedNodes > 1000000) { + throw new System.Exception("Probable infinite loop. Over 1,000,000 nodes searched"); + } + } + + counter++; + } + } + + void IPathInternals.OnEnterPool () { OnEnterPool(); } + void IPathInternals.Reset () { Reset(); } + void IPathInternals.ReturnPath () { ReturnPath(); } + void IPathInternals.PrepareBase (PathHandler handler) { PrepareBase(handler); } + void IPathInternals.Prepare (ref Path.SearchContext ctx) { Prepare(ref ctx); } + void IPathInternals.Cleanup (ref Path.SearchContext ctx) { Cleanup(ref ctx); } + void IPathInternals.CalculateStep (ref Path.SearchContext ctx, long targetTick) { CalculateStep(ref ctx, targetTick); } + void IPathInternals.DebugString (System.Text.StringBuilder builder, PathLog logMode) { DebugString(builder, logMode); } + } + + /// Used for hiding internal methods of the Path class + internal interface IPathInternals { + bool Pooled { get; set; } + void AdvanceState(PathState s); + void OnEnterPool(); + void Reset(); + void ReturnPath(); + void PrepareBase(PathHandler handler); + void Prepare(ref Path.SearchContext ctx); + void Cleanup(ref Path.SearchContext ctx); + void CalculateStep(ref Path.SearchContext ctx, long targetTick); + void DebugString(System.Text.StringBuilder builder, PathLog logMode); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs.meta new file mode 100644 index 0000000..17cffcf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/Path.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b179046d83ec84f0c91efc5335f78a30 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs new file mode 100644 index 0000000..93acd35 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs @@ -0,0 +1,272 @@ +using System.Collections.Generic; +using Unity.Collections; +using UnityEngine; + +namespace Pathfinding { + using Pathfinding.Collections; + using Unity.Profiling; + + /// + /// Stores temporary node data for a single pathfinding request. + /// Every node has one PathNode per thread used. + /// It stores e.g G score, H score and other temporary variables needed + /// for path calculation, but which are not part of the graph structure. + /// + /// See: Pathfinding.PathHandler + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + public struct PathNode { + /// The path request (in this thread, if multithreading is used) which last used this node + public ushort pathID; + + /// + /// Index of the node in the binary heap. + /// The open list in the A* algorithm is backed by a binary heap. + /// To support fast 'decrease key' operations, the index of the node + /// is saved here. + /// + public ushort heapIndex; + + /// Bitpacked variable which stores several fields + private uint flags; + + public static readonly PathNode Default = new PathNode { pathID = 0, heapIndex = BinaryHeap.NotInHeap, flags = 0 }; + + /// Parent index uses the first 26 bits + private const uint ParentIndexMask = (1U << 26) - 1U; + + private const int FractionAlongEdgeOffset = 26; + private const uint FractionAlongEdgeMask = ((1U << 30) - 1U) & ~ParentIndexMask; + public const int FractionAlongEdgeQuantization = 1 << (30 - 26); + + public static uint ReverseFractionAlongEdge(uint v) => (FractionAlongEdgeQuantization - 1) - v; + + public static uint QuantizeFractionAlongEdge (float v) { + v *= FractionAlongEdgeQuantization - 1; + v += 0.5f; + return Unity.Mathematics.math.clamp((uint)v, 0, FractionAlongEdgeQuantization - 1); + } + + public static float UnQuantizeFractionAlongEdge (uint v) { + return (float)v * (1.0f / (FractionAlongEdgeQuantization - 1)); + } + + /// Flag 1 is at bit 30 + private const int Flag1Offset = 30; + private const uint Flag1Mask = 1U << Flag1Offset; + + /// Flag 2 is at bit 31 + private const int Flag2Offset = 31; + private const uint Flag2Mask = 1U << Flag2Offset; + + public uint fractionAlongEdge { + get => (flags & FractionAlongEdgeMask) >> FractionAlongEdgeOffset; + set => flags = (flags & ~FractionAlongEdgeMask) | ((value << FractionAlongEdgeOffset) & FractionAlongEdgeMask); + } + + public uint parentIndex { + get => flags & ParentIndexMask; + set => flags = (flags & ~ParentIndexMask) | value; + } + + /// + /// Use as temporary flag during pathfinding. + /// Path types can use this during pathfinding to mark + /// nodes. When done, this flag should be reverted to its default state (false) to + /// avoid messing up other pathfinding requests. + /// + public bool flag1 { + get => (flags & Flag1Mask) != 0; + set => flags = (flags & ~Flag1Mask) | (value ? Flag1Mask : 0U); + } + + /// + /// Use as temporary flag during pathfinding. + /// Path types can use this during pathfinding to mark + /// nodes. When done, this flag should be reverted to its default state (false) to + /// avoid messing up other pathfinding requests. + /// + public bool flag2 { + get => (flags & Flag2Mask) != 0; + set => flags = (flags & ~Flag2Mask) | (value ? Flag2Mask : 0U); + } + } + + public enum TemporaryNodeType { + Start, + End, + Ignore, + } + + public struct TemporaryNode { + public uint associatedNode; + public Int3 position; + public int targetIndex; + public TemporaryNodeType type; + } + + /// Handles thread specific path data. + public class PathHandler { + /// + /// Current PathID. + /// See: + /// + private ushort pathID; + + public readonly int threadID; + public readonly int totalThreadCount; + internal readonly GlobalNodeStorage nodeStorage; + public int numTemporaryNodes { [IgnoredByDeepProfiler] get; private set; } + + /// + /// All path nodes with an index greater or equal to this are temporary nodes that only exist for the duration of a single path. + /// + /// This is a copy of NodeStorage.nextNodeIndex. This is used to avoid having to access the NodeStorage while pathfinding as it's an extra indirection. + /// + public uint temporaryNodeStartIndex { [IgnoredByDeepProfiler] get; private set; } + UnsafeSpan temporaryNodes; + + /// + /// Reference to the per-node data for this thread. + /// + /// Note: Only guaranteed to point to a valid allocation while the path is being calculated. + /// + /// Be careful when storing copies of this array, as it may be re-allocated by the AddTemporaryNode method. + /// + public UnsafeSpan pathNodes; +#if UNITY_EDITOR + UnsafeSpan debugPathNodes; +#endif + + /// + /// Binary heap to keep track of nodes on the "Open list". + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + public BinaryHeap heap = new BinaryHeap(128); + + /// ID for the path currently being calculated or last path that was calculated + public ushort PathID { get { return pathID; } } + + /// + /// StringBuilder that paths can use to build debug strings. + /// Better for performance and memory usage to use a single StringBuilder instead of each path creating its own + /// + public readonly System.Text.StringBuilder DebugStringBuilder = new System.Text.StringBuilder(); + + internal PathHandler (GlobalNodeStorage nodeStorage, int threadID, int totalThreadCount) { + this.threadID = threadID; + this.totalThreadCount = totalThreadCount; + this.nodeStorage = nodeStorage; + temporaryNodes = default; + } + + public void InitializeForPath (Path p) { + var lastPathId = pathID; + pathID = p.pathID; + numTemporaryNodes = 0; + // Get the path nodes for this thread (may have been resized since last we calculated a path) + pathNodes = nodeStorage.pathfindingThreadData[threadID].pathNodes; + + // The index at which temporary nodes start. The GlobalNodeStorage allocates some extra path nodes + // for use as temporary nodes. This is usually a small number, as they are only needed for the start/end points, + // and for off-mesh links (in some cases). + // We could hypothetically start already at nodeStorage.nextNodeIndex, but this could lead to us + // allocating an unnecessarily large array for the #temporaryNodes array, which would be a waste of memory. + temporaryNodeStartIndex = nodeStorage.reservedPathNodeData; + // The number of temporary nodes we are allowed to use during this path calculation. + // Note: This value will never shrink between path calculations, because the GlobalNodeStorage will never + // reduce the number of temporary nodes it reserves. + var tempNodeCount = pathNodes.Length - (int)temporaryNodeStartIndex; + if (tempNodeCount > temporaryNodes.Length) { + temporaryNodes = temporaryNodes.Reallocate(Allocator.Persistent, tempNodeCount); + } + +#if UNITY_EDITOR + var astar = AstarPath.active; + var shouldLog = astar.showGraphs && (astar.debugMode == GraphDebugMode.F || astar.debugMode == GraphDebugMode.H || astar.debugMode == GraphDebugMode.G || astar.showSearchTree); + debugPathNodes = shouldLog ? nodeStorage.pathfindingThreadData[threadID].debugPathNodes : default; +#endif + + // Path IDs have overflowed 65K, cleanup is needed to avoid bugs where we think + // we have already visited a node when we haven't. + // Since pathIDs are handed out sequentially, we can check if the new path id + // is smaller than the last one. + if (pathID < lastPathId) { + ClearPathIDs(); + } + } + + /// + /// Returns the PathNode corresponding to the specified node. + /// The PathNode is specific to this PathHandler since multiple PathHandlers + /// are used at the same time if multithreading is enabled. + /// + public PathNode GetPathNode (GraphNode node, uint variant = 0) { + return pathNodes[node.NodeIndex + variant]; + } + + public bool IsTemporaryNode(uint pathNodeIndex) => pathNodeIndex >= temporaryNodeStartIndex; + + /// + /// Add a new temporary node for this path request. + /// + /// Warning: This may invalidate all memory references to path nodes in this path. + /// + public uint AddTemporaryNode (TemporaryNode node) { + if (numTemporaryNodes >= temporaryNodes.Length) { + // Reallocate the node storage to fit more temporary nodes + // This will invalidate all references to path nodes and temporary nodes, + // so we must ensure that no `ref` variables live across this call. + nodeStorage.GrowTemporaryNodeStorage(threadID); + pathNodes = nodeStorage.pathfindingThreadData[threadID].pathNodes; + temporaryNodes = temporaryNodes.Reallocate(Allocator.Persistent, pathNodes.Length - (int)temporaryNodeStartIndex); + } + + var index = temporaryNodeStartIndex + (uint)numTemporaryNodes; + temporaryNodes[numTemporaryNodes] = node; + pathNodes[index] = PathNode.Default; + numTemporaryNodes++; + return index; + } + + public GraphNode GetNode(uint nodeIndex) => nodeStorage.GetNode(nodeIndex); + + public ref TemporaryNode GetTemporaryNode (uint nodeIndex) { + if (nodeIndex < temporaryNodeStartIndex || nodeIndex >= temporaryNodeStartIndex + numTemporaryNodes) + throw new System.ArgumentOutOfRangeException(); + return ref temporaryNodes[(int)(nodeIndex - temporaryNodeStartIndex)]; + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public void LogVisitedNode (uint pathNodeIndex, uint h, uint g) { +#if UNITY_EDITOR + if (debugPathNodes.Length > 0 && !IsTemporaryNode(pathNodeIndex)) { + var parent = pathNodes[pathNodeIndex].parentIndex; + debugPathNodes[pathNodeIndex] = new GlobalNodeStorage.DebugPathNode { + h = h, + g = g, + parentIndex = parent >= temporaryNodeStartIndex ? 0 : parent, + pathID = pathID, + fractionAlongEdge = (byte)pathNodes[pathNodeIndex].fractionAlongEdge, + }; + } +#endif + } + + /// + /// Set all nodes' pathIDs to 0. + /// See: Pathfinding.PathNode.pathID + /// + public void ClearPathIDs () { + for (int i = 0; i < pathNodes.Length; i++) { + pathNodes[i].pathID = 0; + } + } + + public void Dispose () { + heap.Dispose(); + temporaryNodes.Free(Allocator.Persistent); + pathNodes = default; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs.meta new file mode 100644 index 0000000..b197ba3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathHandler.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7cc885607b0534cc6a4a34c4caa58d89 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs new file mode 100644 index 0000000..4f1ada1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs @@ -0,0 +1,547 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using UnityEngine.Profiling; +using UnityEngine.Assertions; +using Pathfinding.Sync; + +namespace Pathfinding { +#if NETFX_CORE + using Thread = Pathfinding.WindowsStore.Thread; +#else + using Thread = System.Threading.Thread; +#endif + + public class PathProcessor { + public event System.Action OnPathPreSearch; + public event System.Action OnPathPostSearch; + public event System.Action OnQueueUnblocked; + + internal BlockableChannel queue; + readonly AstarPath astar; + readonly PathReturnQueue returnQueue; + + PathHandler[] pathHandlers; + + /// References to each of the pathfinding threads + Thread[] threads; + bool multithreaded; + + /// + /// When no multithreading is used, the IEnumerator is stored here. + /// When no multithreading is used, a coroutine is used instead. It is not directly called with StartCoroutine + /// but a separate function has just a while loop which increments the main IEnumerator. + /// This is done so other functions can step the thread forward at any time, without having to wait for Unity to update it. + /// See: + /// See: + /// + IEnumerator threadCoroutine; + BlockableChannel.Receiver coroutineReceiver; + + readonly List locks = new List(); + int nextLockID = 0; + + static readonly Unity.Profiling.ProfilerMarker MarkerCalculatePath = new Unity.Profiling.ProfilerMarker("Calculating Path"); + static readonly Unity.Profiling.ProfilerMarker MarkerPreparePath = new Unity.Profiling.ProfilerMarker("Prepare Path"); + + /// + /// Number of parallel pathfinders. + /// Returns the number of concurrent processes which can calculate paths at once. + /// When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used). + /// See: threadInfos + /// See: IsUsingMultithreading + /// + public int NumThreads { + get { + return pathHandlers.Length; + } + } + + /// Returns whether or not multithreading is used + public bool IsUsingMultithreading { + get { + return multithreaded; + } + } + + internal PathProcessor (AstarPath astar, PathReturnQueue returnQueue, int processors, bool multithreaded) { + this.astar = astar; + this.returnQueue = returnQueue; + + // Set up path queue with the specified number of receivers + queue = new BlockableChannel(); + threads = null; + threadCoroutine = null; + pathHandlers = new PathHandler[0]; + } + + /// + /// Changes the number of threads used for pathfinding. + /// + /// If multithreading is disabled, processors must be equal to 1. + /// + public void SetThreadCount (int processors, bool multithreaded) { + if (threads != null || threadCoroutine != null || pathHandlers.Length > 0) throw new System.Exception("Call StopThreads before setting the thread count"); + + if (processors < 1) { + throw new System.ArgumentOutOfRangeException("processors"); + } + + if (!multithreaded && processors != 1) { + throw new System.Exception("Only a single non-multithreaded processor is allowed"); + } + + pathHandlers = new PathHandler[processors]; + this.multithreaded = multithreaded; + + for (int i = 0; i < processors; i++) { + pathHandlers[i] = new PathHandler(astar.nodeStorage, i, processors); + } + astar.nodeStorage.SetThreadCount(processors); + StartThreads(); + } + + void StartThreads () { + if (threads != null || threadCoroutine != null) throw new System.Exception("Call StopThreads before starting threads"); + + queue.Reopen(); + + // Ensure the node storage is up to date. + // Per-thread data may have been cleared if the AstarPath object + // was disabled. + astar.nodeStorage.SetThreadCount(pathHandlers.Length); + + if (multithreaded) { + threads = new Thread[this.pathHandlers.Length]; + + // Start lots of threads + for (int i = 0; i < this.pathHandlers.Length; i++) { + var pathHandler = pathHandlers[i]; + var receiver = queue.AddReceiver(); + threads[i] = new Thread(() => CalculatePathsThreaded(pathHandler, receiver)); +#if !UNITY_SWITCH || UNITY_EDITOR + // Note: Setting the thread name seems to crash when deploying for Switch: https://forum.arongranberg.com/t/path-processor-crashing-nintendo-switch-build/6584 + threads[i].Name = "Pathfinding Thread " + i; +#endif + threads[i].IsBackground = true; + threads[i].Start(); + } + } else { + coroutineReceiver = queue.AddReceiver(); + // Start coroutine if not using multithreading + threadCoroutine = CalculatePaths(pathHandlers[0]); + } + } + + /// Prevents pathfinding from running while held + public struct GraphUpdateLock : System.IDisposable { + PathProcessor pathProcessor; + int id; + + public GraphUpdateLock (PathProcessor pathProcessor, bool block) { + this.pathProcessor = pathProcessor; + Profiler.BeginSample("Pausing pathfinding"); + id = pathProcessor.Lock(block); + Profiler.EndSample(); + } + + /// + /// True while this lock is preventing the pathfinding threads from processing more paths. + /// Note that the pathfinding threads may not be paused yet (if this lock was obtained using PausePathfinding(false)). + /// + public bool Held => pathProcessor != null && pathProcessor.locks.Contains(id); + + /// Allow pathfinding to start running again if no other locks are still held + public void Release() => pathProcessor.Unlock(id); + + void System.IDisposable.Dispose () { + Release(); + } + } + + int Lock (bool block) { + queue.isBlocked = true; + if (block) { + while (!queue.allReceiversBlocked) { + Assert.IsTrue(threads != null || threadCoroutine != null); + if (IsUsingMultithreading) { + Thread.Sleep(1); + } else { + TickNonMultithreaded(); + } + } + } + + nextLockID++; + locks.Add(nextLockID); + return nextLockID; + } + + void Unlock (int id) { + if (!locks.Remove(id)) { + throw new System.ArgumentException("This lock has already been released"); + } + + // Check if there are no remaining active locks + if (locks.Count == 0) { + if (OnQueueUnblocked != null) OnQueueUnblocked(); + + queue.isBlocked = false; + } + } + + /// + /// Prevents pathfinding threads from starting to calculate any new paths. + /// + /// Returns: A lock object. You need to call Unlock on that object to allow pathfinding to resume. + /// + /// Note: In most cases this should not be called from user code. + /// + /// If true, this call will block until all pathfinding threads are paused. + /// otherwise the threads will be paused as soon as they are done with what they are currently doing. + public GraphUpdateLock PausePathfinding (bool block) { + return new GraphUpdateLock(this, block); + } + + /// + /// Does pathfinding calculations when not using multithreading. + /// + /// This method should be called once per frame if is true. + /// + public void TickNonMultithreaded () { + // Process paths + if (threadCoroutine == null) throw new System.InvalidOperationException("Cannot tick non-multithreaded pathfinding when no coroutine has been started"); + + try { + if (!threadCoroutine.MoveNext()) { + threadCoroutine = null; + coroutineReceiver.Close(); + } + } catch (System.Exception e) { + Debug.LogException(e); + Debug.LogError("Unhandled exception during pathfinding. Terminating."); + queue.Close(); + + // This will kill pathfinding + threadCoroutine = null; + coroutineReceiver.Close(); + } + } + + /// + /// Calls 'Join' on each of the threads to block until they have completed. + /// + /// This will also clean up any unmanaged memory used by the threads. + /// + public void StopThreads () { + // Don't accept any more path calls to this AstarPath instance. + // This will cause all pathfinding threads to exit (if any exist) + queue.Close(); + + if (threads != null) { + for (int i = 0; i < threads.Length; i++) { + if (!threads[i].Join(200)) { + Debug.LogError("Could not terminate pathfinding thread["+i+"] in 200ms, trying Thread.Abort"); + threads[i].Abort(); + } + } + threads = null; + } + if (threadCoroutine != null) { + Assert.IsTrue(queue.numReceivers > 0); + while (queue.numReceivers > 0) TickNonMultithreaded(); + Assert.IsNull(threadCoroutine); + } + + Assert.AreEqual(queue.numReceivers, 0, "Not all receivers were blocked and terminated when stopping threads"); + + // Dispose unmanaged data + for (int i = 0; i < pathHandlers.Length; i++) { + pathHandlers[i].Dispose(); + } + pathHandlers = new PathHandler[0]; + } + + /// + /// Cleans up all native memory managed by this instance. + /// + /// You may use this instance again by calling SetThreadCount. + /// + public void Dispose () { + StopThreads(); + } + + /// + /// Main pathfinding method (multithreaded). + /// This method will calculate the paths in the pathfinding queue when multithreading is enabled. + /// + /// See: CalculatePaths + /// See: + /// + void CalculatePathsThreaded (PathHandler pathHandler, BlockableChannel.Receiver receiver) { + UnityEngine.Profiling.Profiler.BeginThreadProfiling("Pathfinding", "Pathfinding thread #" + (pathHandler.threadID+1)); + + try { + // Max number of ticks we are allowed to continue working in one run. + // One tick is 1/10000 of a millisecond. + // We need to check once in a while if the thread should be stopped. + long maxTicks = (long)(10*10000); + long targetTick = System.DateTime.UtcNow.Ticks + maxTicks; + while (true) { + // The path we are currently calculating + if (receiver.Receive(out var path) == BlockableChannel.PopState.Closed) { + if (astar.logPathResults == PathLog.Heavy) + Debug.LogWarning("Shutting down pathfinding thread #" + pathHandler.threadID); + receiver.Close(); + return; + } + MarkerCalculatePath.Begin(); + // Access the internal implementation methods + IPathInternals ipath = (IPathInternals)path; + + + MarkerPreparePath.Begin(); + ipath.PrepareBase(pathHandler); + + // Now processing the path + // Will advance to Processing + ipath.AdvanceState(PathState.Processing); + + // Call some callbacks + if (OnPathPreSearch != null) { + OnPathPreSearch(path); + } + + // Tick for when the path started, used for calculating how long time the calculation took + long startTicks = System.DateTime.UtcNow.Ticks; + + var searchContext = new Path.SearchContext { + pathHandler = pathHandler, + path = path, + pathID = path.pathID, + }; + + // Prepare the path + ipath.Prepare(ref searchContext); + + // When using a heuristic, break ties using the H score. + // When not using a heuristic, break ties by the insertion order of the nodes. + // This will make paths a lot prettier, especially on grid graphs. + pathHandler.heap.tieBreaking = searchContext.heuristicObjective.hasHeuristic ? BinaryHeap.TieBreaking.HScore : BinaryHeap.TieBreaking.InsertionOrder; + MarkerPreparePath.End(); + + + if (path.CompleteState == PathCompleteState.NotCalculated) { + // For visualization purposes, we set the last computed path to p, so we can view debug info on it in the editor (scene view). + astar.debugPathData = pathHandler; + astar.debugPathID = searchContext.pathID; + + // Loop while the path has not been fully calculated + while (path.CompleteState == PathCompleteState.NotCalculated) { + // Do some work on the path calculation. + // The function will return when it has taken too much time + // or when it has finished calculation + ipath.CalculateStep(ref searchContext, targetTick); + + targetTick = System.DateTime.UtcNow.Ticks + maxTicks; + + // Cancel function (and thus the thread) if no more paths should be accepted. + // This is done when the A* object is about to be destroyed + // The path is returned and then this function will be terminated + if (queue.isClosed) { + path.FailWithError("AstarPath object destroyed"); + } + } + + path.duration = (System.DateTime.UtcNow.Ticks - startTicks)*0.0001F; + +#if ProfileAstar + System.Threading.Interlocked.Increment(ref AstarPath.PathsCompleted); + System.Threading.Interlocked.Add(ref AstarPath.TotalSearchTime, System.DateTime.UtcNow.Ticks - startTicks); +#endif + } + + // Cleans up node tagging and other things + ipath.Cleanup(ref searchContext); + pathHandler.heap.Clear(pathHandler.pathNodes); + + + if (path.immediateCallback != null) path.immediateCallback(path); + + if (OnPathPostSearch != null) { + OnPathPostSearch(pathHandler, path); + } + + // Push the path onto the return stack + // It will be detected by the main Unity thread and returned as fast as possible (the next late update hopefully) + returnQueue.Enqueue(path); + + // Will advance to ReturnQueue + ipath.AdvanceState(PathState.ReturnQueue); + + MarkerCalculatePath.End(); + } + } catch (System.Exception e) { +#if !NETFX_CORE + if (e is ThreadAbortException) { + if (astar.logPathResults == PathLog.Heavy) + Debug.LogWarning("Shutting down pathfinding thread #" + pathHandler.threadID); + receiver.Close(); + return; + } +#endif + + Debug.LogException(e); + Debug.LogError("Unhandled exception during pathfinding. Terminating."); + // Unhandled exception, kill pathfinding + queue.Close(); + } finally { + UnityEngine.Profiling.Profiler.EndThreadProfiling(); + } + + Debug.LogError("Error : This part should never be reached."); + receiver.Close(); + } + + /// + /// Main pathfinding method. + /// This method will calculate the paths in the pathfinding queue. + /// + /// See: CalculatePathsThreaded + /// See: StartPath + /// + IEnumerator CalculatePaths (PathHandler pathHandler) { + // Max number of ticks before yielding/sleeping + long maxTicks = (long)(astar.maxFrameTime*10000); + long targetTick = System.DateTime.UtcNow.Ticks + maxTicks; + + while (true) { + // The path we are currently calculating + Path p = null; + + // Try to get the next path to be calculated + bool blockedBefore = false; + while (p == null) { + switch (coroutineReceiver.ReceiveNoBlock(blockedBefore, out p)) { + case BlockableChannel.PopState.Ok: + break; + case BlockableChannel.PopState.Wait: + blockedBefore = true; + yield return null; + break; + case BlockableChannel.PopState.Closed: + yield break; + } + } + + IPathInternals ip = (IPathInternals)p; + + // Max number of ticks we are allowed to use for pathfinding in one frame + // One tick is 1/10000 of a millisecond + maxTicks = (long)(astar.maxFrameTime*10000); + + ip.PrepareBase(pathHandler); + + // Now processing the path + // Will advance to Processing + ip.AdvanceState(PathState.Processing); + + // Call some callbacks + // It needs to be stored in a local variable to avoid race conditions + var tmpOnPathPreSearch = OnPathPreSearch; + if (tmpOnPathPreSearch != null) tmpOnPathPreSearch(p); + + // Tick for when the path started, used for calculating how long time the calculation took + long startTicks = System.DateTime.UtcNow.Ticks; + long totalTicks = 0; + + var searchContext = new Path.SearchContext { + pathHandler = pathHandler, + path = p, + pathID = p.pathID, + }; + + ip.Prepare(ref searchContext); + + // When using a heuristic, break ties using the H score. + // When not using a heuristic, break ties by the insertion order of the nodes. + // This will make paths a lot prettier, especially on grid graphs. + pathHandler.heap.tieBreaking = searchContext.heuristicObjective.hasHeuristic ? BinaryHeap.TieBreaking.HScore : BinaryHeap.TieBreaking.InsertionOrder; + + // Check if the Prepare call caused the path to complete + // If this happens the path usually failed + if (p.CompleteState == PathCompleteState.NotCalculated) { + // For debug uses, we set the last computed path to p, so we can view debug info on it in the editor (scene view). + astar.debugPathData = searchContext.pathHandler; + astar.debugPathID = searchContext.pathID; + + // The error can turn up in the Init function + while (p.CompleteState == PathCompleteState.NotCalculated) { + // Run some pathfinding calculations. + // The function will return when it has taken too much time + // or when it has finished calculating the path. + ip.CalculateStep(ref searchContext, targetTick); + + + // If the path has finished calculating, we can break here directly instead of sleeping + // Improves latency + if (p.CompleteState != PathCompleteState.NotCalculated) break; + + totalTicks += System.DateTime.UtcNow.Ticks-startTicks; + // Yield/sleep so other threads can work + + yield return null; + + startTicks = System.DateTime.UtcNow.Ticks; + + // Cancel function (and thus the thread) if no more paths should be accepted. + // This is done when the A* object is about to be destroyed + // The path is returned and then this function will be terminated (see similar IF statement higher up in the function) + if (queue.isClosed) { + p.FailWithError("AstarPath object destroyed"); + } + + targetTick = System.DateTime.UtcNow.Ticks + maxTicks; + } + + totalTicks += System.DateTime.UtcNow.Ticks-startTicks; + p.duration = totalTicks*0.0001F; + +#if ProfileAstar + System.Threading.Interlocked.Increment(ref AstarPath.PathsCompleted); +#endif + } + + // Cleans up node tagging and other things + ip.Cleanup(ref searchContext); + pathHandler.heap.Clear(pathHandler.pathNodes); + + + // Call the immediate callback + // It needs to be stored in a local variable to avoid race conditions + var tmpImmediateCallback = p.immediateCallback; + if (tmpImmediateCallback != null) tmpImmediateCallback(p); + + + // It needs to be stored in a local variable to avoid race conditions + var tmpOnPathPostSearch = OnPathPostSearch; + if (tmpOnPathPostSearch != null) tmpOnPathPostSearch(pathHandler, p); + + + // Push the path onto the return stack + // It will be detected by the main Unity thread and returned as fast as possible (the next late update) + returnQueue.Enqueue(p); + + ip.AdvanceState(PathState.ReturnQueue); + + + // Wait a bit if we have calculated a lot of paths + if (System.DateTime.UtcNow.Ticks > targetTick) { + yield return null; + targetTick = System.DateTime.UtcNow.Ticks + maxTicks; + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs.meta new file mode 100644 index 0000000..764f634 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f1b519f8b6e2c450aaae5cb2df6c73be +timeCreated: 1443114816 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs new file mode 100644 index 0000000..12ead96 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs @@ -0,0 +1,93 @@ +using UnityEngine; +using System.Collections.Generic; +using UnityEngine.Profiling; +using Pathfinding.Util; + +namespace Pathfinding { + class PathReturnQueue { + /// + /// Holds all paths which are waiting to be flagged as completed. + /// See: + /// + Queue pathReturnQueueWriting = new Queue(); + Queue pathReturnQueueReading = new Queue(); + + /// + /// Paths are claimed silently by some object to prevent them from being recycled while still in use. + /// This will be set to the AstarPath object. + /// + readonly System.Object pathsClaimedSilentlyBy; + + readonly System.Action OnReturnedPaths; + + public PathReturnQueue (System.Object pathsClaimedSilentlyBy, System.Action OnReturnedPaths) { + this.pathsClaimedSilentlyBy = pathsClaimedSilentlyBy; + this.OnReturnedPaths = OnReturnedPaths; + } + + public void Enqueue (Path path) { + lock (this) { + pathReturnQueueWriting.Enqueue(path); + } + } + + /// + /// Returns all paths in the return stack. + /// Paths which have been processed are put in the return stack. + /// This function will pop all items from the stack and return them to e.g the Seeker requesting them. + /// + /// Do not return all paths at once if it takes a long time, instead return some and wait until the next call. + public void ReturnPaths (bool timeSlice) { + Profiler.BeginSample("Calling Path Callbacks"); + + // Hard coded limit on 1.0 ms + long targetTick = timeSlice ? System.DateTime.UtcNow.Ticks + 1 * 10000 : 0; + + int counter = 0; + int totalReturned = 0; + + // Go through all paths that have been calculated by the pathfinding threads and call their callbacks to indicate that they are calculated + while (true) { + if (pathReturnQueueReading.Count == 0) { + // Swap queues + lock (this) { + // We use double-buffering to avoid having to take a lock every single iteration. + // This way, we only need to take the lock twice per frame. + Memory.Swap(ref pathReturnQueueReading, ref pathReturnQueueWriting); + } + + if (pathReturnQueueReading.Count == 0) break; + } + + // Move to the next path + Path path = pathReturnQueueReading.Dequeue(); + + ((IPathInternals)path).AdvanceState(PathState.Returning); + + try { + // Return the path + ((IPathInternals)path).ReturnPath(); + } catch (System.Exception e) { + Debug.LogException(e); + } + + ((IPathInternals)path).AdvanceState(PathState.Returned); + + path.Release(pathsClaimedSilentlyBy, true); + + counter++; + totalReturned++; + // At least 5 paths will be returned, even if timeSlice is enabled + if (counter > 5 && timeSlice) { + counter = 0; + if (System.DateTime.UtcNow.Ticks >= targetTick) { + break; + } + } + } + + if (totalReturned > 0) OnReturnedPaths(); + Profiler.EndSample(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs.meta new file mode 100644 index 0000000..4181918 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/PathReturnQueue.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 70ca370ae38794366be2fa32880f362e +timeCreated: 1443114816 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs new file mode 100644 index 0000000..6be4deb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs @@ -0,0 +1,295 @@ +using System.Runtime.CompilerServices; + +namespace Pathfinding { + /// + /// Specifies which nodes are traversable and which are not. + /// + /// This struct is used to specify which nodes are traversable when searching for paths and when doing linecasts on graphs, among other things. + /// + /// It can be used to filter out nodes based on their tags, or even using completely custom filters. + /// + /// Note: For performance reasons, this struct is often passed by reference (using the ref keyword). It's a moderately large struct, and it is often used in performance-critical code. + /// Passing it by reference avoids copying the struct, and gives us some moderate performance improvements. + /// + /// + /// // Custom filter + /// var traversalConstraint = new TraversalConstraint((GraphNode node) => ((Vector3)node.position).y > 50); + /// + /// traversalConstraint.tags = 1 << 3; // Only allow traversing nodes with tag 3 + /// + /// // Convert to nearest node constraint + /// var nearestNodeConstraint = traversalConstraint.ToNearestNodeConstraint(); + /// var node = AstarPath.active.GetNearest(transform.position, nearestNodeConstraint).node; + /// + /// // Check if the node is traversable + /// traversalConstraint.CanTraverse(node); // True + /// + /// + /// + /// var graph = AstarPath.active.data.recastGraph; + /// var start = transform.position; + /// var end = start + Vector3.forward * 10; + /// var trace = new List(); + /// + /// var traversalConstraint = TraversalConstraint.None; + /// traversalConstraint.tags = 1 << 3; // Only allow traversing nodes with tag 3 + /// + /// if (graph.Linecast(start, end, out GraphHitInfo hit, ref traversalConstraint, trace)) { + /// Debug.Log("Linecast traversed " + trace.Count + " nodes before hitting an obstacle"); + /// Debug.DrawLine(start, hit.point, Color.red); + /// Debug.DrawLine(hit.point, end, Color.blue); + /// } else { + /// Debug.Log("Linecast traversed " + trace.Count + " nodes"); + /// Debug.DrawLine(start, end, Color.green); + /// } + /// + /// + /// + /// ABPath path = ABPath.Construct(currentPosition, destination, null); + /// + /// path.traversalConstraint.traversalProvider = GridShapeTraversalProvider.SquareShape(3); + /// + /// // If you are writing your own movement script + /// seeker.StartPath(path); + /// + /// // If you are using an existing movement script (you may also want to set ai.canSearch to false) + /// // ai.SetPath(path); + /// + /// + /// See: + /// See: + /// See: + /// + public struct TraversalConstraint { + internal object filterObj; + + /// + /// Set of tags which are traversable. + /// + /// This is a bitmask, i.e bit 0 indicates that tag 0 is traversable, bit 3 indicates tag 3 is traversable etc. + /// + /// By default all tags are traversable, which corresponds to the value -1. + /// + /// + /// var traversalConstraint = TraversalConstraint.None; + /// + /// // Allows the first and third tags to be searched, but not the rest + /// traversalConstraint.tags = (1 << 0) | (1 << 2); + /// + /// // Allows the two named tags to be searched, but not the rest + /// traversalConstraint.tags = PathfindingTag.FromName("Grass").ToMask() | PathfindingTag.FromName("Lava").ToMask(); + /// + /// + /// If you are using the Seeker or FollowerEntity components, you can access this in the inspector via the tags foldout: + /// [Open online documentation to see images] + /// + /// See: + /// See: tags (view in online documentation for working links) + /// See: bitmasks (view in online documentation for working links) + /// See: + /// + public int tags; + + /// + /// %Graphs which are traversable. + /// + /// This is a bitmask, meaning that bit 0 specifies whether or not the first graph in the graphs list should be included in the search, + /// bit 1 specifies whether or not the second graph should be included, and so on. + /// + /// + /// var traversalConstraint = TraversalConstraint.None; + /// + /// // Allows the first and fourth graphs to be searched, but not the rest + /// traversalConstraint.graphMask = GraphMask.FromGraphIndex(0) | GraphMask.FromGraphIndex(3); + /// + /// // Allows the two named graphs to be searched, but not the rest + /// traversalConstraint.graphMask = GraphMask.FromGraphName("My Custom Graph Name") | GraphMask.FromGraphName("My Other Graph"); + /// + /// + /// Note: When used for pathfinding, this does only affects which nodes the path starts and ends on. If a valid graph is connected to an "invalid" graph, it may get searched anyway. + /// + /// See: + /// See: + /// See: bitmasks (view in online documentation for working links) + /// + public GraphMask graphMask; + internal FilterType filterType; + + internal enum FilterType { + None, + TraversalProvider, + Func, + } + + /// + /// Allow traversing only nodes that are also traversable by the given . + /// + /// The other constraints on this struct will still apply, in addition to the ITraversalProvider. + /// + /// Note: Either an ITraversalProvider or a callback filter () can be set, but not both. + /// Setting this will clear , and vice versa. + /// + /// See: + /// See: traversal_provider (view in online documentation for working links) + /// See: + /// + public ITraversalProvider traversalProvider { + get => filterObj as ITraversalProvider; + set { + filterObj = value; + filterType = value != null ? FilterType.TraversalProvider : FilterType.None; + } + } + + /// + /// Allow traversing only nodes which the given filter function returns true for. + /// + /// The other constraints on this struct will still apply, in addition to the filter function. + /// + /// Note: Either an ITraversalProvider ( or a filter function can be set, but not both. + /// Setting this will clear , and vice versa. + /// + /// + /// var traversalConstraint = TraversalConstraint.None; + /// + /// // Set a custom filter. This can be an arbitrary function which takes a GraphNode and returns true or false. + /// traversalConstraint.filter = (GraphNode node) => ((Vector3)node.position).y > 50; + /// + /// // Search for a path which only traverses nodes above y=50 + /// var path = ABPath.Construct(transform.position, transform.position + Vector3.forward * 10); + /// path.traversalConstraint = traversalConstraint; + /// AstarPath.StartPath(path); + /// + /// + /// See: + /// + public System.Func filter { + get => filterObj as System.Func; + set { + filterObj = value; + filterType = value != null ? FilterType.Func : FilterType.None; + } + } + + /// + /// Traversal constraint which allows all (walkable) nodes on all graphs to be traversed. + /// + /// Note: Unwalkable () nodes can never be traversed, regardless of the settings in this struct. + /// + /// The returned struct's method will return true for all nodes except those with the property set to false. + /// + public static readonly TraversalConstraint None = new TraversalConstraint { + tags = ~0, + graphMask = GraphMask.everything + }; + + /// + /// Filter diagonal connections on grid graphs using for effects applied by any assigned ITraversalProvider. + /// If a traversal provider is used, returns for it. + /// Otherwise returns true. + /// + /// See: + /// + public bool filterDiagonalGridConnections => filterType != FilterType.TraversalProvider || Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref filterObj).filterDiagonalGridConnections; + + /// + /// Create a new traversal constraint with the given filter function. + /// + /// See: + /// + public TraversalConstraint(System.Func filter) { + this = None; + this.filter = filter; + } + + /// + /// Create a new traversal constraint using the given . + /// + /// See: + /// + public TraversalConstraint(ITraversalProvider traversalProvider) { + this = None; + this.traversalProvider = traversalProvider; + } + + /// + /// True if the node is traversable with respect to the constraints set in this struct. + /// + /// Note: The is not checked. It is assumed that graph filtering is done before this method is called. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanTraverse (GraphNode node) { + if (!CanTraverseSkipUserFilter(node)) return false; + + switch (filterType) { + case FilterType.TraversalProvider: + // This is a hot method, so we want to avoid all checks that we possibly can. + // This cast is guaranteed to succeed, so we can skip the check using an unsafe cast. + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref filterObj).CanTraverse(ref this, node); + case FilterType.Func: + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As >(ref filterObj)(node); + default: + return true; + } + } + + /// + /// True if the connection between from and to is traversable with respect to the constraints set in this struct. + /// + /// This is identical to CanTraverse(to), unless an ITraversalProvider is used, which may apply different rules for traversal between two nodes. + /// + /// Note: It is assumed that the from node is traversable, since the agent came from there. The rules are not checked against it for performance reasons. + /// + /// Note: The is not checked. It is assumed that graph filtering is done before this method is called. + /// + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanTraverse (GraphNode from, GraphNode to) { + if (!CanTraverseSkipUserFilter(to)) return false; + + switch (filterType) { + case FilterType.TraversalProvider: + // This is a hot method, so we want to avoid all checks that we possibly can. + // This cast is guaranteed to succeed, so we can skip the check using an unsafe cast. + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As(ref filterObj).CanTraverse(ref this, from, to); + case FilterType.Func: + return Unity.Collections.LowLevel.Unsafe.UnsafeUtility.As >(ref filterObj)(to); + default: + return true; + } + } + + /// + /// True if the node is traversable with respect to the constraints set in this struct, but without checking the or the . + /// This method can be used from the to check additional nodes without causing infinite recursion. + /// + /// Note: This method does not check the or the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanTraverseSkipUserFilter(GraphNode node) => node.Walkable && ((tags >> (int)node.Tag & 0x1) != 0); + + /// + /// A nearest node constraint which uses the same settings as this traversal constraint. + /// + /// + /// var nearestNodeConstraint = traversalConstraint.ToNearestNodeConstraint(); + /// + /// // Use the nearest node constraint to find the closest node + /// var nearest = AstarPath.active.GetNearest(transform.position, nearestNodeConstraint); + /// + /// + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NearestNodeConstraint ToNearestNodeConstraint () { + return new NearestNodeConstraint { + traversal = this, + distanceMetric = DistanceMetric.Euclidean, + area = -1, + maxDistanceSqr = -1, + walkable = NearestNodeConstraint.WalkabilityConstraint.Walkable + }; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs.meta new file mode 100644 index 0000000..222ee95 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 027dccc49c45c584abcbe03c41320e14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs new file mode 100644 index 0000000..a41b373 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs @@ -0,0 +1,239 @@ +using System.Runtime.CompilerServices; + +namespace Pathfinding { + /// + /// Defines the cost of traversing specific nodes when pathfinding. + /// + /// To make agents avoid certain nodes, you can set a high penalty for traversing them. + /// This will make the pathfinding algorithm prefer other nodes instead. + /// + /// You can set costs based on the node's tag, or you can use a custom traversal provider to set costs based on other criteria with completely custom logic. + /// + /// + /// var traversalCosts = new TraversalCosts(); + /// + /// // Make tags 1 and 4 have a higher penalty + /// var tagCostMultipliers = new float[32]; + /// tagCostMultipliers[1] = 2.5f; + /// tagCostMultipliers[4] = 5.0f; + /// traversalCosts.tagCostMultipliers = tagCostMultipliers; + /// + /// var path = ABPath.Construct(Vector3.zero, Vector3.one); + /// path.traversalCosts = traversalCosts; + /// + /// + /// Traversal cost calculation + /// Assume we have a path with 3 nodes: A, B, and C. + /// The path spends 1 meters inside node A, 3 meters inside node B, and 2 meters inside node C. + /// + /// The total cost of traversing the path is: + /// + /// + /// var totalCost = 0f; + /// totalCost += costs.GetTraversalCostMultiplier(A) * 1000; // 1 meter in A + /// totalCost += costs.GetConnectionCost(A, B); // cost of moving from A to B + /// totalCost += costs.GetTraversalCostMultiplier(B) * 3000; // 3 meters in B + /// totalCost += costs.GetConnectionCost(B, C); // cost of moving from B to C + /// totalCost += costs.GetTraversalCostMultiplier(C) * 2000; // 2 meters in C + /// + /// + /// or, assuming no traversal provider is set: + /// + /// + /// var totalCost = 0f; + /// totalCost += costs.tagCostMultipliers[A.Tag] * 1000; // 1 meter in A + /// totalCost += costs.tagEntryCosts[B.Tag] + B.Penalty; // cost of moving from A to B + /// totalCost += costs.tagCostMultipliers[B.Tag] * 3000; // 3 meters in B + /// totalCost += costs.tagEntryCosts[C.Tag] + C.Penalty; // cost of moving from B to C + /// totalCost += costs.tagCostMultipliers[C.Tag] * 2000; // 2 meters in C + /// + /// + /// Note: On navmesh/recast graphs, the distance the path moves inside each node (triangle) is approximated. + /// It cannot be exactly calculated for performance reasons. Typically it averages out to around 10-20% higher than the actual distance. + /// This is usually not a problem in practice, but you should keep this in mind and not rely on exact values. + /// + /// See: tags (view in online documentation for working links) + /// See: traversal_provider (view in online documentation for working links) + /// See: + /// See: + /// See: + /// See: + /// + [System.Serializable] + public struct TraversalCosts { + /// + /// Traversal provider to forward all cost calculations to. + /// + /// If not null, this will override the method's output completely. + /// The traversal provider will be called for each node that is explored by a path search. + /// + public ITraversalProvider traversalProvider; + [UnityEngine.SerializeField] + uint[] tagEntryCostsInternal; + [UnityEngine.SerializeField] + float[] tagCostMultipliersInternal; + + /// + /// How much it costs to enter a node with a given tag. + /// + /// This can be used to make agents avoid, or prefer, nodes with certain tags. + /// + /// For example, every time a path enters tag 0 (which is the default tag), it will cost an extra tagEntryCosts[0]. + /// + /// For most pathfinding purposes, you can think of this as just the additional cost of traversing a node with the given tag. + /// The distinction between entering and exiting a node is a very small one, and will not make a difference in most cases. + /// + /// The index in the array corresponds to the tag. + /// All entry costs are positive values since the A* algorithm cannot handle negative costs. + /// + /// If null (the default), all tags will be treated as having an entry cost of zero. + /// However, the path will still receive cost from the field. + /// + /// This cost will be applied for every node that is entered, regardless of if the previous node had the same tag or not. + /// + /// Note: This array must be of length 32. + /// + /// + /// var traversalCosts = new TraversalCosts(); + /// + /// // Make tags 1 and 4 have a higher penalty + /// var tagEntryCosts = new uint[32]; + /// tagEntryCosts[1] = 1000; + /// tagEntryCosts[4] = 20000; + /// traversalCosts.tagEntryCosts = tagEntryCosts; + /// + /// var path = ABPath.Construct(Vector3.zero, Vector3.one); + /// path.traversalCosts = traversalCosts; + /// + /// + /// In the and inspectors, this field (called "Cost per node") is hidden if + /// your scene only contains navmesh/recast graphs, since using per-node costs on those graphs is not recommended + /// due to the significant differences in node sizes and shapes that they have. Use instead. + /// + /// [Open online documentation to see images] + /// + /// See: + /// See: + /// See: tags (view in online documentation for working links) + /// + public uint[] tagEntryCosts { + get => tagEntryCostsInternal; + set { + if (value != null && value.Length != 32) throw new System.ArgumentException("Entry costs array must be of length 32"); + tagEntryCostsInternal = value; + } + } + + /// + /// Multiplier for the cost of moving some distance across a node with a given tag. + /// + /// This will be multiplied by the traversed distance in millimeters (see ) to get the final cost. + /// The default value is 1, which means that the cost will be the same as the distance. + /// If you set this to 2, the cost will be twice as high as the distance. + /// + /// For example, moving 1 world unit across a node with tag 0 will cost 1000 * tagCostMultipliers[0] units of movement cost. + /// + /// If null (the default), all tags will be treated as having a multiplier of 1. + /// + /// Note: Prefer to make tags more costly (cost multiplier >1), rather than making them cheaper than the default (cost multiplier <1). + /// If you set any cost multiplier to less than 1, you'll also need to reduce to the lowest cost multiplier that you use in the project, + /// otherwise the pathfinding algorithm may not find the optimal path. Alternatively you could compensate with a higher value, so that the + /// agent can never move to the target with a lower cost than the default. See https://en.wikipedia.org/wiki/Admissible_heuristic. + /// + /// Note: This array must be of length 32. + /// + /// In the inspector, this value is displayed as "Cost per world unit", and is shown as multiplied by 1000 (), to make it the cost per world unit. + /// While in code, it is a multiplier of the default cost. + /// + /// [Open online documentation to see images] + /// + /// + /// var traversalCosts = new TraversalCosts(); + /// + /// // Make tags 1 and 4 have a higher penalty + /// var tagCostMultipliers = new float[32]; + /// tagCostMultipliers[1] = 2.5f; + /// tagCostMultipliers[4] = 5.0f; + /// traversalCosts.tagCostMultipliers = tagCostMultipliers; + /// + /// var path = ABPath.Construct(Vector3.zero, Vector3.one); + /// path.traversalCosts = traversalCosts; + /// + /// + /// See: + /// See: + /// See: + /// See: + /// + public float[] tagCostMultipliers { + get => tagCostMultipliersInternal; + set { + if (value != null && value.Length != 32) throw new System.ArgumentException("Tag cost multiplier array must be of length 32"); + tagCostMultipliersInternal = value; + } + } + + /// + /// True if traversal costs might be non-default, and false if they are guaranteed to be default for all nodes. + /// + /// If false, then will always return 1, and GetConnectionCost will always return the node's penalty. + /// + public bool hasCosts => tagCostMultipliersInternal != null || tagEntryCostsInternal != null || traversalProvider != null; + + /// Cost for traversing the given node + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [System.Obsolete("Use GetTraversalCostMultiplier instead")] + public uint GetTraversalCost (GraphNode node) { + // return traversalProvider != null ? traversalProvider.GetTraversalCost(ref this, node) : GetDefaultTraversalCost(node); + return 0; + } + + /// + /// Multiplier for the cost of traversing some distance across the given node. + /// + /// This will be multiplied by the traversed distance in millimeters (see ) to get the final cost. + /// + /// If is set, this call will be forwarded to it. Otherwise, the cost will be taken from . + /// + /// See: + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetTraversalCostMultiplier (GraphNode node) { + return traversalProvider?.GetTraversalCostMultiplier(ref this, node) ?? tagCostMultipliersInternal ? [node.Tag] ?? 1f; + } + + /// + /// Instantaneous cost for moving between from and to. + /// This cost is not scaled by the distance between the two nodes. + /// + /// If is set, this call will be forwarded to it. Otherwise, the cost will be taken from . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetConnectionCost (GraphNode from, GraphNode to) { + return traversalProvider?.GetConnectionCost(ref this, from, to) ?? ((tagEntryCostsInternal ? [to.Tag] ?? 0) + to.Penalty); + } + + /// Like GetTraversalCostMultiplier, but ignores any that may be set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetDefaultTraversalCostMultiplier (GraphNode node) { + return tagCostMultipliersInternal ? [node.Tag] ?? 1f; + } + + /// Cost for traversing the given node, ignoring any that may be set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetDefaultConnectionCost (GraphNode from, GraphNode to) { + return to.Penalty + GetTagEntryCost(to.Tag); + } + + /// + /// Cost for entering a node with the given tag. + /// + /// See: tags (view in online documentation for working links) + /// + /// A value between 0 (inclusive) and 32 (exclusive). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetTagEntryCost (uint tag) { + return tagEntryCostsInternal ? [tag] ?? 0; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs.meta new file mode 100644 index 0000000..ef467f3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pathfinding/TraversalCosts.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a88aad26b6fc0d40b63c9456bb8f155 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling.meta new file mode 100644 index 0000000..0d347ad --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 23af21159d0e0934f81f256ba806658a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs new file mode 100644 index 0000000..da09722 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs @@ -0,0 +1,226 @@ +#if !UNITY_EDITOR +// Extra optimizations when not running in the editor, but less error checking +#define ASTAR_OPTIMIZE_POOLING +#endif + +using System; +using System.Collections.Generic; + +namespace Pathfinding.Pooling { + /// + /// Lightweight Array Pool. + /// Handy class for pooling arrays of type T. + /// + /// Usage: + /// - Claim a new array using SomeClass[] foo = ArrayPool.Claim (capacity); + /// - Use it and do stuff with it + /// - Release it with ArrayPool.Release (ref foo); + /// + /// Warning: Arrays returned from the Claim method may contain arbitrary data. + /// You cannot rely on it being zeroed out. + /// + /// After you have released a array, you should never use it again, if you do use it + /// your code may modify it at the same time as some other code is using it which + /// will likely lead to bad results. + /// + /// Since: Version 3.8.6 + /// See: Pathfinding.Pooling.ListPool + /// + public static class ArrayPool { +#if !ASTAR_NO_POOLING + /// + /// Maximum length of an array pooled using ClaimWithExactLength. + /// Arrays with lengths longer than this will silently not be pooled. + /// + const int MaximumExactArrayLength = 256; + + static readonly bool containsReferenceTypes; + + /// + /// Internal pool. + /// The arrays in each bucket have lengths of 2^i + /// + static readonly Stack[] pool = new Stack[31]; + static readonly Stack[] exactPool = new Stack[MaximumExactArrayLength+1]; +#if !ASTAR_OPTIMIZE_POOLING + static readonly HashSet inPool = new HashSet(); +#endif + + static ArrayPool () { + // Check if T is a reference type + containsReferenceTypes = typeof(T).IsClass || typeof(T).IsInterface || typeof(T).IsArray; + + if (!containsReferenceTypes) { + var fields = typeof(T).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + // Check if T has any fields which are reference types. + // Just one level deep, because we are lazy + foreach (var field in fields) { + if (field.FieldType.IsClass || field.FieldType.IsInterface || field.FieldType.IsArray) { + containsReferenceTypes = true; + break; + } + } + } + } +#endif + + /// + /// Returns an array with at least the specified length. + /// Warning: Returned arrays may contain arbitrary data. + /// You cannot rely on it being zeroed out. + /// + /// The returned array will always be a power of two, or zero. + /// + public static T[] Claim (int minimumLength) { + if (minimumLength <= 0) { + return ClaimWithExactLength(0); + } + + int bucketIndex = 0; + while ((1 << bucketIndex) < minimumLength && bucketIndex < 30) { + bucketIndex++; + } + + if (bucketIndex == 30) + throw new System.ArgumentException("Too high minimum length"); + +#if !ASTAR_NO_POOLING + lock (pool) { + if (pool[bucketIndex] == null) { + pool[bucketIndex] = new Stack(); + } + + if (pool[bucketIndex].Count > 0) { + var array = pool[bucketIndex].Pop(); +#if !ASTAR_OPTIMIZE_POOLING + inPool.Remove(array); +#endif + return array; + } + } +#endif + return new T[1 << bucketIndex]; + } + + /// + /// Returns an array with the specified length. + /// Use with caution as pooling too many arrays with different lengths that + /// are rarely being reused will lead to an effective memory leak. + /// + /// Use if you just need an array that is at least as large as some value. + /// + /// Warning: Returned arrays may contain arbitrary data. + /// You cannot rely on it being zeroed out. + /// + public static T[] ClaimWithExactLength (int length) { +#if !ASTAR_NO_POOLING + bool isPowerOfTwo = length != 0 && (length & (length - 1)) == 0; + if (isPowerOfTwo) { + // Will return the correct array length + return Claim(length); + } + + if (length <= MaximumExactArrayLength) { + lock (pool) { + Stack stack = exactPool[length]; + if (stack != null && stack.Count > 0) { + var array = stack.Pop(); +#if !ASTAR_OPTIMIZE_POOLING + inPool.Remove(array); +#endif + return array; + } + } + } +#endif + return new T[length]; + } + + /// + /// Pool an array. + /// If the array was got using the method then the allowNonPowerOfTwo parameter must be set to true. + /// The parameter exists to make sure that non power of two arrays are not pooled unintentionally which could lead to memory leaks. + /// + public static void Release (ref T[] array, bool allowNonPowerOfTwo = false) { + if (array == null) return; + if (array.GetType() != typeof(T[])) { + throw new System.ArgumentException("Expected array type " + typeof(T[]).Name + " but found " + array.GetType().Name + "\nAre you using the correct generic class?\n"); + } + +#if !ASTAR_NO_POOLING + bool isPowerOfTwo = array.Length != 0 && (array.Length & (array.Length - 1)) == 0; + if (!isPowerOfTwo && !allowNonPowerOfTwo && array.Length != 0) throw new System.ArgumentException("Length is not a power of 2"); + + if (containsReferenceTypes) { + // Clear the array to avoid leaking references to objects that could have otherwise been collected by the GC + Array.Fill(array, default); + } + + lock (pool) { +#if !ASTAR_OPTIMIZE_POOLING + if (!inPool.Add(array)) { + throw new InvalidOperationException("You are trying to pool an array twice. Please make sure that you only pool it once."); + } +#endif + if (isPowerOfTwo) { + int bucketIndex = 0; + while ((1 << bucketIndex) < array.Length && bucketIndex < 30) { + bucketIndex++; + } + + if (pool[bucketIndex] == null) { + pool[bucketIndex] = new Stack(); + } + + pool[bucketIndex].Push(array); + } else if (array.Length <= MaximumExactArrayLength) { + Stack stack = exactPool[array.Length]; + if (stack == null) stack = exactPool[array.Length] = new Stack(); + stack.Push(array); + } else { +#if !ASTAR_OPTIMIZE_POOLING + // If the array is too long, do not pool it + inPool.Remove(array); +#endif + } + } +#endif + array = null; + } + } + + /// Extension methods for List + public static class ListExtensions { + /// + /// Identical to ToArray but it uses ArrayPool to avoid allocations if possible. + /// + /// Use with caution as pooling too many arrays with different lengths that + /// are rarely being reused will lead to an effective memory leak. + /// + public static T[] ToArrayFromPool(this List list) { + var arr = ArrayPool.ClaimWithExactLength(list.Count); + list.CopyTo(arr); + return arr; + } + + /// + /// Clear a list faster than List.Clear. + /// It turns out that the List.Clear method will clear all elements in the underlaying array + /// not just the ones up to Count. If the list only has a few elements, but the capacity + /// is huge, this can cause performance problems. Using the RemoveRange method to remove + /// all elements in the list does not have this problem, however it is implemented in a + /// stupid way, so it will clear the elements twice (completely unnecessarily) so it will + /// only be faster than using the Clear method if the number of elements in the list is + /// less than half of the capacity of the list. + /// + /// Hopefully this method can be removed when Unity upgrades to a newer version of Mono. + /// + public static void ClearFast(this List list) { + if (list.Count*2 < list.Capacity) { + list.RemoveRange(0, list.Count); + } else { + list.Clear(); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs.meta new file mode 100644 index 0000000..25e0d39 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ArrayPool.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 787a564bf2d894ee09284b775074864c +timeCreated: 1470483941 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs new file mode 100644 index 0000000..9e4e404 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs @@ -0,0 +1,211 @@ +#if !UNITY_EDITOR +// Extra optimizations when not running in the editor, but less error checking +#define ASTAR_OPTIMIZE_POOLING +#endif + +using System; +using System.Collections.Generic; + +namespace Pathfinding.Pooling { + /// + /// Lightweight List Pool. + /// Handy class for pooling lists of type T. + /// + /// Usage: + /// - Claim a new list using List foo = ListPool.Claim (); + /// - Use it and do stuff with it + /// - Release it with ListPool.Release (foo); + /// + /// You do not need to clear the list before releasing it. + /// After you have released a list, you should never use it again, if you do use it, you will + /// mess things up quite badly in the worst case. + /// + /// Since: Version 3.2 + /// See: Pathfinding.Util.StackPool + /// + public static class ListPool { + /// Internal pool + static readonly List > pool = new List >(); + +#if !ASTAR_NO_POOLING + static readonly List > largePool = new List >(); + static readonly HashSet > inPool = new HashSet >(); +#endif + + /// + /// When requesting a list with a specified capacity, search max this many lists in the pool before giving up. + /// Must be greater or equal to one. + /// + const int MaxCapacitySearchLength = 8; + const int LargeThreshold = 5000; + const int MaxLargePoolSize = 8; + + /// + /// Claim a list. + /// Returns a pooled list if any are in the pool. + /// Otherwise it creates a new one. + /// After usage, this list should be released using the Release function (though not strictly necessary). + /// + public static List Claim () { +#if ASTAR_NO_POOLING + return new List(); +#else + lock (pool) { + if (pool.Count > 0) { + List ls = pool[pool.Count-1]; + pool.RemoveAt(pool.Count-1); + inPool.Remove(ls); + return ls; + } + + return new List(); + } +#endif + } + + static int FindCandidate (List > pool, int capacity) { + // Loop through the last MaxCapacitySearchLength items + // and check if any item has a capacity greater or equal to the one that + // is desired. If so return it. + // Otherwise take the largest one or if there are no lists in the pool + // then allocate a new one with the desired capacity + List list = null; + int listIndex = -1; + + for (int i = 0; i < pool.Count && i < MaxCapacitySearchLength; i++) { + // ith last item + var candidate = pool[pool.Count-1-i]; + + // Find the largest list that is not too large (arbitrary decision to try to prevent some memory bloat if the list was not just a temporary list). + if ((list == null || candidate.Capacity > list.Capacity) && candidate.Capacity < capacity*16) { + list = candidate; + listIndex = pool.Count-1-i; + + if (list.Capacity >= capacity) { + return listIndex; + } + } + } + + return listIndex; + } + + /// + /// Claim a list with minimum capacity + /// Returns a pooled list if any are in the pool. + /// Otherwise it creates a new one. + /// After usage, this list should be released using the Release function (though not strictly necessary). + /// A subset of the pool will be searched for a list with a high enough capacity and one will be returned + /// if possible, otherwise the list with the largest capacity found will be returned. + /// + public static List Claim (int capacity) { +#if ASTAR_NO_POOLING + return new List(capacity); +#else + lock (pool) { + var currentPool = pool; + var listIndex = FindCandidate(pool, capacity); + + if (capacity > LargeThreshold) { + var largeListIndex = FindCandidate(largePool, capacity); + if (largeListIndex != -1) { + currentPool = largePool; + listIndex = largeListIndex; + } + } + + if (listIndex == -1) { + return new List(capacity); + } else { + var list = currentPool[listIndex]; + // Swap current item and last item to enable a more efficient removal + inPool.Remove(list); + currentPool[listIndex] = currentPool[currentPool.Count-1]; + currentPool.RemoveAt(currentPool.Count-1); + return list; + } + } +#endif + } + + /// + /// Makes sure the pool contains at least count pooled items with capacity size. + /// This is good if you want to do all allocations at start. + /// + public static void Warmup (int count, int size) { + lock (pool) { + var tmp = new List[count]; + for (int i = 0; i < count; i++) tmp[i] = Claim(size); + for (int i = 0; i < count; i++) Release(tmp[i]); + } + } + + + /// + /// Releases a list and sets the variable to null. + /// After the list has been released it should not be used anymore. + /// + /// Throws: System.InvalidOperationException + /// Releasing a list when it has already been released will cause an exception to be thrown. + /// + /// See: + /// + public static void Release (ref List list) { + Release(list); + list = null; + } + + /// + /// Releases a list. + /// After the list has been released it should not be used anymore. + /// + /// Throws: System.InvalidOperationException + /// Releasing a list when it has already been released will cause an exception to be thrown. + /// + /// See: + /// + public static void Release (List list) { +#if !ASTAR_NO_POOLING + list.ClearFast(); + + lock (pool) { +#if !ASTAR_OPTIMIZE_POOLING + if (!inPool.Add(list)) { + throw new InvalidOperationException("You are trying to pool a list twice. Please make sure that you only pool it once."); + } +#endif + if (list.Capacity > LargeThreshold) { + largePool.Add(list); + + // Remove the list which was used the longest time ago from the pool if it + // exceeds the maximum size as it probably just contributes to memory bloat + if (largePool.Count > MaxLargePoolSize) { + largePool.RemoveAt(0); + } + } else { + pool.Add(list); + } + } +#endif + } + + /// + /// Clears the pool for lists of this type. + /// This is an O(n) operation, where n is the number of pooled lists. + /// + public static void Clear () { + lock (pool) { +#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING + inPool.Clear(); +#endif + pool.Clear(); + } + } + + /// Number of lists of this type in the pool + public static int GetSize () { + // No lock required since int writes are atomic + return pool.Count; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs.meta new file mode 100644 index 0000000..92e0b9c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ListPool.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2b76b7593907b44d9a6ef1b186fee0a7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs new file mode 100644 index 0000000..ba9f0e4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs @@ -0,0 +1,131 @@ +#if !UNITY_EDITOR +// Extra optimizations when not running in the editor, but less error checking +#define ASTAR_OPTIMIZE_POOLING +#endif + +using System; +using System.Collections.Generic; + +namespace Pathfinding.Pooling { + public interface IAstarPooledObject { + void OnEnterPool(); + } + + /// + /// Lightweight object Pool for IAstarPooledObject. + /// Handy class for pooling objects of type T which implements the IAstarPooledObject interface. + /// + /// Usage: + /// - Claim a new object using SomeClass foo = ObjectPool.Claim (); + /// - Use it and do stuff with it + /// - Release it with ObjectPool.Release (foo); + /// + /// After you have released a object, you should never use it again. + /// + /// Since: Version 3.2 + /// Version: Since 3.7.6 this class is thread safe + /// See: Pathfinding.Pooling.ListPool + /// See: ObjectPoolSimple + /// + public static class ObjectPool where T : class, IAstarPooledObject, new(){ + public static T Claim () { + return ObjectPoolSimple.Claim(); + } + + public static void Release (ref T obj) { + obj.OnEnterPool(); + ObjectPoolSimple.Release(ref obj); + } + } + + /// + /// Lightweight object Pool. + /// Handy class for pooling objects of type T. + /// + /// Usage: + /// - Claim a new object using SomeClass foo = ObjectPool.Claim (); + /// - Use it and do stuff with it + /// - Release it with ObjectPool.Release (foo); + /// + /// After you have released a object, you should never use it again. + /// + /// Since: Version 3.2 + /// Version: Since 3.7.6 this class is thread safe + /// See: Pathfinding.Pooling.ListPool + /// See: ObjectPool + /// + public static class ObjectPoolSimple where T : class, new(){ + /// Internal pool + static List pool = new List(); + +#if !ASTAR_NO_POOLING + static readonly HashSet inPool = new HashSet(); +#endif + + /// + /// Claim a object. + /// Returns a pooled object if any are in the pool. + /// Otherwise it creates a new one. + /// After usage, this object should be released using the Release function (though not strictly necessary). + /// + public static T Claim () { +#if ASTAR_NO_POOLING + return new T(); +#else + lock (pool) { + if (pool.Count > 0) { + T ls = pool[pool.Count-1]; + pool.RemoveAt(pool.Count-1); + inPool.Remove(ls); + return ls; + } else { + return new T(); + } + } +#endif + } + + /// + /// Releases an object. + /// After the object has been released it should not be used anymore. + /// The variable will be set to null to prevent silly mistakes. + /// + /// Throws: System.InvalidOperationException + /// Releasing an object when it has already been released will cause an exception to be thrown. + /// However enabling ASTAR_OPTIMIZE_POOLING will prevent this check. + /// + /// See: Claim + /// + public static void Release (ref T obj) { +#if !ASTAR_NO_POOLING + lock (pool) { +#if !ASTAR_OPTIMIZE_POOLING + if (!inPool.Add(obj)) { + throw new InvalidOperationException("You are trying to pool an object twice. Please make sure that you only pool it once."); + } +#endif + pool.Add(obj); + } +#endif + obj = null; + } + + /// + /// Clears the pool for objects of this type. + /// This is an O(n) operation, where n is the number of pooled objects. + /// + public static void Clear () { + lock (pool) { +#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING + inPool.Clear(); +#endif + pool.Clear(); + } + } + + /// Number of objects of this type in the pool + public static int GetSize () { + return pool.Count; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs.meta new file mode 100644 index 0000000..6240b52 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/ObjectPool.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 10837c5b030bd47a2a0e6e213fea0868 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs new file mode 100644 index 0000000..5a8d723 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs @@ -0,0 +1,87 @@ +//#define ASTAR_NO_POOLING // Disable pooling for some reason. Maybe for debugging or just for measuring the difference. +using System; +using System.Collections.Generic; + +namespace Pathfinding.Pooling { + /// Pools path objects to reduce load on the garbage collector + public static class PathPool { + static readonly Dictionary > pool = new Dictionary >(); + static readonly Dictionary totalCreated = new Dictionary(); + + /// + /// Adds a path to the pool. + /// This function should not be used directly. Instead use the Path.Claim and Path.Release functions. + /// + public static void Pool (Path path) { +#if !ASTAR_NO_POOLING + if (((IPathInternals)path).Pooled) { + throw new System.ArgumentException("The path is already pooled."); + } + ((IPathInternals)path).Pooled = true; + ((IPathInternals)path).OnEnterPool(); + var tp = path.GetType(); + + lock (pool) { + if (!pool.TryGetValue(tp, out var poolStack)) { + poolStack = new Stack(); + pool[tp] = poolStack; + } + + poolStack.Push(path); + } +#endif + } + + /// Total created instances of paths of the specified type + public static int GetTotalCreated (Type type) { + int created; + + if (totalCreated.TryGetValue(type, out created)) { + return created; + } else { + return 0; + } + } + + /// Number of pooled instances of a path of the specified type + public static int GetSize (Type type) { + Stack poolStack; + + if (pool.TryGetValue(type, out poolStack)) { + return poolStack.Count; + } else { + return 0; + } + } + + /// Get a path from the pool or create a new one if the pool is empty + public static T GetPath() where T : Path, new() { +#if ASTAR_NO_POOLING + T result = new T(); + ((IPathInternals)result).Reset(); + return result; +#else + T result; + lock (pool) { + Stack poolStack; + if (pool.TryGetValue(typeof(T), out poolStack) && poolStack.Count > 0) { + // Guaranteed to have the correct type + result = poolStack.Pop() as T; + } else { + result = new T(); + + // Make sure an entry for the path type exists + if (!totalCreated.ContainsKey(typeof(T))) { + totalCreated[typeof(T)] = 0; + } + + totalCreated[typeof(T)]++; + } + } + ((IPathInternals)result).Pooled = false; + ((IPathInternals)result).Reset(); + return result; +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs.meta new file mode 100644 index 0000000..9ff4458 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/PathPool.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cefe1014ab62848a89016fb97b1f8f7b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs new file mode 100644 index 0000000..0e413e6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs @@ -0,0 +1,98 @@ +//#define ASTAR_NO_POOLING //@SHOWINEDITOR Disable pooling for some reason. Could be debugging or just for measuring the difference. + +using System.Collections.Generic; + +namespace Pathfinding.Pooling { + /// + /// Lightweight Stack Pool. + /// Handy class for pooling stacks of type T. + /// + /// Usage: + /// - Claim a new stack using Stack foo = StackPool.Claim (); + /// - Use it and do stuff with it + /// - Release it with StackPool.Release (foo); + /// + /// You do not need to clear the stack before releasing it. + /// After you have released a stack, you should never use it again. + /// + /// Warning: This class is not thread safe + /// + /// Since: Version 3.2 + /// See: Pathfinding.Pooling.ListPool + /// + public static class StackPool { + /// Internal pool + static readonly List > pool; + + /// Static constructor + static StackPool () { + pool = new List >(); + } + + /// + /// Claim a stack. + /// Returns a pooled stack if any are in the pool. + /// Otherwise it creates a new one. + /// After usage, this stack should be released using the Release function (though not strictly necessary). + /// + public static Stack Claim () { +#if ASTAR_NO_POOLING + return new Stack(); +#else + lock (pool) { + if (pool.Count > 0) { + Stack ls = pool[pool.Count-1]; + pool.RemoveAt(pool.Count-1); + return ls; + } + } + + return new Stack(); +#endif + } + + /// + /// Makes sure the pool contains at least count pooled items. + /// This is good if you want to do all allocations at start. + /// + public static void Warmup (int count) { + var tmp = new Stack[count]; + + for (int i = 0; i < count; i++) tmp[i] = Claim(); + for (int i = 0; i < count; i++) Release(tmp[i]); + } + + /// + /// Releases a stack. + /// After the stack has been released it should not be used anymore. + /// Releasing a stack twice will cause an error. + /// + public static void Release (Stack stack) { +#if !ASTAR_NO_POOLING + stack.Clear(); + + lock (pool) { + for (int i = 0; i < pool.Count; i++) + if (pool[i] == stack) UnityEngine.Debug.LogError("The Stack is released even though it is inside the pool"); + + pool.Add(stack); + } +#endif + } + + /// + /// Clears all pooled stacks of this type. + /// This is an O(n) operation, where n is the number of pooled stacks + /// + public static void Clear () { + lock (pool) { + pool.Clear(); + } + } + + /// Number of stacks of this type in the pool + public static int GetSize () { + return pool.Count; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs.meta new file mode 100644 index 0000000..1c8a3b5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Pooling/StackPool.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: de467bbbb1ff84668ae8262caad00941 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO.meta new file mode 100644 index 0000000..ff5b6ea --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 51dd3ade9cc0849ab9be4c7bad40988d diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs.meta new file mode 100644 index 0000000..521b101 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgent.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5a85e178962ba475ca424001ea4c13ca +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs new file mode 100644 index 0000000..e6af325 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs @@ -0,0 +1,2039 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace Pathfinding.RVO { + using Pathfinding; + using Pathfinding.Util; + using Unity.Burst; + using Unity.Jobs; + using Unity.Mathematics; + using Unity.Collections; + using Pathfinding.Collections; + using Pathfinding.Drawing; + using Pathfinding.ECS.RVO; + using static Unity.Burst.CompilerServices.Aliasing; + using Unity.Profiling; + using System.Diagnostics; + + [BurstCompile(CompileSynchronously = false, FloatMode = FloatMode.Fast)] + public struct JobRVOPreprocess : IJob where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + + [ReadOnly] + public SimulatorBurst.AgentOutputData previousOutput; + + [WriteOnly] + public SimulatorBurst.TemporaryAgentData temporaryAgentData; + + public int startIndex; + public int endIndex; + + public void Execute () { + for (int i = startIndex; i < endIndex; i++) { + if (!agentData.version[i].Valid) continue; + + // Manually controlled overrides the agent being locked. + // If one for some reason uses them at the same time. + var locked = agentData.locked[i] & !agentData.manuallyControlled[i]; + + if (locked) { + temporaryAgentData.desiredTargetPointInVelocitySpace[i] = float2.zero; + temporaryAgentData.desiredVelocity[i] = float3.zero; + temporaryAgentData.currentVelocity[i] = float3.zero; + } else { + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(agentData.movementPlane[i]); + + var desiredTargetPointInVelocitySpace = movementPlane.ToPlane(agentData.targetPoint[i] - agentData.position[i]); + temporaryAgentData.desiredTargetPointInVelocitySpace[i] = desiredTargetPointInVelocitySpace; + + // Estimate our current velocity + // This is necessary because other agents need to know + // how this agent is moving to be able to avoid it + var currentVelocity = math.normalizesafe(previousOutput.targetPoint[i] - agentData.position[i]) * previousOutput.speed[i]; + + // Calculate the desired velocity from the point we want to reach + temporaryAgentData.desiredVelocity[i] = movementPlane.ToWorld(math.normalizesafe(desiredTargetPointInVelocitySpace) * agentData.desiredSpeed[i], 0); + + var collisionNormal = math.normalizesafe(agentData.collisionNormal[i]); + // Check if the velocity is going into the wall + // If so: remove that component from the velocity + // Note: if the collisionNormal is zero then the dot prodct will produce a zero as well and nothing will happen. + float dot = math.dot(currentVelocity, collisionNormal); + currentVelocity -= math.min(0, dot) * collisionNormal; + temporaryAgentData.currentVelocity[i] = currentVelocity; + } + } + } + } + + /// + /// Inspired by StarCraft 2's avoidance of locked units. + /// See: http://www.gdcvault.com/play/1014514/AI-Navigation-It-s-Not + /// + [BurstCompile(FloatMode = FloatMode.Fast)] + public struct JobHorizonAvoidancePhase1 : Pathfinding.Jobs.IJobParallelForBatched where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + + [ReadOnly] + public NativeArray desiredTargetPointInVelocitySpace; + + [ReadOnly] + public NativeArray neighbours; + + public SimulatorBurst.HorizonAgentData horizonAgentData; + +#if UNITY_EDITOR + public CommandBuilder draw; +#endif + + public bool allowBoundsChecks { get { return true; } } + + /// + /// Super simple bubble sort. + /// TODO: This will be replaced by a better implementation from the Unity.Collections library when that is stable. + /// + static void Sort(NativeSlice arr, NativeSlice keys) where T : struct { + bool changed = true; + + while (changed) { + changed = false; + for (int i = 0; i < arr.Length - 1; i++) { + if (keys[i] > keys[i+1]) { + var tmp = keys[i]; + var tmp2 = arr[i]; + keys[i] = keys[i+1]; + keys[i+1] = tmp; + arr[i] = arr[i+1]; + arr[i+1] = tmp2; + changed = true; + } + } + } + } + + + /// Calculates the shortest difference between two given angles given in radians. + public static float DeltaAngle (float current, float target) { + float num = Mathf.Repeat(target - current, math.PI*2); + + if (num > math.PI) { + num -= math.PI*2; + } + return num; + } + + public void Execute (int startIndex, int count) { + NativeArray angles = new NativeArray(SimulatorBurst.MaxNeighbourCount*2, Allocator.Temp); + NativeArray deltas = new NativeArray(SimulatorBurst.MaxNeighbourCount*2, Allocator.Temp); + + for (int i = startIndex; i < startIndex + count; i++) { + if (!agentData.version[i].Valid) continue; + + if (agentData.locked[i] || agentData.manuallyControlled[i]) { + horizonAgentData.horizonSide[i] = 0; + horizonAgentData.horizonMinAngle[i] = 0; + horizonAgentData.horizonMaxAngle[i] = 0; + continue; + } + + float desiredAngle = math.atan2(desiredTargetPointInVelocitySpace[i].y, desiredTargetPointInVelocitySpace[i].x); + + int eventCount = 0; + + int inside = 0; + + float radius = agentData.radius[i]; + + var position = agentData.position[i]; + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(agentData.movementPlane[i]); + + var distSqToEndOfPath = math.all(math.isfinite(agentData.endOfPath[i])) ? math.lengthsq(agentData.endOfPath[i] - position) : float.PositiveInfinity; + + var agentNeighbours = neighbours.Slice(i*SimulatorBurst.MaxNeighbourCount, SimulatorBurst.MaxNeighbourCount); + for (int j = 0; j < agentNeighbours.Length && agentNeighbours[j] != -1; j++) { + var other = agentNeighbours[j]; + if (!agentData.locked[other] && !agentData.manuallyControlled[other]) continue; + + var relativePosition = movementPlane.ToPlane(agentData.position[other] - position); + float dist = math.length(relativePosition); + var otherRadius = agentData.radius[other]; + + var distanceUntilCollision = dist - (radius + otherRadius); + if (distanceUntilCollision*distanceUntilCollision > distSqToEndOfPath) continue; + + float angle = math.atan2(relativePosition.y, relativePosition.x) - desiredAngle; + float deltaAngle; + + if (distanceUntilCollision <= 0) { + // Collision + deltaAngle = math.PI * 0.49f; + } else { + // One degree + const float AngleMargin = math.PI / 180f; + deltaAngle = math.asin((radius + otherRadius)/dist) + AngleMargin; + } + + float aMin = DeltaAngle(0, angle - deltaAngle); + float aMax = aMin + DeltaAngle(aMin, angle + deltaAngle); + + if (aMin < 0 && aMax > 0) inside++; + + angles[eventCount] = aMin; + deltas[eventCount] = 1; + eventCount++; + angles[eventCount] = aMax; + deltas[eventCount] = -1; + eventCount++; + } + + // If no angle range includes angle 0 then we are already done + if (inside == 0) { + horizonAgentData.horizonSide[i] = 0; + horizonAgentData.horizonMinAngle[i] = 0; + horizonAgentData.horizonMaxAngle[i] = 0; + continue; + } + + // Sort the events by their angle in ascending order + Sort(deltas.Slice(0, eventCount), angles.Slice(0, eventCount)); + + // Find the first index for which the angle is positive + int firstPositiveIndex = 0; + for (; firstPositiveIndex < eventCount; firstPositiveIndex++) if (angles[firstPositiveIndex] > 0) break; + + // Walk in the positive direction from angle 0 until the end of the group of angle ranges that include angle 0 + int tmpInside = inside; + int tmpIndex = firstPositiveIndex; + for (; tmpIndex < eventCount; tmpIndex++) { + tmpInside += deltas[tmpIndex]; + if (tmpInside == 0) break; + } + var maxAngle = tmpIndex == eventCount ? math.PI : angles[tmpIndex]; + + // Walk in the negative direction from angle 0 until the end of the group of angle ranges that include angle 0 + tmpInside = inside; + tmpIndex = firstPositiveIndex - 1; + for (; tmpIndex >= 0; tmpIndex--) { + tmpInside -= deltas[tmpIndex]; + if (tmpInside == 0) break; + } + var minAngle = tmpIndex == -1 ? -math.PI : angles[tmpIndex]; + + //horizonBias = -(minAngle + maxAngle); + + // Indicates that a new side should be chosen. The "best" one will be chosen later. + if (horizonAgentData.horizonSide[i] == 0) horizonAgentData.horizonSide[i] = 2; + //else horizonBias = math.PI * horizonSide; + + horizonAgentData.horizonMinAngle[i] = minAngle + desiredAngle; + horizonAgentData.horizonMaxAngle[i] = maxAngle + desiredAngle; + } + } + } + + /// + /// Inspired by StarCraft 2's avoidance of locked units. + /// See: http://www.gdcvault.com/play/1014514/AI-Navigation-It-s-Not + /// + [BurstCompile(FloatMode = FloatMode.Fast)] + public struct JobHorizonAvoidancePhase2 : Pathfinding.Jobs.IJobParallelForBatched where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public NativeArray neighbours; + [ReadOnly] + public NativeArray versions; + public NativeArray desiredVelocity; + public NativeArray desiredTargetPointInVelocitySpace; + + [ReadOnly] + public NativeArray movementPlane; + + public SimulatorBurst.HorizonAgentData horizonAgentData; + + public bool allowBoundsChecks => false; + + public void Execute (int startIndex, int count) { + for (int i = startIndex; i < startIndex + count; i++) { + if (!versions[i].Valid) continue; + + // Note: Assumes this code is run synchronous (i.e not included in the double buffering part) + //offsetVelocity = (position - Position) / simulator.DeltaTime; + + if (horizonAgentData.horizonSide[i] == 0) { + continue; + } + + if (horizonAgentData.horizonSide[i] == 2) { + float sum = 0; + var agentNeighbours = neighbours.Slice(i*SimulatorBurst.MaxNeighbourCount, SimulatorBurst.MaxNeighbourCount); + for (int j = 0; j < agentNeighbours.Length && agentNeighbours[j] != -1; j++) { + var other = agentNeighbours[j]; + var otherHorizonBias = -(horizonAgentData.horizonMinAngle[other] + horizonAgentData.horizonMaxAngle[other]); + sum += otherHorizonBias; + } + var horizonBias = -(horizonAgentData.horizonMinAngle[i] + horizonAgentData.horizonMaxAngle[i]); + sum += horizonBias; + + horizonAgentData.horizonSide[i] = sum < 0 ? -1 : 1; + } + + float bestAngle = horizonAgentData.horizonSide[i] < 0 ? horizonAgentData.horizonMinAngle[i] : horizonAgentData.horizonMaxAngle[i]; + float2 desiredDirection; + math.sincos(bestAngle, out desiredDirection.y, out desiredDirection.x); + + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(this.movementPlane[i]); + + desiredVelocity[i] = movementPlane.ToWorld(math.length(desiredVelocity[i]) * desiredDirection, 0); + desiredTargetPointInVelocitySpace[i] = math.length(desiredTargetPointInVelocitySpace[i]) * desiredDirection; + } + } + } + + [BurstCompile(FloatMode = FloatMode.Fast)] + public struct JobHardCollisions : Pathfinding.Jobs.IJobParallelForBatched where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + [ReadOnly] + public NativeArray neighbours; + [WriteOnly] + public NativeArray collisionVelocityOffsets; + + public float deltaTime; + public bool enabled; + + /// + /// How aggressively hard collisions are resolved. + /// Should be a value between 0 and 1. + /// + const float CollisionStrength = 0.8f; + + public bool allowBoundsChecks => false; + + public void Execute (int startIndex, int count) { + if (!enabled) { + for (int i = startIndex; i < startIndex + count; i++) { + collisionVelocityOffsets[i] = float2.zero; + } + return; + } + + for (int i = startIndex; i < startIndex + count; i++) { + if (!agentData.version[i].Valid || agentData.locked[i]) { + collisionVelocityOffsets[i] = float2.zero; + continue; + } + + var agentNeighbours = neighbours.Slice(i*SimulatorBurst.MaxNeighbourCount, SimulatorBurst.MaxNeighbourCount); + var radius = agentData.radius[i]; + var totalOffset = float2.zero; + float totalWeight = 0; + + var position = agentData.position[i]; + var movementPlane = new MovementPlaneWrapper(); + movementPlane.Set(agentData.movementPlane[i]); + + for (int j = 0; j < agentNeighbours.Length && agentNeighbours[j] != -1; j++) { + var other = agentNeighbours[j]; + var relativePosition = movementPlane.ToPlane(position - agentData.position[other]); + + var dirSqrLength = math.lengthsq(relativePosition); + var combinedRadius = agentData.radius[other] + radius; + if (dirSqrLength < combinedRadius*combinedRadius) { + if (dirSqrLength > 0.00000001f) { + // Collision + var dirLength = math.sqrt(dirSqrLength); + var normalizedDir = relativePosition * (1.0f / dirLength); + + // Overlap amount + var weight = combinedRadius - dirLength; + + // Position offset required to make the agents not collide anymore + var offset = normalizedDir * weight; + // In a later step a weighted average will be taken so that the average offset is extracted + var weightedOffset = offset * weight; + + totalOffset += weightedOffset; + totalWeight += weight; + } else { + // The agents are in the exact same place. Just move in some direction randomly. + // Use a pseudo-random value based on the indices i and j instead of UnityEngine.Random + float pseudoRandom = math.frac(i * 0.521353f + j * 0.753177f); + math.sincos(pseudoRandom * math.PI * 2, out float sin, out float cos); + totalOffset += 100 * new float2(cos, sin) * radius * 0.01f; + totalWeight += 100; + } + } + } + + var offsetVelocity = totalOffset * (1.0f / (0.0001f + totalWeight)); + offsetVelocity *= (CollisionStrength * 0.5f) / deltaTime; + + collisionVelocityOffsets[i] = offsetVelocity; + } + } + } + + [BurstCompile(CompileSynchronously = false, FloatMode = FloatMode.Fast)] + public struct JobRVOCalculateNeighbours : Pathfinding.Jobs.IJobParallelForBatched where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + + [ReadOnly] + public RVOQuadtreeBurst quadtree; + + public NativeArray outNeighbours; + + [WriteOnly] + public SimulatorBurst.AgentOutputData output; + + public bool allowBoundsChecks { get { return false; } } + + public void Execute (int startIndex, int count) { + NativeArray neighbourDistances = new NativeArray(SimulatorBurst.MaxNeighbourCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + for (int i = startIndex; i < startIndex + count; i++) { + if (!agentData.version[i].Valid) continue; + CalculateNeighbours(i, outNeighbours, neighbourDistances); + } + } + + void CalculateNeighbours (int agentIndex, NativeArray neighbours, NativeArray neighbourDistances) { + int maxNeighbourCount = math.min(SimulatorBurst.MaxNeighbourCount, agentData.maxNeighbours[agentIndex]); + // Write the output starting at this index in the neighbours array + var outputIndex = agentIndex * SimulatorBurst.MaxNeighbourCount; + + int numNeighbours = quadtree.QueryKNearest(new RVOQuadtreeBurst.QuadtreeQuery { + position = agentData.position[agentIndex], + speed = agentData.maxSpeed[agentIndex], + agentRadius = agentData.radius[agentIndex], + timeHorizon = agentData.agentTimeHorizon[agentIndex], + outputStartIndex = outputIndex, + maxCount = maxNeighbourCount, + result = neighbours, + layerMask = agentData.collidesWith[agentIndex], + layers = agentData.layer, + resultDistances = neighbourDistances, + }); + + output.numNeighbours[agentIndex] = numNeighbours; + + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(agentData.movementPlane[agentIndex]); + + movementPlane.ToPlane(agentData.position[agentIndex], out float localElevation); + + // Filter out invalid neighbours + for (int i = 0; i < numNeighbours; i++) { + int otherIndex = neighbours[outputIndex + i]; + if (otherIndex == -1) throw new System.Exception("Invalid neighbour index"); + // Interval along the y axis in which the agents overlap + movementPlane.ToPlane(agentData.position[otherIndex], out float otherElevation); + float maxY = math.min(localElevation + agentData.height[agentIndex], otherElevation + agentData.height[otherIndex]); + float minY = math.max(localElevation, otherElevation); + + // The agents cannot collide if they are on different y-levels. + // Also do not avoid the agent itself. + // Use binary OR to reduce branching. + if ((maxY < minY) | (otherIndex == agentIndex)) { + numNeighbours--; + neighbours[outputIndex + i] = neighbours[outputIndex + numNeighbours]; + i--; + } + } + + // Add a token indicating the size of the neighbours list + if (numNeighbours < SimulatorBurst.MaxNeighbourCount) neighbours[outputIndex + numNeighbours] = -1; + } + } + + /// + /// Calculates if the agent has reached the end of its path and if its blocked from further progress towards it. + /// + /// If many agents have the same destination they can often end up crowded around a single point. + /// It is often desirable to detect this and mark all agents around that destination as having at least + /// partially reached the end of their paths. + /// + /// This job uses the following heuristics to determine this: + /// + /// 1. If an agent wants to move in a particular direction, but there's another agent in the way that makes it have to reduce its velocity, + /// the other agent is considered to be "blocking" the current agent. + /// 2. If the agent is within a small distance of the destination + /// THEN it is considered to have reached the end of its path. + /// 3. If the agent is blocked by another agent, + /// AND the other agent is blocked by this agent in turn, + /// AND if the destination is between the two agents, + /// THEN the the agent is considered to have reached the end of its path. + /// 4. If the agent is blocked by another agent which has reached the end of its path, + /// AND this agent is is moving slowly + /// AND this agent cannot move furter forward than 50% of its radius. + /// THEN the agent is considered to have reached the end of its path. + /// + /// Heuristics 2 and 3 are calculated initially, and then using heuristic 4 the set of agents which have reached their destinations expands outwards. + /// + /// These heuristics are robust enough that they can be used even if for example the agents are stuck in a winding maze + /// and only one agent is actually able to reach the destination. + /// + /// This job doesn't affect the movement of the agents by itself. + /// However, it is built with the intention that the FlowFollowingStrength parameter will be set + /// elsewhere to 1 for agents which have reached the end of their paths. This will make the agents stop gracefully + /// when the end of their paths is crowded instead of continuing to try to desperately reach the destination. + /// + [BurstCompile(CompileSynchronously = false, FloatMode = FloatMode.Fast)] + public struct JobDestinationReached: IJob where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + + [ReadOnly] + public SimulatorBurst.TemporaryAgentData temporaryAgentData; + + public SimulatorBurst.AgentOutputData output; + public int numAgents; +#if UNITY_EDITOR + public CommandBuilder draw; +#endif + + private static readonly ProfilerMarker MarkerInvert = new ProfilerMarker("InvertArrows"); + private static readonly ProfilerMarker MarkerAlloc = new ProfilerMarker("Alloc"); + private static readonly ProfilerMarker MarkerFirstPass = new ProfilerMarker("FirstPass"); + + struct TempAgentData { + public bool blockedAndSlow; + public float distToEndSq; + } + + public void Execute () { + MarkerAlloc.Begin(); + for (int agentIndex = 0; agentIndex < numAgents; agentIndex++) { + output.effectivelyReachedDestination[agentIndex] = ReachedEndOfPath.NotReached; + } + + // For each agent, store which agents it blocks + var inArrows = new NativeArray(agentData.position.Length*SimulatorBurst.MaxBlockingAgentCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + // Number of agents that each agent blocks + var inArrowCounts = new NativeArray(agentData.position.Length, Allocator.Temp, NativeArrayOptions.ClearMemory); + var que = new NativeCircularBuffer(16, Allocator.Temp); + // True for an agent if it is in the queue, or if it should never be queued again + var queued = new NativeArray(numAgents, Allocator.Temp, NativeArrayOptions.ClearMemory); + var tempData = new NativeArray(numAgents, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + MarkerAlloc.End(); + MarkerInvert.Begin(); + + for (int agentIndex = 0; agentIndex < numAgents; agentIndex++) { + if (!agentData.version[agentIndex].Valid) continue; + for (int i = 0; i < SimulatorBurst.MaxBlockingAgentCount; i++) { + var blockingAgentIndex = output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount + i]; + if (blockingAgentIndex == -1) break; + var count = inArrowCounts[blockingAgentIndex]; + if (count >= SimulatorBurst.MaxBlockingAgentCount) continue; + inArrows[blockingAgentIndex*SimulatorBurst.MaxBlockingAgentCount + count] = agentIndex; + inArrowCounts[blockingAgentIndex] = count+1; + } + } + MarkerInvert.End(); + + MarkerFirstPass.Begin(); + for (int agentIndex = 0; agentIndex < numAgents; agentIndex++) { + if (!agentData.version[agentIndex].Valid) continue; + + var position = agentData.position[agentIndex]; + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(agentData.movementPlane[agentIndex]); + + var ourSpeed = output.speed[agentIndex]; + var ourEndOfPath = agentData.endOfPath[agentIndex]; + + // Ignore if destination is not set + if (!math.isfinite(ourEndOfPath.x)) continue; + + var distToEndSq = math.lengthsq(movementPlane.ToPlane(ourEndOfPath - position, out float endOfPathElevationDifference)); + var ourHeight = agentData.height[agentIndex]; + var reachedEndOfPath = false; + var flowFollowing = false; + var ourRadius = agentData.radius[agentIndex]; + var forwardClearance = output.forwardClearance[agentIndex]; + + // Heuristic 2 + if (distToEndSq < ourRadius*ourRadius*(0.5f*0.5f) && endOfPathElevationDifference < ourHeight && endOfPathElevationDifference > -ourHeight*0.5f) { + reachedEndOfPath = true; + } + + var closeToBlocked = forwardClearance < ourRadius*0.5f; + var slowish = ourSpeed*ourSpeed < math.max(0.01f*0.01f, math.lengthsq(temporaryAgentData.desiredVelocity[agentIndex])*0.25f); + var blockedAndSlow = closeToBlocked && slowish; + tempData[agentIndex] = new TempAgentData { + blockedAndSlow = blockedAndSlow, + distToEndSq = distToEndSq + }; + + // Heuristic 3 + for (int i = 0; i < SimulatorBurst.MaxBlockingAgentCount; i++) { + var blockingAgentIndex = output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount + i]; + if (blockingAgentIndex == -1) break; + + var otherPosition = agentData.position[blockingAgentIndex]; + var distBetweenAgentsSq = math.lengthsq(movementPlane.ToPlane(position - otherPosition)); + var circleRadius = (math.sqrt(distBetweenAgentsSq) + ourRadius + agentData.radius[blockingAgentIndex])*0.5f; + var endWithinCircle = math.lengthsq(movementPlane.ToPlane(ourEndOfPath - 0.5f*(position + otherPosition))) < circleRadius*circleRadius; + if (endWithinCircle) { + // Check if the other agent has an arrow pointing to this agent (i.e. it is blocked by this agent) + var loop = false; + for (int j = 0; j < SimulatorBurst.MaxBlockingAgentCount; j++) { + var arrowFromAgent = inArrows[agentIndex*SimulatorBurst.MaxBlockingAgentCount + j]; + if (arrowFromAgent == -1) break; + if (arrowFromAgent == blockingAgentIndex) { + loop = true; + break; + } + } + + if (loop) { + flowFollowing = true; + + if (blockedAndSlow) { + reachedEndOfPath = true; + } + } + } + } + + var effectivelyReached = reachedEndOfPath ? ReachedEndOfPath.Reached : (flowFollowing ? ReachedEndOfPath.ReachedSoon : ReachedEndOfPath.NotReached); + if (effectivelyReached != output.effectivelyReachedDestination[agentIndex]) { + output.effectivelyReachedDestination[agentIndex] = effectivelyReached; + + if (effectivelyReached == ReachedEndOfPath.Reached) { + // Mark this agent as queued to prevent it from being added to the queue again. + queued[agentIndex] = true; + + // Changing to the Reached flag may affect the calculations for other agents. + // So we iterate over all agents that may be affected and enqueue them again. + var count = inArrowCounts[agentIndex]; + for (int i = 0; i < count; i++) { + var inArrow = inArrows[agentIndex*SimulatorBurst.MaxBlockingAgentCount + i]; + if (!queued[inArrow]) que.PushEnd(inArrow); + } + } + } + } + MarkerFirstPass.End(); + + + int iteration = 0; + while (que.Length > 0) { + var agentIndex = que.PopStart(); + iteration++; + // If we are already at the reached stage, the result can never change. + if (output.effectivelyReachedDestination[agentIndex] == ReachedEndOfPath.Reached) continue; + queued[agentIndex] = false; + + var ourSpeed = output.speed[agentIndex]; + var ourEndOfPath = agentData.endOfPath[agentIndex]; + // Ignore if destination is not set + // TODO: Will this never trigger due to FloatMode.Fast? + // Should be ok anyway, since the distance calculations below will filter it out anyway. + if (!math.isfinite(ourEndOfPath.x)) continue; + + var ourPosition = agentData.position[agentIndex]; + var blockedAndSlow = tempData[agentIndex].blockedAndSlow; + var distToEndSq = tempData[agentIndex].distToEndSq; + var ourRadius = agentData.radius[agentIndex]; + var reachedEndOfPath = false; + var flowFollowing = false; + + // Heuristic 4 + for (int i = 0; i < SimulatorBurst.MaxBlockingAgentCount; i++) { + var blockingAgentIndex = output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount + i]; + if (blockingAgentIndex == -1) break; + + var otherEndOfPath = agentData.endOfPath[blockingAgentIndex]; + var otherRadius = agentData.radius[blockingAgentIndex]; + + // Check if the other agent has a destination in roughly the same position as this agent. + // If we are further from the destination we tolarate larger deviations. + var endOfPathsOverlapping = math.lengthsq(otherEndOfPath - ourEndOfPath) <= distToEndSq*(0.5f*0.5f); + var otherReached = output.effectivelyReachedDestination[blockingAgentIndex] == ReachedEndOfPath.Reached; + + if (otherReached && (endOfPathsOverlapping || math.lengthsq(ourEndOfPath - agentData.position[blockingAgentIndex]) < math.lengthsq(ourRadius+otherRadius))) { + var otherSpeed = output.speed[blockingAgentIndex]; + flowFollowing |= math.min(ourSpeed, otherSpeed) < 0.01f; + reachedEndOfPath |= blockedAndSlow; + } + } + + var effectivelyReached = reachedEndOfPath ? ReachedEndOfPath.Reached : (flowFollowing ? ReachedEndOfPath.ReachedSoon : ReachedEndOfPath.NotReached); + // We do not check for all things that are checked in the first pass. So incorporate the previous information by taking the max. + effectivelyReached = (ReachedEndOfPath)math.max((int)effectivelyReached, (int)output.effectivelyReachedDestination[agentIndex]); + + if (effectivelyReached != output.effectivelyReachedDestination[agentIndex]) { + output.effectivelyReachedDestination[agentIndex] = effectivelyReached; + + if (effectivelyReached == ReachedEndOfPath.Reached) { + // Mark this agent as queued to prevent it from being added to the queue again. + queued[agentIndex] = true; + + // Changes to the Reached flag may affect the calculations for other agents. + // So we iterate over all agents that may be affected and enqueue them again. + var count = inArrowCounts[agentIndex]; + for (int i = 0; i < count; i++) { + var inArrow = inArrows[agentIndex*SimulatorBurst.MaxBlockingAgentCount + i]; + if (!queued[inArrow]) que.PushEnd(inArrow); + } + } + } + } + } + } + + // Note: FloatMode should not be set to Fast because that causes inaccuracies which can lead to + // agents failing to avoid walls sometimes. + [BurstCompile(CompileSynchronously = true, FloatMode = FloatMode.Default)] + public struct JobRVO : Pathfinding.Jobs.IJobParallelForBatched where MovementPlaneWrapper : struct, IMovementPlaneWrapper { + [ReadOnly] + public SimulatorBurst.AgentData agentData; + + [ReadOnly] + public SimulatorBurst.TemporaryAgentData temporaryAgentData; + + [ReadOnly] + public NavmeshEdges.NavmeshBorderData navmeshEdgeData; + + [WriteOnly] + public SimulatorBurst.AgentOutputData output; + + public float deltaTime; + public float symmetryBreakingBias; + public float priorityMultiplier; + public bool useNavmeshAsObstacle; + + public bool allowBoundsChecks { get { return true; } } + + const int MaxObstacleCount = 50; + +#if UNITY_EDITOR + public CommandBuilder draw; +#endif + + public void Execute (int startIndex, int batchSize) { + ExecuteORCA(startIndex, batchSize); + } + + struct SortByKey : IComparer { + public UnsafeSpan keys; + + public int Compare (int x, int y) { + return keys[x].CompareTo(keys[y]); + } + } + + /// + /// Sorts the array in place using insertion sort. + /// This is a stable sort. + /// See: http://en.wikipedia.org/wiki/Insertion_sort + /// + /// Used only because Unity.Collections.NativeSortExtension.Sort seems to have some kind of code generation bug when using Burst 1.8.2, causing it to throw exceptions. + /// + static void InsertionSort(UnsafeSpan data, U comparer) where T : unmanaged where U : IComparer { + for (int i = 1; i < data.Length; i++) { + var value = data[i]; + int j = i - 1; + while (j >= 0 && comparer.Compare(data[j], value) > 0) { + data[j + 1] = data[j]; + j--; + } + data[j + 1] = value; + } + } + + private static readonly ProfilerMarker MarkerConvertObstacles1 = new ProfilerMarker("RVOConvertObstacles1"); + private static readonly ProfilerMarker MarkerConvertObstacles2 = new ProfilerMarker("RVOConvertObstacles2"); + + /// + /// Generates ORCA half-planes for all obstacles near the agent. + /// For more details refer to the ORCA (Optimal Reciprocal Collision Avoidance) paper. + /// + /// This function takes in several arrays which are just used for temporary data. This is to avoid the overhead of allocating the arrays once for every agent. + /// + void GenerateObstacleVOs (int agentIndex, NativeList adjacentObstacleIdsScratch, NativeArray adjacentObstacleVerticesScratch, NativeArray segmentDistancesScratch, NativeArray sortedVerticesScratch, NativeArray orcaLines, NativeArray orcaLineToAgent, [NoAlias] ref int numLines, [NoAlias] in MovementPlaneWrapper movementPlane, float2 optimalVelocity) { + if (!useNavmeshAsObstacle) return; + + var localPosition = movementPlane.ToPlane(agentData.position[agentIndex], out var agentElevation); + var agentHeight = agentData.height[agentIndex]; + var agentRadius = agentData.radius[agentIndex]; + var obstacleRadius = agentRadius * 0.01f; + var inverseObstacleTimeHorizon = math.rcp(agentData.obstacleTimeHorizon[agentIndex]); + + ExpectNotAliased(in agentData.collisionNormal, in agentData.position); + + var hierarchicalNodeIndex = agentData.hierarchicalNodeIndex[agentIndex]; + if (hierarchicalNodeIndex == -1) return; + + var size = (obstacleRadius + agentRadius + agentData.obstacleTimeHorizon[agentIndex] * agentData.maxSpeed[agentIndex]) * new float3(2, 0, 2); + size.y = agentData.height[agentIndex] * 2f; + var bounds = new Bounds(new Vector3(localPosition.x, agentElevation, localPosition.y), size); + var boundingRadiusSq = math.lengthsq(bounds.extents); + adjacentObstacleIdsScratch.Clear(); + + var worldBounds = movementPlane.ToWorld(bounds); + navmeshEdgeData.GetObstaclesInRange(hierarchicalNodeIndex, worldBounds, adjacentObstacleIdsScratch); + +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.Obstacles)) { + draw.PushMatrix(movementPlane.matrix); + draw.PushMatrix(new float4x4( + new float4(1, 0, 0, 0), + new float4(0, 0, -1, 0), + new float4(0, 1, 0, 0), + new float4(0, 0, 0, 1) + )); + draw.WireBox(bounds, Color.blue); + draw.PopMatrix(); + draw.PopMatrix(); + } +#endif + + // TODO: For correctness all obstacles should be added in nearest-to-farthest order. + // This loop should be split up. + for (int oi = 0; oi < adjacentObstacleIdsScratch.Length; oi++) { + MarkerConvertObstacles1.Begin(); + var obstacleId = adjacentObstacleIdsScratch[oi]; + + var obstacleAllocations = navmeshEdgeData.obstacleData.obstacles[obstacleId]; + var vertices = navmeshEdgeData.obstacleData.obstacleVertices.GetSpan(obstacleAllocations.verticesAllocation); + var groups = navmeshEdgeData.obstacleData.obstacleVertexGroups.GetSpan(obstacleAllocations.groupsAllocation); + int vertexOffset = 0; + int candidateVertexCount = 0; + for (int i = 0; i < groups.Length; i++) { + var group = groups[i]; + // Check if the group does not overlap with our bounds at all + if (!math.all((group.boundsMx >= worldBounds.min) & (group.boundsMn <= worldBounds.max))) { + vertexOffset += group.vertexCount; + continue; + } + + + var startVertex = vertexOffset; + var endVertex = vertexOffset + group.vertexCount - 1; + if (endVertex >= adjacentObstacleVerticesScratch.Length) { + // Too many vertices. Skip remaining vertices. + break; + } + + for (int vi = startVertex; vi < startVertex + group.vertexCount; vi++) { + // X coordinate is the index of the previous vertex, the y coordinate is the next vertex + adjacentObstacleVerticesScratch[vi] = new int2(vi - 1, vi + 1); + } + // UnityEngine.Assertions.Assert.AreEqual(vertexCount, endVertex + 1); + + // Patch the start and end vertices to be correct. + // In a chain the last vertex doesn't start a new segment so we just make it loop back on itself. + // In a loop the last vertex connects to the first vertex. + adjacentObstacleVerticesScratch[startVertex] = new int2(group.type == ObstacleType.Loop ? endVertex : startVertex, adjacentObstacleVerticesScratch[startVertex].y); + adjacentObstacleVerticesScratch[endVertex] = new int2(adjacentObstacleVerticesScratch[endVertex].x, group.type == ObstacleType.Loop ? startVertex : endVertex); + + for (int vi = 0; vi < group.vertexCount; vi++) { + var vertex = vertices[vi + vertexOffset]; + int next = adjacentObstacleVerticesScratch[vi + startVertex].y; + var pos = movementPlane.ToPlane(vertex) - localPosition; + var nextPos = movementPlane.ToPlane(vertices[next]) - localPosition; + var dir = nextPos - pos; + var closestT = ClosestPointOnSegment(pos, dir / math.lengthsq(dir), float2.zero, 0, 1); + var dist = math.lengthsq(pos + dir*closestT); + segmentDistancesScratch[vi + startVertex] = dist; + + if (dist <= boundingRadiusSq && candidateVertexCount < sortedVerticesScratch.Length) { + sortedVerticesScratch[candidateVertexCount] = vi + startVertex; + candidateVertexCount++; + } + } + + vertexOffset += group.vertexCount; + } + + MarkerConvertObstacles1.End(); + + MarkerConvertObstacles2.Begin(); + // Sort obstacle segments by distance from the agent + InsertionSort(sortedVerticesScratch.AsUnsafeSpan().Slice(0, candidateVertexCount), new SortByKey { + keys = segmentDistancesScratch.AsUnsafeSpan().Slice(0, vertexOffset) + }); + + for (int i = 0; i < candidateVertexCount; i++) { + // In the unlikely event that we exceed the maximum number of obstacles, we just skip the remaining ones. + if (numLines >= MaxObstacleCount) break; + + // Processing the obstacle defined by v1 and v2 + // + // v0 v3 + // \ / + // \ / + // v1 ========= v2 + // + var v1Index = sortedVerticesScratch[i]; + + // If the obstacle is too far away, we can skip it. + // Since the obstacles are sorted by distance we can break here. + if (segmentDistancesScratch[v1Index] > 0.25f*size.x*size.x) break; + + var v0Index = adjacentObstacleVerticesScratch[v1Index].x; + var v2Index = adjacentObstacleVerticesScratch[v1Index].y; + if (v2Index == v1Index) continue; + var v3Index = adjacentObstacleVerticesScratch[v2Index].y; + UnityEngine.Assertions.Assert.AreNotEqual(v1Index, v3Index); + UnityEngine.Assertions.Assert.AreNotEqual(v0Index, v2Index); + + var v0 = vertices[v0Index]; + var v1 = vertices[v1Index]; + var v2 = vertices[v2Index]; + var v3 = vertices[v3Index]; + + var v0Position = movementPlane.ToPlane(v0) - localPosition; + var v1Position = movementPlane.ToPlane(v1, out var e1) - localPosition; + var v2Position = movementPlane.ToPlane(v2, out var e2) - localPosition; + var v3Position = movementPlane.ToPlane(v3) - localPosition; + + // Assume the obstacle has the same height as the agent, then check if they overlap along the elevation axis. + if (math.max(e1, e2) + agentHeight < agentElevation || math.min(e1, e2) > agentElevation + agentHeight) { + // The obstacle is not in the agent's elevation range. Ignore it. + continue; + } + + var length = math.length(v2Position - v1Position); + if (length < 0.0001f) continue; + var segmentDir = (v2Position - v1Position) * math.rcp(length); + + if (det(segmentDir, -v1Position) > obstacleRadius) { + // Agent is significantly on the wrong side of the segment (on the "inside"). Ignore it. + continue; + } + + // Check if this velocity obstacle completely behind previously added ORCA lines. + // If so, this obstacle is redundant and we can ignore it. + // This is not just a performance optimization. Using the ORCA lines for closer + // obstacles is better since obstacles further away can add ORCA lines that + // restrict the velocity space unnecessarily. The ORCA line is more conservative than the VO. + bool alreadyCovered = false; + + const float EPSILON = 0.0001f; + for (var j = 0; j < numLines; j++) { + var line = orcaLines[j]; + if ( + // Check if this velocity-obstacle is completely inside the previous ORCA line's infeasible half-plane region. + det(inverseObstacleTimeHorizon * v1Position - line.point, line.direction) - inverseObstacleTimeHorizon * obstacleRadius >= -EPSILON && + det(inverseObstacleTimeHorizon * v2Position - line.point, line.direction) - inverseObstacleTimeHorizon * obstacleRadius >= -EPSILON + ) { + alreadyCovered = true; + break; + } + } + if (alreadyCovered) { + continue; + } + + var obstacleOptimizationVelocity = float2.zero; + var distanceAlongSegment = math.dot(obstacleOptimizationVelocity - v1Position, segmentDir); + var closestPointOnSegment = v1Position + distanceAlongSegment * segmentDir; + var distanceToLineSq = math.lengthsq(closestPointOnSegment - obstacleOptimizationVelocity); + var distanceToSegmentSq = math.lengthsq((v1Position + math.clamp(distanceAlongSegment, 0, length) * segmentDir)); + + var v1Convex = leftOrColinear(v1Position - v0Position, segmentDir); + var v2Convex = leftOrColinear(segmentDir, v3Position - v2Position); + + if (distanceToSegmentSq < obstacleRadius*obstacleRadius) { + if (distanceAlongSegment < 0.0f) { + // Collision with left vertex, ignore if the vertex is not convex + if (v1Convex) { + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = -v1Position * 0.1f, + direction = math.normalizesafe(rot90(v1Position)), + }; + } + } else if (distanceAlongSegment > length) { + // Collision with right vertex + // Ignore if the vertex is not convex, or if it will be taken care of + // by the neighbour obstacle segment. + if (v2Convex && leftOrColinear(v2Position, v3Position - v2Position)) { + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = -v2Position * 0.1f, + direction = math.normalizesafe(rot90(v2Position)), + }; + } + } else { + // Collision with segment + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = -closestPointOnSegment * 0.1f, + direction = -segmentDir, + }; + } + continue; + } + + // Represents rays starting points on the VO circles, going in a tangent direction away from the agent. + float2 leftLegDirection, rightLegDirection; + + if ((distanceAlongSegment < 0 || distanceAlongSegment > 1) && distanceToLineSq <= obstacleRadius*obstacleRadius) { + // Obliquely viewed so that the circle around one of the vertices is all that is visible from p. p = obstacleOptimizationVelocity + // _____________________________ _ _ _ _ _ _ _ _ _ _ _ _ + // _/ \_ _/ \_ + // / \ / \ + // | v1 | | v2 | + // \_ _/ \_ _/ p + // \_____/_________________\_____/ _ _ _ _ _ _ _ _ _ _ _ _ + + // Collapse segment to a single point, making sure that v0 and v3 are still the neighbouring vertices. + if (distanceAlongSegment < 0) { + // Collapse to v1 + // Ignore if not convex + if (!v1Convex) continue; + v3Position = v2Position; + v2Position = v1Position; + v2Convex = v1Convex; + } else { + // Collapse to v2 + if (!v2Convex) continue; + v0Position = v1Position; + v1Position = v2Position; + v1Convex = v2Convex; + } + var vertexDistSq = math.lengthsq(v1Position); + // Distance from p to the points where the legs (tangents) touch the circle around the vertex. + float leg = math.sqrt(vertexDistSq - obstacleRadius*obstacleRadius); + var posNormal = new float2(-v1Position.y, v1Position.x); + // These become normalized + leftLegDirection = (v1Position*leg + posNormal*obstacleRadius) / vertexDistSq; + rightLegDirection = (v1Position*leg - posNormal*obstacleRadius) / vertexDistSq; + } else { + // This is the common case (several valid positions of p are shown). p = obstacleOptimizationVelocity + // + // p + // _____________________________ + // _/ \_ _/ \_ + // / \ / \ + // | v1 | | v2 | + // \_ _/ \_ _/ + // \_____/_________________\_____/ + // + // p p + + if (v1Convex) { + var vertexDistSq = math.lengthsq(v1Position); + float leg = math.sqrt(vertexDistSq - obstacleRadius*obstacleRadius); + var posNormal = new float2(-v1Position.y, v1Position.x); + // This becomes normalized + leftLegDirection = (v1Position*leg + posNormal*obstacleRadius) / vertexDistSq; + } else { + leftLegDirection = -segmentDir; + } + + if (v2Convex) { + var vertexDistSq = math.lengthsq(v2Position); + float leg = math.sqrt(vertexDistSq - obstacleRadius*obstacleRadius); + var posNormal = new float2(-v2Position.y, v2Position.x); + rightLegDirection = (v2Position*leg - posNormal*obstacleRadius) / vertexDistSq; + } else { + rightLegDirection = segmentDir; + } + } + + // Legs should never point into the obstacle for legs added by convex vertices. + // The neighbouring vertex will add a better obstacle for those cases. + // + // In that case we replace the legs with the neighbouring segments, and if the closest + // point is on those segments we know we can ignore them because the + // neighbour will handle it. + // + // It's important that we don't include the case when they are colinear, + // because if v1=v0 (or v2=v3), which can happen at the end of a chain, the + // determinant will always be zero and so they will seem colinear. + // + // Note: One might think that this should apply to all vertices, not just convex ones. + // Consider this case where you might think a non-convex vertices otherwise would + // cause 'ghost' obstacles: + // ___ + // | | A + // | | + // | \ + // |____\ B + // <-X + // + // If X is an agent, moving to the left. It could get stuck against the segment A. + // This is because the vertex between A and B is concave, and it will generate a leg + // pointing downwards. + // + // However, this does not cause a problem in practice. Because if the horizontal segment at the bottom is added first (as it should be) + // then A and B will be discarded since they will be completely behind the ORCA line added by the horizontal segment. + bool isLeftLegForeign = false; + bool isRightLegForeign = false; + if (v1Convex && left(leftLegDirection, v0Position - v1Position)) { + // Left leg points into obstacle + leftLegDirection = v0Position - v1Position; + isLeftLegForeign = true; + } + + if (v2Convex && right(rightLegDirection, v3Position - v2Position)) { + // Right leg points into obstacle + rightLegDirection = v3Position - v2Position; + isRightLegForeign = true; + } + + + // The velocity obstacle for this segment consists of a left leg, right leg, + // a cutoff line, and two circular arcs where the legs and the cutoff line join together. + // LeftLeg RightLeg + // \ _____________________________ / + // \ _/ \_ _/ \_ / + // \ / \ / \ / + // \| v1 | | v2 |/ + // \_ _/ \_ _/ + // \_____/_________________\_____/ + // Cutoff Line + // + // In case only one vertex makes up the obstacle then we instead have just a left leg, right leg, and a single circular arc. + // + // LeftLeg RightLeg + // \ _____ / + // \ _/ \_ / + // \ / \ / + // \| |/ + // \_ _/ + // \_____/ + // + + + // We first check if the velocity will be projected on those circular segments. + var leftCutoff = inverseObstacleTimeHorizon * v1Position; + var rightCutoff = inverseObstacleTimeHorizon * v2Position; + var cutoffDir = rightCutoff - leftCutoff; + var cutoffLength = math.lengthsq(cutoffDir); + + // Projection on the cutoff line (between 0 and 1 if the projection is on the cutoff segment) + var t = cutoffLength <= 0.00001f ? 0.5f : math.dot(optimalVelocity - leftCutoff, cutoffDir)/cutoffLength; + // Negative if the closest point on the rays reprensenting the legs is before the ray starts + var tLeft = math.dot(optimalVelocity - leftCutoff, leftLegDirection); + var tRight = math.dot(optimalVelocity - rightCutoff, rightLegDirection); + + + // Check if the projected velocity is on the circular arcs + if ((t < 0.0f && tLeft < 0.0f) || (t > 1.0f && tRight < 0.0f) || (cutoffLength <= 0.00001f && tLeft < 0.0f && tRight < 0.0f)) { + var arcCenter = t <= 0.5f ? leftCutoff : rightCutoff; + + var unitW = math.normalizesafe(optimalVelocity - arcCenter); + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = arcCenter + obstacleRadius * inverseObstacleTimeHorizon * unitW, + direction = new float2(unitW.y, -unitW.x), + }; + continue; + } + + // If the closest point is not on the arcs, then we project it on the legs or the cutoff line and pick the closest one. + // Note that all these distances should be reduced by obstacleRadius, but we only compare the values, so this doesn't matter. + float distToCutoff = (t > 1.0f || t < 0.0f || cutoffLength < 0.0001f ? math.INFINITY : math.lengthsq(optimalVelocity - (leftCutoff + t * cutoffDir))); + float distToLeftLeg = (tLeft < 0.0f ? math.INFINITY : math.lengthsq(optimalVelocity - (leftCutoff + tLeft * leftLegDirection))); + float distToRightLeg = (tRight < 0.0f ? math.INFINITY : math.lengthsq(optimalVelocity - (rightCutoff + tRight * rightLegDirection))); + var selected = 0; + var mn = distToCutoff; + if (distToLeftLeg < mn) { + mn = distToLeftLeg; + selected = 1; + } + if (distToRightLeg < mn) { + mn = distToRightLeg; + selected = 2; + } + + if (selected == 0) { + // Project on cutoff line + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = leftCutoff + obstacleRadius * inverseObstacleTimeHorizon * new float2(segmentDir.y, -segmentDir.x), + direction = -segmentDir, + }; + } else if (selected == 1) { + if (!isLeftLegForeign) { + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = leftCutoff + obstacleRadius * inverseObstacleTimeHorizon * new float2(-leftLegDirection.y, leftLegDirection.x), + direction = leftLegDirection, + }; + } + } else if (selected == 2) { + if (!isRightLegForeign) { + orcaLineToAgent[numLines] = -1; + orcaLines[numLines++] = new ORCALine { + point = rightCutoff + obstacleRadius * inverseObstacleTimeHorizon * new float2(rightLegDirection.y, -rightLegDirection.x), + direction = -rightLegDirection, + }; + } + } + } + MarkerConvertObstacles2.End(); + } + } + + public void ExecuteORCA (int startIndex, int batchSize) { + int endIndex = startIndex + batchSize; + + NativeArray orcaLines = new NativeArray(SimulatorBurst.MaxNeighbourCount + MaxObstacleCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray scratchBuffer = new NativeArray(SimulatorBurst.MaxNeighbourCount + MaxObstacleCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray segmentDistancesScratch = new NativeArray(SimulatorBurst.MaxObstacleVertices, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray sortedVerticesScratch = new NativeArray(SimulatorBurst.MaxObstacleVertices, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray adjacentObstacleVertices = new NativeArray(4 * SimulatorBurst.MaxObstacleVertices, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeArray orcaLineToAgent = new NativeArray(SimulatorBurst.MaxNeighbourCount + MaxObstacleCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + NativeList adjacentObstacleIdsScratch = new NativeList(16, Allocator.Temp); + + for (int agentIndex = startIndex; agentIndex < endIndex; agentIndex++) { + if (!agentData.version[agentIndex].Valid) continue; + + if (agentData.manuallyControlled[agentIndex]) { + output.speed[agentIndex] = agentData.desiredSpeed[agentIndex]; + output.targetPoint[agentIndex] = agentData.targetPoint[agentIndex]; + output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount] = -1; + continue; + } + + var position = agentData.position[agentIndex]; + + if (agentData.locked[agentIndex]) { + output.speed[agentIndex] = 0; + output.targetPoint[agentIndex] = position; + output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount] = -1; + continue; + } + + MovementPlaneWrapper movementPlane = default; + movementPlane.Set(agentData.movementPlane[agentIndex]); + + // The RVO algorithm assumes we will continue to + // move in roughly the same direction + float2 optimalVelocity = movementPlane.ToPlane(temporaryAgentData.currentVelocity[agentIndex]); + int numLines = 0; + // TODO: Obstacles are typically behind agents, so it's better to add the agent orca lines first to improve culling. + // However, the 3D optimization program requires obstacle lines to be added first. Not to mention that the culling + // is not strictly accurate for fixed obstacle since they cannot be moved backwards by the 3D linear program. + GenerateObstacleVOs(agentIndex, adjacentObstacleIdsScratch, adjacentObstacleVertices, segmentDistancesScratch, sortedVerticesScratch, orcaLines, orcaLineToAgent, ref numLines, in movementPlane, optimalVelocity); + int numFixedLines = numLines; + + var neighbours = temporaryAgentData.neighbours.Slice(agentIndex*SimulatorBurst.MaxNeighbourCount, SimulatorBurst.MaxNeighbourCount); + + float agentTimeHorizon = agentData.agentTimeHorizon[agentIndex]; + float inverseAgentTimeHorizon = math.rcp(agentTimeHorizon); + float priority = agentData.priority[agentIndex]; + + var localPosition = movementPlane.ToPlane(position); + var agentRadius = agentData.radius[agentIndex]; + var distSqToEndOfPath = math.all(math.isfinite(agentData.endOfPath[agentIndex])) ? math.lengthsq(agentData.endOfPath[agentIndex] - position) : float.PositiveInfinity; + + for (int neighbourIndex = 0; neighbourIndex < neighbours.Length; neighbourIndex++) { + int otherIndex = neighbours[neighbourIndex]; + // Indicates that there are no more neighbours (see JobRVOCalculateNeighbours) + if (otherIndex == -1) break; + + var otherPosition = agentData.position[otherIndex]; + var relativePosition = movementPlane.ToPlane(otherPosition - position); + float combinedRadius = agentRadius + agentData.radius[otherIndex]; + + var otherPriority = agentData.priority[otherIndex] * priorityMultiplier; + + // TODO: Remove branches to possibly vectorize + float avoidanceStrength; + if (agentData.locked[otherIndex] || agentData.manuallyControlled[otherIndex]) { + avoidanceStrength = 1; + } else if (otherPriority > 0.00001f || priority > 0.00001f) { + avoidanceStrength = otherPriority / (priority + otherPriority); + } else { + // Both this agent's priority and the other agent's priority is zero or negative + // Assume they have the same priority + avoidanceStrength = 0.5f; + } + + // We assume that the other agent will continue to move with roughly the same velocity if the priorities for the agents are similar. + // If the other agent has a higher priority than this agent (avoidanceStrength > 0.5) then we will assume it will move more along its + // desired velocity. This will have the effect of other agents trying to clear a path for where a high priority agent wants to go. + // If this is not done then even high priority agents can get stuck when it is really crowded and they have had to slow down. + float2 otherOptimalVelocity = movementPlane.ToPlane(math.lerp(temporaryAgentData.currentVelocity[otherIndex], temporaryAgentData.desiredVelocity[otherIndex], math.clamp(2*avoidanceStrength - 1, 0, 1))); + + if (agentData.flowFollowingStrength[otherIndex] > 0) { + // When flow following strength is 1 the component of the other agent's velocity that is in the direction of this agent is removed. + // That is, we pretend that the other agent does not move towards this agent at all. + // This will make it impossible for the other agent to "push" this agent away. + var strength = agentData.flowFollowingStrength[otherIndex] * agentData.flowFollowingStrength[agentIndex]; + var relativeDir = math.normalizesafe(relativePosition); + otherOptimalVelocity -= relativeDir * (strength * math.min(0, math.dot(otherOptimalVelocity, relativeDir))); + } + + var dist = math.length(relativePosition); + + // Figure out an approximate time to collision. We avoid using the current velocities of the agents because that leads to oscillations, + // as the agents change their velocities, which results in a change to the time to collision, which makes them change their velocities again. + var minimumDistanceToCollision = math.max(0, dist - combinedRadius); + if (agentData.locked[otherIndex] && minimumDistanceToCollision*minimumDistanceToCollision > distSqToEndOfPath) { + // The other agent is locked and we cannot collide with it until we have reached the end of our path. + // That means it can be safely ignored. + // This will reduce "shyness" around locked agents. + // TODO: This should ideally be done for non-locked agents too, but with a better heuristic. + continue; + } + + var minimumTimeToCollision = minimumDistanceToCollision / math.max(combinedRadius, agentData.desiredSpeed[agentIndex] + agentData.desiredSpeed[otherIndex]); + + // Adjust the radius to make the avoidance smoother. + // The agent will slowly start to take another agent into account instead of making a sharp turn. + float normalizedTime = minimumTimeToCollision * inverseAgentTimeHorizon; + // normalizedTime <= 0.5 => 0% effect + // normalizedTime = 1.0 => 100% effect + var factor = math.clamp((normalizedTime - 0.5f)*2.0f, 0, 1); + combinedRadius *= 1 - factor; + + // Adjust the time horizon to make the agent approach another agent less conservatively. + // This makes the velocity curve closer to sqrt(1-t) instead of exp(-t) as it comes to a stop, which looks nicer. + var tempInverseTimeHorizon = 1.0f/math.max(0.1f*agentTimeHorizon, agentTimeHorizon * math.clamp(math.sqrt(2f*minimumTimeToCollision), 0, 1)); + + orcaLines[numLines] = new ORCALine(localPosition, relativePosition, optimalVelocity, otherOptimalVelocity, combinedRadius, 0.1f, tempInverseTimeHorizon); + orcaLineToAgent[numLines] = otherIndex; + numLines++; +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.AgentVelocityObstacles)) { + draw.PushMatrix(math.mul(float4x4.TRS(position, quaternion.identity, 1), movementPlane.matrix)); + var voCenter = math.lerp(optimalVelocity, otherOptimalVelocity, 0.5f); + DrawVO(draw, relativePosition * tempInverseTimeHorizon + otherOptimalVelocity, combinedRadius * tempInverseTimeHorizon, otherOptimalVelocity, Color.black); + draw.PopMatrix(); + } +#endif + } + + // Add an obstacle for the collision normal. + // This is mostly deprecated, but kept for compatibility. + var collisionNormal = math.normalizesafe(movementPlane.ToPlane(agentData.collisionNormal[agentIndex])); + if (math.any(collisionNormal != 0)) { + orcaLines[numLines] = new ORCALine { + point = float2.zero, + direction = new float2(collisionNormal.y, -collisionNormal.x), + }; + orcaLineToAgent[numLines] = -1; + numLines++; + } + + var desiredVelocity = movementPlane.ToPlane(temporaryAgentData.desiredVelocity[agentIndex]); + var desiredTargetPointInVelocitySpace = temporaryAgentData.desiredTargetPointInVelocitySpace[agentIndex]; + var originalDesiredVelocity = desiredVelocity; + var symmetryBias = symmetryBreakingBias * (1 - agentData.flowFollowingStrength[agentIndex]); + // Bias the desired velocity to avoid symmetry issues (esp. when two agents are heading straight towards one another). + // Do not bias velocities if the agent is heading towards an obstacle (not an agent). + bool insideAnyVO = BiasDesiredVelocity(orcaLines.AsUnsafeSpan().Slice(numFixedLines, numLines - numFixedLines), ref desiredVelocity, ref desiredTargetPointInVelocitySpace, symmetryBias); + // If the velocity is outside all agent orca half-planes, do a more thorough check of all orca lines (including obstacles). + insideAnyVO = insideAnyVO || DistanceInsideVOs(orcaLines.AsUnsafeSpan().Slice(0, numLines), desiredVelocity) > 0; + + +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.ObstacleVelocityObstacles)) { + draw.PushColor(new Color(1, 1, 1, 0.2f)); + draw.PushMatrix(math.mul(float4x4.TRS(position, quaternion.identity, 1), movementPlane.matrix)); + for (int i = 0; i < numLines; i++) { + orcaLines[i].DrawAsHalfPlane(draw, agentData.radius[agentIndex] * 5.0f, 1.0f, i >= numFixedLines ? Color.magenta : Color.Lerp(Color.magenta, Color.black, 0.5f)); + } + draw.PopMatrix(); + draw.PopColor(); + } +#endif + + if (!insideAnyVO && math.all(math.abs(temporaryAgentData.collisionVelocityOffsets[agentIndex]) < 0.001f)) { + // Desired velocity can be used directly since it was not inside any velocity obstacle. + // No need to run optimizer because this will be the global minima. + // This is also a special case in which we can set the + // calculated target point to the desired target point + // instead of calculating a point based on a calculated velocity + // which is an important difference when the agent is very close + // to the target point + // TODO: Not actually guaranteed to be global minima if desiredTargetPointInVelocitySpace.magnitude < desiredSpeed + // maybe do something different here? +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.DesiredVelocity)) { + draw.xy.Cross(movementPlane.ToWorld(localPosition + desiredVelocity), Color.magenta); + draw.xy.Cross(movementPlane.ToWorld(localPosition + desiredTargetPointInVelocitySpace), Color.yellow); + } +#endif + + output.targetPoint[agentIndex] = position + movementPlane.ToWorld(desiredTargetPointInVelocitySpace, 0); + output.speed[agentIndex] = agentData.desiredSpeed[agentIndex]; + output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount] = -1; + output.forwardClearance[agentIndex] = float.PositiveInfinity; + } else { + var maxSpeed = agentData.maxSpeed[agentIndex]; + var allowedVelocityDeviationAngles = agentData.allowedVelocityDeviationAngles[agentIndex]; + LinearProgram2Output lin; + if (math.all(allowedVelocityDeviationAngles == 0)) { + // Common case, the desired velocity is a point + lin = LinearProgram2D(orcaLines, numLines, maxSpeed, desiredVelocity, false); + } else { + // The desired velocity is a segment, not a point + + // Rotate the desired velocity allowedVelocityDeviationAngles.x radians and allowedVelocityDeviationAngles.y radians respectively + math.sincos(allowedVelocityDeviationAngles, out float2 s, out float2 c); + var xs = desiredVelocity.x*c - desiredVelocity.y*s; + var ys = desiredVelocity.x*s + desiredVelocity.y*c; + var desiredVelocityLeft = new float2(xs.x, ys.x); + var desiredVelocityRight = new float2(xs.y, ys.y); + + var desiredVelocityLeftDir = desiredVelocity - desiredVelocityLeft; + + // Normalize and store length + var desiredVelocityLeftSegmentLength = math.length(desiredVelocityLeftDir); + desiredVelocityLeftDir = math.select(float2.zero, desiredVelocityLeftDir * math.rcp(desiredVelocityLeftSegmentLength), desiredVelocityLeftSegmentLength > math.FLT_MIN_NORMAL); + + var desiredVelocityRightDir = desiredVelocity - desiredVelocityRight; + var desiredVelocityRightSegmentLength = math.length(desiredVelocityRightDir); + desiredVelocityRightDir = math.select(float2.zero, desiredVelocityRightDir * math.rcp(desiredVelocityRightSegmentLength), desiredVelocityRightSegmentLength > math.FLT_MIN_NORMAL); + + // var tOptimal = ClosestPointOnSegment(desiredVelocityLeft, desiredVelocityDir, desiredVelocity, 0, desiredVelocitySegmentLength); + + var lin1 = LinearProgram2DSegment(orcaLines, numLines, maxSpeed, desiredVelocityLeft, desiredVelocityLeftDir, 0, desiredVelocityLeftSegmentLength, 1.0f); + var lin2 = LinearProgram2DSegment(orcaLines, numLines, maxSpeed, desiredVelocityRight, desiredVelocityRightDir, 0, desiredVelocityRightSegmentLength, 1.0f); + + if (lin1.firstFailedLineIndex < lin2.firstFailedLineIndex) { + lin = lin1; + } else if (lin2.firstFailedLineIndex < lin1.firstFailedLineIndex) { + lin = lin2; + } else { + lin = math.lengthsq(lin1.velocity - desiredVelocity) < math.lengthsq(lin2.velocity - desiredVelocity) ? lin1 : lin2; + } + } + + float2 newVelocity; + if (lin.firstFailedLineIndex < numLines) { + newVelocity = lin.velocity; + LinearProgram3D(orcaLines, numLines, numFixedLines, lin.firstFailedLineIndex, maxSpeed, ref newVelocity, scratchBuffer); + } else { + newVelocity = lin.velocity; + } + +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.ChosenVelocity)) { + draw.xy.Cross(position + movementPlane.ToWorld(newVelocity), Color.white); + draw.Arrow(position + movementPlane.ToWorld(desiredVelocity), position + movementPlane.ToWorld(newVelocity), Color.magenta); + } +#endif + + var blockedByAgentCount = 0; + for (int i = 0; i < numLines && blockedByAgentCount < SimulatorBurst.MaxBlockingAgentCount; i++) { + if (orcaLineToAgent[i] != -1 && det(orcaLines[i].direction, orcaLines[i].point - newVelocity) >= -0.001f) { + // We are blocked by this line + output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount + blockedByAgentCount] = orcaLineToAgent[i]; + blockedByAgentCount++; + } + } + if (blockedByAgentCount < SimulatorBurst.MaxBlockingAgentCount) output.blockedByAgents[agentIndex*SimulatorBurst.MaxBlockingAgentCount + blockedByAgentCount] = -1; + + var collisionVelocityOffset = temporaryAgentData.collisionVelocityOffsets[agentIndex]; + if (math.any(collisionVelocityOffset != 0)) { + // Make the agent move to avoid intersecting other agents (hard collisions) + newVelocity += temporaryAgentData.collisionVelocityOffsets[agentIndex]; + + // Adding the collision offset may have made the velocity invalid, causing it to intersect the wall-velocity-obstacles. + // We run a second optimization on only the wall-velocity-obstacles to make sure the velocity is valid. + newVelocity = LinearProgram2D(orcaLines, numFixedLines, maxSpeed, newVelocity, false).velocity; + } + + output.targetPoint[agentIndex] = position + movementPlane.ToWorld(newVelocity, 0); + output.speed[agentIndex] = math.min(math.length(newVelocity), maxSpeed); + + var targetDir = math.normalizesafe(movementPlane.ToPlane(agentData.targetPoint[agentIndex] - position)); + var forwardClearance = CalculateForwardClearance(neighbours, movementPlane, position, agentRadius, targetDir); + output.forwardClearance[agentIndex] = forwardClearance; + +#if UNITY_EDITOR + if (agentData.HasDebugFlag(agentIndex, AgentDebugFlags.ForwardClearance) && forwardClearance < float.PositiveInfinity) { + draw.PushLineWidth(2); + draw.Ray(position, movementPlane.ToWorld(targetDir) * forwardClearance, Color.red); + draw.PopLineWidth(); + } +#endif + } + } + } + + /// + /// Find the distance we can move towards our target without colliding with anything. + /// May become negative if we are currently colliding with something. + /// + float CalculateForwardClearance (NativeSlice neighbours, MovementPlaneWrapper movementPlane, float3 position, float radius, float2 targetDir) { + // TODO: Take obstacles into account. + var smallestIntersectionDistance = float.PositiveInfinity; + for (int i = 0; i < neighbours.Length; i++) { + var other = neighbours[i]; + if (other == -1) break; + var otherPosition = agentData.position[other]; + var combinedRadius = radius + agentData.radius[other]; + // Intersect the ray from our agent towards the destination and check the distance to the intersection with the other agent. + var otherDir = movementPlane.ToPlane(otherPosition - position); + // Squared cosine of the angle between otherDir and ourTargetDir + var cosAlpha = math.dot(math.normalizesafe(otherDir), targetDir); + + // Check if the agent is behind us + if (cosAlpha < 0) continue; + + var distToOtherSq = math.lengthsq(otherDir); + var distToClosestPointAlongRay = math.sqrt(distToOtherSq) * cosAlpha; + var discriminant = combinedRadius*combinedRadius - (distToOtherSq - distToClosestPointAlongRay*distToClosestPointAlongRay); + + // Check if we have any intersection at all + if (discriminant < 0) continue; + var distToIntersection = distToClosestPointAlongRay - math.sqrt(discriminant); + smallestIntersectionDistance = math.min(smallestIntersectionDistance, distToIntersection); + } + return smallestIntersectionDistance; + } + + /// True if vector2 is to the left of vector1 or if they are colinear. + static bool leftOrColinear (float2 vector1, float2 vector2) { + return det(vector1, vector2) >= 0; + } + + /// True if vector2 is to the left of vector1. + static bool left (float2 vector1, float2 vector2) { + return det(vector1, vector2) > 0; + } + + /// True if vector2 is to the right of vector1 or if they are colinear. + static bool rightOrColinear (float2 vector1, float2 vector2) { + return det(vector1, vector2) <= 0; + } + + /// True if vector2 is to the right of vector1. + static bool right (float2 vector1, float2 vector2) { + return det(vector1, vector2) < 0; + } + + /// + /// Determinant of the 2x2 matrix defined by vector1 and vector2. + /// Alternatively, the Z component of the cross product of vector1 and vector2. + /// + static float det (float2 vector1, float2 vector2) { + return vector1.x * vector2.y - vector1.y * vector2.x; + } + + static float2 rot90 (float2 v) { + return new float2(-v.y, v.x); + } + + /// + /// A half-plane defined as the line splitting plane. + /// + /// For ORCA purposes, the infeasible region of the half-plane is on the right side of the line. + /// + struct ORCALine { + public float2 point; + public float2 direction; + + public void DrawAsHalfPlane (CommandBuilder draw, float halfPlaneLength, float halfPlaneWidth, Color color) { + var normal = new float2(direction.y, -direction.x); + draw.xy.Line(point - direction*10, point + direction*10, color); + + var p = point + normal*halfPlaneWidth*0.5f; + draw.SolidBox(new float3(p, 0), quaternion.RotateZ(math.atan2(direction.y, direction.x)), new float3(halfPlaneLength, halfPlaneWidth, 0.01f), new Color(0, 0, 0, 0.5f)); + } + + public ORCALine(float2 position, float2 relativePosition, float2 velocity, float2 otherVelocity, float combinedRadius, float timeStep, float invTimeHorizon) { + var relativeVelocity = velocity - otherVelocity; + float combinedRadiusSq = combinedRadius*combinedRadius; + float distSq = math.lengthsq(relativePosition); + + if (distSq > combinedRadiusSq) { + combinedRadius *= 1.001f; + // No collision + + // A velocity obstacle is built which is shaped like a truncated cone (see ORCA paper). + // The cone is truncated by an arc centered at relativePosition/timeHorizon + // with radius combinedRadius/timeHorizon. + // The cone extends in the direction of relativePosition. + + // Vector from truncation arc center to relative velocity + var w = relativeVelocity - invTimeHorizon * relativePosition; + var wLengthSq = math.lengthsq(w); + + float dot1 = math.dot(w, relativePosition); + + if (dot1 < 0.0f && dot1*dot1 > combinedRadiusSq * wLengthSq) { + // Project on cut-off circle + float wLength = math.sqrt(wLengthSq); + var normalizedW = w / wLength; + + direction = new float2(normalizedW.y, -normalizedW.x); + var u = (combinedRadius * invTimeHorizon - wLength) * normalizedW; + point = velocity + 0.5f * u; + } else { + // Project on legs + // Distance from the agent to the point where the "legs" start on the VO + float legDistance = math.sqrt(distSq - combinedRadiusSq); + + if (det(relativePosition, w) > 0.0f) { + // Project on left leg + // Note: This vector is actually normalized + direction = (relativePosition * legDistance + new float2(-relativePosition.y, relativePosition.x) * combinedRadius) / distSq; + } else { + // Project on right leg + // Note: This vector is actually normalized + direction = (-relativePosition * legDistance + new float2(-relativePosition.y, relativePosition.x) * combinedRadius) / distSq; + } + + float dot2 = math.dot(relativeVelocity, direction); + var u = dot2 * direction - relativeVelocity; + point = velocity + 0.5f * u; + } + } else { + float invTimeStep = math.rcp(timeStep); + var dist = math.sqrt(distSq); + var normalizedDir = math.select(0, relativePosition / dist, dist > math.FLT_MIN_NORMAL); + var u = normalizedDir * (dist - combinedRadius - 0.001f) * 0.3f * invTimeStep; + direction = math.normalizesafe(new float2(u.y, -u.x)); + point = math.lerp(velocity, otherVelocity, 0.5f) + u * 0.5f; + + + // Original code, the above is a version which works better + // Collision + // Project on cut-off circle of timeStep + //float invTimeStep = 1.0f / timeStep; + // Vector from cutoff center to relative velocity + //float2 w = relativeVelocity - invTimeStep * relativePosition; + //float wLength = math.length(w); + //float2 unitW = w / wLength; + //direction = new float2(unitW.y, -unitW.x); + //var u = (combinedRadius * invTimeStep - wLength) * unitW; + //point = velocity + 0.5f * u; + } + } + } + + /// + /// Calculates how far inside the infeasible region of the ORCA half-planes the velocity is. + /// Returns 0 if the velocity is in the feasible region of all half-planes. + /// + static float DistanceInsideVOs (UnsafeSpan lines, float2 velocity) { + float maxDistance = 0.0f; + + for (int i = 0; i < lines.Length; i++) { + var distance = det(lines[i].direction, lines[i].point - velocity); + maxDistance = math.max(maxDistance, distance); + } + + return maxDistance; + } + + /// + /// Bias towards the right side of agents. + /// Rotate desiredVelocity at most [value] number of radians. 1 radian ≈ 57° + /// This breaks up symmetries. + /// + /// The desired velocity will only be rotated if it is inside a velocity obstacle (VO). + /// If it is inside one, it will not be rotated further than to the edge of it + /// + /// The targetPointInVelocitySpace will be rotated by the same amount as the desired velocity + /// + /// Returns: True if the desired velocity was inside any VO + /// + static bool BiasDesiredVelocity (UnsafeSpan lines, ref float2 desiredVelocity, ref float2 targetPointInVelocitySpace, float maxBiasRadians) { + float maxDistance = DistanceInsideVOs(lines, desiredVelocity); + + if (maxDistance == 0.0f) return false; + + var desiredVelocityMagn = math.length(desiredVelocity); + + // Avoid division by zero below + if (desiredVelocityMagn >= 0.001f) { + // Rotate the desired velocity clockwise (to the right) at most maxBiasRadians number of radians. + // We clamp the angle so that we do not rotate more than to the edge of the VO. + // Assuming maxBiasRadians is small, we can just move it instead and it will give approximately the same effect. + // See https://en.wikipedia.org/wiki/Small-angle_approximation + var angle = math.min(maxBiasRadians, maxDistance / desiredVelocityMagn); + desiredVelocity += new float2(desiredVelocity.y, -desiredVelocity.x) * angle; + targetPointInVelocitySpace += new float2(targetPointInVelocitySpace.y, -targetPointInVelocitySpace.x) * angle; + } + return true; + } + + /// + /// Clip a line to the feasible region of the half-plane given by the clipper. + /// The clipped line is `line.point + line.direction*tLeft` to `line.point + line.direction*tRight`. + /// + /// Returns false if the line is parallel to the clipper's border. + /// + static bool ClipLine (ORCALine line, ORCALine clipper, ref float tLeft, ref float tRight) { + float denominator = det(line.direction, clipper.direction); + float numerator = det(clipper.direction, line.point - clipper.point); + + if (math.abs(denominator) < 0.0001f) { + // The two lines are almost parallel + return false; + } + + float t = numerator / denominator; + + if (denominator >= 0.0f) { + // Line i bounds the line on the right + tRight = math.min(tRight, t); + } else { + // Line i bounds the line on the left + tLeft = math.max(tLeft, t); + } + return true; + } + + static bool ClipBoundary (NativeArray lines, int lineIndex, float radius, out float tLeft, out float tRight) { + var line = lines[lineIndex]; + if (!VectorMath.LineCircleIntersectionFactors(line.point, line.direction, radius, out tLeft, out tRight)) { + return false; + } + + // Go through all previous lines/half-planes and clip the current line against them + for (int i = 0; i < lineIndex; i++) { + float denominator = det(line.direction, lines[i].direction); + float numerator = det(lines[i].direction, line.point - lines[i].point); + + if (math.abs(denominator) < 0.0001f) { + // The two lines are almost parallel + if (numerator < 0.0f) { + // This line is completely "behind" the other line. So we can ignore it. + return false; + } else continue; + } + + float t = numerator / denominator; + + if (denominator >= 0.0f) { + // Line i bounds the line on the right + tRight = math.min(tRight, t); + } else { + // Line i bounds the line on the left + tLeft = math.max(tLeft, t); + } + + if (tLeft > tRight) { + // The line is completely outside the previous half-planes + return false; + } + } + return true; + } + + static bool LinearProgram1D (NativeArray lines, int lineIndex, float radius, float2 optimalVelocity, bool directionOpt, ref float2 result) { + if (!ClipBoundary(lines, lineIndex, radius, out float tLeft, out float tRight)) return false; + var line = lines[lineIndex]; + + if (directionOpt) { + // Optimize direction + if (math.dot(optimalVelocity, line.direction) > 0.0f) { + // Take right extreme + result = line.point + tRight * line.direction; + } else { + // Take left extreme + result = line.point + tLeft * line.direction; + } + } else { + // Optimize closest point + float t = math.dot(line.direction, optimalVelocity - line.point); + result = line.point + math.clamp(t, tLeft, tRight) * line.direction; + } + return true; + } + + struct LinearProgram2Output { + public float2 velocity; + public int firstFailedLineIndex; + } + + static LinearProgram2Output LinearProgram2D (NativeArray lines, int numLines, float radius, float2 optimalVelocity, bool directionOpt) { + float2 result; + + if (directionOpt) { + // Optimize direction. Note that the optimization velocity is of unit length in this case + result = optimalVelocity * radius; + } else if (math.lengthsq(optimalVelocity) > radius*radius) { + // Optimize closest point and outside circle + result = math.normalize(optimalVelocity) * radius; + } else { + // Optimize closest point and inside circle + result = optimalVelocity; + } + + for (int i = 0; i < numLines; i++) { + // Check if point is in the infeasible region of the half-plane + if (det(lines[i].direction, lines[i].point - result) > 0.0f) { + // Result does not satisfy constraint i. Compute new optimal result + var tempResult = result; + if (!LinearProgram1D(lines, i, radius, optimalVelocity, directionOpt, ref result)) { + return new LinearProgram2Output { + velocity = tempResult, + firstFailedLineIndex = i, + }; + } + } + } + + return new LinearProgram2Output { + velocity = result, + firstFailedLineIndex = numLines, + }; + } + + static float ClosestPointOnSegment (float2 a, float2 dir, float2 p, float t0, float t1) { + return math.clamp(math.dot(p - a, dir), t0, t1); + } + + /// + /// Closest point on segment a to segment b. + /// The segments are given by infinite lines and bounded by t values. p = line.point + line.dir*t. + /// + /// It is assumed that the two segments do not intersect. + /// + static float2 ClosestSegmentSegmentPointNonIntersecting (ORCALine a, ORCALine b, float ta1, float ta2, float tb1, float tb2) { + // We know that the two segments do not intersect, so at least one of the closest points + // must be one of the line segment endpoints. + var ap0 = a.point + a.direction*ta1; + var ap1 = a.point + a.direction*ta2; + var bp0 = b.point + b.direction * tb1; + var bp1 = b.point + b.direction * tb2; + + var t0 = ClosestPointOnSegment(a.point, a.direction, bp0, ta1, ta2); + var t1 = ClosestPointOnSegment(a.point, a.direction, bp1, ta1, ta2); + var t2 = ClosestPointOnSegment(b.point, b.direction, ap0, tb1, tb2); + var t3 = ClosestPointOnSegment(b.point, b.direction, ap1, tb1, tb2); + + var c0 = a.point + a.direction * t0; + var c1 = a.point + a.direction * t1; + var c2 = b.point + b.direction * t2; + var c3 = b.point + b.direction * t3; + + var d0 = math.lengthsq(c0 - bp0); + var d1 = math.lengthsq(c1 - bp1); + var d2 = math.lengthsq(c2 - ap0); + var d3 = math.lengthsq(c3 - ap1); + + var result = c0; + var d = d0; + if (d1 < d) { + result = c1; + d = d1; + } + if (d2 < d) { + result = ap0; + d = d2; + } + if (d3 < d) { + result = ap1; + d = d3; + } + return result; + } + + /// Like LinearProgram2D, but the optimal velocity space is a segment instead of a point, however the current result has collapsed to a point + static LinearProgram2Output LinearProgram2DCollapsedSegment (NativeArray lines, int numLines, int startLine, float radius, float2 currentResult, float2 optimalVelocityStart, float2 optimalVelocityDir, float optimalTLeft, float optimalTRight) { + for (int i = startLine; i < numLines; i++) { + // Check if point is in the infeasible region of the half-plane + if (det(lines[i].direction, lines[i].point - currentResult) > 0.0f) { + // Result does not satisfy constraint i. Compute new optimal result + if (!ClipBoundary(lines, i, radius, out float tLeft2, out float tRight2)) { + // We are partially not feasible, but no part of this constraint's boundary is in the feasible region. + // This means that there is no feasible solution at all. + return new LinearProgram2Output { + velocity = currentResult, + firstFailedLineIndex = i, + }; + } + + // Optimize closest point + currentResult = ClosestSegmentSegmentPointNonIntersecting(lines[i], new ORCALine { + point = optimalVelocityStart, + direction = optimalVelocityDir, + }, tLeft2, tRight2, optimalTLeft, optimalTRight); + } + } + + return new LinearProgram2Output { + velocity = currentResult, + firstFailedLineIndex = numLines, + }; + } + + /// Like LinearProgram2D, but the optimal velocity space is a segment instead of a point + static LinearProgram2Output LinearProgram2DSegment (NativeArray lines, int numLines, float radius, float2 optimalVelocityStart, float2 optimalVelocityDir, float optimalTLeft, float optimalTRight, float optimalT) { + var hasIntersection = VectorMath.LineCircleIntersectionFactors(optimalVelocityStart, optimalVelocityDir, radius, out float resultTLeft, out float resultTRight); + resultTLeft = math.max(resultTLeft, optimalTLeft); + resultTRight = math.min(resultTRight, optimalTRight); + hasIntersection &= resultTLeft <= resultTRight; + + if (!hasIntersection) { + // In case the optimal velocity segment is not inside the max velocity circle, then collapse to a single optimal velocity which + // is closest segment point to the circle + var t = math.clamp(math.dot(-optimalVelocityStart, optimalVelocityDir), optimalTLeft, optimalTRight); + var closestOnCircle = math.normalizesafe(optimalVelocityStart + optimalVelocityDir * t) * radius; + + // The best point is now a single point, not a segment. + // So we can fall back to simpler code. + return LinearProgram2DCollapsedSegment(lines, numLines, 0, radius, closestOnCircle, optimalVelocityStart, optimalVelocityDir, optimalTLeft, optimalTRight); + } + + for (int i = 0; i < numLines; i++) { + // Check if optimal line segment is at least partially in the infeasible region of the half-plane + var line = lines[i]; + var leftInfeasible = det(line.direction, line.point - (optimalVelocityStart + optimalVelocityDir*resultTLeft)) > 0.0f; + var rightInfeasible = det(line.direction, line.point - (optimalVelocityStart + optimalVelocityDir*resultTRight)) > 0.0f; + if (leftInfeasible || rightInfeasible) { + if (!ClipBoundary(lines, i, radius, out float tLeft, out float tRight)) { + // We are partially not feasible, but no part of this constraint's boundary is in the feasible region. + // This means that there is no feasible solution at all. + return new LinearProgram2Output { + velocity = optimalVelocityStart + optimalVelocityDir * math.clamp(optimalT, resultTLeft, resultTRight), + firstFailedLineIndex = i, + }; + } + + // Check if the optimal line segment is completely in the infeasible region + if (leftInfeasible && rightInfeasible) { + if (math.abs(det(line.direction, optimalVelocityDir)) < 0.001f) { + // Lines are almost parallel. + // Project the optimal velocity on the boundary + var t1 = ClosestPointOnSegment(line.point, line.direction, optimalVelocityStart + optimalVelocityDir*resultTLeft, tLeft, tRight); + var t2 = ClosestPointOnSegment(line.point, line.direction, optimalVelocityStart + optimalVelocityDir*resultTRight, tLeft, tRight); + var t3 = ClosestPointOnSegment(line.point, line.direction, optimalVelocityStart + optimalVelocityDir*optimalT, tLeft, tRight); + optimalVelocityStart = line.point; + optimalVelocityDir = line.direction; + resultTLeft = t1; + resultTRight = t2; + optimalT = t3; + } else { + // Find closest point on the constraint boundary segment to the optimal velocity segment + var result = ClosestSegmentSegmentPointNonIntersecting(line, new ORCALine { + point = optimalVelocityStart, + direction = optimalVelocityDir, + }, tLeft, tRight, optimalTLeft, optimalTRight); + + // The best point is now a single point, not a segment. + // So we can fall back to simpler code. + return LinearProgram2DCollapsedSegment(lines, numLines, i+1, radius, result, optimalVelocityStart, optimalVelocityDir, optimalTLeft, optimalTRight); + } + } else { + // Clip optimal velocity segment to the constraint boundary. + // If this returns false and the lines are almost parallel, then we don't do anything + // because we already know they intersect. So the two lines must be almost identical. + ClipLine(new ORCALine { + point = optimalVelocityStart, + direction = optimalVelocityDir, + }, line, ref resultTLeft, ref resultTRight); + } + } + } + + var resultT = math.clamp(optimalT, resultTLeft, resultTRight); + + return new LinearProgram2Output { + velocity = optimalVelocityStart + optimalVelocityDir * resultT, + firstFailedLineIndex = numLines, + }; + } + + /// + /// Finds the velocity with the smallest maximum penetration into the given half-planes. + /// + /// Assumes there are no points in the feasible region of the given half-planes. + /// + /// Runs a 3-dimensional linear program, but projected down to 2D. + /// If there are no feasible regions outside all half-planes then we want to find the velocity + /// for which the maximum penetration into infeasible regions is minimized. + /// Conceptually we can solve this by taking our half-planes, and moving them outwards at a fixed speed + /// until there is exactly 1 feasible point. + /// We can formulate this in 3D space by thinking of the half-planes in 3D (velocity.x, velocity.y, penetration-depth) space, as sloped planes. + /// Moving the planes outwards then corresponds to decreasing the z coordinate. + /// In 3D space we want to find the point above all planes with the lowest z coordinate. + /// We do this by going through each plane and testing if it is possible that this plane + /// is the one with the maximum penetration. + /// If so, we know that the point will lie on the portion of that plane bounded by the intersections + /// with the other planes. We generate projected half-planes which represent the intersections with the + /// other 3D planes, and then we run a new optimization to find the point which penetrates this + /// half-plane the least. + /// + /// The half-planes of all obstacles and agents. + /// The number of half-planes in lines. + /// The number of half-planes in lines which are fixed (0..numFixedLines). These will be treated as static obstacles which should be avoided at all costs. + /// The index of the first half-plane in lines for which the previous optimization failed (see \reflink{LinearProgram2Output.firstFailedLineIndex}). + /// Maximum possible speed. This represents a circular velocity obstacle. + /// Input is best velocity as output by \reflink{LinearProgram2D}. Output is the new best velocity. The velocity with the smallest maximum penetration into the given half-planes. + /// A buffer of length at least numLines to use for scratch space. + static void LinearProgram3D (NativeArray lines, int numLines, int numFixedLines, int beginLine, float radius, ref float2 result, NativeArray scratchBuffer) { + float distance = 0.0f; + + NativeArray projectedLines = scratchBuffer; + NativeArray.Copy(lines, projectedLines, numFixedLines); + + for (int i = beginLine; i < numLines; i++) { + // Check if #result is more than #distance units inside the infeasible region of the half-plane + if (det(lines[i].direction, lines[i].point - result) > distance) { + int numProjectedLines = numFixedLines; + for (int j = numFixedLines; j < i; j++) { + float determinant = det(lines[i].direction, lines[j].direction); + if (math.abs(determinant) < 0.001f) { + // Lines i and j are parallel + if (math.dot(lines[i].direction, lines[j].direction) > 0.0f) { + // Line i and j point in the same direction + continue; + } else { + // Line i and j point in the opposite direction + projectedLines[numProjectedLines] = new ORCALine { + point = 0.5f * (lines[i].point + lines[j].point), + direction = math.normalize(lines[j].direction - lines[i].direction), + }; + numProjectedLines++; + } + } else { + projectedLines[numProjectedLines] = new ORCALine { + // The intersection between the two lines + point = lines[i].point + (det(lines[j].direction, lines[i].point - lines[j].point) / determinant) * lines[i].direction, + // The direction along which the intersection of the two 3D-planes intersect (projected onto the XY plane) + direction = math.normalize(lines[j].direction - lines[i].direction), + }; + numProjectedLines++; + } + } + + var lin = LinearProgram2D(projectedLines, numProjectedLines, radius, new float2(-lines[i].direction.y, lines[i].direction.x), true); + if (lin.firstFailedLineIndex < numProjectedLines) { + // This should in principle not happen. The result is by definition + // already in the feasible region of this linear program. If it fails, + // it is due to small floating point error, and the current result is + // kept. + } else { + result = lin.velocity; + } + + distance = det(lines[i].direction, lines[i].point - result); + } + } + } + + static void DrawVO (CommandBuilder draw, float2 circleCenter, float radius, float2 origin, Color color) { +#if UNITY_EDITOR + draw.PushColor(color); + float alpha = math.atan2((origin - circleCenter).y, (origin - circleCenter).x); + float gamma = radius/math.length(origin-circleCenter); + float delta = gamma <= 1.0f ? math.abs(math.acos(gamma)) : 0; + + draw.xy.Circle(circleCenter, radius, alpha-delta, alpha+delta); + float2 p1 = new float2(math.cos(alpha-delta), math.sin(alpha-delta)) * radius; + float2 p2 = new float2(math.cos(alpha+delta), math.sin(alpha+delta)) * radius; + + float2 p1t = -new float2(-p1.y, p1.x); + float2 p2t = new float2(-p2.y, p2.x); + p1 += circleCenter; + p2 += circleCenter; + + draw.xy.Ray(p1, math.normalizesafe(p1t)*100); + draw.xy.Ray(p2, math.normalizesafe(p2t)*100); + draw.PopColor(); +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs.meta new file mode 100644 index 0000000..1e6a7e3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOAgentBurst.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87a0a74f7df9c401eaeb12dab0863446 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs.meta new file mode 100644 index 0000000..0811a7a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreObstacle.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 19c43d572022a4278a4d426f536b5ee4 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs.meta new file mode 100644 index 0000000..d9a2b4a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulator.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f373cccc6991444b0b8b6e8c512842db +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs new file mode 100644 index 0000000..b8f8049 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs @@ -0,0 +1,1271 @@ +using UnityEngine; +using System.Collections.Generic; +using Unity.Jobs; +using Unity.Mathematics; +using Unity.Collections; + +/// Local avoidance related classes +namespace Pathfinding.RVO { + using System; + using Pathfinding.Jobs; + using Pathfinding.Drawing; + using Pathfinding.Util; + using Pathfinding.Sync; + using Pathfinding.ECS.RVO; + using Pathfinding.Collections; + + public interface IMovementPlaneWrapper { + float2 ToPlane(float3 p); + float2 ToPlane(float3 p, out float elevation); + float3 ToWorld(float2 p, float elevation = 0); + Bounds ToWorld(Bounds bounds); + + /// Maps from 2D (X, Y, 0) coordinates to world coordinates + float4x4 matrix { get; } + void Set(NativeMovementPlane plane); + } + + public struct XYMovementPlane : IMovementPlaneWrapper { + public float2 ToPlane(float3 p) => p.xy; + public float2 ToPlane (float3 p, out float elevation) { + elevation = p.z; + return p.xy; + } + public float3 ToWorld(float2 p, float elevation = 0) => new float3(p.x, p.y, elevation); + public Bounds ToWorld (Bounds bounds) { + var center = bounds.center; + var size = bounds.size; + return new Bounds(new Vector3(center.x, center.z, center.y), new Vector3(size.x, size.z, size.y)); + } + + public float4x4 matrix { + get { + return float4x4.identity; + } + } + public void Set (NativeMovementPlane plane) { } + } + + public struct XZMovementPlane : IMovementPlaneWrapper { + public float2 ToPlane(float3 p) => p.xz; + public float2 ToPlane (float3 p, out float elevation) { + elevation = p.y; + return p.xz; + } + public float3 ToWorld(float2 p, float elevation = 0) => new float3(p.x, elevation, p.y); + public Bounds ToWorld(Bounds bounds) => bounds; + public void Set (NativeMovementPlane plane) { } + public float4x4 matrix => float4x4.RotateX(math.radians(90)); + } + + public struct ArbitraryMovementPlane : IMovementPlaneWrapper { + NativeMovementPlane plane; + + public float2 ToPlane(float3 p) => plane.ToPlane(p); + public float2 ToPlane(float3 p, out float elevation) => plane.ToPlane(p, out elevation); + public float3 ToWorld(float2 p, float elevation = 0) => plane.ToWorld(p, elevation); + public Bounds ToWorld(Bounds bounds) => plane.ToWorld(bounds); + public void Set (NativeMovementPlane plane) { + this.plane = plane; + } + public float4x4 matrix { + get { + return math.mul(float4x4.TRS(0, plane.rotation, 1), new float4x4( + new float4(1, 0, 0, 0), + new float4(0, 0, 1, 0), + new float4(0, 1, 0, 0), + new float4(0, 0, 0, 1) + )); + } + } + } + + [System.Flags] + public enum AgentDebugFlags : byte { + Nothing = 0, + ObstacleVelocityObstacles = 1 << 0, + AgentVelocityObstacles = 1 << 1, + ReachedState = 1 << 2, + DesiredVelocity = 1 << 3, + ChosenVelocity = 1 << 4, + Obstacles = 1 << 5, + ForwardClearance = 1 << 6, + + [System.Obsolete("Renamed to ObstacleVelocityObstacles")] + ObstacleVOs = ObstacleVelocityObstacles, + [System.Obsolete("Renamed to AgentVelocityObstacles")] + AgentVOs = AgentVelocityObstacles, + } + + /// + /// Exposes properties of an Agent class. + /// + /// See: RVOController + /// See: RVOSimulator + /// + public interface IAgent { + /// + /// Internal index of the agent. + /// See: + /// + int AgentIndex { get; } + + /// + /// Position of the agent. + /// The agent does not move by itself, a movement script has to be responsible for + /// reading the CalculatedTargetPoint and CalculatedSpeed properties and move towards that point with that speed. + /// This property should ideally be set every frame. + /// + Vector3 Position { get; set; } + + /// + /// Optimal point to move towards to avoid collisions. + /// The movement script should move towards this point with a speed of . + /// + /// See: RVOController.CalculateMovementDelta. + /// + Vector3 CalculatedTargetPoint { get; } + + /// + /// True if the agent's movement is affected by any other agents or obstacles. + /// + /// If the agent is all alone, and can just move in a straight line to its target, this will be false. + /// If it has to adjust its velocity, even slightly, to avoid collisions, this will be true. + /// + bool AvoidingAnyAgents { get; } + + /// + /// Optimal speed of the agent to avoid collisions. + /// The movement script should move towards with this speed. + /// + float CalculatedSpeed { get; } + + /// + /// Point towards which the agent should move. + /// Usually you set this once per frame. The agent will try move as close to the target point as possible. + /// Will take effect at the next simulation step. + /// + /// Note: The system assumes that the agent will stop when it reaches the target point + /// so if you just want to move the agent in a particular direction, make sure that you set the target point + /// a good distance in front of the character as otherwise the system may not avoid colisions that well. + /// What would happen is that the system (in simplified terms) would think that the agents would stop + /// before the collision and thus it wouldn't slow down or change course. See the image below. + /// In the image the desiredSpeed is the length of the blue arrow and the target point + /// is the point where the black arrows point to. + /// In the upper case the agent does not avoid the red agent (you can assume that the red + /// agent has a very small velocity for simplicity) while in the lower case it does. + /// If you are following a path a good way to pick the target point is to set it to + /// + /// targetPoint = directionToNextWaypoint.normalized * remainingPathDistance + /// + /// Where remainingPathDistance is the distance until the character would reach the end of the path. + /// This works well because at the end of the path the direction to the next waypoint will just be the + /// direction to the last point on the path and remainingPathDistance will be the distance to the last point + /// in the path, so targetPoint will be set to simply the last point in the path. However when remainingPathDistance + /// is large the target point will be so far away that the agent will essentially be told to move in a particular + /// direction, which is precisely what we want. + /// [Open online documentation to see images] + /// + /// Target point in world space. + /// Desired speed of the agent. In world units per second. The agent will try to move with this + /// speed if possible. + /// Max speed of the agent. In world units per second. If necessary (for example if another agent + /// is on a collision trajectory towards this agent) the agent can move at this speed. + /// Should be at least as high as desiredSpeed, but it is recommended to use a slightly + /// higher value than desiredSpeed (for example desiredSpeed*1.2). + /// Point in world space which is the agent's final desired destination on the navmesh. + /// This is typically the end of the path the agent is following. + /// May be set to (+inf,+inf,+inf) to mark the agent as not having a well defined end of path. + /// If this is set, multiple agents with roughly the same end of path will crowd more naturally around this point. + /// They will be able to realize that they cannot get closer if there are many agents trying to get closer to the same destination and then stop. + void SetTarget(Vector3 targetPoint, float desiredSpeed, float maxSpeed, Vector3 endOfPath); + + /// + /// Plane in which the agent moves. + /// Local avoidance calculations are always done in 2D and this plane determines how to convert from 3D to 2D. + /// + /// In a typical 3D game the agents move in the XZ plane and in a 2D game they move in the XY plane. + /// By default this is set to the XZ plane. + /// + /// See: + /// See: + /// + Util.SimpleMovementPlane MovementPlane { get; set; } + + /// Locked agents will be assumed not to move + bool Locked { get; set; } + + /// + /// Radius of the agent in world units. + /// Agents are modelled as circles/cylinders. + /// + float Radius { get; set; } + + /// + /// Height of the agent in world units. + /// Agents are modelled as circles/cylinders. + /// + float Height { get; set; } + + /// + /// Max number of estimated seconds to look into the future for collisions with agents. + /// As it turns out, this variable is also very good for controling agent avoidance priorities. + /// Agents with lower values will avoid other agents less and thus you can make 'high priority agents' by + /// giving them a lower value. + /// + float AgentTimeHorizon { get; set; } + + /// Max number of estimated seconds to look into the future for collisions with obstacles + float ObstacleTimeHorizon { get; set; } + + /// + /// Max number of agents to take into account. + /// Decreasing this value can lead to better performance, increasing it can lead to better quality of the simulation. + /// + int MaxNeighbours { get; set; } + + /// Number of neighbours that the agent took into account during the last simulation step + int NeighbourCount { get; } + + /// + /// Specifies the avoidance layer for this agent. + /// The mask on other agents will determine if they will avoid this agent. + /// + RVOLayer Layer { get; set; } + + /// + /// Layer mask specifying which layers this agent will avoid. + /// You can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ... + /// + /// See: http://en.wikipedia.org/wiki/Mask_(computing) + /// See: bitmasks (view in online documentation for working links) + /// + RVOLayer CollidesWith { get; set; } + + /// + /// Determines how strongly this agent just follows the flow instead of making other agents avoid it. + /// The default value is 0, if it is greater than zero (up to the maximum value of 1) other agents will + /// not avoid this character as much. However it works in a different way to . + /// + /// A group of agents with FlowFollowingStrength set to a high value that all try to reach the same point + /// will end up just settling to stationary positions around that point, none will push the others away to any significant extent. + /// This is tricky to achieve with priorities as priorities are all relative, so setting all agents to a low priority is the same thing + /// as not changing priorities at all. + /// + /// Should be a value in the range [0, 1]. + /// + /// TODO: Add video + /// + float FlowFollowingStrength { get; set; } + + /// Draw debug information in the scene view + AgentDebugFlags DebugFlags { get; set; } + + /// + /// How strongly other agents will avoid this agent. + /// Usually a value between 0 and 1. + /// Agents with similar priorities will avoid each other with an equal strength. + /// If an agent sees another agent with a higher priority than itself it will avoid that agent more strongly. + /// In the extreme case (e.g this agent has a priority of 0 and the other agent has a priority of 1) it will treat the other agent as being a moving obstacle. + /// Similarly if an agent sees another agent with a lower priority than itself it will avoid that agent less. + /// + /// In general the avoidance strength for this agent is: + /// + /// if this.priority > 0 or other.priority > 0: + /// avoidanceStrength = other.priority / (this.priority + other.priority); + /// else: + /// avoidanceStrength = 0.5 + /// + /// + float Priority { get; set; } + + int HierarchicalNodeIndex { get; set; } + + /// + /// Callback which will be called right before avoidance calculations are started. + /// Used to update the other properties with the most up to date values + /// + System.Action PreCalculationCallback { set; } + + /// + /// Callback which will be called right the agent is removed from the simulation. + /// This agent should not be used anymore after this callback has been called. + /// + System.Action DestroyedCallback { set; } + + /// + /// Set the normal of a wall (or something else) the agent is currently colliding with. + /// This is used to make the RVO system aware of things like physics or an agent being clamped to the navmesh. + /// The velocity of this agent that other agents observe will be modified so that there is no component + /// into the wall. The agent will however not start to avoid the wall, for that you will need to add RVO obstacles. + /// + /// This value will be cleared after the next simulation step, normally it should be set every frame + /// when the collision is still happening. + /// + void SetCollisionNormal(Vector3 normal); + + /// + /// Set the current velocity of the agent. + /// This will override the local avoidance input completely. + /// It is useful if you have a player controlled character and want other agents to avoid it. + /// + /// Calling this method will mark the agent as being externally controlled for 1 simulation step. + /// Local avoidance calculations will be skipped for the next simulation step but will be resumed + /// after that unless this method is called again. + /// + void ForceSetVelocity(Vector3 velocity); + + public ReachedEndOfPath CalculatedEffectivelyReachedDestination { get; } + + /// + /// Add obstacles to avoid for this agent. + /// + /// The obstacles are based on nearby borders of the navmesh. + /// You should call this method every frame. + /// + /// The node to start the obstacle search at. This is typically the node the agent is standing on. + public void SetObstacleQuery(GraphNode sourceNode); + } + + /// + /// Type of obstacle shape. + /// See: + /// + public enum ObstacleType { + /// A chain of vertices, the first and last segments end at a point + Chain, + /// A loop of vertices, the last vertex connects back to the first one + Loop, + } + + public struct ObstacleVertexGroup { + /// Type of obstacle shape + public ObstacleType type; + /// Number of vertices that this group consists of + public int vertexCount; + public float3 boundsMn; + public float3 boundsMx; + } + + /// Represents a set of obstacles + public struct UnmanagedObstacle { + /// The allocation in which represents all vertices used for these obstacles + public int verticesAllocation; + /// The allocation in which represents the obstacle groups + public int groupsAllocation; + } + + // TODO: Change to byte? + + /// + /// Indicates if the agent has reached the end of its path, or been blocked by other agents. + /// + /// In the video below, the agents will get a red ring around them for the Reached state, + /// and a brown ring for the ReachedSoon state. + /// + /// [Open online documentation to see videos] + /// + /// See: + /// + public enum ReachedEndOfPath { + /// The agent has no reached the end of its path yet + NotReached, + /// + /// The agent will soon reached the end of the path, or be blocked by other agents such that it cannot get closer. + /// Typically the agent can only move forward for a fraction of a second before it will become blocked. + /// + ReachedSoon, + /// + /// The agent has reached the end of the path, or it is blocked by other agents such that it cannot get closer right now. + /// If multiple have roughly the same end of path they will end up crowding around that point and all agents in the crowd will get this status. + /// + Reached, + } + + // TODO: Change to byte? + /// Plane which movement is primarily happening in + public enum MovementPlane { + /// Movement happens primarily in the XZ plane (3D) + XZ, + /// Movement happens primarily in the XY plane (2D) + XY, + /// For curved worlds. See: spherical (view in online documentation for working links) + Arbitrary, + } + + // Note: RVOLayer must not be marked with the [System.Flags] attribute because then Unity will show all RVOLayer fields as mask fields + // which we do not want + public enum RVOLayer { + DefaultAgent = 1 << 0, + DefaultObstacle = 1 << 1, + Layer2 = 1 << 2, + Layer3 = 1 << 3, + Layer4 = 1 << 4, + Layer5 = 1 << 5, + Layer6 = 1 << 6, + Layer7 = 1 << 7, + Layer8 = 1 << 8, + Layer9 = 1 << 9, + Layer10 = 1 << 10, + Layer11 = 1 << 11, + Layer12 = 1 << 12, + Layer13 = 1 << 13, + Layer14 = 1 << 14, + Layer15 = 1 << 15, + Layer16 = 1 << 16, + Layer17 = 1 << 17, + Layer18 = 1 << 18, + Layer19 = 1 << 19, + Layer20 = 1 << 20, + Layer21 = 1 << 21, + Layer22 = 1 << 22, + Layer23 = 1 << 23, + Layer24 = 1 << 24, + Layer25 = 1 << 25, + Layer26 = 1 << 26, + Layer27 = 1 << 27, + Layer28 = 1 << 28, + Layer29 = 1 << 29, + Layer30 = 1 << 30 + } + + /// + /// Local Avoidance Simulator. + /// This class handles local avoidance simulation for a number of agents using + /// Reciprocal Velocity Obstacles (RVO) and Optimal Reciprocal Collision Avoidance (ORCA). + /// + /// This class will handle calculation of velocities from desired velocities supplied by a script. + /// It is, however, not responsible for moving any objects in a Unity Scene. For that there are other scripts (see below). + /// + /// Agents be added and removed at any time. + /// + /// See: RVOSimulator + /// See: RVOAgentBurst + /// See: Pathfinding.RVO.IAgent + /// + /// You will most likely mostly use the wrapper class . + /// + public class SimulatorBurst { + /// Number of agents in this simulation + int numAgents = 0; + + /// + /// Scope for drawing gizmos even on frames during which the simulation is not running. + /// This is used to draw the obstacles, quadtree and agent debug lines. + /// + Drawing.RedrawScope debugDrawingScope; + + /// + /// Quadtree for this simulation. + /// Used internally by the simulation to perform fast neighbour lookups for each agent. + /// Please only read from this tree, do not rebuild it since that can interfere with the simulation. + /// It is rebuilt when necessary. + /// + /// Warning: Before accessing this, you should call or . + /// + public RVOQuadtreeBurst quadtree; + + public bool drawQuadtree; + + Action[] agentPreCalculationCallbacks = new Action[0]; + Action[] agentDestroyCallbacks = new Action[0]; + + Stack freeAgentIndices = new Stack(); + TemporaryAgentData temporaryAgentData; + HorizonAgentData horizonAgentData; + + /// + /// Internal simulation data. + /// Can be used if you need very high performance access to the agent data. + /// Normally you would use the SimulatorBurst.Agent class instead (implements the IAgent interface). + /// + /// Warning: Before accessing this, you should call or . + /// + public AgentData simulationData; + + /// + /// Internal simulation data. + /// Can be used if you need very high performance access to the agent data. + /// Normally you would use the SimulatorBurst.Agent class instead (implements the IAgent interface). + /// + /// Warning: Before accessing this, you should call or . + /// + public AgentOutputData outputData; + + public const int MaxNeighbourCount = 50; + public const int MaxBlockingAgentCount = 7; + + public const int MaxObstacleVertices = 256; + + public struct AgentNeighbourLookup { + [ReadOnly] + [NativeDisableParallelForRestriction] + NativeArray neighbours; + + public AgentNeighbourLookup(NativeArray neighbours) { + this.neighbours = neighbours; + } + + /// Read-only span with all agent indices that a given agent took into account during its last simulation step + public UnsafeSpan GetNeighbours (int agentIndex) { + var startIndex = agentIndex * MaxNeighbourCount; + var endIndex = startIndex; + while (neighbours[endIndex] != -1) endIndex++; + return neighbours.AsUnsafeReadOnlySpan().Slice(startIndex, endIndex - startIndex); + } + } + + /// + /// Lookup to find neighbours of a agents. + /// + /// Warning: Before accessing this, you should call or . + /// + public AgentNeighbourLookup GetAgentNeighbourLookup () { + return new AgentNeighbourLookup(temporaryAgentData.neighbours); + } + + struct Agent : IAgent { + public SimulatorBurst simulator; + public AgentIndex agentIndex; + + public int AgentIndex => agentIndex.Index; + public Vector3 Position { get => simulator.simulationData.position[AgentIndex]; set => simulator.simulationData.position[AgentIndex] = value; } + public bool Locked { get => simulator.simulationData.locked[AgentIndex]; set => simulator.simulationData.locked[AgentIndex] = value; } + public float Radius { get => simulator.simulationData.radius[AgentIndex]; set => simulator.simulationData.radius[AgentIndex] = value; } + public float Height { get => simulator.simulationData.height[AgentIndex]; set => simulator.simulationData.height[AgentIndex] = value; } + public float AgentTimeHorizon { get => simulator.simulationData.agentTimeHorizon[AgentIndex]; set => simulator.simulationData.agentTimeHorizon[AgentIndex] = value; } + public float ObstacleTimeHorizon { get => simulator.simulationData.obstacleTimeHorizon[AgentIndex]; set => simulator.simulationData.obstacleTimeHorizon[AgentIndex] = value; } + public int MaxNeighbours { get => simulator.simulationData.maxNeighbours[AgentIndex]; set => simulator.simulationData.maxNeighbours[AgentIndex] = value; } + public RVOLayer Layer { get => simulator.simulationData.layer[AgentIndex]; set => simulator.simulationData.layer[AgentIndex] = value; } + public RVOLayer CollidesWith { get => simulator.simulationData.collidesWith[AgentIndex]; set => simulator.simulationData.collidesWith[AgentIndex] = value; } + public float FlowFollowingStrength { get => simulator.simulationData.flowFollowingStrength[AgentIndex]; set => simulator.simulationData.flowFollowingStrength[AgentIndex] = value; } + public AgentDebugFlags DebugFlags { get => simulator.simulationData.debugFlags[AgentIndex]; set => simulator.simulationData.debugFlags[AgentIndex] = value; } + public float Priority { get => simulator.simulationData.priority[AgentIndex]; set => simulator.simulationData.priority[AgentIndex] = value; } + public int HierarchicalNodeIndex { get => simulator.simulationData.hierarchicalNodeIndex[AgentIndex]; set => simulator.simulationData.hierarchicalNodeIndex[AgentIndex] = value; } + public SimpleMovementPlane MovementPlane { get => new SimpleMovementPlane(simulator.simulationData.movementPlane[AgentIndex].rotation); set => simulator.simulationData.movementPlane[AgentIndex] = new NativeMovementPlane(value); } + public Action PreCalculationCallback { set => simulator.agentPreCalculationCallbacks[AgentIndex] = value; } + public Action DestroyedCallback { set => simulator.agentDestroyCallbacks[AgentIndex] = value; } + + public Vector3 CalculatedTargetPoint { + get { + simulator.BlockUntilSimulationStepDone(); + return simulator.outputData.targetPoint[AgentIndex]; + } + } + + public float CalculatedSpeed { + get { + simulator.BlockUntilSimulationStepDone(); + return simulator.outputData.speed[AgentIndex]; + } + } + + public ReachedEndOfPath CalculatedEffectivelyReachedDestination { + get { + simulator.BlockUntilSimulationStepDone(); + return simulator.outputData.effectivelyReachedDestination[AgentIndex]; + } + } + + public int NeighbourCount { + get { + simulator.BlockUntilSimulationStepDone(); + return simulator.outputData.numNeighbours[AgentIndex]; + } + } + + public bool AvoidingAnyAgents { + get { + simulator.BlockUntilSimulationStepDone(); + return simulator.outputData.blockedByAgents[AgentIndex*SimulatorBurst.MaxBlockingAgentCount] != -1; + } + } + + public void SetObstacleQuery (GraphNode sourceNode) { + HierarchicalNodeIndex = sourceNode != null && !sourceNode.Destroyed && sourceNode.Walkable ? sourceNode.HierarchicalNodeIndex : -1; + } + + public void SetTarget (Vector3 targetPoint, float desiredSpeed, float maxSpeed, Vector3 endOfPath) { + simulator.simulationData.SetTarget(AgentIndex, targetPoint, desiredSpeed, maxSpeed, endOfPath); + } + + public void SetCollisionNormal (Vector3 normal) { + simulator.simulationData.collisionNormal[AgentIndex] = normal; + } + + public void ForceSetVelocity (Vector3 velocity) { + // A bit hacky, but it is approximately correct + // assuming the agent does not move significantly + simulator.simulationData.targetPoint[AgentIndex] = simulator.simulationData.position[AgentIndex] + (float3)velocity * 1000; + simulator.simulationData.desiredSpeed[AgentIndex] = velocity.magnitude; + simulator.simulationData.allowedVelocityDeviationAngles[AgentIndex] = float2.zero; + simulator.simulationData.manuallyControlled[AgentIndex] = true; + } + } + + /// Holds internal obstacle data for the local avoidance simulation + public struct ObstacleData { + /// + /// Groups of vertices representing obstacles. + /// An obstacle is either a cycle or a chain of vertices + /// + public SlabAllocator obstacleVertexGroups; + /// Vertices of all obstacles + public SlabAllocator obstacleVertices; + /// Obstacle sets, each one is represented as a set of obstacle vertex groups + public NativeList obstacles; + + public void Init (Allocator allocator) { + if (!obstacles.IsCreated) obstacles = new NativeList(0, allocator); + if (!obstacleVertexGroups.IsCreated) obstacleVertexGroups = new SlabAllocator(4, allocator); + if (!obstacleVertices.IsCreated) obstacleVertices = new SlabAllocator(16, allocator); + } + + public void Dispose () { + if (obstacleVertexGroups.IsCreated) { + obstacleVertexGroups.Dispose(); + obstacleVertices.Dispose(); + obstacles.Dispose(); + } + } + } + + /// Holds internal agent data for the local avoidance simulation + public struct AgentData { + // Note: All 3D vectors are in world space + public NativeArray version; + public NativeArray radius; + public NativeArray height; + public NativeArray desiredSpeed; + public NativeArray maxSpeed; + public NativeArray agentTimeHorizon; + public NativeArray obstacleTimeHorizon; + public NativeArray locked; + public NativeArray maxNeighbours; + public NativeArray layer; + public NativeArray collidesWith; + public NativeArray flowFollowingStrength; + public NativeArray position; + public NativeArray collisionNormal; + public NativeArray manuallyControlled; + public NativeArray priority; + public NativeArray debugFlags; + public NativeArray targetPoint; + /// x = signed left angle in radians, y = signed right angle in radians (should be greater than x) + public NativeArray allowedVelocityDeviationAngles; + public NativeArray movementPlane; + public NativeArray endOfPath; + /// Which obstacle data in the array the agent should use for avoidance + public NativeArray agentObstacleMapping; + public NativeArray hierarchicalNodeIndex; + + public void Realloc (int size, Allocator allocator) { + Util.Memory.Realloc(ref version, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref radius, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref height, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref desiredSpeed, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref maxSpeed, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref agentTimeHorizon, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref obstacleTimeHorizon, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref locked, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref maxNeighbours, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref layer, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref collidesWith, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref flowFollowingStrength, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref position, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref collisionNormal, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref manuallyControlled, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref priority, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref debugFlags, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref targetPoint, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref movementPlane, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref allowedVelocityDeviationAngles, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref endOfPath, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref agentObstacleMapping, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref hierarchicalNodeIndex, size, allocator, NativeArrayOptions.UninitializedMemory); + } + + public void SetTarget (int agentIndex, float3 targetPoint, float desiredSpeed, float maxSpeed, float3 endOfPath) { + maxSpeed = math.max(maxSpeed, 0); + desiredSpeed = math.clamp(desiredSpeed, 0, maxSpeed); + + this.targetPoint[agentIndex] = targetPoint; + this.desiredSpeed[agentIndex] = desiredSpeed; + this.maxSpeed[agentIndex] = maxSpeed; + this.endOfPath[agentIndex] = endOfPath; + // TODO: Set allowedVelocityDeviationAngles here + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool HasDebugFlag(int agentIndex, AgentDebugFlags flag) => Unity.Burst.CompilerServices.Hint.Unlikely((debugFlags[agentIndex] & flag) != 0); + + public void Dispose () { + version.Dispose(); + radius.Dispose(); + height.Dispose(); + desiredSpeed.Dispose(); + maxSpeed.Dispose(); + agentTimeHorizon.Dispose(); + obstacleTimeHorizon.Dispose(); + locked.Dispose(); + maxNeighbours.Dispose(); + layer.Dispose(); + collidesWith.Dispose(); + flowFollowingStrength.Dispose(); + position.Dispose(); + collisionNormal.Dispose(); + manuallyControlled.Dispose(); + priority.Dispose(); + debugFlags.Dispose(); + targetPoint.Dispose(); + movementPlane.Dispose(); + allowedVelocityDeviationAngles.Dispose(); + endOfPath.Dispose(); + agentObstacleMapping.Dispose(); + hierarchicalNodeIndex.Dispose(); + } + }; + + public struct AgentOutputData { + public NativeArray targetPoint; + public NativeArray speed; + public NativeArray numNeighbours; + [NativeDisableParallelForRestrictionAttribute] + public NativeArray blockedByAgents; + public NativeArray effectivelyReachedDestination; + public NativeArray forwardClearance; + + public void Realloc (int size, Allocator allocator) { + Util.Memory.Realloc(ref targetPoint, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref speed, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref numNeighbours, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref blockedByAgents, size * MaxBlockingAgentCount, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref effectivelyReachedDestination, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref forwardClearance, size, allocator, NativeArrayOptions.UninitializedMemory); + } + + public void Move (int fromIndex, int toIndex) { + targetPoint[toIndex] = targetPoint[fromIndex]; + speed[toIndex] = speed[fromIndex]; + numNeighbours[toIndex] = numNeighbours[fromIndex]; + effectivelyReachedDestination[toIndex] = effectivelyReachedDestination[fromIndex]; + for (int i = 0; i < MaxBlockingAgentCount; i++) { + blockedByAgents[toIndex * MaxBlockingAgentCount + i] = blockedByAgents[fromIndex * MaxBlockingAgentCount + i]; + } + forwardClearance[toIndex] = forwardClearance[fromIndex]; + } + + public void Dispose () { + targetPoint.Dispose(); + speed.Dispose(); + numNeighbours.Dispose(); + blockedByAgents.Dispose(); + effectivelyReachedDestination.Dispose(); + forwardClearance.Dispose(); + } + }; + + public struct HorizonAgentData { + public NativeArray horizonSide; + public NativeArray horizonMinAngle; + public NativeArray horizonMaxAngle; + + public void Realloc (int size, Allocator allocator) { + Util.Memory.Realloc(ref horizonSide, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref horizonMinAngle, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref horizonMaxAngle, size, allocator, NativeArrayOptions.UninitializedMemory); + } + + public void Move (int fromIndex, int toIndex) { + horizonSide[toIndex] = horizonSide[fromIndex]; + // The other values are temporary values that don't have to be moved + } + + public void Dispose () { + horizonSide.Dispose(); + horizonMinAngle.Dispose(); + horizonMaxAngle.Dispose(); + } + } + + public struct TemporaryAgentData { + public NativeArray desiredTargetPointInVelocitySpace; + public NativeArray desiredVelocity; + public NativeArray currentVelocity; + public NativeArray collisionVelocityOffsets; + public NativeArray neighbours; + + public void Realloc (int size, Allocator allocator) { + Util.Memory.Realloc(ref desiredTargetPointInVelocitySpace, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref desiredVelocity, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref currentVelocity, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref collisionVelocityOffsets, size, allocator, NativeArrayOptions.UninitializedMemory); + Util.Memory.Realloc(ref neighbours, size * MaxNeighbourCount, allocator, NativeArrayOptions.UninitializedMemory); + } + + public void Dispose () { + desiredTargetPointInVelocitySpace.Dispose(); + desiredVelocity.Dispose(); + currentVelocity.Dispose(); + neighbours.Dispose(); + collisionVelocityOffsets.Dispose(); + } + } + + /// + /// Bias agents to pass each other on the right side. + /// If the desired velocity of an agent puts it on a collision course with another agent or an obstacle + /// its desired velocity will be rotated this number of radians (1 radian is approximately 57°) to the right. + /// This helps to break up symmetries and makes it possible to resolve some situations much faster. + /// + /// When many agents have the same goal this can however have the side effect that the group + /// clustered around the target point may as a whole start to spin around the target point. + /// + /// Recommended values are in the range of 0 to 0.2. + /// + /// If this value is negative, the agents will be biased towards passing each other on the left side instead. + /// + public float SymmetryBreakingBias { get; set; } + + /// Use hard collisions + public bool HardCollisions { get; set; } + + public bool UseNavmeshAsObstacle { get; set; } + + public Rect AgentBounds { + get { + rwLock.ReadSync().Unlock(); + return quadtree.bounds; + } + } + + /// Number of agents in the simulation + public int AgentCount => numAgents; + + public MovementPlane MovementPlane => movementPlane; + + /// Determines if the XY (2D) or XZ (3D) plane is used for movement + public readonly MovementPlane movementPlane = MovementPlane.XZ; + + /// Used to synchronize access to the simulation data + RWLock rwLock = new RWLock(); + + public void BlockUntilSimulationStepDone () { + rwLock.WriteSync().Unlock(); + } + + /// Create a new simulator. + /// The plane that the movement happens in. XZ for 3D games, XY for 2D games. + public SimulatorBurst (MovementPlane movementPlane) { + this.movementPlane = movementPlane; + + AllocateAgentSpace(); + + // Just to make sure the quadtree is in a valid state + quadtree.BuildJob(simulationData.position, simulationData.version, simulationData.desiredSpeed, simulationData.radius, 0, movementPlane).Run(); + } + + /// Removes all agents from the simulation + public void ClearAgents () { + BlockUntilSimulationStepDone(); + for (int i = 0; i < agentDestroyCallbacks.Length; i++) agentDestroyCallbacks[i]?.Invoke(); + numAgents = 0; + } + + /// + /// Frees all used memory. + /// Warning: You must call this when you are done with the simulator, otherwise some resources can linger and lead to memory leaks. + /// + public void OnDestroy () { + debugDrawingScope.Dispose(); + BlockUntilSimulationStepDone(); + ClearAgents(); + simulationData.Dispose(); + temporaryAgentData.Dispose(); + outputData.Dispose(); + quadtree.Dispose(); + horizonAgentData.Dispose(); + } + + void AllocateAgentSpace () { + if (numAgents > agentPreCalculationCallbacks.Length || agentPreCalculationCallbacks.Length == 0) { + var prevSize = simulationData.version.Length; + int newSize = Mathf.Max(64, Mathf.Max(numAgents, agentPreCalculationCallbacks.Length * 2)); + simulationData.Realloc(newSize, Allocator.Persistent); + temporaryAgentData.Realloc(newSize, Allocator.Persistent); + outputData.Realloc(newSize, Allocator.Persistent); + horizonAgentData.Realloc(newSize, Allocator.Persistent); + Memory.Realloc(ref agentPreCalculationCallbacks, newSize); + Memory.Realloc(ref agentDestroyCallbacks, newSize); + for (int i = prevSize; i < newSize; i++) simulationData.version[i] = new AgentIndex(0, i); + } + } + + public bool anyAgentsInSimulation => numAgents > freeAgentIndices.Count; + + /// + /// Add an agent at the specified position. + /// You can use the returned interface to read and write parameters + /// and set for example radius and desired point to move to. + /// + /// See: + /// + /// See \reflink{IAgent.Position} + public IAgent AddAgent (Vector3 position) { + var agentIndex = AddAgentBurst(position); + return new Agent { simulator = this, agentIndex = agentIndex }; + } + + /// + /// Add an agent at the specified position. + /// You can use the returned index to read and write parameters + /// and set for example radius and desired point to move to. + /// + /// See: + /// + public AgentIndex AddAgentBurst (float3 position) { + BlockUntilSimulationStepDone(); + + int agentIndex; + if (freeAgentIndices.Count > 0) { + agentIndex = freeAgentIndices.Pop(); + } else { + agentIndex = numAgents++; + AllocateAgentSpace(); + } + + var packedAgentIndex = simulationData.version[agentIndex].WithIncrementedVersion(); + UnityEngine.Assertions.Assert.AreEqual(packedAgentIndex.Index, agentIndex); + + simulationData.version[agentIndex] = packedAgentIndex; + simulationData.radius[agentIndex] = 5; + simulationData.height[agentIndex] = 5; + simulationData.desiredSpeed[agentIndex] = 0; + simulationData.maxSpeed[agentIndex] = 1; + simulationData.agentTimeHorizon[agentIndex] = 2; + simulationData.obstacleTimeHorizon[agentIndex] = 2; + simulationData.locked[agentIndex] = false; + simulationData.maxNeighbours[agentIndex] = 10; + simulationData.layer[agentIndex] = RVOLayer.DefaultAgent; + simulationData.collidesWith[agentIndex] = (RVOLayer)(-1); + simulationData.flowFollowingStrength[agentIndex] = 0; + simulationData.position[agentIndex] = position; + simulationData.collisionNormal[agentIndex] = float3.zero; + simulationData.manuallyControlled[agentIndex] = false; + simulationData.priority[agentIndex] = 0.5f; + simulationData.debugFlags[agentIndex] = AgentDebugFlags.Nothing; + simulationData.targetPoint[agentIndex] = position; + // Set the default movement plane. Default to the XZ plane even if movement plane is arbitrary (the user will have to set a custom one later) + simulationData.movementPlane[agentIndex] = new NativeMovementPlane((movementPlane == MovementPlane.XY ? SimpleMovementPlane.XYPlane : SimpleMovementPlane.XZPlane).rotation); + simulationData.allowedVelocityDeviationAngles[agentIndex] = float2.zero; + simulationData.endOfPath[agentIndex] = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + simulationData.agentObstacleMapping[agentIndex] = -1; + simulationData.hierarchicalNodeIndex[agentIndex] = -1; + + outputData.speed[agentIndex] = 0; + outputData.numNeighbours[agentIndex] = 0; + outputData.targetPoint[agentIndex] = position; + outputData.blockedByAgents[agentIndex * MaxBlockingAgentCount] = -1; + outputData.effectivelyReachedDestination[agentIndex] = ReachedEndOfPath.NotReached; + + temporaryAgentData.neighbours[agentIndex * MaxNeighbourCount] = -1; + + horizonAgentData.horizonSide[agentIndex] = 0; + agentPreCalculationCallbacks[agentIndex] = null; + agentDestroyCallbacks[agentIndex] = null; + + return packedAgentIndex; + } + + /// Deprecated: Use AddAgent(Vector3) instead + [System.Obsolete("Use AddAgent(Vector3) instead", true)] + public IAgent AddAgent (IAgent agent) { return null; } + + /// + /// Removes a specified agent from this simulation. + /// The agent can be added again later by using AddAgent. + /// + /// See: AddAgent(IAgent) + /// See: ClearAgents + /// + public void RemoveAgent (IAgent agent) { + if (agent == null) throw new System.ArgumentNullException(nameof(agent)); + Agent realAgent = (Agent)agent; + RemoveAgent(realAgent.agentIndex); + } + + public void RemoveAgent (AgentIndex agent, bool okIfMissing = false) { + BlockUntilSimulationStepDone(); + + if (!agent.TryGetIndex(ref simulationData, out var index)) { + if (okIfMissing) return; + else throw new System.InvalidOperationException("Trying to remove agent which does not exist"); + } + + // Increment version and set deleted bit + simulationData.version[index] = simulationData.version[index].WithIncrementedVersion().WithDeleted(); + // Avoid memory leaks + agentPreCalculationCallbacks[index] = null; + try { + if (agentDestroyCallbacks[index] != null) agentDestroyCallbacks[index](); + } catch (System.Exception e) { + Debug.LogException(e); + } + agentDestroyCallbacks[index] = null; + freeAgentIndices.Push(index); + } + + void PreCalculation (JobHandle dependency) { + bool blocked = false; + for (int i = 0; i < numAgents; i++) { + var callback = agentPreCalculationCallbacks[i]; + if (callback != null) { + if (!blocked) { + dependency.Complete(); + // The pre-calculation callback may want to read the simulation data + rwLock.ReadSync().Unlock(); + blocked = true; + } + callback.Invoke(); + } + } + } + + /// Should be called once per frame. + /// Jobs that need to complete before local avoidance runs. + /// Length of timestep in seconds. + /// If true, debug gizmos will be allowed to render (they never render in standalone games, though). + /// Allocator to use for some temporary allocations. Should be a rewindable allocator since no disposal will be done. + public JobHandle Update (JobHandle dependency, float dt, bool drawGizmos, Allocator allocator) { + var x = 0; + if (x != 0) { + // We need to specify these types somewhere in their concrete form. + // Otherwise the burst compiler doesn't understand that it has to compile them. + // This code will never run. + new JobRVO().ScheduleBatch(0, 0); + new JobRVO().ScheduleBatch(0, 0); + new JobRVO().ScheduleBatch(0, 0); + + new JobRVOPreprocess().Schedule(); + new JobRVOPreprocess().Schedule(); + new JobRVOPreprocess().Schedule(); + + new JobHorizonAvoidancePhase1().ScheduleBatch(0, 0); + new JobHorizonAvoidancePhase1().ScheduleBatch(0, 0); + new JobHorizonAvoidancePhase1().ScheduleBatch(0, 0); + + new JobHorizonAvoidancePhase2().ScheduleBatch(0, 0); + new JobHorizonAvoidancePhase2().ScheduleBatch(0, 0); + new JobHorizonAvoidancePhase2().ScheduleBatch(0, 0); + + new JobRVOCalculateNeighbours().ScheduleBatch(0, 0); + new JobRVOCalculateNeighbours().ScheduleBatch(0, 0); + new JobRVOCalculateNeighbours().ScheduleBatch(0, 0); + + new JobHardCollisions().ScheduleBatch(0, 0); + new JobHardCollisions().ScheduleBatch(0, 0); + new JobHardCollisions().ScheduleBatch(0, 0); + + new JobDestinationReached().Schedule(); + new JobDestinationReached().Schedule(); + new JobDestinationReached().Schedule(); + } + + // The burst jobs are specialized for the type of movement plane used. This improves performance for the XY and XZ movement planes quite a lot. + // Note: The agents' own movement planes could be colinear with e.g. the XY plane, but may add an additional rotation, + // so we must ensure that we always use the movement plane wrappers for all conversions. + // Otherwise some conversions may add a rotation, and some may not. + // All external communication with the rest of the world happens in world space, so we just need to be consistent internally. + if (movementPlane == MovementPlane.XY) return UpdateInternal(dependency, dt, drawGizmos, allocator); + else if (movementPlane == MovementPlane.XZ) return UpdateInternal(dependency, dt, drawGizmos, allocator); + else return UpdateInternal(dependency, dt, drawGizmos, allocator); + } + + /// + /// Takes an async read-only lock on the simulation data. + /// + /// This can be used to access , , , and in a job. + /// + /// Use the field when you schedule the job using the simulation data, + /// and then call with the job handle of that job. + /// + public RWLock.ReadLockAsync LockSimulationDataReadOnly () { + return this.rwLock.Read(); + } + + /// + /// Takes an async read/write lock on the simulation data. + /// + /// This can be used to access , , , and in a job. + /// + /// Use the field when you schedule the job using the simulation data, + /// and then call with the job handle of that job. + /// + public RWLock.WriteLockAsync LockSimulationDataReadWrite () { + return this.rwLock.Write(); + } + + JobHandle UpdateInternal(JobHandle dependency, float deltaTime, bool drawGizmos, Allocator allocator) where T : struct, IMovementPlaneWrapper { + if (!anyAgentsInSimulation) { + // No agents, nothing to do + // This saves some performance, since scheduling jobs has some overhead + return dependency; + } + + // Prevent a zero delta time + deltaTime = math.max(deltaTime, 1.0f/2000f); + + UnityEngine.Profiling.Profiler.BeginSample("Read agent data"); + + // Read agent data from RVOController components on the main thread. + // We cannot do this in a job because RVOController data may be changed at any time + // on the main thread. + PreCalculation(dependency); + + UnityEngine.Profiling.Profiler.EndSample(); + + var writeLock = rwLock.Write(); + dependency = JobHandle.CombineDependencies(dependency, writeLock.dependency); + + var quadtreeJob = quadtree.BuildJob(simulationData.position, simulationData.version, outputData.speed, simulationData.radius, numAgents, movementPlane).Schedule(dependency); + + var preprocessJob = new JobRVOPreprocess { + agentData = simulationData, + previousOutput = outputData, + temporaryAgentData = temporaryAgentData, + startIndex = 0, + endIndex = numAgents, + }.Schedule(dependency); + + int batchSize = math.max(numAgents / 64, 8); + var neighboursJob = new JobRVOCalculateNeighbours { + agentData = simulationData, + quadtree = quadtree, + outNeighbours = temporaryAgentData.neighbours, + output = outputData, + }.ScheduleBatch(numAgents, batchSize, JobHandle.CombineDependencies(preprocessJob, quadtreeJob)); + + // Make the threads start working now, we have enough work scheduled that they have stuff to do. + JobHandle.ScheduleBatchedJobs(); + + var combinedJob = JobHandle.CombineDependencies(preprocessJob, neighboursJob); + +#if UNITY_EDITOR + debugDrawingScope.Rewind(); + var draw = DrawingManager.GetBuilder(debugDrawingScope); +#endif + + var horizonJob1 = new JobHorizonAvoidancePhase1 { + agentData = simulationData, + neighbours = temporaryAgentData.neighbours, + desiredTargetPointInVelocitySpace = temporaryAgentData.desiredTargetPointInVelocitySpace, + horizonAgentData = horizonAgentData, +#if UNITY_EDITOR + draw = draw, +#endif + }.ScheduleBatch(numAgents, batchSize, combinedJob); + + var horizonJob2 = new JobHorizonAvoidancePhase2 { + neighbours = temporaryAgentData.neighbours, + versions = simulationData.version, + desiredVelocity = temporaryAgentData.desiredVelocity, + desiredTargetPointInVelocitySpace = temporaryAgentData.desiredTargetPointInVelocitySpace, + horizonAgentData = horizonAgentData, + movementPlane = simulationData.movementPlane, + }.ScheduleBatch(numAgents, batchSize, horizonJob1); + + var hardCollisionsJob1 = new JobHardCollisions { + agentData = simulationData, + neighbours = temporaryAgentData.neighbours, + collisionVelocityOffsets = temporaryAgentData.collisionVelocityOffsets, + deltaTime = deltaTime, + enabled = HardCollisions, + }.ScheduleBatch(numAgents, batchSize, combinedJob); + + RWLock.CombinedReadLockAsync navmeshEdgeDataLock; + NavmeshEdges.NavmeshBorderData navmeshEdgeData; + bool hasAstar = AstarPath.active != null; + if (hasAstar) { + navmeshEdgeData = AstarPath.active.GetNavmeshBorderData(out navmeshEdgeDataLock); + } else { + navmeshEdgeData = NavmeshEdges.NavmeshBorderData.CreateEmpty(allocator); + navmeshEdgeDataLock = default; + } + var rvoJobData = new JobRVO { + agentData = simulationData, + temporaryAgentData = temporaryAgentData, + navmeshEdgeData = navmeshEdgeData, + output = outputData, + deltaTime = deltaTime, + symmetryBreakingBias = Mathf.Max(0, SymmetryBreakingBias), +#if UNITY_EDITOR + draw = draw, +#endif + useNavmeshAsObstacle = UseNavmeshAsObstacle, + priorityMultiplier = 1f, + // priorityMultiplier = 0.1f, + }; + + combinedJob = JobHandle.CombineDependencies(horizonJob2, hardCollisionsJob1, navmeshEdgeDataLock.dependency); + + // JobHandle rvoJob = combinedJob; + // for (int k = 0; k < 3; k++) { + // var preprocessJob2 = new JobRVOPreprocess { + // agentData = simulationData, + // previousOutput = outputData, + // temporaryAgentData = temporaryAgentData, + // startIndex = 0, + // endIndex = numAgents, + // }.Schedule(rvoJob); + // rvoJob = new JobRVO { + // agentData = simulationData, + // temporaryAgentData = temporaryAgentData, + // navmeshEdgeData = navmeshEdgeData, + // output = outputData, + // deltaTime = deltaTime, + // symmetryBreakingBias = Mathf.Max(0, SymmetryBreakingBias), + // draw = draw, + // priorityMultiplier = (k+1) * (1.0f/3.0f), + // }.ScheduleBatch(numAgents, batchSize, preprocessJob2); + // } + var rvoJob = rvoJobData.ScheduleBatch(numAgents, batchSize, combinedJob); + if (hasAstar) { + navmeshEdgeDataLock.UnlockAfter(rvoJob); + } else { + navmeshEdgeData.DisposeEmpty(rvoJob); + } + + var reachedJob = new JobDestinationReached { + agentData = simulationData, + temporaryAgentData = temporaryAgentData, + output = outputData, +#if UNITY_EDITOR + draw = draw, +#endif + numAgents = numAgents, + }.Schedule(rvoJob); + + // Clear some fields that are reset every simulation tick + var clearJob = simulationData.collisionNormal.MemSet(float3.zero).Schedule(reachedJob); + var clearJob2 = simulationData.manuallyControlled.MemSet(false).Schedule(reachedJob); + var clearJob3 = simulationData.hierarchicalNodeIndex.MemSet(-1).Schedule(reachedJob); + + dependency = JobHandle.CombineDependencies(reachedJob, clearJob, clearJob2); + dependency = JobHandle.CombineDependencies(dependency, clearJob3); + +#if UNITY_EDITOR + if (drawQuadtree && drawGizmos) { + dependency = JobHandle.CombineDependencies(dependency, new RVOQuadtreeBurst.DebugDrawJob { + draw = draw, + quadtree = quadtree, + }.Schedule(quadtreeJob)); + } + + draw.DisposeAfter(dependency); +#endif + + writeLock.UnlockAfter(dependency); + return dependency; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs.meta new file mode 100644 index 0000000..55b77cd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOCoreSimulatorBurst.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b618542be33248ba974eb1f4c16e73b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs new file mode 100644 index 0000000..169dc6e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs @@ -0,0 +1,344 @@ +namespace Pathfinding.RVO { + using Pathfinding; + using UnityEngine; + using Pathfinding.Util; + using Unity.Mathematics; + using Unity.Collections; + using Pathfinding.Collections; + using System.Collections.Generic; + using Unity.Burst; + using Unity.Profiling; + using Pathfinding.Sync; +#if MODULE_COLLECTIONS_2_1_0_OR_NEWER + using NativeHashMapIntInt = Unity.Collections.NativeHashMap; +#else + using NativeHashMapIntInt = Unity.Collections.NativeParallelHashMap; +#endif + + [BurstCompile] + public static class RVOObstacleCache { + public struct ObstacleSegment { + public float3 vertex1; + public float3 vertex2; + public int vertex1LinkId; + public int vertex2LinkId; + } + + static ulong HashKey (GraphNode sourceNode, int traversableTags, SimpleMovementPlane movementPlane) { + var hash = (ulong)sourceNode.NodeIndex; + hash = hash * 786433 ^ (ulong)traversableTags; + // The rotation is not particularly important for the obstacle. It's only used + // to simplify the output a bit. So we allow similar rotations to share the same hash. + const float RotationQuantization = 4; + hash = hash * 786433 ^ (ulong)(movementPlane.rotation.x*RotationQuantization); + hash = hash * 786433 ^ (ulong)(movementPlane.rotation.y*RotationQuantization); + hash = hash * 786433 ^ (ulong)(movementPlane.rotation.z*RotationQuantization); + hash = hash * 786433 ^ (ulong)(movementPlane.rotation.w*RotationQuantization); + return hash; + } + + /// + /// Collects an unordered list of contour segments based on the given nodes. + /// + /// Note: All nodes must be from the same graph. + /// + public static void CollectContours (List nodes, NativeList obstacles) { + if (nodes.Count == 0) return; + if (nodes[0] is TriangleMeshNode) { + for (int i = 0; i < nodes.Count; i++) { + var tnode = nodes[i] as TriangleMeshNode; + var used = 0; + if (tnode.connections != null) { + for (int j = 0; j < tnode.connections.Length; j++) { + var conn = tnode.connections[j]; + if (conn.isEdgeShared) { + used |= 1 << conn.shapeEdge; + } + } + } + + tnode.GetVertices(out var v0, out var v1, out var v2); + for (int edgeIndex = 0; edgeIndex < 3; edgeIndex++) { + if ((used & (1 << edgeIndex)) == 0) { + // This edge is not shared, therefore it's a contour edge + Int3 leftVertex, rightVertex; + switch (edgeIndex) { + case 0: + leftVertex = v0; + rightVertex = v1; + break; + case 1: + leftVertex = v1; + rightVertex = v2; + break; + case 2: + default: + leftVertex = v2; + rightVertex = v0; + break; + } + var leftVertexHash = leftVertex.GetHashCode(); + var rightVertexHash = rightVertex.GetHashCode(); + + obstacles.Add(new ObstacleSegment { + vertex1 = (Vector3)leftVertex, + vertex2 = (Vector3)rightVertex, + vertex1LinkId = leftVertexHash, + vertex2LinkId = rightVertexHash, + }); + } + } + } + } else if (nodes[0] is GridNodeBase) { + GridGraph graph; + if (nodes[0] is LevelGridNode) graph = LevelGridNode.GetGridGraph(nodes[0].GraphIndex); + else + graph = GridNode.GetGridGraph(nodes[0].GraphIndex); + unsafe { + // Offsets from the center of the node to the corners of the node, in world space + // Index dir is the offset to the left corner of the edge in direction dir + // See GridNodeBase.GetNeighbourAlongDirection for the direction indices + Vector3* offsets = stackalloc Vector3[4]; + for (int dir = 0; dir < 4; dir++) { + var dl = (dir + 1) % 4; + offsets[dir] = graph.transform.TransformVector(0.5f * new Vector3(GridGraph.neighbourXOffsets[dir] + GridGraph.neighbourXOffsets[dl], 0, GridGraph.neighbourZOffsets[dir] + GridGraph.neighbourZOffsets[dl])); + } + + for (int i = 0; i < nodes.Count; i++) { + var gnode = nodes[i] as GridNodeBase; + if (gnode.HasConnectionsToAllAxisAlignedNeighbours) continue; + + for (int dir = 0; dir < 4; dir++) { + if (!gnode.HasConnectionInDirection(dir)) { + // ┌─────────┬─────────┐ + // │ │ │ + // │ nl1 │ nl2 │ ^ + // │ │ │ | + // ├────────vL─────────┤ dl + // │ │#########│ + // │ node │#########│ dir-> + // │ │#########│ + // ├────────vR─────────┤ dr + // │ │ │ | + // │ nr1 │ nr2 │ v + // │ │ │ + // └─────────┴─────────┘ + var dl = (dir + 1) % 4; + var dr = (dir - 1 + 4) % 4; + var nl1 = gnode.GetNeighbourAlongDirection(dl); + var nr1 = gnode.GetNeighbourAlongDirection(dr); + + // All this hashing code looks slow, but really it's not compared to all the memory accesses to get the node data + uint leftVertexHash; + if (nl1 != null) { + var nl2 = nl1.GetNeighbourAlongDirection(dir); + if (nl2 != null) { + // Outer corner. Uniquely determined by the 3 nodes that touch the corner. + var a = gnode.NodeIndex; + var b = nl1.NodeIndex; + var c = nl2.NodeIndex; + // Sort the values so that a <= b <= c + if (a > b) Memory.Swap(ref a, ref b); + if (b > c) Memory.Swap(ref b, ref c); + if (a > b) Memory.Swap(ref a, ref b); + leftVertexHash = math.hash(new uint3(a, b, c)); + } else { + // Straight wall. Uniquely determined by the 2 nodes that touch the corner and the direction to the wall. + var a = gnode.NodeIndex; + var b = nl1.NodeIndex; + // Sort the values so that a <= b + if (a > b) Memory.Swap(ref a, ref b); + leftVertexHash = math.hash(new uint3(a, b, (uint)dir)); + } + } else { + // Inner corner. Uniquely determined by the single node that touches the corner and the direction to it. + var diagonalToCorner = dir + 4; + leftVertexHash = math.hash(new uint2(gnode.NodeIndex, (uint)diagonalToCorner)); + } + + uint rightVertexHash; + if (nr1 != null) { + var nr2 = nr1.GetNeighbourAlongDirection(dir); + if (nr2 != null) { + // Outer corner. Uniquely determined by the 3 nodes that touch the corner. + var a = gnode.NodeIndex; + var b = nr1.NodeIndex; + var c = nr2.NodeIndex; + // Sort the values so that a <= b <= c + if (a > b) Memory.Swap(ref a, ref b); + if (b > c) Memory.Swap(ref b, ref c); + if (a > b) Memory.Swap(ref a, ref b); + rightVertexHash = math.hash(new uint3(a, b, c)); + } else { + // Straight wall. Uniquely determined by the 2 nodes that touch the corner and the direction to the wall. + var a = gnode.NodeIndex; + var b = nr1.NodeIndex; + // Sort the values so that a <= b + if (a > b) Memory.Swap(ref a, ref b); + rightVertexHash = math.hash(new uint3(a, b, (uint)dir)); + } + } else { + // Inner corner. Uniquely determined by the single node that touches the corner and the direction to it. + // Note: It's not a typo that we use `dr+4` here and `dir+4` above. They are different directions. + var diagonalToCorner = dr + 4; + rightVertexHash = math.hash(new uint2(gnode.NodeIndex, (uint)diagonalToCorner)); + } + + var nodePos = (Vector3)gnode.position; + obstacles.Add(new ObstacleSegment { + vertex1 = nodePos + offsets[dir], // Left corner. Yes, it should be dir, not dl, as the offsets array already points to the left corners of each segment. + vertex2 = nodePos + offsets[dr], // Right corner + vertex1LinkId = (int)leftVertexHash, + vertex2LinkId = (int)rightVertexHash, + }); + } + } + } + } + } + } + + private static readonly ProfilerMarker MarkerAllocate = new ProfilerMarker("Allocate"); + + /// Trace contours generated by CollectContours. + /// Obstacle segments, typically the borders of the navmesh. In no particular order. + /// Each edge must be oriented the same way (e.g. all clockwise, or all counter-clockwise around the obstacles). + /// The movement plane used for simplification. The up direction will be treated as less important for purposes of simplification. + /// The ID of the obstacle to write into the outputObstacles array. + /// Array to write the obstacle to. + /// Allocator to use for the vertices of the obstacle. + /// Allocator to use for the obstacle metadata. + /// Lock to use when allocating from the allocators. + /// If true, the obstacle will be simplified. This means that colinear vertices (when projected onto the movement plane) will be removed. + [BurstCompile] + internal static unsafe void TraceContours (ref UnsafeSpan obstaclesSpan, ref NativeMovementPlane movementPlane, int obstacleId, UnmanagedObstacle* outputObstacles, ref SlabAllocator verticesAllocator, ref SlabAllocator obstaclesAllocator, ref SpinLock spinLock, bool simplifyObstacles) { + var obstacles = obstaclesSpan; + if (obstacles.Length == 0) { + outputObstacles[obstacleId] = new UnmanagedObstacle { + verticesAllocation = SlabAllocator.ZeroLengthArray, + groupsAllocation = SlabAllocator.ZeroLengthArray, + }; + return; + } + + MarkerAllocate.Begin(); + var traceLookup = new NativeHashMapIntInt(obstacles.Length, Unity.Collections.Allocator.Temp); + // For each edge: Will be 0 if the segment should be ignored or if it has been visited, 1 if it has not been visited and has an ingoing edge, and 2 if it has not been visited and has no ingoing edge. + var priority = new NativeArray(obstacles.Length, Unity.Collections.Allocator.Temp, Unity.Collections.NativeArrayOptions.UninitializedMemory); + MarkerAllocate.End(); + for (int i = 0; i < obstacles.Length; i++) { + // var obstacle = obstacles[i]; + // Add the edge to the lookup. But if it already exists, ignore it. + // That it already exists is very much a special case that can happen if there is + // overlapping geometry (typically added by a NavmeshAdd component). + // In that case the "outer edge" that we want to trace is kinda undefined, but we will + // do our best with it. + if (traceLookup.TryAdd(obstacles[i].vertex1LinkId, i)) { + priority[i] = 2; + } else { + priority[i] = 0; + } + } + for (int i = 0; i < obstacles.Length; i++) { + if (traceLookup.TryGetValue(obstacles[i].vertex2LinkId, out var other) && priority[other] > 0) { + // The other segment has an ingoing edge. This means it cannot be the start of a contour. + // Reduce the priority so that we only consider it when looking for cycles. + priority[other] = 1; + } + } + + var outputMetadata = new NativeList(16, Allocator.Temp); + var outputVertices = new NativeList(16, Allocator.Temp); + // We now have a hashmap of directed edges (vertex1 -> vertex2) such that these edges are directed the same (cw or ccw), and "outer edges". + // We can now follow these directed edges to trace out the contours of the mesh. + var toPlaneMatrix = movementPlane.AsWorldToPlaneMatrix(); + for (int allowLoops = 0; allowLoops <= 1; allowLoops++) { + var minPriority = allowLoops == 1 ? 1 : 2; + for (int i = 0; i < obstacles.Length; i++) { + if (priority[i] >= minPriority) { + int startVertices = outputVertices.Length; + outputVertices.Add(obstacles[i].vertex1); + + var lastAdded = obstacles[i].vertex1; + var candidateVertex = obstacles[i].vertex2; + var index = i; + var obstacleType = ObstacleType.Chain; + var boundsMn = lastAdded; + var boundsMx = lastAdded; + while (true) { + if (priority[index] == 0) { + // This should not happen for a regular navmesh. + // But it can happen if there are degenerate edges or overlapping triangles. + // In that case we will just stop here + break; + } + priority[index] = 0; + + float3 nextVertex; + if (traceLookup.TryGetValue(obstacles[index].vertex2LinkId, out int nextIndex)) { + nextVertex = 0.5f * (obstacles[index].vertex2 + obstacles[nextIndex].vertex1); + } else { + nextVertex = obstacles[index].vertex2; + nextIndex = -1; + } + + // Try to simplify and see if we even need to add the vertex C. + var p1 = lastAdded; + var p2 = nextVertex; + var p3 = candidateVertex; + var d1 = toPlaneMatrix.ToPlane(p2 - p1); + var d2 = toPlaneMatrix.ToPlane(p3 - p1); + var det = VectorMath.Determinant(d1, d2); + if (math.abs(det) < 0.01f && simplifyObstacles) { + // We don't need to add candidateVertex. It's just a straight line (p1,p2,p3 are colinear). + } else { + outputVertices.Add(candidateVertex); + boundsMn = math.min(boundsMn, candidateVertex); + boundsMx = math.max(boundsMx, candidateVertex); + lastAdded = p3; + } + + if (nextIndex == i) { + // Loop + outputVertices[startVertices] = nextVertex; + obstacleType = ObstacleType.Loop; + break; + } else if (nextIndex == -1) { + // End of chain + outputVertices.Add(nextVertex); + boundsMn = math.min(boundsMn, nextVertex); + boundsMx = math.max(boundsMx, nextVertex); + break; + } + + index = nextIndex; + candidateVertex = nextVertex; + } + + outputMetadata.Add(new ObstacleVertexGroup { + type = obstacleType, + vertexCount = outputVertices.Length - startVertices, + boundsMn = boundsMn, + boundsMx = boundsMx, + }); + } + } + } + + int obstacleSet, vertexSet; + if (outputMetadata.Length > 0) { + spinLock.Lock(); + obstacleSet = obstaclesAllocator.Allocate(outputMetadata); + vertexSet = verticesAllocator.Allocate(outputVertices); + spinLock.Unlock(); + } else { + obstacleSet = SlabAllocator.ZeroLengthArray; + vertexSet = SlabAllocator.ZeroLengthArray; + } + outputObstacles[obstacleId] = new UnmanagedObstacle { + verticesAllocation = vertexSet, + groupsAllocation = obstacleSet, + }; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs.meta new file mode 100644 index 0000000..344ea02 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOObstacleCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8a3a9a0945d2b43f5b8cb871ffbc1b2b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs.meta new file mode 100644 index 0000000..f18d4ad --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtree.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4fadcced8ad4d40d59f3eea45426359d +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs new file mode 100644 index 0000000..b365832 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs @@ -0,0 +1,711 @@ +namespace Pathfinding.RVO { + using UnityEngine; + using Pathfinding.ECS.RVO; + using Unity.Burst; + using Unity.Jobs; + using Unity.Mathematics; + using Unity.Collections; + using Pathfinding.Drawing; + + /// + /// Quadtree for quick nearest neighbour search of rvo agents. + /// See: Pathfinding.RVO.Simulator + /// + public struct RVOQuadtreeBurst { + const int LeafSize = 16; + const int MaxDepth = 10; + + NativeArray agents; + NativeArray childPointers; + NativeArray boundingBoxBuffer; + NativeArray agentCountBuffer; + NativeArray agentPositions; + NativeArray agentRadii; + NativeArray maxSpeeds; + NativeArray maxRadius; + NativeArray nodeAreas; + MovementPlane movementPlane; + + const int LeafNodeBit = 1 << 30; + const int BitPackingShift = 15; + const int BitPackingMask = (1 << BitPackingShift) - 1; + const int MaxAgents = BitPackingMask; + + /// + /// For a given number, contains the index of the first non-zero bit. + /// Only the values 0 through 15 are used when movementPlane is XZ or XY. + /// + /// Use bytes instead of ints to save some precious L1 cache memory. + /// + static readonly byte[] ChildLookup = new byte[256]; + + static RVOQuadtreeBurst() { + for (int v = 0; v < 256; v++) { + for (int i = 0; i < 8; i++) { + if (((v >> i) & 0x1) != 0) { + ChildLookup[v] = (byte)i; + break; + } + } + } + } + + public Rect bounds { + get { + return boundingBoxBuffer.IsCreated ? Rect.MinMaxRect(boundingBoxBuffer[0].x, boundingBoxBuffer[0].y, boundingBoxBuffer[1].x, boundingBoxBuffer[1].y) : new Rect(); + } + } + + static int InnerNodeCountUpperBound (int numAgents, MovementPlane movementPlane) { + // Every LeafSize number of nodes can cause a split at most MaxDepth + // number of times. Each split needs 4 (or 8) units of space. + // Round the value up by adding LeafSize-1 to the numerator. + // This is an upper bound. Most likely the tree will contain significantly fewer nodes. + return ((movementPlane == MovementPlane.Arbitrary ? 8 : 4) * MaxDepth * numAgents + LeafSize-1)/LeafSize; + } + + public void Dispose () { + agents.Dispose(); + childPointers.Dispose(); + boundingBoxBuffer.Dispose(); + agentCountBuffer.Dispose(); + maxSpeeds.Dispose(); + maxRadius.Dispose(); + nodeAreas.Dispose(); + agentPositions.Dispose(); + agentRadii.Dispose(); + } + + void Reserve (int minSize) { + if (!boundingBoxBuffer.IsCreated) { + boundingBoxBuffer = new NativeArray(4, Allocator.Persistent); + agentCountBuffer = new NativeArray(1, Allocator.Persistent); + } + // Create a new agent's array. Round up to nearest multiple multiple of 2 to avoid re-allocating often if the agent count slowly increases + int roundedAgents = math.ceilpow2(minSize); + Util.Memory.Realloc(ref agents, roundedAgents, Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref agentPositions, roundedAgents, Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref agentRadii, roundedAgents, Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref childPointers, InnerNodeCountUpperBound(roundedAgents, movementPlane), Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref maxSpeeds, childPointers.Length, Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref nodeAreas, childPointers.Length, Allocator.Persistent, NativeArrayOptions.ClearMemory); + Util.Memory.Realloc(ref maxRadius, childPointers.Length, Allocator.Persistent, NativeArrayOptions.ClearMemory); + } + + public JobBuild BuildJob (NativeArray agentPositions, NativeArray agentVersions, NativeArray agentSpeeds, NativeArray agentRadii, int numAgents, MovementPlane movementPlane) { + if (numAgents >= MaxAgents) throw new System.Exception("Too many agents. Cannot have more than " + MaxAgents); + Reserve(numAgents); + + this.movementPlane = movementPlane; + + return new JobBuild { + agents = agents, + agentVersions = agentVersions, + agentPositions = agentPositions, + agentSpeeds = agentSpeeds, + agentRadii = agentRadii, + outMaxSpeeds = maxSpeeds, + outMaxRadius = maxRadius, + outArea = nodeAreas, + outAgentRadii = this.agentRadii, // Will be copied. These are copied so that the quadtree remains in a valid state even after new agents have been added/removed. This is important for the QueryArea method which may be called at any time. + outAgentPositions = this.agentPositions, // Will be copied + outBoundingBox = boundingBoxBuffer, + outAgentCount = agentCountBuffer, + outChildPointers = childPointers, + numAgents = numAgents, + movementPlane = movementPlane, + }; + } + + [BurstCompile(CompileSynchronously = true, FloatMode = FloatMode.Fast)] + public struct JobBuild : IJob { + /// Length should be greater or equal to agentPositions.Length + public NativeArray agents; + + [ReadOnly] + public NativeArray agentPositions; + + [ReadOnly] + public NativeArray agentVersions; + + [ReadOnly] + public NativeArray agentSpeeds; + + [ReadOnly] + public NativeArray agentRadii; + + /// Should have size 2 + [WriteOnly] + public NativeArray outBoundingBox; + + /// Should have size 1 + [WriteOnly] + public NativeArray outAgentCount; + + /// Should have size: InnerNodeCountUpperBound(numAgents) + public NativeArray outChildPointers; + + /// Should have size: InnerNodeCountUpperBound(numAgents) + public NativeArray outMaxSpeeds; + + /// Should have size: InnerNodeCountUpperBound(numAgents) + public NativeArray outMaxRadius; + + /// Should have size: InnerNodeCountUpperBound(numAgents) + public NativeArray outArea; + + [WriteOnly] + public NativeArray outAgentPositions; + + [WriteOnly] + public NativeArray outAgentRadii; + + public int numAgents; + + public MovementPlane movementPlane; + + static int Partition (NativeSlice indices, int startIndex, int endIndex, NativeSlice coordinates, float splitPoint) { + for (int i = startIndex; i < endIndex; i++) { + if (coordinates[indices[i]] > splitPoint) { + endIndex--; + var tmp = indices[i]; + indices[i] = indices[endIndex]; + indices[endIndex] = tmp; + i--; + } + } + return endIndex; + } + + void BuildNode (float3 boundsMin, float3 boundsMax, int depth, int agentsStart, int agentsEnd, int nodeOffset, ref int firstFreeChild) { + if (agentsEnd - agentsStart > LeafSize && depth < MaxDepth) { + if (movementPlane == MovementPlane.Arbitrary) { + // Split the node into 8 equally sized (by volume) child nodes + var xs = new NativeSlice(agentPositions).SliceWithStride(0); + var ys = new NativeSlice(agentPositions).SliceWithStride(4); + var zs = new NativeSlice(agentPositions).SliceWithStride(8); + + float3 boundsMid = (boundsMin + boundsMax) * 0.5f; + int s0 = agentsStart; + int s8 = agentsEnd; + int s4 = Partition(agents, s0, s8, xs, boundsMid.x); + int s2 = Partition(agents, s0, s4, ys, boundsMid.y); + int s6 = Partition(agents, s4, s8, ys, boundsMid.y); + int s1 = Partition(agents, s0, s2, zs, boundsMid.z); + int s3 = Partition(agents, s2, s4, zs, boundsMid.z); + int s5 = Partition(agents, s4, s6, zs, boundsMid.z); + int s7 = Partition(agents, s6, s8, zs, boundsMid.z); + + // Note: guaranteed to be large enough + int childIndex = firstFreeChild; + outChildPointers[nodeOffset] = childIndex; + firstFreeChild += 8; + + // x y z + // low low low + // low low high + // low high low + // low high high + // high low low + // high low high + // high high low + // high high high + var min = boundsMin; + var mid = boundsMid; + var max = boundsMax; + BuildNode(new float3(min.x, min.y, min.z), new float3(mid.x, mid.y, mid.z), depth + 1, s0, s1, childIndex + 0, ref firstFreeChild); + BuildNode(new float3(min.x, min.y, mid.z), new float3(mid.x, mid.y, max.z), depth + 1, s1, s2, childIndex + 1, ref firstFreeChild); + BuildNode(new float3(min.x, mid.y, min.z), new float3(mid.x, max.y, mid.z), depth + 1, s2, s3, childIndex + 2, ref firstFreeChild); + BuildNode(new float3(min.x, mid.y, mid.z), new float3(mid.x, max.y, max.z), depth + 1, s3, s4, childIndex + 3, ref firstFreeChild); + BuildNode(new float3(mid.x, min.y, min.z), new float3(max.x, mid.y, mid.z), depth + 1, s4, s5, childIndex + 4, ref firstFreeChild); + BuildNode(new float3(mid.x, min.y, mid.z), new float3(max.x, mid.y, max.z), depth + 1, s5, s6, childIndex + 5, ref firstFreeChild); + BuildNode(new float3(mid.x, mid.y, min.z), new float3(max.x, max.y, mid.z), depth + 1, s6, s7, childIndex + 6, ref firstFreeChild); + BuildNode(new float3(mid.x, mid.y, mid.z), new float3(max.x, max.y, max.z), depth + 1, s7, s8, childIndex + 7, ref firstFreeChild); + } else if (movementPlane == MovementPlane.XY) { + // Split the node into 4 equally sized (by area) child nodes + var xs = new NativeSlice(agentPositions).SliceWithStride(0); + var ys = new NativeSlice(agentPositions).SliceWithStride(4); + + float3 boundsMid = (boundsMin + boundsMax) * 0.5f; + int s0 = agentsStart; + int s4 = agentsEnd; + int s2 = Partition(agents, s0, s4, xs, boundsMid.x); + int s1 = Partition(agents, s0, s2, ys, boundsMid.y); + int s3 = Partition(agents, s2, s4, ys, boundsMid.y); + + // Note: guaranteed to be large enough + int childIndex = firstFreeChild; + outChildPointers[nodeOffset] = childIndex; + firstFreeChild += 4; + + // x y + // low low + // low high + // high low + // high high + BuildNode(new float3(boundsMin.x, boundsMin.y, boundsMin.z), new float3(boundsMid.x, boundsMid.y, boundsMax.z), depth + 1, s0, s1, childIndex + 0, ref firstFreeChild); + BuildNode(new float3(boundsMin.x, boundsMid.y, boundsMin.z), new float3(boundsMid.x, boundsMax.y, boundsMax.z), depth + 1, s1, s2, childIndex + 1, ref firstFreeChild); + BuildNode(new float3(boundsMid.x, boundsMin.y, boundsMin.z), new float3(boundsMax.x, boundsMid.y, boundsMax.z), depth + 1, s2, s3, childIndex + 2, ref firstFreeChild); + BuildNode(new float3(boundsMid.x, boundsMid.y, boundsMin.z), new float3(boundsMax.x, boundsMax.y, boundsMax.z), depth + 1, s3, s4, childIndex + 3, ref firstFreeChild); + } else { + // Split the node into 4 equally sized (by area) child nodes + var xs = new NativeSlice(agentPositions).SliceWithStride(0); + var zs = new NativeSlice(agentPositions).SliceWithStride(8); + + float3 boundsMid = (boundsMin + boundsMax) * 0.5f; + int s0 = agentsStart; + int s4 = agentsEnd; + int s2 = Partition(agents, s0, s4, xs, boundsMid.x); + int s1 = Partition(agents, s0, s2, zs, boundsMid.z); + int s3 = Partition(agents, s2, s4, zs, boundsMid.z); + + // Note: guaranteed to be large enough + int childIndex = firstFreeChild; + outChildPointers[nodeOffset] = childIndex; + firstFreeChild += 4; + + // x z + // low low + // low high + // high low + // high high + BuildNode(new float3(boundsMin.x, boundsMin.y, boundsMin.z), new float3(boundsMid.x, boundsMax.y, boundsMid.z), depth + 1, s0, s1, childIndex + 0, ref firstFreeChild); + BuildNode(new float3(boundsMin.x, boundsMin.y, boundsMid.z), new float3(boundsMid.x, boundsMax.y, boundsMax.z), depth + 1, s1, s2, childIndex + 1, ref firstFreeChild); + BuildNode(new float3(boundsMid.x, boundsMin.y, boundsMin.z), new float3(boundsMax.x, boundsMax.y, boundsMid.z), depth + 1, s2, s3, childIndex + 2, ref firstFreeChild); + BuildNode(new float3(boundsMid.x, boundsMin.y, boundsMid.z), new float3(boundsMax.x, boundsMax.y, boundsMax.z), depth + 1, s3, s4, childIndex + 3, ref firstFreeChild); + } + } else { + // Bitpack the start and end indices + outChildPointers[nodeOffset] = agentsStart | (agentsEnd << BitPackingShift) | LeafNodeBit; + } + } + + void CalculateSpeeds (int nodeCount) { + for (int i = nodeCount - 1; i >= 0; i--) { + if ((outChildPointers[i] & LeafNodeBit) != 0) { + int startIndex = outChildPointers[i] & BitPackingMask; + int endIndex = (outChildPointers[i] >> BitPackingShift) & BitPackingMask; + float speed = 0; + for (int j = startIndex; j < endIndex; j++) speed = math.max(speed, agentSpeeds[agents[j]]); + outMaxSpeeds[i] = speed; + + float radius = 0; + for (int j = startIndex; j < endIndex; j++) radius = math.max(radius, agentRadii[agents[j]]); + outMaxRadius[i] = radius; + + float area = 0; + for (int j = startIndex; j < endIndex; j++) area += agentRadii[agents[j]]*agentRadii[agents[j]]; + outArea[i] = area; + } else { + // Take the maximum of all child speeds + // This is guaranteed to have been calculated already because we do the loop in reverse and child indices are always greater than the current index + int childIndex = outChildPointers[i]; + if (movementPlane == MovementPlane.Arbitrary) { + // 8 children + float maxSpeed = 0; + float maxRadius = 0; + float area = 0; + for (int j = 0; j < 8; j++) { + maxSpeed = math.max(maxSpeed, outMaxSpeeds[childIndex + j]); + maxRadius = math.max(maxRadius, outMaxSpeeds[childIndex + j]); + area += outArea[childIndex + j]; + } + outMaxSpeeds[i] = maxSpeed; + outMaxRadius[i] = maxRadius; + outArea[i] = area; + } else { + // 4 children + outMaxSpeeds[i] = math.max(math.max(outMaxSpeeds[childIndex], outMaxSpeeds[childIndex+1]), math.max(outMaxSpeeds[childIndex+2], outMaxSpeeds[childIndex+3])); + outMaxRadius[i] = math.max(math.max(outMaxRadius[childIndex], outMaxRadius[childIndex+1]), math.max(outMaxRadius[childIndex+2], outMaxRadius[childIndex+3])); + + // Sum of child areas + outArea[i] = outArea[childIndex] + outArea[childIndex+1] + outArea[childIndex+2] + outArea[childIndex+3]; + } + } + } + } + + public void Execute () { + float3 mn = float.PositiveInfinity; + float3 mx = float.NegativeInfinity; + int existingAgentCount = 0; + for (int i = 0; i < numAgents; i++) { + if (agentVersions[i].Valid) { + agents[existingAgentCount++] = i; + mn = math.min(mn, agentPositions[i]); + mx = math.max(mx, agentPositions[i]); + } + } + + outAgentCount[0] = existingAgentCount; + + if (existingAgentCount == 0) { + outBoundingBox[0] = outBoundingBox[1] = float3.zero; + return; + } + + outBoundingBox[0] = mn; + outBoundingBox[1] = mx; + + int firstFreeChild = 1; + BuildNode(mn, mx, 0, 0, existingAgentCount, 0, ref firstFreeChild); + + CalculateSpeeds(firstFreeChild); + + NativeArray.Copy(agentPositions, outAgentPositions, numAgents); + NativeArray.Copy(agentRadii, outAgentRadii, numAgents); + } + } + + public struct QuadtreeQuery { + public float3 position; + public float speed, timeHorizon, agentRadius; + public int outputStartIndex, maxCount; + public RVOLayer layerMask; + public NativeArray layers; + public NativeArray result; + public NativeArray resultDistances; + } + + /// + /// A very large distance. Used as a sentinel value in the QueryKNearest method. + /// We don't use actual infinity, because the code may be compiled using FastMath, which makes the compiler assume that infinities do not exist. + /// This should be much larger than any distance used in practice. + /// + const float DistanceInfinity = 1e30f; + + public int QueryKNearest (QuadtreeQuery query) { + if (!agents.IsCreated) return 0; + float maxRadius = DistanceInfinity; + + for (int i = 0; i < query.maxCount; i++) query.result[query.outputStartIndex + i] = -1; + for (int i = 0; i < query.maxCount; i++) query.resultDistances[i] = DistanceInfinity; + + QueryRec(ref query, 0, boundingBoxBuffer[0], boundingBoxBuffer[1], ref maxRadius); + + int numFound = 0; + while (numFound < query.maxCount && query.resultDistances[numFound] < DistanceInfinity) numFound++; + return numFound; + } + + void QueryRec (ref QuadtreeQuery query, int treeNodeIndex, float3 nodeMin, float3 nodeMax, ref float maxRadius) { + // Note: the second agentRadius usage should actually be the radius of the other agents, not this agent + // Determine the radius that we need to search to take all agents into account + // but for performance reasons and for simplicity we assume that agents have approximately the same radius. + // Thus an agent with a very small radius may in some cases detect an agent with a very large radius too late + // however this effect should be minor. + var radius = math.min(math.max((maxSpeeds[treeNodeIndex] + query.speed)*query.timeHorizon, query.agentRadius) + query.agentRadius, maxRadius); + float3 p = query.position; + + if ((childPointers[treeNodeIndex] & LeafNodeBit) != 0) { + // Leaf node + int maxCount = query.maxCount; + int startIndex = childPointers[treeNodeIndex] & BitPackingMask; + int endIndex = (childPointers[treeNodeIndex] >> BitPackingShift) & BitPackingMask; + + var result = query.result; + var resultDistances = query.resultDistances; + for (int j = startIndex; j < endIndex; j++) { + var agent = agents[j]; + float sqrDistance = math.lengthsq(p - agentPositions[agent]); + if (sqrDistance < radius*radius && (query.layers[agent] & query.layerMask) != 0) { + // Close enough + + // Insert the agent into the results list using insertion sort + for (int k = 0; k < maxCount; k++) { + if (sqrDistance < resultDistances[k]) { + // Move the remaining items one step in the array + for (int q = maxCount - 1; q > k; q--) { + result[query.outputStartIndex + q] = result[query.outputStartIndex + q-1]; + resultDistances[q] = resultDistances[q-1]; + } + result[query.outputStartIndex + k] = agent; + resultDistances[k] = sqrDistance; + + if (k == maxCount - 1) { + // We reached the end of the array. This means that we just updated the largest distance. + // We can use this to restrict the future search. We know that no other agent distance we find can be larger than this value. + maxRadius = math.min(maxRadius, math.sqrt(sqrDistance)); + radius = math.min(radius, maxRadius); + } + break; + } + } + } + } + } else { + // Not a leaf node + int childrenStartIndex = childPointers[treeNodeIndex]; + + float3 nodeMid = (nodeMin + nodeMax) * 0.5f; + if (movementPlane == MovementPlane.Arbitrary) { + // First visit the child that overlaps the query position. + // This is important to do first as it usually reduces the maxRadius significantly + // and thus reduces the number of children we have to search later. + var mainChildIndex = (p.x < nodeMid.x ? 0 : 4) | (p.y < nodeMid.y ? 0 : 2) | (p.z < nodeMid.z ? 0 : 1); + { + var selector = new bool3((mainChildIndex & 0x4) != 0, (mainChildIndex & 0x2) != 0, (mainChildIndex & 0x1) != 0); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + mainChildIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + } + + // Visit a child if a cube with sides of length 2*radius (centered at p) touches the child. + // We calculate this info for all 8 children at the same time. + // Each child contains three checks, one for each axis. + // For example for the child which is lower than mid on the x-axis and z-axis, but higher than mid on the y axis + // the check we want to do looks like: (p.x - radius < nodeMid.x && p.y + radius > nodeMid.y && p.z - radius < nodeMid.z) + var lessThanMid = p - radius < nodeMid; + var greaterThanMid = p + radius > nodeMid; + // If for example lessThanMid.x is false, then we can exclude all 4 children that require that check + var branch1 = math.select(new int3(0b11110000, 0b11001100, 0b10101010), new int3(0xFF, 0xFF, 0xFF), lessThanMid); + var branch2 = math.select(new int3(0b00001111, 0b00110011, 0b01010101), new int3(0xFF, 0xFF, 0xFF), greaterThanMid); + var toVisitByAxis = branch1 & branch2; + // Combine the checks for each axis + // Bitmask of which children we want to visit (1 = visit, 0 = don't visit) + var childrenToVisit = toVisitByAxis.x & toVisitByAxis.y & toVisitByAxis.z; + + childrenToVisit &= ~(1 << mainChildIndex); + + // Loop over all children that we will visit. + // It's nice with a loop because we will usually only have a single branch. + while (childrenToVisit != 0) { + var childIndex = ChildLookup[childrenToVisit]; + var selector = new bool3((childIndex & 0x4) != 0, (childIndex & 0x2) != 0, (childIndex & 0x1) != 0); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + childIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + childrenToVisit &= ~(1 << childIndex); + } + } else if (movementPlane == MovementPlane.XY) { + var mainChildIndex = (p.x < nodeMid.x ? 0 : 2) | (p.y < nodeMid.y ? 0 : 1); + { + // Note: mx.z will become nodeMid.z which is technically incorrect, but we don't care about the Z coordinate here anyway + var selector = new bool3((mainChildIndex & 0x2) != 0, (mainChildIndex & 0x1) != 0, false); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + mainChildIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + } + + var lessThanMid = p.xy - radius < nodeMid.xy; + var greaterThanMid = p.xy + radius > nodeMid.xy; + + var v = new bool4(lessThanMid.x & lessThanMid.y, lessThanMid.x & greaterThanMid.y, greaterThanMid.x & lessThanMid.y, greaterThanMid.x & greaterThanMid.y); + // Build a bitmask of which children to visit + var childrenToVisit = (v.x ? 1 : 0) | (v.y ? 2 : 0) | (v.z ? 4 : 0) | (v.w ? 8 : 0); + childrenToVisit &= ~(1 << mainChildIndex); + + // Loop over all children that we will visit. + // It's nice with a loop because we will usually only have a single branch. + while (childrenToVisit != 0) { + var childIndex = ChildLookup[childrenToVisit]; + // Note: mx.z will become nodeMid.z which is technically incorrect, but we don't care about the Z coordinate here anyway + var selector = new bool3((childIndex & 0x2) != 0, (childIndex & 0x1) != 0, false); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + childIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + childrenToVisit &= ~(1 << childIndex); + } + } else { + var mainChildIndex = (p.x < nodeMid.x ? 0 : 2) | (p.z < nodeMid.z ? 0 : 1); + { + // Note: mx.y will become nodeMid.y which is technically incorrect, but we don't care about the Y coordinate here anyway + var selector = new bool3((mainChildIndex & 0x2) != 0, false, (mainChildIndex & 0x1) != 0); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + mainChildIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + } + + var lessThanMid = p.xz - radius < nodeMid.xz; + var greaterThanMid = p.xz + radius > nodeMid.xz; + + var v = new bool4(lessThanMid.x & lessThanMid.y, lessThanMid.x & greaterThanMid.y, greaterThanMid.x & lessThanMid.y, greaterThanMid.x & greaterThanMid.y); + var childrenToVisit = (v.x ? 1 : 0) | (v.y ? 2 : 0) | (v.z ? 4 : 0) | (v.w ? 8 : 0); + childrenToVisit &= ~(1 << mainChildIndex); + + while (childrenToVisit != 0) { + var childIndex = ChildLookup[childrenToVisit]; + // Note: mx.y will become nodeMid.y which is technically incorrect, but we don't care about the Y coordinate here anyway + var selector = new bool3((childIndex & 0x2) != 0, false, (childIndex & 0x1) != 0); + + var mn = math.select(nodeMin, nodeMid, selector); + var mx = math.select(nodeMid, nodeMax, selector); + QueryRec(ref query, childrenStartIndex + childIndex, mn, mx, ref maxRadius); + radius = math.min(radius, maxRadius); + childrenToVisit &= ~(1 << childIndex); + } + } + } + } + + /// Find the total agent area inside the circle at position with the given radius + public float QueryArea (float3 position, float radius) { + if (!agents.IsCreated || agentCountBuffer[0] == 0) return 0f; + return math.PI * QueryAreaRec(0, position, radius, boundingBoxBuffer[0], boundingBoxBuffer[1]); + } + + float QueryAreaRec (int treeNodeIndex, float3 p, float radius, float3 nodeMin, float3 nodeMax) { + float3 nodeMid = (nodeMin + nodeMax) * 0.5f; + // Radius of a circle that is guaranteed to cover the entire node + float nodeRadius = math.length(nodeMax - nodeMid); + float dist = math.lengthsq(nodeMid - p); + var maxAgentRadius = maxRadius[treeNodeIndex]; + var thresholdDistance = radius - (nodeRadius + maxAgentRadius); + + if (thresholdDistance > 0 && dist < thresholdDistance*thresholdDistance) { + // Node is completely inside the circle. Return the precalculated area of all agents inside the node. + return nodeAreas[treeNodeIndex]; + } + + if (dist > (radius + (nodeRadius + maxAgentRadius))*(radius + (nodeRadius + maxAgentRadius))) { + return 0; + } + + if ((childPointers[treeNodeIndex] & LeafNodeBit) != 0) { + // Leaf node + // Node is partially inside the circle + + int startIndex = childPointers[treeNodeIndex] & BitPackingMask; + int endIndex = (childPointers[treeNodeIndex] >> BitPackingShift) & BitPackingMask; + + float k = 0; + float area = 0; + for (int j = startIndex; j < endIndex; j++) { + var agent = agents[j]; + k += agentRadii[agent]*agentRadii[agent]; + float sqrDistance = math.lengthsq(p - agentPositions[agent]); + float agentRadius = agentRadii[agent]; + if (sqrDistance < (radius + agentRadius)*(radius + agentRadius)) { + float innerRadius = radius - agentRadius; + // Slight approximation at the edge of the circle. + // This is the approximate fraction of the agent that is inside the circle. + float fractionInside = sqrDistance < innerRadius*innerRadius ? 1.0f : 1.0f - (math.sqrt(sqrDistance) - innerRadius) / (2*agentRadius); + area += agentRadius*agentRadius * fractionInside; + } + } + return area; + } else { + float area = 0; + // Not a leaf node + int childIndex = childPointers[treeNodeIndex]; + float radiusWithMargin = radius + maxAgentRadius; + if (movementPlane == MovementPlane.Arbitrary) { + bool3 lower = (p - radiusWithMargin) < nodeMid; + bool3 upper = (p + radiusWithMargin) > nodeMid; + if (lower[0]) { + if (lower[1]) { + if (lower[2]) area += QueryAreaRec(childIndex + 0, p, radius, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMid.y, nodeMid.z)); + if (upper[2]) area += QueryAreaRec(childIndex + 1, p, radius, new float3(nodeMin.x, nodeMin.y, nodeMid.z), new float3(nodeMid.x, nodeMid.y, nodeMax.z)); + } + if (upper[1]) { + if (lower[2]) area += QueryAreaRec(childIndex + 2, p, radius, new float3(nodeMin.x, nodeMid.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMid.z)); + if (upper[2]) area += QueryAreaRec(childIndex + 3, p, radius, new float3(nodeMin.x, nodeMid.y, nodeMid.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z)); + } + } + if (upper[0]) { + if (lower[1]) { + if (lower[2]) area += QueryAreaRec(childIndex + 4, p, radius, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMid.y, nodeMid.z)); + if (upper[2]) area += QueryAreaRec(childIndex + 5, p, radius, new float3(nodeMid.x, nodeMin.y, nodeMid.z), new float3(nodeMax.x, nodeMid.y, nodeMax.z)); + } + if (upper[1]) { + if (lower[2]) area += QueryAreaRec(childIndex + 6, p, radius, new float3(nodeMid.x, nodeMid.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMid.z)); + if (upper[2]) area += QueryAreaRec(childIndex + 7, p, radius, new float3(nodeMid.x, nodeMid.y, nodeMid.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z)); + } + } + } else if (movementPlane == MovementPlane.XY) { + bool2 lower = (p - radiusWithMargin).xy < nodeMid.xy; + bool2 upper = (p + radiusWithMargin).xy > nodeMid.xy; + if (lower[0]) { + if (lower[1]) area += QueryAreaRec(childIndex + 0, p, radius, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMid.y, nodeMax.z)); + if (upper[1]) area += QueryAreaRec(childIndex + 1, p, radius, new float3(nodeMin.x, nodeMid.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z)); + } + if (upper[0]) { + if (lower[1]) area += QueryAreaRec(childIndex + 2, p, radius, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMid.y, nodeMax.z)); + if (upper[1]) area += QueryAreaRec(childIndex + 3, p, radius, new float3(nodeMid.x, nodeMid.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z)); + } + } else { + bool2 lower = (p - radiusWithMargin).xz < nodeMid.xz; + bool2 upper = (p + radiusWithMargin).xz > nodeMid.xz; + if (lower[0]) { + if (lower[1]) area += QueryAreaRec(childIndex + 0, p, radius, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMid.z)); + if (upper[1]) area += QueryAreaRec(childIndex + 1, p, radius, new float3(nodeMin.x, nodeMin.y, nodeMid.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z)); + } + if (upper[0]) { + if (lower[1]) area += QueryAreaRec(childIndex + 2, p, radius, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMid.z)); + if (upper[1]) area += QueryAreaRec(childIndex + 3, p, radius, new float3(nodeMid.x, nodeMin.y, nodeMid.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z)); + } + } + return area; + } + } + + [BurstCompile] + public struct DebugDrawJob : IJob { + public CommandBuilder draw; + [ReadOnly] + public RVOQuadtreeBurst quadtree; + + public void Execute () { + quadtree.DebugDraw(draw); + } + } + + public void DebugDraw (CommandBuilder draw) { + if (!agentCountBuffer.IsCreated) return; + var numAgents = agentCountBuffer[0]; + if (numAgents == 0) return; + + DebugDraw(0, boundingBoxBuffer[0], boundingBoxBuffer[1], draw); + for (int i = 0; i < numAgents; i++) { + draw.Cross(agentPositions[agents[i]], 0.5f, Palette.Colorbrewer.Set1.Red); + } + } + + void DebugDraw (int nodeIndex, float3 nodeMin, float3 nodeMax, CommandBuilder draw) { + float3 nodeMid = (nodeMin + nodeMax) * 0.5f; + + draw.WireBox(nodeMid, nodeMax - nodeMin, Palette.Colorbrewer.Set1.Orange); + + if ((childPointers[nodeIndex] & LeafNodeBit) != 0) { + int startIndex = childPointers[nodeIndex] & BitPackingMask; + int endIndex = (childPointers[nodeIndex] >> BitPackingShift) & BitPackingMask; + + for (int j = startIndex; j < endIndex; j++) { + draw.Line(nodeMid, agentPositions[agents[j]], Color.black); + } + } else { + int childIndex = childPointers[nodeIndex]; + if (movementPlane == MovementPlane.Arbitrary) { + DebugDraw(childIndex + 0, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMid.y, nodeMid.z), draw); + DebugDraw(childIndex + 1, new float3(nodeMin.x, nodeMin.y, nodeMid.z), new float3(nodeMid.x, nodeMid.y, nodeMax.z), draw); + DebugDraw(childIndex + 2, new float3(nodeMin.x, nodeMid.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMid.z), draw); + DebugDraw(childIndex + 3, new float3(nodeMin.x, nodeMid.y, nodeMid.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z), draw); + DebugDraw(childIndex + 4, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMid.y, nodeMid.z), draw); + DebugDraw(childIndex + 5, new float3(nodeMid.x, nodeMin.y, nodeMid.z), new float3(nodeMax.x, nodeMid.y, nodeMax.z), draw); + DebugDraw(childIndex + 6, new float3(nodeMid.x, nodeMid.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMid.z), draw); + DebugDraw(childIndex + 7, new float3(nodeMid.x, nodeMid.y, nodeMid.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z), draw); + } else if (movementPlane == MovementPlane.XY) { + DebugDraw(childIndex + 0, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMid.y, nodeMax.z), draw); + DebugDraw(childIndex + 1, new float3(nodeMin.x, nodeMid.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z), draw); + DebugDraw(childIndex + 2, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMid.y, nodeMax.z), draw); + DebugDraw(childIndex + 3, new float3(nodeMid.x, nodeMid.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z), draw); + } else { + DebugDraw(childIndex + 0, new float3(nodeMin.x, nodeMin.y, nodeMin.z), new float3(nodeMid.x, nodeMax.y, nodeMid.z), draw); + DebugDraw(childIndex + 1, new float3(nodeMin.x, nodeMin.y, nodeMid.z), new float3(nodeMid.x, nodeMax.y, nodeMax.z), draw); + DebugDraw(childIndex + 2, new float3(nodeMid.x, nodeMin.y, nodeMin.z), new float3(nodeMax.x, nodeMax.y, nodeMid.z), draw); + DebugDraw(childIndex + 3, new float3(nodeMid.x, nodeMin.y, nodeMid.z), new float3(nodeMax.x, nodeMax.y, nodeMax.z), draw); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs.meta new file mode 100644 index 0000000..0482032 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/RVO/RVOQuadtreeBurst.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a4ff665235b244238a6c694d7d38b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization.meta new file mode 100644 index 0000000..568309f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fb135586fe44a402c8ded728d001a26c diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs new file mode 100644 index 0000000..a6d84fe --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs @@ -0,0 +1,985 @@ +using System; +using System.IO; +using System.Collections.Generic; +using UnityEngine; +using Pathfinding.Util; +using Pathfinding.Collections; +using Pathfinding.Pooling; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Collections; + +#if ASTAR_NO_ZIP +using Pathfinding.Serialization.Zip; +#elif NETFX_CORE +// For Universal Windows Platform +using ZipEntry = System.IO.Compression.ZipArchiveEntry; +using ZipFile = System.IO.Compression.ZipArchive; +#else +using Pathfinding.Ionic.Zip; +#endif + +namespace Pathfinding.Serialization { + /// Holds information passed to custom graph serializers + public class GraphSerializationContext { + private readonly GraphNode[] id2NodeMapping; + + /// + /// Deserialization stream. + /// Will only be set when deserializing + /// + public readonly BinaryReader reader; + + /// + /// Serialization stream. + /// Will only be set when serializing + /// + public readonly BinaryWriter writer; + + /// + /// Index of the graph which is currently being processed. + /// Version: uint instead of int after 3.7.5 + /// + public readonly uint graphIndex; + + /// Metadata about graphs being deserialized + public readonly GraphMeta meta; + + /// True if a graph should be persisted. Indexed by graph index + public bool[] persistentGraphs; + + public GraphSerializationContext (BinaryReader reader, GraphNode[] id2NodeMapping, uint graphIndex, GraphMeta meta) { + this.reader = reader; + this.id2NodeMapping = id2NodeMapping; + this.graphIndex = graphIndex; + this.meta = meta; + } + + public GraphSerializationContext (BinaryWriter writer, bool[] persistentGraphs) { + this.writer = writer; + this.persistentGraphs = persistentGraphs; + } + + public void SerializeNodeReference (GraphNode node) { + writer.Write(node == null ? -1 : (int)node.NodeIndex); + } + + public void SerializeConnections (Connection[] connections, bool serializeMetadata) { + if (connections == null) { + writer.Write(-1); + } else { + int persistentConnections = 0; + for (int i = 0; i < connections.Length; i++) persistentConnections += persistentGraphs[connections[i].node.GraphIndex] ? 1 : 0; + writer.Write(persistentConnections); + for (int i = 0; i < connections.Length; i++) { + // Ignore connections to nodes in graphs which are not serialized + if (!persistentGraphs[connections[i].node.GraphIndex]) continue; + + SerializeNodeReference(connections[i].node); + writer.Write(connections[i].cost); + if (serializeMetadata) writer.Write(connections[i].shapeEdgeInfo); + } + } + } + + public Connection[] DeserializeConnections (bool deserializeMetadata) { + int count = reader.ReadInt32(); + + if (count == -1) { + return null; + } else { + var connections = ArrayPool.ClaimWithExactLength(count); + + for (int i = 0; i < count; i++) { + var target = DeserializeNodeReference(); + var cost = reader.ReadUInt32(); + if (deserializeMetadata) { + byte shapeEdgeInfo = Connection.NoSharedEdge; + // Keep all compatibility checks behind a single check for performance + if (meta.version < AstarSerializer.V4_3_87) { + if (meta.version < AstarSerializer.V4_1_0) { + // Read nothing + } else if (meta.version < AstarSerializer.V4_3_68) { + // Read, but discard data + reader.ReadByte(); + } else { + shapeEdgeInfo = reader.ReadByte(); + } + if (meta.version < AstarSerializer.V4_3_85) { + // Previously some additional bits were set to 1 + shapeEdgeInfo &= 0b1111 | (1 << 6); + } + if (meta.version < AstarSerializer.V4_3_87) { + shapeEdgeInfo |= Connection.IncomingConnection | Connection.OutgoingConnection; + } + } else { + shapeEdgeInfo = reader.ReadByte(); + } + + connections[i] = new Connection( + target, + cost, + shapeEdgeInfo + ); + } else { + connections[i] = new Connection(target, cost, true, true); + } + } + return connections; + } + + // TODO: Do we need to patch one way connections after deserializing? + } + + public GraphNode DeserializeNodeReference () { + var id = reader.ReadInt32(); + + if (id2NodeMapping == null) throw new Exception("Calling DeserializeNodeReference when not deserializing node references"); + + if (id == -1) return null; + GraphNode node = id2NodeMapping[id]; + if (node == null) throw new Exception("Invalid id ("+id+")"); + return node; + } + + /// Write a Vector3 + public void SerializeVector3 (Vector3 v) { + writer.Write(v.x); + writer.Write(v.y); + writer.Write(v.z); + } + + /// Read a Vector3 + public Vector3 DeserializeVector3 () { + return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + } + + /// Write an Int3 + public void SerializeInt3 (Int3 v) { + writer.Write(v.x); + writer.Write(v.y); + writer.Write(v.z); + } + + /// Read an Int3 + public Int3 DeserializeInt3 () { + return new Int3(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); + } + + public UnsafeSpan ReadSpan(Allocator allocator) where T : unmanaged { + var res = new UnsafeSpan(allocator, reader.ReadInt32()); + var s = res.Reinterpret(UnsafeUtility.SizeOf()); + unsafe { + var n = reader.Read(new System.Span(s.ptr, s.Length)); + if (n != s.Length) throw new Exception("Unexpected end of stream"); + } + return res; + } + } + + /// + /// Handles low level serialization and deserialization of graph settings and data. + /// Mostly for internal use. You can use the methods in the AstarData class for + /// higher level serialization and deserialization. + /// + /// See: AstarData + /// + public class AstarSerializer { + private AstarData data; + + /// Zip which the data is loaded from + private ZipFile zip; + + /// Memory stream with the zip data + private MemoryStream zipStream; + + /// Graph metadata + private GraphMeta meta; + + /// Settings for serialization + private SerializeSettings settings; + + /// + /// Root GameObject used for deserialization. + /// This should be the GameObject which holds the AstarPath component. + /// Important when deserializing when the component is on a prefab. + /// + private GameObject contextRoot; + + /// Graphs that are being serialized or deserialized + private NavGraph[] graphs; + + /// True if the graph should be serialized, indexed by graph index + bool[] persistentGraphs; + + /// + /// Index used for the graph in the file. + /// If some graphs were null in the file then graphIndexInZip[graphs[i]] may not equal i. + /// Used for deserialization. + /// + private Dictionary graphIndexInZip; + + /// Extension to use for binary files + const string binaryExt = ".binary"; + + /// Extension to use for json files + const string jsonExt = ".json"; + + /// + /// Checksum for the serialized data. + /// Used to provide a quick equality check in editor code + /// + private uint checksum = 0xffffffff; + + System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); + + /// Cached StringBuilder to avoid excessive allocations + static System.Text.StringBuilder _stringBuilder = new System.Text.StringBuilder(); + + /// + /// Returns a cached StringBuilder. + /// This function only has one string builder cached and should + /// thus only be called from a single thread and should not be called while using an earlier got string builder. + /// + static System.Text.StringBuilder GetStringBuilder () { _stringBuilder.Length = 0; return _stringBuilder; } + + /// Cached version object for 3.8.3 + public static readonly System.Version V3_8_3 = new System.Version(3, 8, 3); + + /// Cached version object for 3.9.0 + public static readonly System.Version V3_9_0 = new System.Version(3, 9, 0); + + /// Cached version object for 4.1.0 + public static readonly System.Version V4_1_0 = new System.Version(4, 1, 0); + + /// Cached version object for 4.3.2 + public static readonly System.Version V4_3_2 = new System.Version(4, 3, 2); + + /// Cached version object for 4.3.6 + public static readonly System.Version V4_3_6 = new System.Version(4, 3, 6); + + /// Cached version object for 4.3.37 + public static readonly System.Version V4_3_37 = new System.Version(4, 3, 37); + + /// Cached version object for 4.3.12 + public static readonly System.Version V4_3_12 = new System.Version(4, 3, 12); + + /// Cached version object for 4.3.68 + public static readonly System.Version V4_3_68 = new System.Version(4, 3, 68); + + /// Cached version object for 4.3.74 + public static readonly System.Version V4_3_74 = new System.Version(4, 3, 74); + + /// Cached version object for 4.3.80 + public static readonly System.Version V4_3_80 = new System.Version(4, 3, 80); + + /// Cached version object for 4.3.83 + public static readonly System.Version V4_3_83 = new System.Version(4, 3, 83); + + /// Cached version object for 4.3.85 + public static readonly System.Version V4_3_85 = new System.Version(4, 3, 85); + + /// Cached version object for 4.3.87 + public static readonly System.Version V4_3_87 = new System.Version(4, 3, 87); + + /// Cached version object for 5.1.0 + public static readonly System.Version V5_1_0 = new System.Version(5, 1, 0); + + /// Cached version object for 5.2.0 + public static readonly System.Version V5_2_0 = new System.Version(5, 2, 0); + + public AstarSerializer (AstarData data, GameObject contextRoot) : this(data, SerializeSettings.Settings, contextRoot) { + } + + public AstarSerializer (AstarData data, SerializeSettings settings, GameObject contextRoot) { + this.data = data; + this.contextRoot = contextRoot; + this.settings = settings; + } + + void AddChecksum (byte[] bytes) { + checksum = Checksum.GetChecksum(bytes, checksum); + } + + void AddEntry (string name, byte[] bytes) { +#if NETFX_CORE + var entry = zip.CreateEntry(name); + using (var stream = entry.Open()) { + stream.Write(bytes, 0, bytes.Length); + } +#else + zip.AddEntry(name, bytes); +#endif + } + + public uint GetChecksum () { return checksum; } + + #region Serialize + + public void OpenSerialize () { + // Create a new zip file, here we will store all the data + zipStream = new MemoryStream(); +#if NETFX_CORE + zip = new ZipFile(zipStream, System.IO.Compression.ZipArchiveMode.Create); +#else + zip = new ZipFile(); + zip.AlternateEncoding = System.Text.Encoding.UTF8; + zip.AlternateEncodingUsage = ZipOption.Always; + // Don't use parallel defate + zip.ParallelDeflateThreshold = -1; +#endif + meta = new GraphMeta(); + } + + public byte[] CloseSerialize () { + // As the last step, serialize metadata + byte[] bytes = SerializeMeta(); + AddChecksum(bytes); + AddEntry("meta"+jsonExt, bytes); + +#if !ASTAR_NO_ZIP && !NETFX_CORE + // Set dummy dates on every file to prevent the binary data to change + // for identical settings and graphs. + // Prevents the scene from being marked as dirty in the editor + // If ASTAR_NO_ZIP is defined this is not relevant since the replacement zip + // implementation does not even store dates + var dummy = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + foreach (var entry in zip.Entries) { + entry.AccessedTime = dummy; + entry.CreationTime = dummy; + entry.LastModified = dummy; + entry.ModifiedTime = dummy; + } +#endif + + // Save all entries to a single byte array +#if !NETFX_CORE + zip.Save(zipStream); +#endif + zip.Dispose(); + bytes = zipStream.ToArray(); + + zip = null; + zipStream = null; + return bytes; + } + + public void SerializeGraphs (NavGraph[] _graphs) { + if (graphs != null) throw new InvalidOperationException("Cannot serialize graphs multiple times."); + graphs = _graphs; + + if (zip == null) throw new NullReferenceException("You must not call CloseSerialize before a call to this function"); + + if (graphs == null) graphs = new NavGraph[0]; + + persistentGraphs = new bool[data.graphs.Length]; + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] == null) continue; + + // Ignore graph if null or if it should not persist + persistentGraphs[graphs[i].graphIndex] = graphs[i] != null && graphs[i].persistent; + + if (!persistentGraphs[graphs[i].graphIndex]) continue; + + // Serialize the graph to a byte array + byte[] bytes = Serialize(graphs[i]); + + AddChecksum(bytes); + AddEntry("graph"+i+jsonExt, bytes); + } + } + + /// Serialize metadata about all graphs + byte[] SerializeMeta () { + if (graphs == null) throw new System.Exception("No call to SerializeGraphs has been done"); + + meta.version = AstarPath.Version; + meta.graphs = graphs.Length; + meta.guids = new List(); + meta.typeNames = new List(); + + // For each graph, save the guid + // of the graph and the type of it + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] != null && persistentGraphs[graphs[i].graphIndex]) { + meta.guids.Add(graphs[i].guid.ToString()); + meta.typeNames.Add(graphs[i].GetType().FullName); + } else { + meta.guids.Add(null); + meta.typeNames.Add(null); + } + } + + // Grab a cached string builder to avoid allocations + var output = GetStringBuilder(); + TinyJsonSerializer.Serialize(meta, output); + return encoding.GetBytes(output.ToString()); + } + + /// Serializes the graph settings to JSON and returns the data + public byte[] Serialize (NavGraph graph) { + // Grab a cached string builder to avoid allocations + var output = GetStringBuilder(); + + TinyJsonSerializer.Serialize(graph, output); + return encoding.GetBytes(output.ToString()); + } + + static int GetMaxNodeIndexInAllGraphs (NavGraph[] graphs) { + int maxIndex = 0; + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] == null || !graphs[i].persistent) continue; + graphs[i].GetNodes((GraphNode node, ref int maxIndex) => { + maxIndex = Math.Max((int)node.NodeIndex, maxIndex); + if (node.Destroyed) { + Debug.LogError("Graph contains destroyed nodes. This is a bug."); + } + }, ref maxIndex); + } + return maxIndex; + } + + static byte[] SerializeNodeIndices (NavGraph[] graphs) { + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + + int maxNodeIndex = GetMaxNodeIndexInAllGraphs(graphs); + + writer.Write(maxNodeIndex); + + // While writing node indices, verify that the max node index is the same + // (user written graphs might have gotten it wrong) + int maxNodeIndex2 = 0; + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] == null || !graphs[i].persistent) continue; + graphs[i].GetNodes(node => { + maxNodeIndex2 = Math.Max((int)node.NodeIndex, maxNodeIndex2); + writer.Write(node.NodeIndex); + }); + } + + // Nice to verify if users are writing their own graph types + if (maxNodeIndex2 != maxNodeIndex) throw new Exception("Some graphs are not consistent in their GetNodes calls, sequential calls give different results."); + + byte[] bytes = stream.ToArray(); + writer.Close(); + + return bytes; + } + + /// Serializes info returned by NavGraph.SerializeExtraInfo + static byte[] SerializeGraphExtraInfo (NavGraph graph, bool[] persistentGraphs) { + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + var ctx = new GraphSerializationContext(writer, persistentGraphs); + + ((IGraphInternals)graph).SerializeExtraInfo(ctx); + byte[] bytes = stream.ToArray(); + writer.Close(); + + return bytes; + } + + /// + /// Used to serialize references to other nodes e.g connections. + /// Nodes use the GraphSerializationContext.GetNodeIdentifier and + /// GraphSerializationContext.GetNodeFromIdentifier methods + /// for serialization and deserialization respectively. + /// + static byte[] SerializeGraphNodeReferences (NavGraph graph, bool[] persistentGraphs) { + var stream = new MemoryStream(); + var writer = new BinaryWriter(stream); + var ctx = new GraphSerializationContext(writer, persistentGraphs); + + graph.GetNodes(node => node.SerializeReferences(ctx)); + writer.Close(); + + var bytes = stream.ToArray(); + return bytes; + } + + public void SerializeExtraInfo () { + if (!settings.nodes) return; + if (graphs == null) throw new InvalidOperationException("Cannot serialize extra info with no serialized graphs (call SerializeGraphs first)"); + + var bytes = SerializeNodeIndices(graphs); + AddChecksum(bytes); + AddEntry("graph_references"+binaryExt, bytes); + + for (int i = 0; i < graphs.Length; i++) { + if (graphs[i] == null || !graphs[i].persistent) continue; + + bytes = SerializeGraphExtraInfo(graphs[i], persistentGraphs); + AddChecksum(bytes); + AddEntry("graph"+i+"_extra"+binaryExt, bytes); + + bytes = SerializeGraphNodeReferences(graphs[i], persistentGraphs); + AddChecksum(bytes); + AddEntry("graph"+i+"_references"+binaryExt, bytes); + } + } + + #endregion + + #region Deserialize + + ZipEntry GetEntry (string name) { +#if NETFX_CORE + return zip.GetEntry(name); +#else + return zip[name]; +#endif + } + + bool ContainsEntry (string name) { + return GetEntry(name) != null; + } + + public bool OpenDeserialize (byte[] bytes) { + // Copy the bytes to a stream + zipStream = new MemoryStream(); + zipStream.Write(bytes, 0, bytes.Length); + zipStream.Position = 0; + try { +#if NETFX_CORE + zip = new ZipFile(zipStream); +#else + zip = ZipFile.Read(zipStream); + // Don't use parallel defate + zip.ParallelDeflateThreshold = -1; +#endif + } catch (Exception e) { + // Catches exceptions when an invalid zip file is found + Debug.LogError("Caught exception when loading from zip\n"+e); + + zipStream.Dispose(); + return false; + } + + if (ContainsEntry("meta" + jsonExt)) { + meta = DeserializeMeta(GetEntry("meta" + jsonExt)); + } else if (ContainsEntry("meta" + binaryExt)) { + meta = DeserializeBinaryMeta(GetEntry("meta" + binaryExt)); + } else { + throw new Exception("No metadata found in serialized data."); + } + + if (FullyDefinedVersion(meta.version) > FullyDefinedVersion(AstarPath.Version)) { + Debug.LogWarning("Trying to load data from a newer version of the A* Pathfinding Project\nCurrent version: "+AstarPath.Version+" Data version: "+meta.version + + "\nThis is usually fine as the stored data is usually backwards and forwards compatible." + + "\nHowever node data (not settings) can get corrupted between versions (even though I try my best to keep compatibility), so it is recommended " + + "to recalculate any caches (those for faster startup) and resave any files. Even if it seems to load fine, it might cause subtle bugs.\n"); + } + return true; + } + + /// + /// Returns a version with all fields fully defined. + /// This is used because by default new Version(3,0,0) > new Version(3,0). + /// This is not the desired behaviour so we make sure that all fields are defined here + /// + static System.Version FullyDefinedVersion (System.Version v) { + return new System.Version(Mathf.Max(v.Major, 0), Mathf.Max(v.Minor, 0), Mathf.Max(v.Build, 0), Mathf.Max(v.Revision, 0)); + } + + public void CloseDeserialize () { + zipStream.Dispose(); + zip.Dispose(); + zip = null; + zipStream = null; + } + + NavGraph DeserializeGraph (int zipIndex, int graphIndex, System.Type[] availableGraphTypes) { + // Get the graph type from the metadata we deserialized earlier + var graphType = meta.GetGraphType(zipIndex, availableGraphTypes); + + // Graph was null when saving, ignore + if (System.Type.Equals(graphType, null)) return null; + + // Create a new graph of the right type + NavGraph graph = data.CreateGraph(graphType); + graph.graphIndex = (uint)graphIndex; + + var jsonName = "graph" + zipIndex + jsonExt; + + if (ContainsEntry(jsonName)) { + // Read the graph settings + TinyJsonDeserializer.Deserialize(GetString(GetEntry(jsonName)), graphType, graph, contextRoot); + } else { + throw new FileNotFoundException("Could not find data for graph " + zipIndex + " in zip. Entry 'graph" + zipIndex + jsonExt + "' does not exist"); + } + + if (graph.guid.ToString() != meta.guids[zipIndex]) + throw new Exception("Guid in graph file not equal to guid defined in meta file. Have you edited the data manually?\n"+graph.guid+" != "+meta.guids[zipIndex]); + + return graph; + } + + /// + /// Deserializes graph settings. + /// Note: Stored in files named "graph" where # is the graph number. + /// + public NavGraph[] DeserializeGraphs (System.Type[] availableGraphTypes, bool allowLoadingNodes, System.Func nextGraphIndex) { + // Allocate a list of graphs to be deserialized + var graphList = new List(); + + graphIndexInZip = new Dictionary(); + + for (int i = 0; i < meta.graphs; i++) { + var graph = DeserializeGraph(i, nextGraphIndex(), availableGraphTypes); + if (graph != null) { + graphList.Add(graph); + graphIndexInZip[graph] = i; + } + } + + graphs = graphList.ToArray(); + + DeserializeEditorSettingsCompatibility(); + if (allowLoadingNodes) DeserializeExtraInfo(); + + return graphs; + } + + bool DeserializeExtraInfo (NavGraph graph) { + var zipIndex = graphIndexInZip[graph]; + var entry = GetEntry("graph"+zipIndex+"_extra"+binaryExt); + + if (entry == null) + return false; + + var reader = GetBinaryReader(entry); + + var ctx = new GraphSerializationContext(reader, null, graph.graphIndex, meta); + + // Call the graph to process the data + ((IGraphInternals)graph).DeserializeExtraInfo(ctx); + return true; + } + + bool AnyDestroyedNodesInGraphs () { + bool result = false; + + for (int i = 0; i < graphs.Length; i++) { + graphs[i].GetNodes(node => { + if (node.Destroyed) { + result = true; + } + }); + } + return result; + } + + GraphNode[] DeserializeNodeReferenceMap () { + // Get the file containing the list of all node indices + // This is correlated with the new indices of the nodes and a mapping from old to new + // is done so that references can be resolved + var entry = GetEntry("graph_references"+binaryExt); + + if (entry == null) throw new Exception("Node references not found in the data. Was this loaded from an older version of the A* Pathfinding Project?"); + + var reader = GetBinaryReader(entry); + int maxNodeIndex = reader.ReadInt32(); + var int2Node = new GraphNode[maxNodeIndex+1]; + + try { + for (int i = 0; i < graphs.Length; i++) { + graphs[i].GetNodes(node => { + var index = reader.ReadInt32(); + int2Node[index] = node; + }); + } + } catch (Exception e) { + throw new Exception("Some graph(s) has thrown an exception during GetNodes, or some graph(s) have deserialized more or fewer nodes than were serialized", e); + } + +#if !NETFX_CORE + // For Windows Store apps the BaseStream.Position property is not supported + // so we have to disable this error check on that platform + if (reader.BaseStream.Position != reader.BaseStream.Length) { + throw new Exception((reader.BaseStream.Length / 4) + " nodes were serialized, but only data for " + (reader.BaseStream.Position / 4) + " nodes was found. The data looks corrupt."); + } +#endif + + reader.Close(); + return int2Node; + } + + void DeserializeNodeReferences (NavGraph graph, GraphNode[] int2Node) { + var zipIndex = graphIndexInZip[graph]; + var entry = GetEntry("graph"+zipIndex+"_references"+binaryExt); + + if (entry == null) throw new Exception("Node references for graph " + zipIndex + " not found in the data. Was this loaded from an older version of the A* Pathfinding Project?"); + + var reader = GetBinaryReader(entry); + var ctx = new GraphSerializationContext(reader, int2Node, graph.graphIndex, meta); + + graph.GetNodes((GraphNode node, ref GraphSerializationContext ctx) => node.DeserializeReferences(ctx), ref ctx); + } + + void DeserializeAndRemoveOldNodeLinks (GraphSerializationContext ctx) { + var count = ctx.reader.ReadInt32(); + for (int i = 0; i < count; i++) { + var linkID = ctx.reader.ReadUInt64(); + var startNode = ctx.DeserializeNodeReference(); + var endNode = ctx.DeserializeNodeReference(); + var connectedNode1 = ctx.DeserializeNodeReference(); + var connectedNode2 = ctx.DeserializeNodeReference(); + var clamped1 = ctx.DeserializeVector3(); + var clamped2 = ctx.DeserializeVector3(); + var postScanCalled = ctx.reader.ReadBoolean(); + + startNode.ClearConnections(true); + endNode.ClearConnections(true); + startNode.Walkable = false; + endNode.Walkable = false; + // In case of one-way links + GraphNode.Disconnect(connectedNode1, startNode); + GraphNode.Disconnect(connectedNode2, endNode); + } + + bool graphRemoved = false; + for (int i = 0; i < graphs.Length && !graphRemoved; i++) { + if (graphs[i] != null && graphs[i] is PointGraph pointGraph) { + bool anyWalkable = false; + int count2 = 0; + pointGraph.GetNodes(node => { + anyWalkable |= node.Walkable; + count2++; + }); + if (!anyWalkable && pointGraph.root == null && 2*count == count2 && (count2 > 0 || pointGraph.name.Contains("used for node links"))) { + // This is very likely an off-mesh link graph that was automatically created + // by the system in an earlier version + // It is not used anymore and should be removed + ((IGraphInternals)graphs[i]).DestroyAllNodes(); + var ls = new List(graphs); + ls.RemoveAt(i); + graphs = ls.ToArray(); + graphRemoved = true; + } + if (pointGraph.name == "PointGraph (used for node links)") { + pointGraph.name = "PointGraph"; + } + } + } + + if (!graphRemoved && count > 0) { + Debug.LogWarning("Old off-mesh links were present in the serialized graph data. Not everything could be cleaned up properly. It is recommended that you re-scan all graphs and save the cache or graph file again. An attempt to migrate the old links was made, but a stray point graph may have been left behind."); + } + } + + + + /// + /// Deserializes extra graph info. + /// Extra graph info is specified by the graph types. + /// See: Pathfinding.NavGraph.DeserializeExtraInfo + /// Note: Stored in files named "graph" where # is the graph number. + /// + void DeserializeExtraInfo () { + bool anyDeserialized = false; + + // Loop through all graphs and deserialize the extra info + // if there is any such info in the zip file + for (int i = 0; i < graphs.Length; i++) { + anyDeserialized |= DeserializeExtraInfo(graphs[i]); + } + + if (!anyDeserialized) { + return; + } + + // Sanity check + // Make sure the graphs don't contain destroyed nodes + if (AnyDestroyedNodesInGraphs()) { + Debug.LogError("Graph contains destroyed nodes. This is a bug."); + } + + // Deserialize map from old node indices to new nodes + var int2Node = DeserializeNodeReferenceMap(); + + // Deserialize node references + for (int i = 0; i < graphs.Length; i++) { + DeserializeNodeReferences(graphs[i], int2Node); + } + + if (meta.version < V4_3_85) { + var entry = GetEntry("node_link2"+binaryExt); + + if (entry != null) { + var reader = GetBinaryReader(entry); + var ctx = new GraphSerializationContext(reader, int2Node, 0, meta); + DeserializeAndRemoveOldNodeLinks(ctx); + } + } + } + + /// Calls PostDeserialization on all loaded graphs + public void PostDeserialization () { + for (int i = 0; i < graphs.Length; i++) { + var ctx = new GraphSerializationContext(null, null, 0, meta); + ((IGraphInternals)graphs[i]).PostDeserialization(ctx); + } + } + + /// + /// Deserializes graph editor settings. + /// For future compatibility this method does not assume that the graphEditors array matches the array in order and/or count. + /// It searches for a matching graph (matching if graphEditor.target == graph) for every graph editor. + /// Multiple graph editors should not refer to the same graph. + /// Note: Stored in files named "graph" where # is the graph number. + /// + /// Note: This method is only used for compatibility, newer versions store everything in the graph.serializedEditorSettings field which is already serialized. + /// + void DeserializeEditorSettingsCompatibility () { + for (int i = 0; i < graphs.Length; i++) { + var zipIndex = graphIndexInZip[graphs[i]]; + ZipEntry entry = GetEntry("graph"+zipIndex+"_editor"+jsonExt); + if (entry == null) continue; + + (graphs[i] as IGraphInternals).SerializedEditorSettings = GetString(entry); + } + } + + /// Returns a binary reader for the data in the zip entry + private static BinaryReader GetBinaryReader (ZipEntry entry) { +#if NETFX_CORE + return new BinaryReader(entry.Open()); +#else + var stream = new MemoryStream((int)entry.UncompressedSize); + + entry.Extract(stream); + stream.Position = 0; + return new System.IO.BinaryReader(stream); +#endif + } + + /// Returns the data in the zip entry as a string + private static string GetString (ZipEntry entry) { +#if NETFX_CORE + var reader = new StreamReader(entry.Open()); +#else + var buffer = new MemoryStream((int)entry.UncompressedSize); + + entry.Extract(buffer); + buffer.Position = 0; + var reader = new StreamReader(buffer); +#endif + string s = reader.ReadToEnd(); + reader.Dispose(); + return s; + } + + private GraphMeta DeserializeMeta (ZipEntry entry) { + return TinyJsonDeserializer.Deserialize(GetString(entry), typeof(GraphMeta)) as GraphMeta; + } + + private GraphMeta DeserializeBinaryMeta (ZipEntry entry) { + var meta = new GraphMeta(); + + var reader = GetBinaryReader(entry); + + if (reader.ReadString() != "A*") throw new System.Exception("Invalid magic number in saved data"); + int major = reader.ReadInt32(); + int minor = reader.ReadInt32(); + int build = reader.ReadInt32(); + int revision = reader.ReadInt32(); + + // Required because when saving a version with a field not set, it will save it as -1 + // and then the Version constructor will throw an exception (which we do not want) + if (major < 0) meta.version = new Version(0, 0); + else if (minor < 0) meta.version = new Version(major, 0); + else if (build < 0) meta.version = new Version(major, minor); + else if (revision < 0) meta.version = new Version(major, minor, build); + else meta.version = new Version(major, minor, build, revision); + + meta.graphs = reader.ReadInt32(); + + meta.guids = new List(); + int count = reader.ReadInt32(); + for (int i = 0; i < count; i++) meta.guids.Add(reader.ReadString()); + + meta.typeNames = new List(); + count = reader.ReadInt32(); + for (int i = 0; i < count; i++) meta.typeNames.Add(reader.ReadString()); + reader.Close(); + + return meta; + } + + + #endregion + + #region Utils + + /// Save the specified data at the specified path + public static void SaveToFile (string path, byte[] data) { +#if NETFX_CORE + throw new System.NotSupportedException("Cannot save to file on this platform"); +#else + using (var stream = new FileStream(path, FileMode.Create)) { + stream.Write(data, 0, data.Length); + } +#endif + } + + /// Load the specified data from the specified path + public static byte[] LoadFromFile (string path) { +#if NETFX_CORE + throw new System.NotSupportedException("Cannot load from file on this platform"); +#else + using (var stream = new FileStream(path, FileMode.Open)) { + var bytes = new byte[(int)stream.Length]; + stream.Read(bytes, 0, (int)stream.Length); + return bytes; + } +#endif + } + + #endregion + } + + /// Metadata for all graphs included in serialization + public class GraphMeta { + /// Project version it was saved with + public Version version; + + /// Number of graphs serialized + public int graphs; + + /// Guids for all graphs + public List guids; + + /// Type names for all graphs + public List typeNames; + + /// Returns the Type of graph number index + public Type GetGraphType (int index, System.Type[] availableGraphTypes) { + // The graph was null when saving. Ignore it + if (String.IsNullOrEmpty(typeNames[index])) return null; + + for (int j = 0; j < availableGraphTypes.Length; j++) { + if (availableGraphTypes[j].FullName == typeNames[index]) return availableGraphTypes[j]; + } + + throw new Exception("No graph of type '" + typeNames[index] + "' could be created, type does not exist"); + } + } + + /// Holds settings for how graphs should be serialized + public class SerializeSettings { + /// + /// Enable to include node data. + /// If false, only settings will be saved + /// + public bool nodes = true; + + /// Serialization settings for only saving graph settings + public static SerializeSettings Settings => new SerializeSettings { + nodes = false + }; + + /// Serialization settings for serializing nodes and settings + public static SerializeSettings NodesAndSettings => new SerializeSettings(); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs.meta new file mode 100644 index 0000000..4c4b58f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/JsonSerializer.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: edd6db33319e94141bb849f4f58261c3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs new file mode 100644 index 0000000..1735ac8 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs @@ -0,0 +1,82 @@ +#if ASTAR_NO_ZIP +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +namespace Pathfinding.Serialization.Zip { + public enum ZipOption { + Always + } + + public class ZipFile { + public System.Text.Encoding AlternateEncoding; + public ZipOption AlternateEncodingUsage = ZipOption.Always; + public int ParallelDeflateThreshold = 0; + + Dictionary dict = new Dictionary(); + + public void AddEntry (string name, byte[] bytes) { + dict[name] = new ZipEntry(name, bytes); + } + + public bool ContainsEntry (string name) { + return dict.ContainsKey(name); + } + + public void Save (System.IO.Stream stream) { + var writer = new System.IO.BinaryWriter(stream); + + writer.Write(dict.Count); + foreach (KeyValuePair pair in dict) { + writer.Write(pair.Key); + writer.Write(pair.Value.bytes.Length); + writer.Write(pair.Value.bytes); + } + } + + public static ZipFile Read (System.IO.Stream stream) { + ZipFile file = new ZipFile(); + + var reader = new System.IO.BinaryReader(stream); + int count = reader.ReadInt32(); + + for (int i = 0; i < count; i++) { + var name = reader.ReadString(); + var length = reader.ReadInt32(); + var bytes = reader.ReadBytes(length); + + file.dict[name] = new ZipEntry(name, bytes); + } + + return file; + } + + public ZipEntry this[string index] { + get { + ZipEntry v; + dict.TryGetValue(index, out v); + return v; + } + } + + public void Dispose () { + } + } + + public class ZipEntry { + internal string name; + internal byte[] bytes; + + public long UncompressedSize => bytes.Length; + + public ZipEntry (string name, byte[] bytes) { + this.name = name; + this.bytes = bytes; + } + + public void Extract (System.IO.Stream stream) { + stream.Write(bytes, 0, bytes.Length); + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs.meta new file mode 100644 index 0000000..55b4b94 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/SimpleZipReplacement.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e4ab3c0aea7564a9c9af3bf72ed95858 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs new file mode 100644 index 0000000..b8fcfcb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs @@ -0,0 +1,527 @@ +using UnityEngine; +using System.Collections.Generic; +using Pathfinding.Util; +using Pathfinding.WindowsStore; +using System; +using System.Linq; +#if NETFX_CORE +using WinRTLegacy; +#endif + +namespace Pathfinding.Serialization { + public class JsonMemberAttribute : System.Attribute { + } + public class JsonOptInAttribute : System.Attribute { + } + /// Indicates that the full type of the instance will always be serialized. This allows inheritance to work properly. + public class JsonDynamicTypeAttribute : System.Attribute { + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class JsonDynamicTypeAliasAttribute : System.Attribute { + public string alias; + public Type type; + + public JsonDynamicTypeAliasAttribute (string alias, Type type) { + this.alias = alias; + this.type = type; + } + } + + // Make sure the class is not stripped out when using code stripping (see https://docs.unity3d.com/Manual/ManagedCodeStripping.html) + [Pathfinding.Util.Preserve] + class SerializableAnimationCurve { + public WrapMode preWrapMode, postWrapMode; + public Keyframe[] keys; + } + + /// + /// A very tiny json serializer. + /// It is not supposed to have lots of features, it is only intended to be able to serialize graph settings + /// well enough. + /// + public class TinyJsonSerializer { + System.Text.StringBuilder output = new System.Text.StringBuilder(); + + Dictionary > serializers = new Dictionary >(); + + static readonly System.Globalization.CultureInfo invariantCulture = System.Globalization.CultureInfo.InvariantCulture; + + public static void Serialize (System.Object obj, System.Text.StringBuilder output) { + new TinyJsonSerializer() { + output = output + }.Serialize(obj); + } + + TinyJsonSerializer () { + serializers[typeof(float)] = v => output.Append(((float)v).ToString("R", invariantCulture)); + serializers[typeof(bool)] = v => output.Append((bool)v ? "true" : "false"); + serializers[typeof(Version)] = serializers[typeof(uint)] = serializers[typeof(int)] = v => output.Append(v.ToString()); + serializers[typeof(string)] = v => output.AppendFormat("\"{0}\"", v.ToString().Replace("\"", "\\\"")); + serializers[typeof(Vector2)] = v => output.AppendFormat("{{ \"x\": {0}, \"y\": {1} }}", ((Vector2)v).x.ToString("R", invariantCulture), ((Vector2)v).y.ToString("R", invariantCulture)); + serializers[typeof(Vector3)] = v => output.AppendFormat("{{ \"x\": {0}, \"y\": {1}, \"z\": {2} }}", ((Vector3)v).x.ToString("R", invariantCulture), ((Vector3)v).y.ToString("R", invariantCulture), ((Vector3)v).z.ToString("R", invariantCulture)); + serializers[typeof(Pathfinding.Util.Guid)] = v => output.AppendFormat("{{ \"value\": \"{0}\" }}", v.ToString()); + serializers[typeof(LayerMask)] = v => output.AppendFormat("{{ \"value\": {0} }}", ((int)(LayerMask)v).ToString()); + } + + void Serialize (System.Object obj, bool serializePrivateFieldsByDefault = false) { + if (obj == null) { + output.Append("null"); + return; + } + + var type = obj.GetType(); + var typeInfo = WindowsStoreCompatibility.GetTypeInfo(type); + if (serializers.ContainsKey(type)) { + serializers[type] (obj); + } else if (typeInfo.IsEnum) { + output.Append('"' + obj.ToString() + '"'); + } else if (obj is System.Collections.IList) { + output.Append("["); + var arr = obj as System.Collections.IList; + for (int i = 0; i < arr.Count; i++) { + if (i != 0) + output.Append(", "); + Serialize(arr[i], serializePrivateFieldsByDefault); + } + output.Append("]"); + } else if (obj is AnimationCurve) { + var curve = obj as AnimationCurve; + Serialize(new SerializableAnimationCurve { preWrapMode = curve.preWrapMode, postWrapMode = curve.postWrapMode, keys = curve.keys }, true); + } else if (obj is UnityEngine.Object) { + SerializeUnityObject(obj as UnityEngine.Object); + } else { +#if NETFX_CORE + var optIn = typeInfo.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonOptInAttribute)); +#else + var optIn = typeInfo.GetCustomAttributes(typeof(JsonOptInAttribute), true).Length > 0; +#endif + output.Append("{"); + bool earlier = false; + + if (typeInfo.GetCustomAttributes(typeof(JsonDynamicTypeAttribute), true).Length > 0) { + output.AppendFormat("\"@type\": \"{0}\"", typeInfo.AssemblyQualifiedName); + earlier = true; + } + + while (true) { +#if NETFX_CORE + var fields = typeInfo.DeclaredFields.Where(f => !f.IsStatic).ToArray(); +#else + var fields = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); +#endif + foreach (var field in fields) { + if (field.DeclaringType != type) continue; + if ((!optIn && (field.IsPublic || serializePrivateFieldsByDefault)) || +#if NETFX_CORE + field.CustomAttributes.Any(attr => attr.GetType() == typeof(JsonMemberAttribute)) +#else + field.GetCustomAttributes(typeof(JsonMemberAttribute), true).Length > 0 +#endif + ) { + if (earlier) { + output.Append(", "); + } + + earlier = true; + output.AppendFormat("\"{0}\": ", field.Name); + Serialize(field.GetValue(obj), serializePrivateFieldsByDefault); + } + } + +#if NETFX_CORE + typeInfo = typeInfo.BaseType; + if (typeInfo == null) break; +#else + type = type.BaseType; + if (type == null) break; +#endif + } + output.Append("}"); + } + } + + void QuotedField (string name, string contents) { + output.AppendFormat("\"{0}\": \"{1}\"", name, contents); + } + + void SerializeUnityObject (UnityEngine.Object obj) { + // Note that a unityengine can be destroyed as well + if (obj == null) { + Serialize(null); + return; + } + + output.Append("{"); + var path = obj.name; +#if UNITY_EDITOR + // Figure out the path of the object relative to a Resources folder. + // In a standalone player this cannot be done unfortunately, so we will assume it is at the top level in the Resources folder. + // Fortunately it should be extremely rare to have to serialize references to unity objects in a standalone player. + var realPath = UnityEditor.AssetDatabase.GetAssetPath(obj); + var match = System.Text.RegularExpressions.Regex.Match(realPath, @"Resources/(.*?)(\.\w+)?$"); + if (match.Success) path = match.Groups[1].Value; +#endif + QuotedField("Name", path); + output.Append(", "); + QuotedField("Type", obj.GetType().FullName); + + //Write scene path if the object is a Component or GameObject + var component = obj as Component; + var go = obj as GameObject; + + if (component != null || go != null) { + if (component != null) { + go = component.gameObject; + } + + var helper = go.GetComponent(); + + if (helper == null) { + Debug.Log("Adding UnityReferenceHelper to Unity Reference '"+obj.name+"'"); + helper = go.AddComponent(); + } + + //Make sure it has a unique GUID + helper.Reset(); + output.Append(", "); + QuotedField("GUID", helper.GetGUID().ToString()); + } + output.Append("}"); + } + } + + /// + /// A very tiny json deserializer. + /// It is not supposed to have lots of features, it is only intended to be able to deserialize graph settings + /// well enough. Not much validation of the input is done. + /// + public class TinyJsonDeserializer { + System.IO.TextReader reader; + string fullTextDebug; + GameObject contextRoot; + + static readonly System.Globalization.NumberFormatInfo numberFormat = System.Globalization.NumberFormatInfo.InvariantInfo; + + /// + /// Deserializes an object of the specified type. + /// Will load all fields into the populate object if it is set (only works for classes). + /// + public static System.Object Deserialize (string text, Type type, System.Object populate = null, GameObject contextRoot = null) { + return new TinyJsonDeserializer() { + reader = new System.IO.StringReader(text), + fullTextDebug = text, + contextRoot = contextRoot, + }.Deserialize(type, populate); + } + + /// + /// Deserializes an object of type tp. + /// Will load all fields into the populate object if it is set (only works for classes). + /// + System.Object Deserialize (Type tp, System.Object populate = null) { + var tpInfo = WindowsStoreCompatibility.GetTypeInfo(tp); + + if (tpInfo.IsEnum) { + return Enum.Parse(tp, EatField()); + } else if (TryEat('n')) { + Eat("ull"); + TryEat(','); + return null; + } else if (Type.Equals(tp, typeof(float))) { + return float.Parse(EatField(), numberFormat); + } else if (Type.Equals(tp, typeof(int))) { + return int.Parse(EatField(), numberFormat); + } else if (Type.Equals(tp, typeof(uint))) { + return uint.Parse(EatField(), numberFormat); + } else if (Type.Equals(tp, typeof(bool))) { + return bool.Parse(EatField()); + } else if (Type.Equals(tp, typeof(string))) { + return EatField(); + } else if (Type.Equals(tp, typeof(Version))) { + return new Version(EatField()); + } else if (Type.Equals(tp, typeof(Vector2))) { + Eat("{"); + var result = new Vector2(); + EatField(); + result.x = float.Parse(EatField(), numberFormat); + EatField(); + result.y = float.Parse(EatField(), numberFormat); + Eat("}"); + return result; + } else if (Type.Equals(tp, typeof(Vector3))) { + Eat("{"); + var result = new Vector3(); + EatField(); + result.x = float.Parse(EatField(), numberFormat); + EatField(); + result.y = float.Parse(EatField(), numberFormat); + EatField(); + result.z = float.Parse(EatField(), numberFormat); + Eat("}"); + return result; + } else if (Type.Equals(tp, typeof(Pathfinding.Util.Guid))) { + Eat("{"); + EatField(); + var result = Pathfinding.Util.Guid.Parse(EatField()); + Eat("}"); + return result; + } else if (Type.Equals(tp, typeof(LayerMask))) { + Eat("{"); + EatField(); + var result = (LayerMask)int.Parse(EatField()); + Eat("}"); + return result; + } else if (tp.IsGenericType && Type.Equals(tp.GetGenericTypeDefinition(), typeof(List<>))) { + System.Collections.IList result = (System.Collections.IList)System.Activator.CreateInstance(tp); + var elementType = tp.GetGenericArguments()[0]; + + Eat("["); + while (!TryEat(']')) { + result.Add(Deserialize(elementType)); + TryEat(','); + } + return result; + } else if (tpInfo.IsArray) { + List ls = new List(); + Eat("["); + while (!TryEat(']')) { + ls.Add(Deserialize(tp.GetElementType())); + TryEat(','); + } + var arr = Array.CreateInstance(tp.GetElementType(), ls.Count); + ls.ToArray().CopyTo(arr, 0); + return arr; + } else if (typeof(UnityEngine.Object).IsAssignableFrom(tp)) { + return DeserializeUnityObject(); + } else { + Eat("{"); + + if (tpInfo.GetCustomAttributes(typeof(JsonDynamicTypeAttribute), true).Length > 0) { + string name = EatField(); + if (name != "@type") { + throw new System.Exception("Expected field '@type' but found '" + name + "'" + "\n\nWhen trying to deserialize: " + fullTextDebug); + } + + string typeName = EatField(); + + var aliases = tpInfo.GetCustomAttributes(typeof(JsonDynamicTypeAliasAttribute), true) as JsonDynamicTypeAliasAttribute[]; + var simpleTypeName = typeName.Split(',')[0]; + Type newType = null; + foreach (var alias in aliases) { + if (alias.alias == simpleTypeName) newType = alias.type; + } + + if (newType == null) newType = Type.GetType(typeName); + tp = newType ?? throw new System.Exception("Could not find a type with the name '" + typeName + "'" + "\n\nWhen trying to deserialize: " + fullTextDebug); + tpInfo = WindowsStoreCompatibility.GetTypeInfo(tp); + } + + var obj = populate ?? Activator.CreateInstance(tp); + while (!TryEat('}')) { + var name = EatField(); + var tmpType = tp; + System.Reflection.FieldInfo field = null; + while (field == null && tmpType != null) { + field = tmpType.GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + tmpType = tmpType.BaseType; + } + + if (field == null) { + // Try a property instead + System.Reflection.PropertyInfo prop = null; + tmpType = tp; + while (prop == null && tmpType != null) { + prop = tmpType.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + tmpType = tmpType.BaseType; + } + + if (prop == null) { + SkipFieldData(); + } else { + prop.SetValue(obj, Deserialize(prop.PropertyType)); + } + } else { + field.SetValue(obj, Deserialize(field.FieldType)); + } + TryEat(','); + } + return obj; + } + } + + UnityEngine.Object DeserializeUnityObject () { + Eat("{"); + var result = DeserializeUnityObjectInner(); + Eat("}"); + return result; + } + + UnityEngine.Object DeserializeUnityObjectInner () { + // Ignore InstanceID field (compatibility only) + var fieldName = EatField(); + + if (fieldName == "InstanceID") { + EatField(); + fieldName = EatField(); + } + + if (fieldName != "Name") throw new Exception("Expected 'Name' field"); + string name = EatField(); + + if (name == null) return null; + + if (EatField() != "Type") throw new Exception("Expected 'Type' field"); + string typename = EatField(); + + // Remove assembly information + if (typename.IndexOf(',') != -1) { + typename = typename.Substring(0, typename.IndexOf(',')); + } + + // Note calling through assembly is more stable on e.g WebGL + var type = WindowsStoreCompatibility.GetTypeInfo(typeof(AstarPath)).Assembly.GetType(typename); + type = type ?? WindowsStoreCompatibility.GetTypeInfo(typeof(Transform)).Assembly.GetType(typename); + + if (Type.Equals(type, null)) { + Debug.LogError("Could not find type '"+typename+"'. Cannot deserialize Unity reference"); + return null; + } + + // Check if there is another field there + EatWhitespace(); + if ((char)reader.Peek() == '"') { + if (EatField() != "GUID") throw new Exception("Expected 'GUID' field"); + string guid = EatField(); + + if (contextRoot != null) { + foreach (var helper in contextRoot.GetComponentsInChildren(true)) { + if (helper.GetGUID() == guid) { + if (Type.Equals(type, typeof(GameObject))) { + return helper.gameObject; + } else { + return helper.GetComponent(type); + } + } + } + } + + foreach (var helper in UnityCompatibility.FindObjectsByTypeUnsortedWithInactive()) { + if (helper.GetGUID() == guid) { + if (Type.Equals(type, typeof(GameObject))) { + return helper.gameObject; + } else { + return helper.GetComponent(type); + } + } + } + } + + // Note: calling LoadAll with an empty string will make it load the whole resources folder, which is probably a bad idea. + if (!string.IsNullOrEmpty(name)) { + // Try to load from resources + UnityEngine.Object[] objs = Resources.LoadAll(name, type); + + for (int i = 0; i < objs.Length; i++) { + if (objs[i].name == name || objs.Length == 1) { + return objs[i]; + } + } + } + + return null; + } + + void EatWhitespace () { + while (char.IsWhiteSpace((char)reader.Peek())) + reader.Read(); + } + + void Eat (string s) { + EatWhitespace(); + for (int i = 0; i < s.Length; i++) { + var c = (char)reader.Read(); + if (c != s[i]) { + throw new Exception("Expected '" + s[i] + "' found '" + c + "'\n\n..." + reader.ReadLine() + "\n\nWhen trying to deserialize: " + fullTextDebug); + } + } + } + + System.Text.StringBuilder builder = new System.Text.StringBuilder(); + string EatUntil (string c, bool inString) { + builder.Length = 0; + bool escape = false; + while (true) { + var readInt = reader.Peek(); + if (!escape && (char)readInt == '"') { + inString = !inString; + } + + var readChar = (char)readInt; + if (readInt == -1) { + throw new Exception("Unexpected EOF" + "\n\nWhen trying to deserialize: " + fullTextDebug); + } else if (!escape && readChar == '\\') { + escape = true; + reader.Read(); + } else if (!inString && c.IndexOf(readChar) != -1) { + break; + } else { + builder.Append(readChar); + reader.Read(); + escape = false; + } + } + + return builder.ToString(); + } + + bool TryEat (char c) { + EatWhitespace(); + if ((char)reader.Peek() == c) { + reader.Read(); + return true; + } + return false; + } + + string EatField () { + var result = EatUntil("\",}]", TryEat('"')); + + TryEat('\"'); + TryEat(':'); + TryEat(','); + return result; + } + + void SkipFieldData () { + var indent = 0; + + while (true) { + EatUntil(",{}[]", false); + var last = (char)reader.Peek(); + + switch (last) { + case '{': + case '[': + indent++; + break; + case '}': + case ']': + indent--; + if (indent < 0) return; + break; + case ',': + if (indent == 0) { + reader.Read(); + return; + } + break; + default: + throw new System.Exception("Should not reach this part"); + } + + reader.Read(); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs.meta new file mode 100644 index 0000000..84772f6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Serialization/TinyJson.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 761fd44fdff0b40648c9b7ce7763a06f +timeCreated: 1463169419 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync.meta new file mode 100644 index 0000000..12e7721 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7642b6b04437248d5ac92b83b2641efc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs new file mode 100644 index 0000000..a6cdcb6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs @@ -0,0 +1,212 @@ +using System.Threading; +using UnityEngine.Assertions; +using Pathfinding.Collections; + +namespace Pathfinding.Sync { + /// + /// Multi-producer-multi-consumer (MPMC) channel. + /// + /// This is a channel that can be used to send data between threads. + /// It is thread safe and can be used by multiple threads at the same time. + /// + /// Additionally, the channel can be put into a blocking mode, which will cause all calls to Receive to block until the channel is unblocked. + /// + internal class BlockableChannel where T : class { + public enum PopState { + Ok, + Wait, + Closed, + } + + readonly System.Object lockObj = new System.Object(); + + CircularBuffer queue = new CircularBuffer(16); + public int numReceivers { get; private set; } + + // Marked as volatile such that the compiler will not try to optimize the allReceiversBlocked property too much (this is more of a theoretical concern than a practical issue). + volatile int waitingReceivers; +#if !UNITY_WEBGL + ManualResetEvent starving = new ManualResetEvent(false); +#endif + volatile bool blocked; + + /// True if has been called + public bool isClosed { get; private set; } + + /// True if the queue is empty + public bool isEmpty { + get { + lock (lockObj) { + return queue.Length == 0; + } + } + } + + /// True if blocking and all receivers are waiting for unblocking + // Note: This is designed to be lock-free for performance. But it will only generate a useful value if called from the same thread that is blocking/unblocking the queue, otherwise the return value could become invalid at any time. + public bool allReceiversBlocked => blocked && waitingReceivers == numReceivers; + + /// If true, all calls to Receive will block until this property is set to false + public bool isBlocked { + get => blocked; + set { + lock (lockObj) { + blocked = value; + if (isClosed) return; + isStarving = value || queue.Length == 0; + } + } + } + + /// All calls to Receive and ReceiveNoBlock will now return PopState.Closed + public void Close () { + lock (lockObj) { + isClosed = true; + isStarving = false; + } + } + + bool isStarving { + get { +#if UNITY_WEBGL + // In WebGL, semaphores are not supported. + // They will compile, but they don't work properly. + // So instead we directly use what the starving semaphore should indicate. + return (blocked || queue.Length == 0) && !isClosed; +#else + return !starving.WaitOne(0); +#endif + } + set { +#if !UNITY_WEBGL + if (value) starving.Reset(); + else starving.Set(); +#endif + } + } + + /// + /// Resets a closed channel so that it can be used again. + /// + /// The existing queue is preserved. + /// + /// This will throw an exception if there are any receivers still active. + /// + public void Reopen () { + lock (lockObj) { + if (numReceivers != 0) throw new System.InvalidOperationException("Can only reopen a channel after Close has been called on all receivers"); + Assert.AreEqual(waitingReceivers, 0); + isClosed = false; + isBlocked = false; + } + } + + public Receiver AddReceiver () { + lock (lockObj) { + if (isClosed) throw new System.InvalidOperationException("Channel is closed"); + this.numReceivers++; + return new Receiver(this); + } + } + + /// Push a path to the front of the queue + public void PushFront (T path) { + lock (lockObj) { + if (isClosed) throw new System.InvalidOperationException("Channel is closed"); + queue.PushStart(path); + if (!blocked) isStarving = false; + } + } + + /// Push a path to the end of the queue + public void Push (T path) { + lock (lockObj) { + if (isClosed) throw new System.InvalidOperationException("Channel is closed"); + queue.PushEnd(path); + if (!blocked) isStarving = false; + } + } + + /// Allows receiving items from a channel + public struct Receiver { + BlockableChannel channel; + + public Receiver(BlockableChannel channel) { + this.channel = channel; + } + + /// + /// Call when a receiver was terminated. + /// + /// After this call, this receiver cannot be used for anything. + /// + public void Close () { + lock (channel.lockObj) { + Assert.IsTrue(channel.numReceivers > 0); + Assert.IsTrue(channel.waitingReceivers < channel.numReceivers); + channel.numReceivers--; + } + channel = null; + } + + /// + /// Receives the next item from the channel. + /// This call will block if there are no items in the channel or if the channel is currently blocked. + /// + /// Returns: PopState.Ok and a non-null item in the normal case. Returns PopState.Closed if the channel has been closed. + /// + public PopState Receive (out T item) { +#if UNITY_WEBGL + throw new System.Exception("Cannot block in WebGL. Use ReceiveNoBlock instead."); +#else + Interlocked.Increment(ref channel.waitingReceivers); + while (true) { + channel.starving.WaitOne(); + // Note that right here, the channel could become blocked, closed or anything really. We have to check conditions again after locking. + lock (channel.lockObj) { + if (channel.isClosed) { + Interlocked.Decrement(ref channel.waitingReceivers); + item = null; + return PopState.Closed; + } + if (channel.queue.Length == 0) channel.isStarving = true; + if (channel.isStarving) continue; + Assert.IsFalse(channel.blocked); + Interlocked.Decrement(ref channel.waitingReceivers); + item = channel.queue.PopStart(); + return PopState.Ok; + } + } +#endif + } + + /// + /// Receives the next item from the channel, this call will not block. + /// To ensure a consistent state, the caller must follow this pattern. + /// 1. Call ReceiveNoBlock(false), if PopState.Wait is returned, wait for a bit (e.g yield return null in a Unity coroutine) + /// 2. try again with PopNoBlock(true), if PopState.Wait, wait for a bit + /// 3. Repeat from step 2. + /// + public PopState ReceiveNoBlock (bool blockedBefore, out T item) { + item = null; + if (!blockedBefore) Interlocked.Increment(ref channel.waitingReceivers); + while (true) { + if (channel.isStarving) return PopState.Wait; + // Note that right here, the channel could become blocked, closed or anything really. We have to check conditions again after locking. + lock (channel.lockObj) { + if (channel.isClosed) { + Interlocked.Decrement(ref channel.waitingReceivers); + return PopState.Closed; + } + if (channel.queue.Length == 0) channel.isStarving = true; + if (channel.isStarving) continue; + Assert.IsFalse(channel.blocked); + Interlocked.Decrement(ref channel.waitingReceivers); + item = channel.queue.PopStart(); + return PopState.Ok; + } + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs.meta new file mode 100644 index 0000000..eb85fd1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/BlockableChannel.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f77616d6d8c14187b76d9dbcf1e0dcad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs new file mode 100644 index 0000000..a8c81ba --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs @@ -0,0 +1,48 @@ +using Unity.Jobs; + +namespace Pathfinding.Sync { + public interface IProgress { + float Progress { get; } + } + + /// + /// A promise that T is being calculated asynchronously. + /// + /// The result can be accessed by calling . This will block until the calculation is complete. + /// + public struct Promise: IProgress, System.IDisposable where T : IProgress, System.IDisposable { + public JobHandle handle; + T result; + + public Promise(JobHandle handle, T result) { + this.handle = handle; + this.result = result; + } + + public bool IsCompleted { + get { + return handle.IsCompleted; + } + } + + public float Progress { + get { + return result.Progress; + } + } + + public T GetValue () { + return result; + } + + public T Complete () { + handle.Complete(); + return result; + } + + public void Dispose () { + Complete(); + result.Dispose(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs.meta new file mode 100644 index 0000000..ccca8ff --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/Promise.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10843f8a3a6b439095bd72ed06bb3054 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs new file mode 100644 index 0000000..d5d8208 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs @@ -0,0 +1,226 @@ +// #define DEBUG_RWLOCK +using Unity.Jobs; + +namespace Pathfinding.Sync { + /// + /// A simple read/write lock for use with the Unity Job System. + /// + /// The RW-lock makes the following assumptions: + /// - Only the main thread will call the methods on this lock. + /// - If jobs are to use locked data, you should call or on the lock and pass the returned JobHandle as a dependency the job, and then call on the lock object, with the newly scheduled job's handle. + /// - When taking a Read lock, you should only read data, but if you take a Write lock you may modify data. + /// - On the main thread, multiple synchronous write locks may be nested. + /// + /// You do not need to care about dependencies when calling the and methods. That's handled automatically for you. + /// + /// See: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + public class RWLock { + JobHandle lastWrite; + JobHandle lastRead; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + int heldSyncLocks; + bool pendingAsync; +#if DEBUG_RWLOCK + string pendingStackTrace; +#endif + + void CheckPendingAsync () { +#if DEBUG_RWLOCK + if (pendingAsync) throw new System.InvalidOperationException("An async lock was previously aquired, but UnlockAfter was never called on it. The lock was aquired at\n" + pendingStackTrace + "\n\n"); +#else + if (pendingAsync) throw new System.InvalidOperationException("An async lock was previously aquired, but UnlockAfter was never called on it."); +#endif + } +#endif + + void AddPendingSync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckPendingAsync(); +#if DEBUG_RWLOCK + pendingStackTrace = System.Environment.StackTrace; +#endif + heldSyncLocks++; +#endif + } + + void RemovePendingSync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (heldSyncLocks <= 0) throw new System.InvalidOperationException("Tried to unlock a lock which was not locked. Did you call Unlock twice?"); + heldSyncLocks--; +#endif + } + + void AddPendingAsync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckPendingAsync(); +#if DEBUG_RWLOCK + if (heldSyncWriteLocks > 0) throw new System.InvalidOperationException("A synchronous lock is already being held. You cannot lock it asynchronously at the same time. The sync lock was aquired at\n" + pendingStackTrace + "\n\n"); + pendingStackTrace = System.Environment.StackTrace; +#else + if (heldSyncLocks > 0) throw new System.InvalidOperationException("A synchronous lock is already being held. You cannot lock it asynchronously at the same time."); +#endif + pendingAsync = true; +#endif + } + + void RemovePendingAsync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + pendingAsync = false; +#endif + } + + /// + /// Aquire a read lock on the main thread. + /// This method will block until all pending write locks have been released. + /// + public LockSync ReadSync () { + AddPendingSync(); + lastWrite.Complete(); + lastWrite = default; // Setting this to default will avoid a call into unity's c++ parts next time we call Complete (improves perf slightly) + return new LockSync(this); + } + + /// + /// Aquire a read lock on the main thread. + /// This method will not block until all asynchronous write locks have been released, instead you should make sure to add the returned JobHandle as a dependency to any jobs that use the locked data. + /// + /// If a synchronous write lock is currently held, this method will throw an exception. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + public ReadLockAsync Read () { + AddPendingAsync(); + return new ReadLockAsync(this, lastWrite); + } + + /// + /// Aquire a write lock on the main thread. + /// This method will block until all pending read and write locks have been released. + /// + public LockSync WriteSync () { + AddPendingSync(); + lastWrite.Complete(); + lastWrite = default; // Setting this to default will avoid a call into unity's c++ parts next time we call Complete (improves perf slightly) + lastRead.Complete(); + return new LockSync(this); + } + + /// + /// Aquire a write lock on the main thread. + /// This method will not block until all asynchronous read and write locks have been released, instead you should make sure to add the returned JobHandle as a dependency to any jobs that use the locked data. + /// + /// If a synchronous write lock is currently held, this method will throw an exception. + /// + /// + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// + /// + public WriteLockAsync Write () { + AddPendingAsync(); + return new WriteLockAsync(this, JobHandle.CombineDependencies(lastRead, lastWrite)); + } + + public readonly struct CombinedReadLockAsync { + readonly RWLock lock1; + readonly RWLock lock2; + public readonly JobHandle dependency; + + public CombinedReadLockAsync(ReadLockAsync lock1, ReadLockAsync lock2) { + this.lock1 = lock1.inner; + this.lock2 = lock2.inner; + dependency = JobHandle.CombineDependencies(lock1.dependency, lock2.dependency); + } + + /// Release the lock after the given job has completed + public readonly void UnlockAfter (JobHandle handle) { + if (lock1 != null) { + lock1.RemovePendingAsync(); + lock1.lastRead = JobHandle.CombineDependencies(lock1.lastRead, handle); + } + if (lock2 != null) { + lock2.RemovePendingAsync(); + lock2.lastRead = JobHandle.CombineDependencies(lock2.lastRead, handle); + } + } + + /// Release the lock immediately + public readonly void Unlock () { + UnityEngine.Assertions.Assert.IsTrue(dependency.IsCompleted, "Calling Unlock without the lock's dependency being completed. You are most likely doing something wrong."); + if (lock1 != null) lock1.RemovePendingAsync(); + if (lock2 != null) lock2.RemovePendingAsync(); + } + } + + public readonly struct ReadLockAsync { + internal readonly RWLock inner; + public readonly JobHandle dependency; + + public ReadLockAsync(RWLock inner, JobHandle dependency) { + this.inner = inner; + this.dependency = dependency; + } + + /// Release the lock after the given job has completed + public readonly void UnlockAfter (JobHandle handle) { + if (inner != null) { + inner.RemovePendingAsync(); + inner.lastRead = JobHandle.CombineDependencies(inner.lastRead, handle); + } + } + } + + public readonly struct WriteLockAsync { + readonly RWLock inner; + public readonly JobHandle dependency; + + public WriteLockAsync(RWLock inner, JobHandle dependency) { + this.inner = inner; + this.dependency = dependency; + } + + /// Release the lock after the given job has completed + public readonly void UnlockAfter (JobHandle handle) { + if (inner != null) { + inner.RemovePendingAsync(); + inner.lastWrite = handle; + } + } + } + + public readonly struct LockSync : System.IDisposable { + readonly RWLock inner; + + public LockSync(RWLock inner) { + this.inner = inner; + } + + /// Release the lock + public readonly void Unlock () { + if (inner != null) inner.RemovePendingSync(); + } + + readonly void System.IDisposable.Dispose () { + Unlock(); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs.meta new file mode 100644 index 0000000..a986b6f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/RWLock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c778b3e3a1c744eda0211a352c282e03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs new file mode 100644 index 0000000..5ca0c0e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs @@ -0,0 +1,29 @@ +using System.Threading; + +/// Synchronization primitives +namespace Pathfinding.Sync { + /// + /// Spin lock which can be used in Burst. + /// Good when the lock is generally uncontested. + /// Very inefficient when the lock is contested. + /// + internal struct SpinLock { + private volatile int locked; + + public void Lock () { + while (Interlocked.CompareExchange(ref locked, 1, 0) != 0) + Unity.Burst.Intrinsics.Common.Pause(); // spin + + // We need to ensure that any optimizer does not reorder loads to before we aquire the lock. + System.Threading.Thread.MemoryBarrier(); + } + + public void Unlock () { + // We need a memory barrier to ensure that all writes are visible to other threads, before we unlock. + // We also need to ensure that any optimizer does not reorder stores to after the unlock. + System.Threading.Thread.MemoryBarrier(); + // Release the lock by writing 0 to it. Use atomics to make it immediately visible to other threads. + if (Interlocked.Exchange(ref locked, 0) == 0) throw new System.InvalidOperationException("Trying to unlock a lock which is not locked"); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs.meta new file mode 100644 index 0000000..14ad166 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/Sync/SpinLock.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d41bf63d13ec497796349f0de6c51da1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs b/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs new file mode 100644 index 0000000..e4ee01b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs @@ -0,0 +1,1661 @@ +using UnityEngine; +using System.Collections.Generic; + +// Empty namespace declaration to avoid errors in the free version +// Which does not have any classes in the RVO namespace +namespace Pathfinding.RVO {} + +namespace Pathfinding { + using Pathfinding.Jobs; + using Pathfinding.Util; + using Pathfinding.Pooling; + using Unity.Burst; + using Unity.Collections; + using Unity.Jobs; + using Unity.Mathematics; + using System; + using Pathfinding.Collections; + + [System.Serializable] + /// Stores editor colors + public class AstarColor : ISerializationCallbackReceiver { + public Color _SolidColor; + public Color _UnwalkableNode; + public Color _BoundsHandles; + + public Color _ConnectionLowLerp; + public Color _ConnectionHighLerp; + + /// + /// Holds user set area colors. + /// Use GetAreaColor to get an area color + /// + public Color[] _AreaColors; + + public static Color SolidColor = new Color(30/255f, 102/255f, 201/255f, 0.9F); + public static Color UnwalkableNode = new Color(1, 0, 0, 0.5F); + public static Color BoundsHandles = new Color(0.29F, 0.454F, 0.741F, 0.9F); + + public static Color ConnectionLowLerp = new Color(0, 1, 0, 0.5F); + public static Color ConnectionHighLerp = new Color(1, 0, 0, 0.5F); + + private static Color[] AreaColors = new Color[1]; + + public static int ColorHash () { + var hash = SolidColor.GetHashCode() ^ UnwalkableNode.GetHashCode() ^ BoundsHandles.GetHashCode() ^ ConnectionLowLerp.GetHashCode() ^ ConnectionHighLerp.GetHashCode(); + + for (int i = 0; i < AreaColors.Length; i++) hash = 7*hash ^ AreaColors[i].GetHashCode(); + return hash; + } + + /// + /// Returns an color for an area, uses both user set ones and calculated. + /// If the user has set a color for the area, it is used, but otherwise the color is calculated using AstarMath.IntToColor + /// + public static Color GetAreaColor (uint area) { + if (area >= AreaColors.Length) return AstarMath.IntToColor((int)area, 1F); + return AreaColors[(int)area]; + } + + /// + /// Returns an color for a tag, uses both user set ones and calculated. + /// If the user has set a color for the tag, it is used, but otherwise the color is calculated using AstarMath.IntToColor + /// See: + /// + public static Color GetTagColor (uint tag) { + if (tag >= AreaColors.Length) return AstarMath.IntToColor((int)tag, 1F); + return AreaColors[(int)tag]; + } + + /// + /// Pushes all local variables out to static ones. + /// This is done because that makes it so much easier to access the colors during Gizmo rendering + /// and it has a positive performance impact as well (gizmo rendering is hot code). + /// It is a bit ugly though, but oh well. + /// + public void PushToStatic () { + _AreaColors = _AreaColors ?? new Color[0]; + + SolidColor = _SolidColor; + UnwalkableNode = _UnwalkableNode; + BoundsHandles = _BoundsHandles; + ConnectionLowLerp = _ConnectionLowLerp; + ConnectionHighLerp = _ConnectionHighLerp; + AreaColors = _AreaColors; + } + + public void OnBeforeSerialize () {} + + public void OnAfterDeserialize () { + // Patch bad initialization code in earlier versions that made it start out with a single transparent color. + if (_AreaColors != null && _AreaColors.Length == 1 && _AreaColors[0] == default) { + _AreaColors = new Color[0]; + } + } + + public AstarColor () { + // Set default colors + _SolidColor = new Color(30/255f, 102/255f, 201/255f, 0.9F); + _UnwalkableNode = new Color(1, 0, 0, 0.5F); + _BoundsHandles = new Color(0.29F, 0.454F, 0.741F, 0.9F); + _ConnectionLowLerp = new Color(0, 1, 0, 0.5F); + _ConnectionHighLerp = new Color(1, 0, 0, 0.5F); + } + } + + + /// + /// Info about what a raycast, linecast or nearest border query hit. + /// + /// This is the return value of the methods. + /// Some members will also be initialized even if nothing was hit, see the individual member descriptions for more info. + /// + /// [Open online documentation to see images] + /// + /// See: + /// See: + /// See: + /// + public struct GraphHitInfo { + /// + /// Start of the segment/ray. + /// Note that the point passed to the Linecast method will be clamped to the surface on the navmesh, but it will be identical when seen from above. + /// + public Vector3 origin; + /// + /// Hit point. + /// + /// This is typically a point on the border of the navmesh. + /// + /// In case no obstacle was hit then this will be set to the endpoint of the segment. + /// + /// If the origin was inside an unwalkable node, then this will be set to the origin point. + /// + public Vector3 point; + /// + /// Node which contained the edge which was hit. + /// If the linecast did not hit anything then this will be set to the last node along the line's path (the one which contains the endpoint). + /// + /// For layered grid graphs the linecast will return true (i.e: no free line of sight) if, when walking the graph, we ended up at the right X,Z coordinate for the end node, + /// but the end node was on a different level (e.g the floor below or above in a building). In this case no node edge was really hit so this field will still be null. + /// + /// If the origin was inside an unwalkable node, then this field will be set to that node. + /// + /// If no node could be found which contained the origin, then this field will be set to null. + /// + public GraphNode node; + /// + /// Where the tangent starts. and together actually describes the edge which was hit. + /// [Open online documentation to see images] + /// + /// If nothing was hit, this will be Vector3.zero. + /// + public Vector3 tangentOrigin; + /// + /// Tangent of the edge which was hit. + /// [Open online documentation to see images] + /// + /// If nothing was hit, this will be Vector3.zero. + /// + /// In some cases, an obstruction could be reported, but no edge was hit per se. + /// For example if the origin was inside an unwalkable node. In those cases this field will be set to Vector3.zero. + /// + public Vector3 tangent; + + /// Distance from to + public readonly float distance => (point-origin).magnitude; + } + + /// + /// Determines how to measure distances to the navmesh. + /// + /// The default is a euclidean distance, which works well for most things. + /// However, another option is to find nodes which are directly below a point. + /// This is very useful if you have a character, and you want to find the closest node to the character's feet. + /// Then projecting the distance so that we find the closest node as seen from above is a good idea. + /// + /// See for more info. + /// + /// [Open online documentation to see images] + /// The default distance metric is euclidean distance. This is the same as the distance you would measure in 3D space. + /// Shown in the image above are two parts of a navmesh in a side view. One part is colored blue, and one part colored red. Both are walkable. + /// In the background you can see if points are closer to the blue part, or to the red part. + /// You can also see a few query points in black, which show the closest point on the navmesh to that point. + /// The dashed circle around the first query point shows all points which are at the same distance from the query point. + /// + /// [Open online documentation to see images] + /// When using , the distance along the up direction is ignored. You can see this in the image + /// above. Note how all query points find their closest point directly below them. + /// Notice in particular the difference in behavior for the query point on the slope. + /// + /// [Open online documentation to see images] + /// When using , the distance along the up direction is instead scaled by (set to 0.2 by default). + /// This makes it behave almost like , but it allows small deviations from just looking directly below the query point. + /// The dashed diamond in the image is similarly the set of points equidistant from the query point. + /// This mode is recommended for character movement since it finds points below the agent, but can better handle situations where the agent, for example, steps off a ledge. + /// + public struct DistanceMetric { + /// + /// Normal of the plane on which nodes will be projected before finding the closest point on them. + /// + /// When zero, this has no effect. + /// + /// When set to the special value (inf, inf, inf) then the graph's natural up direction will be used. + /// + /// Often you do not want to find the closest point on a node in 3D space, but rather + /// find for example the closest point on the node directly below the agent. + /// + /// This allows you to project the nodes onto a plane before finding the closest point on them. + /// For example, if you set this to Vector3.up, then the nodes will be projected onto the XZ plane. + /// Running a GetNearest query will then find the closest node as seen from above. + /// + /// [Open online documentation to see images] + /// + /// This is more flexible, however. You can set the to any value (though usually somewhere between 0 and 1). + /// With a value of 0, the closest node will be found as seen from above. + /// When the distance is greater than 0, moving along the projectionAxis from the query point will only cost times the regular distance, + /// but moving sideways will cost the normal amount. + /// + /// [Open online documentation to see images] + /// + /// A nice value to use is 0.2 for . This will make moving upwards or downwards (along the projection direction) + /// only appear like 20% the original distance to the nearest node search. + /// This allows you to find the closest position directly under the agent, if there is a navmesh directly under the agent, but also to search + /// not directly below the agent if that is necessary. + /// + /// Note: This is only supported by some graph types. The navmesh/recast graphs fully support this. + /// The grid graphs, however, only support this if the direction is parallel to the natural 'up' direction of the graph. + /// The grid graphs will search as if the direction is in the graph's up direction if this value is anything other than Vector3.zero. + /// Point graphs do not support this field at all, and will completely ignore it. + /// + /// Note: On recast/navmesh graphs there is a performance penalty when using this feature with a direction which is not parallel to the natural up direction of the graph. + /// Typically you should only set it to such values if you have a spherical or non-planar world (see spherical) (view in online documentation for working links). + /// + public Vector3 projectionAxis; + + /// + /// Distance scaling along the . + /// + /// See: for details + /// + public float distanceScaleAlongProjectionDirection; + + /// True when using the ClosestAsSeenFromAbove or ClosestAsSeenFromAboveSoft modes + public bool isProjectedDistance => projectionAxis != Vector3.zero; + + /// + /// Returns a DistanceMetric which will find the closest node in euclidean 3D space. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + public static readonly DistanceMetric Euclidean = new DistanceMetric { projectionAxis = Vector3.zero, distanceScaleAlongProjectionDirection = 0 }; + + /// + /// Returns a DistanceMetric which will usually find the closest node as seen from above, but allows some sideways translation if that gives us a node which is significantly closer. + /// + /// The "upwards" direction will be set to the graph's natural up direction. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + /// Note: This is only supported by some graph types. The navmesh/recast/grid graph fully support this. + /// Point graphs do not support this field at all, and will completely ignore it. + /// + /// See: + /// + public static DistanceMetric ClosestAsSeenFromAboveSoft () => new DistanceMetric { projectionAxis = Vector3.positiveInfinity, distanceScaleAlongProjectionDirection = 0.2f }; + + /// + /// Returns a DistanceMetric which will usually find the closest node as seen from above, but allows some sideways translation if that gives us a node which is significantly closer. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + /// Note: This is only supported by some graph types. The navmesh/recast graphs fully support this. + /// The grid graphs, however, only support this if the direction is parallel to the natural 'up' direction of the graph. + /// The grid graphs will search as if the direction is in the graph's up direction if this value is anything other than Vector3.zero. + /// Point graphs do not support this field at all, and will completely ignore it. + /// + /// Note: On recast/navmesh graphs there is a performance penalty when using this feature with a direction which is not parallel to the natural up direction of the graph. + /// Typically you should only set it to such values if you have a spherical or non-planar world (see spherical) (view in online documentation for working links). + /// + /// Note: If you want to use the graph's natural up direction, use the overload which does not take any parameters. + /// + /// Defines what 'from above' means. Usually this should be the same as the natural up direction of the graph. Does not need to be normalized. + public static DistanceMetric ClosestAsSeenFromAboveSoft (Vector3 up) => new DistanceMetric { projectionAxis = up, distanceScaleAlongProjectionDirection = 0.2f }; + + /// + /// Returns a DistanceMetric which will find the closest node as seen from above. + /// + /// The "upwards" direction will be set to the graph's natural up direction. + /// + /// If there are multiple nodes overlapping the point when seen from above (e.g. multiple floors in a building), + /// then the height coordinate will be used as a tie-breaker. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + /// Note: This is only supported by some graph types. The navmesh/recast/grid graph fully support this. + /// Point graphs do not support this field at all, and will completely ignore it. + /// + /// See: + /// + public static DistanceMetric ClosestAsSeenFromAbove () => new DistanceMetric { projectionAxis = Vector3.positiveInfinity, distanceScaleAlongProjectionDirection = 0.0f }; + + /// + /// Returns a DistanceMetric which will find the closest node as seen from above. + /// + /// [Open online documentation to see images] + /// + /// See: + /// + /// Note: This is only supported by some graph types. The navmesh/recast graphs fully support this. + /// The grid graphs, however, only support this if the direction is parallel to the natural 'up' direction of the graph. + /// The grid graphs will search as if the direction is in the graph's up direction if this value is anything other than Vector3.zero. + /// Point graphs do not support this field at all, and will completely ignore it. + /// + /// Note: On recast/navmesh graphs there is a performance penalty when using this feature with a direction which is not parallel to the natural up direction of the graph. + /// Typically you should only set it to such values if you have a spherical or non-planar world (see spherical) (view in online documentation for working links). + /// + /// Note: If you want to use the graph's natural up direction, use the overload which does not take any parameters. + /// + /// Defines what 'from above' means. Usually this should be the same as the natural up direction of the graph. Does not need to be normalized. + public static DistanceMetric ClosestAsSeenFromAbove (Vector3 up) => new DistanceMetric { projectionAxis = up, distanceScaleAlongProjectionDirection = 0.0f }; + } + + /// + /// Result of a nearest node query. + /// + /// See: + /// + public readonly struct NNInfo { + /// + /// Closest node. + /// May be null if there was no node which satisfied all constraints of the search. + /// + public readonly GraphNode node; + + /// + /// Closest point on the navmesh. + /// This is the query position clamped to the closest point on the . + /// + /// If node is null, then this value is (+inf, +inf, +inf). + /// + public readonly Vector3 position; + + /// + /// Cost for picking this node as the closest node. + /// This is typically the squared distance from the query point to . + /// + /// However, it may be different if the used a different cost function. + /// For example, if is , + /// then this value will be the squared distance in the XZ plane. + /// + /// This value is not guaranteed to be smaller or equal to the squared euclidean distance from the query point to . + /// In particular for a navmesh/recast graph with a NearestNodeConstraint it may + /// be slightly greater for some configurations. This is fine because we are only using this value for the rough + /// distance limit performanced by , and it's not a problem if it is slightly inaccurate. + /// + /// See: + /// + /// If is null, then this value is positive infinity. + /// + public readonly float distanceCostSqr; + + /// + /// Closest point on the navmesh. + /// Deprecated: This field has been renamed to + /// + [System.Obsolete("This field has been renamed to 'position'", true)] + public Vector3 clampedPosition { + get { + return position; + } + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public NNInfo (GraphNode node, Vector3 position, float distanceCostSqr) { + this.node = node; + if (node == null) { + // Guarantee that a null node always gives specific values for position and distanceCostSqr + this.position = Vector3.positiveInfinity; + this.distanceCostSqr = float.PositiveInfinity; + } else { + this.position = position; + this.distanceCostSqr = distanceCostSqr; + } + } + + public static readonly NNInfo Empty = new NNInfo(null, Vector3.positiveInfinity, float.PositiveInfinity); + + public static explicit operator Vector3(NNInfo ob) { + return ob.position; + } + + public static explicit operator GraphNode(NNInfo ob) { + return ob.node; + } + } + + /// Info about where in the scanning process a graph is + public enum ScanningStage { + PreProcessingGraphs, + PreProcessingGraph, + ScanningGraph, + PostProcessingGraph, + FinishingScans, + } + + /// + /// Progress info for e.g a progressbar. + /// Used by the scan functions in the project + /// See: + /// + public readonly struct Progress { + /// Current progress as a value between 0 and 1 + public readonly float progress; + internal readonly ScanningStage stage; + internal readonly int graphIndex; + internal readonly int graphCount; + + public Progress (float progress, ScanningStage stage, int graphIndex = 0, int graphCount = 0) { + this.progress = progress; + this.stage = stage; + this.graphIndex = graphIndex; + this.graphCount = graphCount; + } + + public Progress MapTo (float min, float max) { + return new Progress(Mathf.Lerp(min, max, progress), stage, graphIndex, graphCount); + } + + public override string ToString () { + var s = progress.ToString("0%") + " "; + switch (stage) { + case ScanningStage.PreProcessingGraphs: + s += "Pre-processing graphs"; + break; + case ScanningStage.PreProcessingGraph: + s += "Pre-processing graph " + (graphIndex+1) + " of " + graphCount; + break; + case ScanningStage.ScanningGraph: + s += "Scanning graph " + (graphIndex+1) + " of " + graphCount; + break; + case ScanningStage.PostProcessingGraph: + s += "Post-processing graph " + (graphIndex+1) + " of " + graphCount; + break; + case ScanningStage.FinishingScans: + s += "Finalizing graph scans"; + break; + } + return s; + } + } + + /// Graphs which can be updated during runtime + public interface IUpdatableGraph { + /// + /// Schedules a number of graph updates. + /// + /// This method should return a promise. It should not execute the graph updates immediately. + /// The Apply method on the promise will be called when the graph updates should be applied. + /// In the meantime, the graph may perform calculations using e.g. the unity job system. + /// + /// Notes to implementators. + /// This function should (in order): + /// -# Call o.WillUpdateNode on the GUO for every node it will update, it is important that this is called BEFORE any changes are made to the nodes. + /// -# Update walkabilty using special settings such as the usePhysics flag used with the GridGraph. + /// -# Call Apply on the GUO for every node which should be updated with the GUO. + /// -# Update connectivity info if appropriate (GridGraphs updates connectivity, but most other graphs don't since then the connectivity cannot be recovered later). + /// + /// It is guaranteed that the Apply function will be called on the returned promise before this function is called again. + /// + /// Null may be returned if no updates need to be done to the graph. + /// + IGraphUpdatePromise ScheduleGraphUpdates(List graphUpdates); + } + + /// Info about if a graph update has been applied or not + public enum GraphUpdateStage { + /// + /// The graph update object has been created, but not used for anything yet. + /// This is the default value. + /// + Created, + /// The graph update has been sent to the pathfinding system and is scheduled to be applied to the graphs + Pending, + /// The graph update has been applied to all graphs + Applied, + /// + /// The graph update has been aborted and will not be applied. + /// This can happen if the AstarPath component is destroyed while a graph update is queued to be applied. + /// + Aborted, + } + + /// + /// Represents a collection of settings used to update nodes in a specific region of a graph. + /// See: AstarPath.UpdateGraphs + /// See: graph-updates (view in online documentation for working links) + /// + public class GraphUpdateObject { + /// + /// The bounds to update nodes within. + /// Defined in world space. + /// + public Bounds bounds; + + /// + /// Use physics checks to update nodes. + /// When updating a grid graph and this is true, the nodes' position and walkability will be updated using physics checks + /// with settings from "Collision Testing" and "Height Testing". + /// + /// When updating a PointGraph, setting this to true will make it re-evaluate all connections in the graph which passes through the . + /// + /// This has no effect when updating GridGraphs if is turned on. + /// You should not combine and . + /// + /// On RecastGraphs, having this enabled will trigger a complete recalculation of all tiles intersecting the bounds. + /// This is quite slow (but powerful). If you only want to update e.g penalty on existing nodes, leave it disabled. + /// + public bool updatePhysics = true; + + /// + /// Reset penalties to their initial values when updating grid graphs and is true. + /// If you want to keep old penalties even when you update the graph you may want to disable this option. + /// + /// The images below shows two overlapping graph update objects, the right one happened to be applied before the left one. They both have updatePhysics = true and are + /// set to increase the penalty of the nodes by some amount. + /// + /// The first image shows the result when resetPenaltyOnPhysics is false. Both penalties are added correctly. + /// [Open online documentation to see images] + /// + /// This second image shows when resetPenaltyOnPhysics is set to true. The first GUO is applied correctly, but then the second one (the left one) is applied + /// and during its updating, it resets the penalties first and then adds penalty to the nodes. The result is that the penalties from both GUOs are not added together. + /// The green patch in at the border is there because physics recalculation (recalculation of the position of the node, checking for obstacles etc.) affects a slightly larger + /// area than the original GUO bounds because of the Grid Graph -> Collision Testing -> Diameter setting (it is enlarged by that value). So some extra nodes have their penalties reset. + /// + /// [Open online documentation to see images] + /// + /// Bug: Not working with burst + /// + public bool resetPenaltyOnPhysics = true; + + /// + /// Update Erosion for GridGraphs. + /// When enabled, erosion will be recalculated for grid graphs + /// after the GUO has been applied. + /// + /// In the below image you can see the different effects you can get with the different values. + /// The first image shows the graph when no GUO has been applied. The blue box is not identified as an obstacle by the graph, the reason + /// there are unwalkable nodes around it is because there is a height difference (nodes are placed on top of the box) so erosion will be applied (an erosion value of 2 is used in this graph). + /// The orange box is identified as an obstacle, so the area of unwalkable nodes around it is a bit larger since both erosion and collision has made + /// nodes unwalkable. + /// The GUO used simply sets walkability to true, i.e making all nodes walkable. + /// + /// [Open online documentation to see images] + /// + /// When updateErosion=True, the reason the blue box still has unwalkable nodes around it is because there is still a height difference + /// so erosion will still be applied. The orange box on the other hand has no height difference and all nodes are set to walkable. + /// + /// When updateErosion=False, all nodes walkability are simply set to be walkable in this example. + /// + /// See: Pathfinding.GridGraph + /// + /// Bug: Not working with burst + /// + public bool updateErosion = true; + + /// + /// NNConstraint to use. + /// The Pathfinding.NNConstraint.SuitableGraph function will be called on the NNConstraint to enable filtering of which graphs to update. + /// Note: As the Pathfinding.NNConstraint.SuitableGraph function is A* Pathfinding Project Pro only, this variable doesn't really affect anything in the free version. + /// + /// Deprecated: Use the field instead + /// + [System.Obsolete("Use the graphMask field instead")] + public NNConstraintGraphUpdateObjectProxy nnConstraint => new NNConstraintGraphUpdateObjectProxy(this); + + /// + /// Which graphs to update. + /// This ia bitmask containing all graphs which should be updated. + /// + /// See: bitmasks (view in online documentation for working links) + /// See: + /// + public GraphMask graphMask = GraphMask.everything; + + /// + /// Penalty to add to the nodes. + /// A penalty of 1000 is equivalent to the cost of moving 1 world unit. + /// + public int addPenalty; + + /// + /// If true, all nodes' walkable variable will be set to . + /// It is not recommended to combine this with since then you will just overwrite + /// what calculated. + /// + public bool modifyWalkability; + + /// If is true, the nodes' walkable variable will be set to this value + public bool setWalkability; + + /// If true, all nodes' tag will be set to + public bool modifyTag; + + /// If is true, all nodes' tag will be set to this value + public PathfindingTag setTag; + + /// + /// Track which nodes are changed and save backup data. + /// Used internally to revert changes if needed. + /// + /// Deprecated: This field does not do anything anymore. Use instead. + /// + [System.Obsolete("This field does not do anything anymore. Use AstarPath.Snapshot instead.")] + public bool trackChangedNodes; + + /// + /// A shape can be specified if a bounds object does not give enough precision. + /// Note that if you set this, you should set the bounds so that it encloses the shape + /// because the bounds will be used as an initial fast check for which nodes that should + /// be updated. + /// + public GraphUpdateShape shape; + + /// + /// Info about if a graph update has been applied or not. + /// Either an enum (see STAGE_CREATED and associated constants) + /// or a non-negative count of the number of graphs that are waiting to apply this graph update. + /// + internal int internalStage = STAGE_CREATED; + + internal const int STAGE_CREATED = -1; + internal const int STAGE_PENDING = -2; + internal const int STAGE_ABORTED = -3; + internal const int STAGE_APPLIED = 0; + + /// Info about if a graph update has been applied or not + public GraphUpdateStage stage { + get { + switch (internalStage) { + case STAGE_CREATED: + return GraphUpdateStage.Created; + case STAGE_APPLIED: + return GraphUpdateStage.Applied; + case STAGE_ABORTED: + return GraphUpdateStage.Aborted; + // Positive numbers means it is currently being applied, so it is also pending. + default: + case STAGE_PENDING: + return GraphUpdateStage.Pending; + } + } + } + + /// Should be called on every node which is updated with this GUO before it is updated. + /// The node to save fields for. If null, nothing will be done + public virtual void WillUpdateNode (GraphNode node) { + } + + /// + /// Reverts penalties and flags (which includes walkability) on every node which was updated using this GUO. + /// Data for reversion is only saved if is true. + /// + /// See: blocking (view in online documentation for working links) + /// See: + /// + /// Deprecated: Use instead + /// + [System.Obsolete("Use AstarPath.Snapshot instead", true)] + public virtual void RevertFromBackup () {} + + /// + /// Updates the specified node using this GUO's settings. + /// + /// Note: Some graphs may call instead, for better performance. + /// + public virtual void Apply (GraphNode node) { + if (shape == null || shape.Contains(node)) { + // Update penalty and walkability + node.Penalty = (uint)(node.Penalty+addPenalty); + if (modifyWalkability) { + node.Walkable = setWalkability; + } + + // Update tags + if (modifyTag) node.Tag = (uint)setTag; + } + } + + /// Provides burst-readable data to a graph update job + public struct GraphUpdateData { + public NativeArray nodePositions; + public NativeArray nodePenalties; + public NativeArray nodeWalkable; + public NativeArray nodeTags; + public NativeArray nodeNormals; + /// + /// Node indices to update. + /// Remaining nodes should be left alone. + /// Additionally, if math.any(nodeNormals[i]) is false, then the node should not be updated, since it is not a valid node. + /// + public NativeArray nodeIndices; + }; + + /// Job for applying a graph update object + [BurstCompile] + public struct JobGraphUpdate : IJob { + public GraphUpdateShape.BurstShape shape; + public GraphUpdateData data; + + public Bounds bounds; + public int penaltyDelta; + public bool modifyWalkability; + public bool walkabilityValue; + public bool modifyTag; + public int tagValue; + + public void Execute () { + for (int i = 0; i < data.nodeIndices.Length; i++) { + var node = data.nodeIndices[i]; + if (!math.any(data.nodeNormals[node])) continue; + + if (bounds.Contains(data.nodePositions[node]) && shape.Contains(data.nodePositions[node])) { + data.nodePenalties[node] += (uint)penaltyDelta; + if (modifyWalkability) data.nodeWalkable[node] = walkabilityValue; + if (modifyTag) data.nodeTags[node] = tagValue; + } + } + } + }; + + /// + /// Update a set of nodes using this GUO's settings. + /// This is far more efficient since it can utilize the Burst compiler. + /// + /// This method may be called by graph generators instead of the method to update the graph more efficiently. + /// + public virtual void ApplyJob (GraphUpdateData data, JobDependencyTracker dependencyTracker) { + if (addPenalty == 0 && !modifyWalkability && !modifyTag) return; + + new JobGraphUpdate { + shape = shape != null ? new GraphUpdateShape.BurstShape(shape, Allocator.Persistent) : GraphUpdateShape.BurstShape.Everything, + data = data, + bounds = bounds, + penaltyDelta = addPenalty, + modifyWalkability = modifyWalkability, + walkabilityValue = setWalkability, + modifyTag = modifyTag, + tagValue = (int)setTag.value, + }.Schedule(dependencyTracker); + } + + public GraphUpdateObject () { + } + + /// Creates a new GUO with the specified bounds + public GraphUpdateObject (Bounds b) { + bounds = b; + } + } + + /// Graph which has a well defined transformation from graph space to world space + public interface ITransformedGraph { + GraphTransform transform { get; } + } + + /// Graph which supports the Linecast method + public interface IRaycastableGraph { + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + bool Linecast(Vector3 start, Vector3 end); + /// Deprecated: + [System.Obsolete] + bool Linecast(Vector3 start, Vector3 end, GraphNode startNodeHint); + /// Deprecated: + [System.Obsolete] + bool Linecast(Vector3 start, Vector3 end, GraphNode startNodeHint, out GraphHitInfo hit); + + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + /// If you know which node contains the start point, you may pass it here to save a GetNearest call. Otherwise, pass null. If the start point is not actually inside the give node, you may get different behavior on different graph types. Some will clamp the start point to the surface of the hint node, and some will ignore the hint parameter completely. + /// Additional information about what was hit. + /// If you supply a list, it will be filled with all nodes that the linecast traversed. You may pass null if you don't care about this. + /// Can be used to prevent the linecast from traversing some nodes. Use \reflink{TraversalConstraint.None} to not apply any constraints. Note that already unwalkable nodes cannot be made walkable in this way. + bool Linecast(Vector3 start, Vector3 end, GraphNode startNodeHint, out GraphHitInfo hit, ref TraversalConstraint traversalConstraint, List trace); + + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + /// If you know which node contains the start point, you may pass it here to save a GetNearest call. Otherwise, pass null. If the start point is not actually inside the give node, you may get different behavior on different graph types. Some will clamp the start point to the surface of the hint node, and some will ignore the hint parameter completely. + /// Additional information about what was hit. + /// If you supply a list, it will be filled with all nodes that the linecast traversed. You may pass null if you don't care about this. + bool Linecast (Vector3 start, Vector3 end, GraphNode startNodeHint, out GraphHitInfo hit, List trace) { + var constraint = TraversalConstraint.None; + return Linecast(start, end, startNodeHint, out hit, ref constraint, trace); + } + + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + /// Additional information about what was hit. + bool Linecast (Vector3 start, Vector3 end, out GraphHitInfo hit) { + var constraint = TraversalConstraint.None; + return Linecast(start, end, out hit, ref constraint, null); + } + + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + /// Additional information about what was hit. + /// If you supply a list, it will be filled with all nodes that the linecast traversed. You may pass null if you don't care about this. + /// Can be used to prevent the linecast from traversing some nodes. Use \reflink{TraversalConstraint.None} to not apply any constraints. Note that already unwalkable nodes cannot be made walkable in this way. + bool Linecast(Vector3 start, Vector3 end, out GraphHitInfo hit, ref TraversalConstraint traversalConstraint, List trace = null); + + /// + /// Checks if the straight line of sight between the two points on the graph is obstructed. + /// + /// Returns: True if an obstacle was hit, and false otherwise. + /// + /// The start point of the raycast. + /// The end point of the raycast. + /// Additional information about what was hit. + /// If you supply a list, it will be filled with all nodes that the linecast traversed. You may pass null if you don't care about this. + bool Linecast (Vector3 start, Vector3 end, out GraphHitInfo hit, List trace) { + var constraint = TraversalConstraint.None; + return Linecast(start, end, out hit, ref constraint, trace); + } + + [System.Obsolete("Use the overload that takes a TraversalConstraint instead of a filter function")] + bool Linecast (Vector3 start, Vector3 end, out GraphHitInfo hit, List trace, System.Func filter) { + var constraint = TraversalConstraint.None; + constraint.filter = filter; + return Linecast(start, end, out hit, ref constraint, trace); + } + + [System.Obsolete("Use the overload that takes a TraversalConstraint instead of a filter function")] + bool Linecast (Vector3 start, Vector3 end, GraphNode startNodeHint, out GraphHitInfo hit, List trace, System.Func filter) { + var constraint = TraversalConstraint.None; + constraint.filter = filter; + return Linecast(start, end, startNodeHint, out hit, ref constraint, trace); + } + } + + /// + /// Integer Rectangle. + /// Uses an inclusive coordinate range. + /// + /// Works almost like UnityEngine.Rect but with integer coordinates + /// + [System.Serializable] + public struct IntRect { + public int xmin, ymin, xmax, ymax; + + public IntRect (int xmin, int ymin, int xmax, int ymax) { + this.xmin = xmin; + this.xmax = xmax; + this.ymin = ymin; + this.ymax = ymax; + } + + public bool Contains (int x, int y) { + return !(x < xmin || y < ymin || x > xmax || y > ymax); + } + + public bool Contains (IntRect other) { + return xmin <= other.xmin && xmax >= other.xmax && ymin <= other.ymin && ymax >= other.ymax; + } + + public Vector2Int Min { + get { + return new Vector2Int(xmin, ymin); + } + } + + public Vector2Int Max { + get { + return new Vector2Int(xmax, ymax); + } + } + + public int Width { + get { + return xmax-xmin+1; + } + } + + public int Height { + get { + return ymax-ymin+1; + } + } + + public int Area { + get { + return Width * Height; + } + } + + /// + /// Returns if this rectangle is valid. + /// An invalid rect could have e.g xmin > xmax. + /// Rectangles are valid iff they contain at least one point. + /// + // TODO: Make into property + public bool IsValid () { + return xmin <= xmax && ymin <= ymax; + } + + public static bool operator == (IntRect a, IntRect b) { + return a.xmin == b.xmin && a.xmax == b.xmax && a.ymin == b.ymin && a.ymax == b.ymax; + } + + public static bool operator != (IntRect a, IntRect b) { + return a.xmin != b.xmin || a.xmax != b.xmax || a.ymin != b.ymin || a.ymax != b.ymax; + } + + public static explicit operator Rect(IntRect r) => new Rect(r.xmin, r.ymin, r.Width, r.Height); + + public override bool Equals (System.Object obj) { + if (!(obj is IntRect)) return false; + var rect = (IntRect)obj; + + return xmin == rect.xmin && xmax == rect.xmax && ymin == rect.ymin && ymax == rect.ymax; + } + + public override int GetHashCode () { + return xmin*131071 ^ xmax*3571 ^ ymin*3109 ^ ymax*7; + } + + /// + /// Returns the intersection rect between the two rects. + /// The intersection rect is the area which is inside both rects. + /// If the rects do not have an intersection, an invalid rect is returned. + /// See: IsValid + /// + public static IntRect Intersection (IntRect a, IntRect b) { + return new IntRect( + System.Math.Max(a.xmin, b.xmin), + System.Math.Max(a.ymin, b.ymin), + System.Math.Min(a.xmax, b.xmax), + System.Math.Min(a.ymax, b.ymax) + ); + } + + /// Returns if the two rectangles intersect each other + public static bool Intersects (IntRect a, IntRect b) { + return !(a.xmin > b.xmax || a.ymin > b.ymax || a.xmax < b.xmin || a.ymax < b.ymin); + } + + /// + /// Returns a new rect which contains both input rects. + /// This rectangle may contain areas outside both input rects as well in some cases. + /// + public static IntRect Union (IntRect a, IntRect b) { + return new IntRect( + System.Math.Min(a.xmin, b.xmin), + System.Math.Min(a.ymin, b.ymin), + System.Math.Max(a.xmax, b.xmax), + System.Math.Max(a.ymax, b.ymax) + ); + } + + /// + /// Returns a new rect that contains all of a except for the parts covered by b. + /// + /// Throws: An exception if the difference is not a rectangle (e.g. if they only overlap in a corner). + /// + /// + /// ┌───────────┐ + /// │ B │ + /// │ ┌─────┐ │ + /// │ │ │ │ ─► + /// └──┼─────┼──┘ ┌─────┐ + /// │ A │ │ A │ + /// └─────┘ └─────┘ + /// + /// + public static IntRect Exclude (IntRect a, IntRect b) { + if (!b.IsValid() || !a.IsValid()) return a; + var intersection = Intersection(a, b); + if (!intersection.IsValid()) return a; + if (a.xmin == intersection.xmin && a.xmax == intersection.xmax) { + if (a.ymin == intersection.ymin) { + a.ymin = intersection.ymax + 1; + return a; + } else if (a.ymax == intersection.ymax) { + a.ymax = intersection.ymin - 1; + return a; + } else { + throw new ArgumentException("B splits A into two disjoint parts"); + } + } else if (a.ymin == intersection.ymin && a.ymax == intersection.ymax) { + if (a.xmin == intersection.xmin) { + a.xmin = intersection.xmax + 1; + return a; + } else if (a.xmax == intersection.xmax) { + a.xmax = intersection.xmin - 1; + return a; + } else { + throw new ArgumentException("B splits A into two disjoint parts"); + } + } else { + throw new ArgumentException("B covers either a corner of A, or does not touch the edges of A at all"); + } + } + + /// Returns a new IntRect which is expanded to contain the point + public IntRect ExpandToContain (int x, int y) { + return new IntRect( + System.Math.Min(xmin, x), + System.Math.Min(ymin, y), + System.Math.Max(xmax, x), + System.Math.Max(ymax, y) + ); + } + + /// Returns a new IntRect which has been moved by an offset + public IntRect Offset (Vector2Int offset) { + return new IntRect(xmin + offset.x, ymin + offset.y, xmax + offset.x, ymax + offset.y); + } + + /// Returns a new rect which is expanded by range in all directions. + /// How far to expand. Negative values are permitted. + public IntRect Expand (int range) { + return new IntRect(xmin-range, + ymin-range, + xmax+range, + ymax+range + ); + } + + public override string ToString () { + return "[x: "+xmin+"..."+xmax+", y: " + ymin +"..."+ymax+"]"; + } + + /// Returns a list of all integer coordinates inside the rectangle, in row-major order + public List GetInnerCoordinates () { + var list = ListPool.Claim(Width*Height); + for (int y = ymin; y <= ymax; y++) { + for (int x = xmin; x <= xmax; x++) { + list.Add(new Vector2Int(x, y)); + } + } + return list; + } + } + + /// + /// A combination of graphs. + /// Represents a set of graphs, which is usually used to filter which graphs to use for pathfinding, or which graphs to apply an update to. + /// + /// This mask can hold any subset of small graph indices (up to graph index 30), but only a combination of at most 3 graphs of large graph indices (larger than 30). + /// Almost all games will use only one or a few graphs, so you can think of this as being able to represent any combination of graphs. + /// + /// + /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph"); + /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph"); + /// + /// NearestNodeConstraint nn = NearestNodeConstraint.Walkable; + /// + /// nn.graphMask = mask1 | mask2; + /// + /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph' + /// var info = AstarPath.active.GetNearest(somePoint, nn); + /// + /// + /// See: bitmasks (view in online documentation for working links) + /// + [System.Serializable] +#if UNITY_2023_1_OR_NEWER + [Unity.Properties.GeneratePropertyBag] +#endif + public struct GraphMask { + /// + /// Internal representation of the mask. + /// + /// Depending on LargeGraphIndicesBit (bit 31) the mask is in one of two modes: + /// - If LargeGraphIndicesBit is set, then the lower 3x8 bits are used to store indices of 3 graphs (0-254, 255 represents no graph). The InvertedBit (bit 30) is used to indicate if the mask is inverted. + /// - If LargeGraphIndicesBit is not set, then the lower 30 bits are a normal bitmask for the first 30 graphs, and bit 30 (RemainingGraphIndicesBit) represents all graph indices larger or equal to 30. + /// + /// Combining two masks is not trivial and can actually throw an exception if you try to combine too many large graph indices. + /// Fortunately, hitting this limit is very rare, and will never happen in normal use. + /// + /// There are technically two representations that contain all graphs. All bits set, and all bits except the LargeGraphIndicesBit. + /// However, we prevent the latter from being created, to avoid confusion. + /// + /// Note: Has to stay an int, and not an uint, because Unity will refuse to deserialize old values like -1 otherwise, and we really want to keep backwards compatibility. + /// + [SerializeField] + private int value; + + const uint LargeGraphIndicesBit = 1U << 31; + const uint InvertedBit = 1U << 30; + const uint SmallestLargeGraphIndex = 30; + const uint RemainingGraphIndicesBit = 1U << 30; + const uint Everything = ~0U; + + /// A mask containing every graph + public static GraphMask everything => new GraphMask(Everything); + + /// True if the mask contains all graphs, i.e. if it is equal to GraphMask.everything + public bool containsAllGraphs => (uint)value == Everything; + public bool isPureBitmask => (uint)value == Everything || (value & LargeGraphIndicesBit) == 0; + + + public GraphMask (uint value) { + this.value = (int)value; + } + + /// Combines two masks to form the union of them + public static GraphMask operator | (GraphMask lhs, GraphMask rhs) { + if ((uint)lhs.value == Everything || (uint)rhs.value == Everything) return everything; + if (lhs.value == rhs.value) return lhs; + + var union = (uint)lhs.value | (uint)rhs.value; + if ((union & LargeGraphIndicesBit) == 0) { + // There are technically two representations for "everything". We collapse the one which is all bits except the LargeGraphIndicesBit to the one which is all bits. + if (union == (~0U & ~LargeGraphIndicesBit)) return everything; + return new GraphMask(union); + } + + unsafe { + var indices = stackalloc uint[3]; + indices[0] = GraphNode.InvalidGraphIndex; + indices[1] = GraphNode.InvalidGraphIndex; + indices[2] = GraphNode.InvalidGraphIndex; + var cnt = 0; + InsertMask(indices, ref cnt, (uint)lhs.value); + InsertMask(indices, ref cnt, (uint)rhs.value); + + UnityEngine.Assertions.Assert.IsTrue(cnt <= 3); + UnityEngine.Assertions.Assert.IsTrue(cnt > 0); + UnityEngine.Assertions.Assert.IsTrue(indices[0] < indices[1] || indices[0] == GraphNode.InvalidGraphIndex); + UnityEngine.Assertions.Assert.IsTrue(indices[1] < indices[2] || indices[1] == GraphNode.InvalidGraphIndex); + UnityEngine.Assertions.Assert.IsTrue(indices[2] <= 0xFF); + UnityEngine.Assertions.Assert.IsTrue(GraphNode.MaxGraphIndex < 0xFF); + + return new GraphMask(LargeGraphIndicesBit | indices[0] | (indices[1] << 8) | (indices[2] << 16)); + } + } + + static unsafe void InsertMask (uint* indices, ref int cnt, uint mask) { + if ((mask & LargeGraphIndicesBit) == 0) { + if ((mask & RemainingGraphIndicesBit) != 0) { + throw new ArgumentException("Cannot combine more than 3 large graph indices into a single mask. The GraphMask can only represent arbitrary combinations of graph indices smaller than 30. Larger graph indices have some limitations."); + } + + for (int i = 0; i < SmallestLargeGraphIndex; i++) { + if ((mask & (1U << i)) != 0) { + InsertSorted(indices, ref cnt, (uint)i); + } + } + } else { + if ((mask & InvertedBit) != 0) { + throw new ArgumentException("Cannot combine an inverted mask with large graph indices. The GraphMask can only represent arbitrary combinations of graph indices smaller than 30. Larger graph indices have some limitations."); + } + + var g1 = mask & 0xFF; + var g2 = (mask >> 8) & 0xFF; + var g3 = (mask >> 16) & 0xFF; + InsertSorted(indices, ref cnt, g1); + InsertSorted(indices, ref cnt, g2); + InsertSorted(indices, ref cnt, g3); + } + } + + /// Insert one item, as part of an insertion-sort + static unsafe void InsertSorted (uint* indices, ref int cnt, uint value) { + if (value == GraphNode.InvalidGraphIndex) return; + for (int i = 0; i < cnt; i++) { + if (indices[i] == value) return; + } + if (cnt >= 3) throw new ArgumentException("Cannot combine more than 3 large graph indices into a single mask. The GraphMask can only represent arbitrary combinations of graph indices smaller than 30. Larger graph indices have some limitations."); + cnt++; + int insertionIndex = cnt; + while (insertionIndex > 0 && value < indices[insertionIndex-1]) { + insertionIndex--; + } + + new UnsafeSpan(indices, cnt).Move(insertionIndex, insertionIndex+1, cnt-insertionIndex-1); + indices[insertionIndex] = value; + } + + /// Inverts the mask + public static GraphMask operator ~ (GraphMask lhs) { + if ((uint)lhs.value == Everything) return new GraphMask(); + if ((lhs.value & LargeGraphIndicesBit) != 0) return new GraphMask((uint)lhs.value ^ InvertedBit); + else return new GraphMask(~(uint)lhs.value & ~LargeGraphIndicesBit); + } + + public static bool operator == (GraphMask lhs, GraphMask rhs) { + return lhs.value == rhs.value; + } + + public static bool operator != (GraphMask lhs, GraphMask rhs) { + return lhs.value != rhs.value; + } + + public readonly override bool Equals (object obj) { + return obj is GraphMask mask && value == mask.value; + } + + public readonly override int GetHashCode () { + return value; + } + + /// True if this mask contains the given graph + public readonly bool Contains (NavGraph graph) { + return Contains(graph.graphIndex); + } + + /// True if this mask contains the graph with the given graph index + public readonly bool Contains (uint graphIndex) { + var value = (uint)this.value; + // Optimization for the exceedingly common case + if (value == Everything) return true; + + if ((value & LargeGraphIndicesBit) != 0) { + // Special case. Mask contains indices greater or equal to 30 + var g1 = value & 0xFF; + var g2 = (value >> 8) & 0xFF; + var g3 = (value >> 16) & 0xFF; + var res = g1 == graphIndex || g2 == graphIndex || g3 == graphIndex; + return res == ((value & InvertedBit) == 0); + } else { + // Common case. Mask contains a subset of indices smaller than 30 + // And if bit 30 (RemainingGraphIndicesBit) is set, it contains all indices greater or equal to 30 as well + return ((value >> (int)Mathf.Min(graphIndex, SmallestLargeGraphIndex)) & 1) != 0U; + } + } + + /// + /// A bitmask containing the given graph. + /// + /// See: + /// + public static GraphMask FromGraph (NavGraph graph) { + return FromGraphIndex(graph.graphIndex); + } + + public override string ToString () { + return System.Convert.ToString(value, 2); + } + + /// A bitmask containing the given graph index. + public static GraphMask FromGraphIndex (uint graphIndex) { + if (graphIndex < SmallestLargeGraphIndex) { + return new GraphMask(1U << (int)graphIndex); + } else { + UnityEngine.Assertions.Assert.IsTrue(graphIndex <= GraphNode.MaxGraphIndex); + return new GraphMask(LargeGraphIndicesBit | graphIndex | (GraphNode.InvalidGraphIndex << 8) | (GraphNode.InvalidGraphIndex << 16)); + } + } + + /// + /// A bitmask containing the first graph with the given name. + /// + /// GraphMask mask1 = GraphMask.FromGraphName("My Grid Graph"); + /// GraphMask mask2 = GraphMask.FromGraphName("My Other Grid Graph"); + /// + /// NearestNodeConstraint nn = NearestNodeConstraint.Walkable; + /// + /// nn.graphMask = mask1 | mask2; + /// + /// // Find the node closest to somePoint which is either in 'My Grid Graph' OR in 'My Other Grid Graph' + /// var info = AstarPath.active.GetNearest(somePoint, nn); + /// + /// + public static GraphMask FromGraphName (string graphName) { + var graph = AstarPath.active.data.FindGraph(g => g.name == graphName); + + if (graph == null) throw new ArgumentException("Could not find any graph with the name '" + graphName + "'"); + return FromGraph(graph); + } + } + + #region Delegates + + /// + /// Delegate with on Path object as parameter. + /// This is used for callbacks for when a path has been calculated. + /// + public delegate void OnPathDelegate(Path p); + + public delegate void OnGraphDelegate(NavGraph graph); + + public delegate void OnScanDelegate(AstarPath script); + + #endregion + + #region Enums + + [System.Flags] + public enum GraphUpdateThreading { + /// + /// Call UpdateArea in the unity thread. + /// This is the default value. + /// + UnityThread = 0, + /// Calls UpdateAreaInit in the Unity thread before everything else + UnityInit = 1 << 1, + /// Calls UpdateAreaPost in the Unity thread after everything else + UnityPost = 1 << 2 + } + + /// How path results are logged by the system + public enum PathLog { + /// Does not log anything. This is recommended for release since logging path results has a performance overhead. + None, + /// Logs basic info about the paths + Normal, + /// Includes additional info + Heavy, + /// Same as heavy, but displays the info in-game using GUI + InGame, + /// Same as normal, but logs only paths which returned an error + OnlyErrors + } + + /// + /// How to estimate the cost of moving to the destination during pathfinding. + /// + /// The heuristic is the estimated cost from the current node to the target. + /// The different heuristics have roughly the same performance except not using any heuristic at all () + /// which is usually significantly slower. + /// + /// In the image below you can see a comparison of the different heuristic options for an 8-connected grid and + /// for a 4-connected grid. + /// Note that all paths within the green area will all have the same length. The only difference between the heuristics + /// is which of those paths of the same length that will be chosen. + /// Note that while the Diagonal Manhattan and Manhattan options seem to behave very differently on an 8-connected grid + /// they only do it in this case because of very small rounding errors. Usually they behave almost identically on 8-connected grids. + /// + /// [Open online documentation to see images] + /// + /// Generally for a 4-connected grid graph the Manhattan option should be used as it is the true distance on a 4-connected grid. + /// For an 8-connected grid graph the Diagonal Manhattan option is the mathematically most correct option, however the Euclidean option + /// is often preferred, especially if you are simplifying the path afterwards using modifiers. + /// + /// For any graph that is not grid based the Euclidean option is the best one to use. + /// + /// See: Wikipedia: A* search_algorithm + /// + public enum Heuristic { + /// Manhattan distance. See: https://en.wikipedia.org/wiki/Taxicab_geometry + Manhattan, + /// + /// Manhattan distance, but allowing diagonal movement as well. + /// Note: This option is currently hard coded for the XZ plane. It will be equivalent to Manhattan distance if you try to use it in the XY plane (i.e for a 2D game). + /// + DiagonalManhattan, + /// Ordinary distance. See: https://en.wikipedia.org/wiki/Euclidean_distance + Euclidean, + /// + /// Use no heuristic at all. + /// This reduces the pathfinding algorithm to Dijkstra's algorithm. + /// This is usually significantly slower compared to using a heuristic, which is why the A* algorithm is usually preferred over Dijkstra's algorithm. + /// You may have to use this if you have a very non-standard graph. For example a world with a wraparound playfield (think Civilization or Asteroids) and you have custom links + /// with a zero cost from one end of the map to the other end. Usually the A* algorithm wouldn't find the wraparound links because it wouldn't think to look in that direction. + /// See: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + /// + None + } + + /// How to visualize the graphs in the editor + public enum GraphDebugMode { + /// Draw the graphs with a single solid color + SolidColor, + /// + /// Use the G score of the last calculated paths to color the graph. + /// The G score is the cost from the start node to the given node. + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + G, + /// + /// Use the H score (heuristic) of the last calculated paths to color the graph. + /// The H score is the estimated cost from the current node to the target. + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + H, + /// + /// Use the F score of the last calculated paths to color the graph. + /// The F score is the G score + the H score, or in other words the estimated cost total cost of the path. + /// See: https://en.wikipedia.org/wiki/A*_search_algorithm + /// + F, + /// + /// Use the penalty of each node to color the graph. + /// This does not show penalties added by tags. + /// See: graph-updates (view in online documentation for working links) + /// See: + /// + Penalty, + /// + /// Visualize the connected components of the graph. + /// A node with a given color can reach any other node with the same color. + /// + /// See: + /// See: https://en.wikipedia.org/wiki/Connected_component_(graph_theory) + /// + Areas, + /// + /// Use the tag of each node to color the graph. + /// See: tags (view in online documentation for working links) + /// See: + /// + Tags, + /// + /// Visualize the hierarchical graph structure of the graph. + /// This is mostly for internal use. + /// See: + /// + HierarchicalNode, + /// + /// Visualize the obstacles generated from the navmesh border. + /// + /// These obstacles are used for local avoidance, as well as for the in its internal navigation. + /// + /// The graph will be colored the same as for . + /// + NavmeshBorderObstacles, + } + + /// + /// How to visualize graph updates in the editor. + /// This is used to visualize the bounds of graph updates, and how they affect the graph. + /// + /// See: + /// See: + /// + [Flags] + public enum GraphUpdateDebugMode { + Nothing = 0, + /// + /// Visualize the original bounds of graph updates. + /// This is the volume which was specified in the . + /// + /// In the screenshot, it is visualized as a yellow box. + /// [Open online documentation to see images] + /// + /// See: + /// + VisualizeOriginalBounds = 1 << 0, + /// + /// Visualize the affected regions of graph updates. + /// This is typically larger than the original bounds. + /// For recast graphs, it will be the bounds of the tiles that were updated. + /// For grid graphs, it will be the bounds of a rectangle of nodes a bit larger than the original bounds. + /// + /// In the screenshot, it is visualized as an orange box. + /// [Open online documentation to see images] + /// + /// See: + /// + VisualizeAffectedBounds = 1 << 1, + /// + /// If enabled, the visualizations will stick around for a second or so, to make it easier to see what is happening. + /// If disabled, the visualizations will only be shown for a single frame. + /// + VisualizeOverTime = 1 << 2, + } + + /// Number of threads to use + public enum ThreadCount { + AutomaticLowLoad = -1, + AutomaticHighLoad = -2, + None = 0, + One = 1, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight + } + + static class ThreadCountExtensions { + /// + /// Calculates number of threads to use. + /// If count is not Automatic, simply returns count casted to an int. + /// Returns: An int specifying how many threads to use, 0 means a coroutine should be used for pathfinding instead of a separate thread. + /// + /// If count is set to Automatic it will return a value based on the number of processors and memory for the current system. + /// + /// When running on WebGL this method always returns 0 + /// + public static int ToConcreteThreadCount (this ThreadCount count) { +#if UNITY_WEBGL + return 0; +#else + if (count == ThreadCount.AutomaticLowLoad || count == ThreadCount.AutomaticHighLoad) { +#if ASTARDEBUG + Debug.Log(SystemInfo.systemMemorySize + " " + SystemInfo.processorCount + " " + SystemInfo.processorType); +#endif + + int logicalCores = Mathf.Max(1, SystemInfo.processorCount); + int memory = SystemInfo.systemMemorySize; + + if (memory <= 0) { + Debug.LogError("Machine reporting that is has <= 0 bytes of RAM. This is definitely not true, assuming 1 GiB"); + memory = 1024; + } + + if (logicalCores <= 1) return 0; + + if (memory <= 512) return 0; + + if (count == ThreadCount.AutomaticHighLoad) { + if (memory <= 1024) logicalCores = Math.Min(logicalCores, 2); + + // Scale the number of threads based on the number of logical cores + // But if the computer has a lot of cores, limit to half as many threads as cores. + // This is both due to hyperthreading, and just to reduce memory usage. + logicalCores = Math.Min(logicalCores, Math.Max(8, (logicalCores / 2) + 2)); + } else { + // Many computers use hyperthreading, so dividing by two is used to remove the hyperthreading cores, pathfinding + // doesn't scale well past the number of physical cores anyway + logicalCores /= 2; + logicalCores = Mathf.Max(1, logicalCores); + + if (memory <= 1024) logicalCores = Math.Min(logicalCores, 2); + + logicalCores = Math.Min(logicalCores, 6); + } + + return logicalCores; + } else { + int val = (int)count; + return val; + } +#endif + } + } + + /// Internal state of a path in the pipeline + public enum PathState { + /// Path has been created but not yet scheduled + Created = 0, + /// Path is waiting to be calculated + PathQueue = 1, + /// Path is being calculated + Processing = 2, + /// Path is calculated and is waiting to have its callback called + ReturnQueue = 3, + /// The path callback is being called right now (only set inside the callback itself) + Returning = 4, + /// The path has been calculated and its callback has been called + Returned = 5, + } + + + /// State of a path request + public enum PathCompleteState { + /// + /// The path has not been calculated yet. + /// See: + /// + NotCalculated = 0, + /// + /// The path calculation is done, but it failed. + /// See: + /// + Error = 1, + /// + /// The path has been successfully calculated. + /// + /// Warning: Do not use this to check if the path has been calculated yet, as this is set on a separate thread before the state of the path is finalized. Use , callbacks, or instead. + /// You'll run into race conditions if you use this to check if the path has been calculated. + /// + /// See: calling-pathfinding (view in online documentation for working links) + /// + Complete = 2, + /// + /// The path has been calculated, but only a partial path could be found. + /// See: + /// + Partial = 3, + } + + /// What to do when the character is close to the destination + public enum CloseToDestinationMode { + /// The character will stop as quickly as possible when within endReachedDistance (field that exist on most movement scripts) units from the destination + Stop, + /// The character will continue to the exact position of the destination + ContinueToExactDestination, + } + + /// Indicates the side of a line that a point lies on + public enum Side : byte { + /// The point lies exactly on the line + Colinear = 0, + /// The point lies on the left side of the line + Left = 1, + /// The point lies on the right side of the line + Right = 2 + } + + public enum InspectorGridHexagonNodeSize { + /// Value is the distance between two opposing sides in the hexagon + Width, + /// Value is the distance between two opposing vertices in the hexagon + Diameter, + /// Value is the raw node size of the grid + NodeSize + } + + public enum InspectorGridMode { + Grid, + IsometricGrid, + Hexagonal, + Advanced + } + + /// + /// Determines which direction the agent moves in. + /// For 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. + /// For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games. + /// + public enum OrientationMode : byte { + ZAxisForward, + YAxisForward, + } + + #endregion +} + +namespace Pathfinding.Util { + /// Prevents code stripping. See: https://docs.unity3d.com/Manual/ManagedCodeStripping.html + public class PreserveAttribute : System.Attribute { + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs.meta new file mode 100644 index 0000000..63b1d90 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Core/astarclasses.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f499b0552bded40b6a9d474dfb94343a +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Documentation.meta b/FishROV/Assets/ThirdParty/AstarPro/Documentation.meta new file mode 100644 index 0000000..8f7111e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Documentation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2bd84edb620a9b51094b9d533b8d8020 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html b/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html new file mode 100644 index 0000000..bc1cc84 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html @@ -0,0 +1,1720 @@ + + + + + + + + + Get Started Guide - A* Pathfinding Project + + + + + + + + +
+
+
+
+
+ +

Get Started Guide

+ +

How to get started with the A* Pathfinding Project, create your first graph, and make an agent move around on it.

+

Contents

Video Tutorial

If you prefer a video tutorial instead of a text tutorial, here is a video for you. The video tutorial and this text tutorial are very similar, so you can choose the one you prefer.

You can also take a look at the excellent tutorial/review by Code Monkey: https://www.youtube.com/watch?v=46qZgd-T-hk

Overview

This package focuses on 3 core areas:

  • Graphs: which describe where an agent can move.

    +
  • Pathfinding: finding the best path between two points on a graph, or similar tasks.

    +
  • Movement: how an agent follows a path.

    +
+

Additionally, there are many supporting features. Broadly, they can be grouped into:

  • Temporary obstacles: which cut holes in the navmesh or update it in other ways.

    +
  • Off-mesh links: which allow an agent to move or jump between otherwise disconnected parts of the navmesh.

    +
  • Path modifiers: which are used by some movement scripts to smooth or simplify paths (notably these are not used by the FollowerEntity, which you'll use in this tutorial. It has its own internal smoothing instead).

    +
  • Local avoidance: which is used to make agents handle crowds.

    +
+

Introduction

In this tutorial, we will create a simple scene with a few obstacles, generate a navmesh for it, and then make an agent move around on it.

Installation

The first thing you need to do, if you haven't done so already, is to install the A* Pathfinding Project.

Please read the installation guide.

If you want, you can explore the different example scenes in the project before you start with the next section. The example scenes are installed separately. Take a look at the installation guide for more info.

+

Creating a scene with some obstacles

Create a new empty scene, name it "PathfindingTest".

The agent needs something to walk on, and something for it to avoid.

Add a plane to the scene, place it at the scene origin (0,0,0) and scale it to (10,10,10).

Then create some cubes of different scales and place them on the plane; these will be obstacles which the agent should avoid.

You can optionally add some materials to the objects, to make them look nicer.

Your scene should now look something like in the image

Adding Pathfinding

The AstarPath component is the core of this package. It holds all the graphs in the scene and provides a central place for all pathfinding related settings.

This is a singleton, so you should only have one instance of it in your scene. You can add multiple graphs to it, but most games only need one.

Create a new GameObject and name it A*.

Add the AstarPath component to the new GameObject.

Component → Pathfinding → AstarPath

The inspector of the AstarPath component is divided up into sections.

  • Graphs: contains all graphs in the scene.

    +
  • Settings: contains all global pathfinding and logging settings.

    +
  • Save & Load: allows you to save and load graphs to and from files.

    +
  • Optimization: allows you to enable or disable some features project-wide. You typically don't have to touch these.

    +
  • About: shows the current version and has links to the online documentation.

    +
  • Show Graphs Toggle: enables or disables the graphs from being rendered in the scene view.

    +
  • Scan Button: recalculates all graphs. There's also a shortcut for this: Cmd+Alt+S (mac) or Ctrl+Alt+S (windows).

    +
+

See

The A* Inspector, for a more detailed explanation of every setting in the A* inspector.

+

But what is a graph?

A graph represents the traversable surface of the world. This is every point where your agent can walk. Or, more precisely, it is every location where it is valid for the center of an agent to be.

A graph consists of nodes and connections between them. The shape of the nodes can vary. Some graph types use grid tiles, some use triangles, and some use points.

The word navmesh (or navigation mesh) is often used synonymously with the word graph. But it refers specifically to graphs that consist of triangle meshes (like the RecastGraph and NavMeshGraph).

Adding a graph

For this tutorial, we will be using the RecastGraph. It can automatically generate a navmesh from the scene geometry.

The recast graph is a good all-around graph type, which can handle most situations. It can handle both detailed features, and very large worlds, in both 2D and 3D games. However, it is worse at handling dynamic costs for different parts of the world, than, for example, a grid graph.

To create a recast graph, click on the Graphs section, then click on the Add New Graph button, and finally select the recast graph from the list.

The recast graph is pro-only feature. If you are using the free version, you can use the grid graph instead.

When the graph has been added, click on its label to expand the graph settings.

We can actually get a decent navmesh by only changing a single setting. We need to ensure that the recast graph's bounding box, which is visualized in the scene view as a white box, covers our entire world.

Click the Snap Bounds To Scene button, in the recast graph inspector.

You should see that the graph's bounding box has been resized to cover the entire scene.

Now, we can click the Scan button, at the bottom of the inspector, to calculate the graph.

Or use the keyboard shortcut: Cmd+Alt+S (mac) or Ctrl+Alt+S (windows).

If you have done everything correctly, you should now have a generated navmesh in your scene.

Different graphs represent the walkable surface differently. The recast graph uses a triangular mesh, while, for example, the grid graph uses a grid of nodes.

See

We'll use the recast graph's default settings for this tutorial. But check out Get started with recast graphs for a more in-depth guide on how to use the recast graph.

+

Adding an agent

What is a pathfinding test without some moving stuff? Not fun at all, so let's add an agent to play around with.

Create a new GameObject and name it "Agent"

Place it somewhere on the ground

Attach the FollowerEntity component to the agent's root GameObject

If you do not have the Unity Entities package installed, the inspector will prompt you to install it.

The FollowerEntity requires the entities package, but there are other movement scripts in the package that don't.

Add a Cylinder object as a child of the agent, and move it to (0,1,0). This will make it line up with the FollowerEntity's bounds.

There are multiple movement scripts in the package. They have different movement styles and performance characteristics. The role of these components is to take care of searching for paths and to follow them. You give it a destination, and it will figure out how to get there.

The FollowerEntity movement script is a good choice for most games that need smooth movement. It works on all graphs except the point graph, and is pretty performant too.

See

There are also other included movement scripts called AIPath, RichAI and AILerp.

Take a look at Movement scripts for more info. You can even write your own custom movement script, if you want.

+

To prevent the graph from cutting a hole around the agent, we need to move the agent to a separate layer.

Move the agent to a separate layer. For example "TransparentFX", if you have the default unity layers. Any layer other than the default will do.

Change the layer mask in the recast graph settings to exclude the layer that the agent is on.

Scan the graph again. You should now see that the agent does not leave a hole in the navmesh.

We'll also need to tell the agent where to go. You can do this via a script, or you can use the helper component AIDestinationSetter, which allows moving to a GameObject.

Attach the AIDestinationSetter component to the agent.

We'll also need a target for the agent to move to.

Create a new Sphere object, name it Target.

Place it somewhere in the scene, where you want the agent to move.

Assign the Target GameObject to the "Target" field on the AIDestinationSetter component.

Now, if you press play, the agent should move towards the target.

Note

The agent draws its path in the scene view by default. This is controlled by the Path option in the FollowerEntity's Movement Debug Rendering settings.

When you move the target around, the FollowerEntity will automatically do a local repair of its path every frame. It will also recalculate the path from scratch at regular intervals, or when the destination has moved a lot.

+

Setting the destination via code

We haven't quite achieved what we set out to do in this tutorial just yet. Our goal was to make the agent follow our cursor, but right now it only follows a target object. We can, of course, drag it around in the scene view, but that's not quite the same thing.

To solve this, we'll need a small custom script.

Click "Create" in your project view, and select MonoBehaviour Script.

Name the new script FollowCursor.

Double click the created script, to open it in your favorite text editor.

using UnityEngine;
// Use the Pathfinding namespace to be able to
// use most common classes in the package
using Pathfinding;

public class FollowCursor : MonoBehaviour {
IAstarAI ai;

// This runs when the game starts
void OnEnable () {
// Get a reference to our movement script.
// We use the IAstarAI interface to make the code work with all movement scripts
// You can alternatively use the concrete FollowerEntity class,
// but that would make the code less flexible
ai = GetComponent<IAstarAI>();
}

void Update () {
// Get the mouse position
// Or UnityEngine.InputSystem.Mouse.current.position.ReadValue() if using the new Input System
var mousePosition = Input.mousePosition;

// Create a ray from the camera to the mouse position
var ray = Camera.main.ScreenPointToRay(mousePosition);

// Check if the ray hits something
if (Physics.Raycast(ray, out var hit)) {
// Set the destination for the AI to move towards
ai.destination = hit.point;
}
}
}

Add using Pathfinding; at the top of the script.

This allows your script to access most of the common classes in the package.

Create a field to store the agent reference.

Set this field during OnEnable.

In Update, fire a ray from the mouse cursor into the scene, and see if it hits anything.

If so, set the destination of the agent to the hit point.

Remove the AIDestinationSetter component from the agent, since we don't need it anymore.

Attach your new FollowCursor script to the agent.

Adjust the camera so that it looks down on the scene.

Press play! You should now be able to make the agent follow your cursor around in the game view.

Logging settings

Every time a path is calculated by the system, it can optionally be logged to the console. This can be a big help in understanding what the system is doing and also to spot performance issues. Logging is not free, however, so for release builds it is recommended that you disable it.

You can change the logging settings under the A* Inspector → Settings → Debug tab.

Use less debugging to improve performance (a bit) or just to get rid of the console spam. Use more debugging (heavy) if you want more information about what the pathfinding scripts are doing. The InGame option will display the latest path log using the in-game GUI.

Conclusion

That concludes the Get Started tutorial. We have covered the basics of creating a scene with obstacles, adding a graph, and making an agent move around on it, and how to interact with an agent using code.

I wish you the best of luck in your continued exploration of this package.

Here are some suggestions for what to do next:

+

+ +
+
+
+
+
+
+
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html.meta b/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html.meta new file mode 100644 index 0000000..1c3082b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.html.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1efb2d65a34bdefe69bdee6dc00ee5c2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.pdf b/FishROV/Assets/ThirdParty/AstarPro/Documentation/documentation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6af2de4f767a2e8746942e3182baa072a644d179 GIT binary patch literal 1030018 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

  • 8Rr_}O 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

    _r zs^1{&FUvcDgL>EU-rbRMR_O^gsc=S47~$OK{1(kodA*MBj~BCAZLQbf;49_@r{pxlAUEm@%FqAYU@<%y)dw_X~5&ap)V zUw=awBNrp8IRH)hlgeu)Bm5m<$iCUQCno8Iu~zr5dvT>}QFC8rh<8|>UL`RS5@z9H z)Z1gv$$?aQ8bFn^^k%Vi*QJfZlwG^WC8Pr_WXweO#`dYH-;9egoP6G6{iJ^H@$=8~ zo^#BHXQM`5H_b3esi%dYgn;%b?R#KbJnUbm|dqt_9) z%art9)SH#tT7R`MebzEyCdtKrGe$3NSac5vf|;&_(%C}wCDQZjQ`txp+Tn+>k@BaW zpGwo`hL1h%kB*jXI2;Z{EWkawq5`w(&m3tm?U<&aJln!;X>d&?B8_>VHnCgnNuDBM zurc!3-y0yW+xbf|cV$#XPy7EY*>`Ut*-2Jt&TiPVY^+J9O*I35L<+YNqG^G}hvZTT zh~qf{JfxPeILlr3liCsxlz@cTiF$!cQ<^8V$)^2)7RG2I*56_KvFA`m+J4)f$ zx?a{Phm33T`;D8wm0KbK%eU(8_?Lq_5V-5ZV9fIl#Zfxc6b@PTE29uD_D%M+=`Vjv zAOOl64SY=x=%==QwcGn&CIj3rE5uS_m^Ja87J5|2* z`wWS*zFC?>y`y5vIov`-%lDjCez$^5cD@JyLyTdTMqLv{%T_Q8{}xa2nYZ}+@f_ah`zhbFQ|FqXTFNc z_~a?tea82J$Sen60R($RyUzxAtRG$+uYAi9UFfcRlpzqo`?kb!xhh&<^j`I+FD%0L zvn7vb{apN`aN~xrL@{bsf7yPsu})bSn=G{l=_Nzl-bC3snman*j|{5ijRu6aB_Xtap$zf+bKV%mV+k{Jbj49~P_v*e72t zS|0U@q`dM}?n)uK5s>iXFSU>!0_J$?aJZ2}_+F&A31Sf2x%f!TVm^_jWICvnqU12^vmA-?y-qZ%~u2!o#nhy zq0jg6}uEg0DTGympZps@%dIVMo6ya=)R%Rs}=%;_^Ir$^gjAJu-j_F@G6A$nie7as{-N zC8lHmO^0CadTGb*8Uz3Y?~{OMHnZsr3p7^m{Hw74=iXmGYT&nERNUbgq)YHs!0r!x z7{3FsX6c&&ak>m9xk%xkA^tng1zyd&wA1XbHF{@y%Armw4EBW6ATq4z(BIzZP5-Xc z2MPS0I9(#}x3W%yEWT$t!*%YJ9`Nu}*uJxWe_^ST%^A0SCXfeF^?J>w^zmF9@#dN2 zng`xP_H;obR`+*HZNA=e826PHT6IUNQK?s)qXt8<4bUE*XS#A%cpqQaUADcHU;!Cb zUS~_`6<|~^=_KdWE7PRa?)OjK6dgq*cx;iaOSNYRwl4HAY&}4>Am}6b1opluOr};s zw!?G8liP{4_D64oq0$UUzSAf%Xf4OB=Tr9c2zM@#xF3*W3OboyB>Q&FV;Slb$2H@D ze3yYq%<{Dn8Ge82t%z=HPs8yI-^5L?&^;WV{NYbE9@q>|$H&_-Hn^G7SYb?LCu-Sb ziMQCbP`;=>j6>m;JYxi3mJYs7)E?2pOpsvfS5-6@Bof7$&KrS+z;CMhT!c4-;h9U; zd601TgYh`-Ku6l^D+I{B6o@ksVC zaW_P{n>i8V1}f-ae9;G;OJsa>3VtEH=<%QBSPh6W*12dc=X2=6{V_IUHVgWty*KPc-oa^_~KJf$2s z`Sw&GQsP9Ya~GDCIl3e#zg9r!@U0a5a%FB~ci3>`P@s)mzPp(TJCme6;zrJ`npjm+ zH+djEl$7wbSj5?%0XfoFS5E86uRLguDOkZ)Ee}&I*h++CIFtX%8?AJEwOD$;r@y3* z{DT~`O0Ss`o(6tK=n{>4Mu?bLY@WKqor%{sjpvLFyWH*F0)jy3=Oxi0pg={r?TFng z;Y*EOL;Iyj$6vT~Sw)yo#ZaZ1B`7fh86jEh<y23%j!&VM| zCcdLR8O7wEBlbiaEAhsQG;|Wqlm=6g1hlaV;zJYCOgqUADR^b1)oaswkIKx-dmS<# zaytCnt-*2Tdad3+1F~RezUepW#}jHgQBBZTRcmj^U{;H)9pBri5^TTGUW|J?{F#~g zqrzDsLhY2q-=J#s=&Y$SQASZoNW;L~XGcRwsr2GmQ;^Y=DRrsDGOOY2xg2Nh_Cb7oTw8*$F!@BEiA5G`d8B=1n zLbV8W7LUX2M-_(Rsjux1OJ0ldgYYWTgGOF4`jPM6#8h=9Ke*(YlcHyxsE4=avdEI`v6OT-jQ zs5#wJtDHsEu#HWt>fahsa)WUp~SmZrXzkGeg~AYRPIeE1hK^u$vGLB${?Y0J9z<4p&{ zB8ta-RGxvLL|q>DKUY)4ulUeQi31rv06hT)a3vDZsjd>|d`(!5TfKimI{ z1Fnwc_J2YWalG{Nb4!aSKvU3yqjlhxUDua@m0ZGaP(&u>)9l8x%Xa783-yp0XQ2WXe$O z>!E&25lPP`IUx!Ob69K9&#O#)c9a)LB&%d?ymEEiO7bXO?#M=3aI5d~v8&V4VQP;h zmO4g;uXb$K*~NM(%3(v*$6Aubql_6CI2?Vp_b6!uwl1av6{q%5VboZ{BaPWk^(!S z&jqs&TP+Ff&H<&sSrVoo69{Rma%Zhhn;dB;kx6`c!O{MW$T|b2Uj;YS=m(*^GJ6kKNfSskT zlq8EOIt=QlzYB!kCkG@uCbV@8gnNfC4^1FL+xJc$=46QKi*vU+7=M++QyTQRgm5)MbM9a>c=`ox0`*;LPVM=F)g_;igqIP@YTkN z5>7Pq?g{DR#k1sBjdU?3z03mK5ApnUzP%(M_c;d~$!-IF6+CF>c8?2pWW`q{)T2Y0 zQ{TlZ)~3Pl^{zc7ebO_@qP9;(8x&JU9*Nya7miUqt!qh8!Hj42IlD{exfj6TipLvi zOb@K%_+H;lgKX$Jw1^E3OKmhJDNmJ2$@nGd(tGNPX|C~39U-*%Vd7To5iu87U#%n6 z$S--dwO=Atc)uCXU&MRe^M5keoi2D7l7s?>WBJFgH0{6ktzJTFOGG~JamDOUe%rVB z(s+`aiWriK-!nzaAASL`R88gv*+jQ3u z9@YrWhk6{&b~x2Yyoz!$`l5V%lh@^|Dg!a2)%A}yc9NDNzY#Y2iYY0oSgIM(u9%7v z(Y^JL_0hdGH4W8)_jA4Y2|xb^HR`@0$|SENO>cQHDhpQi5yR|e!qivx<80ic$xLBy zcU0h~ZAHqWHHiiWR>~;L2y@=Iaa7V{H^|WbQb{X>ktYY_FoB-anwmSrztZka%?>`V zIL=jj+3&$ZA;_X7CVttRt4F8+@-StR7B&>y8_4Qr*^TIV8DLunsx-;A>BPRX?FzwbugZE1K6E&>j#y>62fG#Fw{>DK2pX+g(c31dQ zvS1ml5_GXS+tSn%jqDmUyX4xniUTy*{W9HBW6H?>h5V*}S z9iZ++Cfk47OU_TD`S4@D&m6BNk!U@VSGbaqgVBQnN=CRlo6SOAX z-=h`ta(YT-D-4T&d>uGe!eu#_A6S=7@CHh9{s3%-<6R(>t z@2C0A8!^%`tl8eauNg8E0%1o7^7Ln6wo&?06jIVPi5p$^$z7aKakUAL@9VSpsb$F= zyedDKS>i_G49_yEt)t7VLv$y2<6+g3##S4VFKc2{QwCgKX!rN~_byjiHoGRIu8rWy z+`=;1NPztj!#82?euG}sAv3faM!)5>1XFcRZR4fCe#`mR{j-yoRzX7YZ0s^y*esLn z;8(k#t|N?9qt;h;^`B@WB(#Mc>3Q&Ix4}cO@n;WOFKr1nr#aCmz!ojCg20sJPb5Bx zAWGMXX03`ks$lPtid zS&0U)`kmyV2zPslBYht+hc)Md0oR1PhijH|-I=RXI%N+g{rI7?<4{Eu+6WUkHrsu9 zGxONIMv&p7nRDzNensNBVQquyX5k5UN@CktmW0@!gZy6mr7Jq5NQ3vUl{m>N?%eNr z+^QJ?`UMYeskD?9Yzbktew8Ax#$(VtCZ)phjKP+G;bZH9*Rj+@Uzz| zU$yXxyv&oj~zb5QZV zr&%F3{ZMzCrhG$+{{=3dmI@8&IFlgAfly6|(IRYf#Qc8++zjY;u} zDb^bI`#H#>zO~rOFfQyCXiGHQMYmK>G;S?NQ)bxX)PybHXP~0ad-srweL`0TK^cx% zAhDNVJXuF3?A>75M(*8>7|syoo6oX&RUSOsJIb8_bJbf`PW7QH^9}Hw8)e}H~ z6NbGV*0mB#BzLFFy&yAzyl{t+a3FHsDuSQssi)=rKJ>v&8*Mv1O*Oo@9K}54w=w5< zRH~>cG&r{U{Fkw-Zj%1QzDM}C0|%hrp-17X`Mr3jOq_%f5>Qfy%n=TNCV5^Yx4inl zvLb!KXeVWczG52CALP0J^6$Eoe+JT!$)G>&5yO z9yJzn=9jJ884okADg3$N?WS4pc9zaIWU&0P7}XO2V+i)D2bjmn7J-*Q0r%0D^S^Su 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 *9VF0~TT-!I%%B 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%0Ad_6LXa;%irjeA4JaVl99SCA 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⁡O|HC+ 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
    P5BTMb@ApUkrBip1fb9 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 LoadStyles method", MessageType.Error); + return; + } + +#if ASTAR_ATAVISM + EditorGUILayout.HelpBox("This is a special version of the A* Pathfinding Project for Atavism. This version only supports scanning recast graphs and exporting them, but no pathfinding during runtime.", MessageType.Info); +#endif + + EditorGUI.BeginChangeCheck(); + + Undo.RecordObject(script, "A* inspector"); + + CheckGraphEditors(); + + EditorGUI.indentLevel = 1; + + // Apparently these can sometimes get eaten by unity components + // so I catch them here for later use + EventType storedEventType = Event.current.type; + string storedEventCommand = Event.current.commandName; + + DrawMainArea(); + + GUILayout.Space(5); + + if (isPrefab) { + EditorGUI.BeginDisabledGroup(true); + GUILayout.Button(new GUIContent("Scan", "Cannot recalculate graphs on prefabs")); + EditorGUI.EndDisabledGroup(); + } else if (GUILayout.Button(new GUIContent("Scan", "Recalculate all graphs. Shortcut cmd+alt+s ( ctrl+alt+s on windows )"))) { + RunTask(MenuScan); + } + + + // Handle undo + SaveGraphsAndUndo(storedEventType, storedEventCommand); + + + if (EditorGUI.EndChangeCheck()) { + RepaintSceneView(); + EditorUtility.SetDirty(script); + } + } + + ///

    + /// Loads GUISkin and sets up styles. + /// See: EditorResourceHelper.LocateEditorAssets + /// Returns: True if all styles were found, false if there was an error somewhere + /// + public static bool LoadStyles () { + if (stylesLoaded) return true; + + // Dummy styles in case the loading fails + var inspectorSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector); + + if (!EditorResourceHelper.LocateEditorAssets()) { + return false; + } + + var skinPath = EditorResourceHelper.editorAssets + "/AstarEditorSkin" + (EditorGUIUtility.isProSkin ? "Dark" : "Light") + ".guiskin"; + astarSkin = AssetDatabase.LoadAssetAtPath(skinPath, typeof(GUISkin)) as GUISkin; + + if (astarSkin != null) { + astarSkin.button = inspectorSkin.button; + } else { + Debug.LogWarning("Could not load editor skin at '" + skinPath + "'"); + return false; + } + + level0AreaStyle = astarSkin.FindStyle("PixelBox"); + + // If the first style is null, then the rest are likely corrupted as well + // Probably due to the user not copying meta files + if (level0AreaStyle == null) { + return false; + } + + level1LabelStyle = astarSkin.FindStyle("BoxHeader"); + level0LabelStyle = astarSkin.FindStyle("TopBoxHeader"); + + level1AreaStyle = astarSkin.FindStyle("PixelBox3"); + graphDeleteButtonStyle = astarSkin.FindStyle("PixelButton"); + graphInfoButtonStyle = astarSkin.FindStyle("InfoButton"); + graphGizmoButtonStyle = astarSkin.FindStyle("GizmoButton"); + graphEditNameButtonStyle = astarSkin.FindStyle("EditButton"); + graphDuplicateButtonStyle = astarSkin.FindStyle("DuplicateButton"); + + helpBox = inspectorSkin.FindStyle("HelpBox") ?? inspectorSkin.box; + thinHelpBox = astarSkin.FindStyle("Banner"); + + stylesLoaded = true; + return true; + } + + /// Draws the main area in the inspector + void DrawMainArea () { + CheckGraphEditors(); + + graphsArea.Begin(); + graphsArea.Header("Graphs", ref script.showGraphs); + + if (graphsArea.BeginFade()) { + bool anyNonNull = false; + for (int i = 0; i < script.graphs.Length; i++) { + if (script.graphs[i] != null && script.graphs[i].showInInspector) { + anyNonNull = true; + DrawGraph(graphEditors[i]); + } + } + + // Draw the Add Graph button + addGraphsArea.Begin(); + addGraphsArea.open |= !anyNonNull; + addGraphsArea.Header("Add New Graph"); + + if (addGraphsArea.BeginFade()) { + script.data.FindGraphTypes(); + for (int i = 0; i < graphTypes.Length; i++) { + if (graphEditorTypes.ContainsKey(graphTypes[i].Name)) { + if (GUILayout.Button(graphEditorTypes[graphTypes[i].Name].displayName)) { + addGraphsArea.open = false; + AddGraph(graphTypes[i]); + } + } else if (!graphTypes[i].Name.Contains("Base") && graphTypes[i] != typeof(LinkGraph)) { + EditorGUI.BeginDisabledGroup(true); + GUILayout.Label(graphTypes[i].Name + " (no editor found)", "Button"); + EditorGUI.EndDisabledGroup(); + } + } + } + addGraphsArea.End(); + } + + graphsArea.End(); + + DrawSettings(); + DrawSerializationSettings(); + DrawOptimizationSettings(); + DrawAboutArea(); + + bool showNavGraphs = EditorGUILayout.Toggle("Show Graphs", script.showNavGraphs); + if (script.showNavGraphs != showNavGraphs) { + script.showNavGraphs = showNavGraphs; + RepaintSceneView(); + } + } + + /// Draws optimizations settings. + void DrawOptimizationSettings () { + optimizationSettingsArea.Begin(); + optimizationSettingsArea.Header("Optimization"); + + if (optimizationSettingsArea.BeginFade()) { + defines = defines ?? OptimizationHandler.FindDefines(); + + EditorGUILayout.HelpBox("Using C# pre-processor directives, performance and memory usage can be improved by disabling features that you don't use in the project.\n" + + "Every change to these settings requires recompiling the scripts", MessageType.Info); + + foreach (var define in defines) { + EditorGUILayout.Separator(); + + var label = new GUIContent(ObjectNames.NicifyVariableName(define.name), define.description); + define.enabled = EditorGUILayout.Toggle(label, define.enabled); + EditorGUILayout.HelpBox(define.description, MessageType.None); + + if (!define.consistent) { + GUIUtilityx.PushTint(Color.red); + EditorGUILayout.HelpBox("This define is not consistent for all build targets, some have it enabled enabled some have it disabled. Press Apply to change them to the same value", MessageType.Error); + GUIUtilityx.PopTint(); + } + } + + EditorGUILayout.Separator(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Apply", GUILayout.Width(150))) { + RunTask(() => { + if (EditorUtility.DisplayDialog("Apply Optimizations", "Applying optimizations requires (in case anything changed) a recompilation of the scripts. The inspector also has to be reloaded. Do you want to continue?", "Ok", "Cancel")) { + OptimizationHandler.ApplyDefines(defines); + AssetDatabase.Refresh(); + defines = null; + } + }); + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + optimizationSettingsArea.End(); + } + + /// + /// Returns a version with all fields fully defined. + /// This is used because by default new Version(3,0,0) > new Version(3,0). + /// This is not the desired behaviour so we make sure that all fields are defined here + /// + public static System.Version FullyDefinedVersion (System.Version v) { + return new System.Version(Mathf.Max(v.Major, 0), Mathf.Max(v.Minor, 0), Mathf.Max(v.Build, 0), Mathf.Max(v.Revision, 0)); + } + + void DrawAboutArea () { + aboutArea.Begin(); + + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("About", level0LabelStyle)) { + aboutArea.open = !aboutArea.open; + GUI.changed = true; + } + +#if !ASTAR_ATAVISM + System.Version newVersion = AstarUpdateChecker.latestVersion; + bool beta = false; + + // Check if either the latest release version or the latest beta version is newer than this version + if (FullyDefinedVersion(AstarUpdateChecker.latestVersion) > FullyDefinedVersion(AstarPath.Version) || FullyDefinedVersion(AstarUpdateChecker.latestBetaVersion) > FullyDefinedVersion(AstarPath.Version)) { + if (FullyDefinedVersion(AstarUpdateChecker.latestVersion) <= FullyDefinedVersion(AstarPath.Version)) { + newVersion = AstarUpdateChecker.latestBetaVersion; + beta = true; + } + } + + // Check if the latest version is newer than this version + if (FullyDefinedVersion(newVersion) > FullyDefinedVersion(AstarPath.Version) + ) { + GUIUtilityx.PushTint(Color.green); + if (GUILayout.Button((beta ? "Beta" : "New") + " version available! "+newVersion, thinHelpBox)) { + Application.OpenURL(AstarUpdateChecker.GetURL("download")); + } + GUIUtilityx.PopTint(); + GUILayout.Space(20); + } +#endif + + GUILayout.EndHorizontal(); + + if (aboutArea.BeginFade()) { + GUILayout.Label("The A* Pathfinding Project was made by Aron Granberg\nYour current version is "+AstarPath.Version); + +#if !ASTAR_ATAVISM + if (FullyDefinedVersion(newVersion) > FullyDefinedVersion(AstarPath.Version)) { + EditorGUILayout.HelpBox("A new "+(beta ? "beta " : "")+"version of the A* Pathfinding Project is available, the new version is "+ + newVersion, MessageType.Info); + + if (GUILayout.Button("What's new?")) { + Application.OpenURL(AstarUpdateChecker.GetURL(beta ? "beta_changelog" : "changelog")); + } + + if (GUILayout.Button("Click here to find out more")) { + Application.OpenURL(AstarUpdateChecker.GetURL("findoutmore")); + } + + GUIUtilityx.PushTint(new Color(0.3F, 0.9F, 0.3F)); + + if (GUILayout.Button("Download new version")) { + Application.OpenURL(AstarUpdateChecker.GetURL("download")); + } + + GUIUtilityx.PopTint(); + } +#endif + + if (GUILayout.Button(new GUIContent("Documentation", "Open the documentation for the A* Pathfinding Project"))) { + Application.OpenURL(AstarUpdateChecker.GetURL("documentation")); + } + + if (GUILayout.Button(new GUIContent("Project Homepage", "Open the homepage for the A* Pathfinding Project"))) { + Application.OpenURL(AstarUpdateChecker.GetURL("homepage")); + } + } + + aboutArea.End(); + } + + void DrawGraphHeader (GraphEditor graphEditor) { + var graph = graphEditor.target; + + // Graph guid, just used to get a unique value + string graphGUIDString = graph.guid.ToString(); + + GUILayout.BeginHorizontal(); + + if (graphNameFocused == graphEditor) { + GUI.SetNextControlName(graphGUIDString); + graph.name = GUILayout.TextField(graph.name ?? "", level1LabelStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false)); + + // Mark the name field as deselected when it has been deselected or when the user presses Return or Escape + if ((Event.current.type == EventType.Repaint && GUI.GetNameOfFocusedControl() != graphGUIDString) || (Event.current.type == EventType.KeyUp && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Escape))) { + if (Event.current.type == EventType.KeyUp) Event.current.Use(); + graphNameFocused = null; + } + } else { + // If the graph name text field is not focused and the graph name is empty, then fill it in + if (graph.name == null || graph.name == "") graph.name = graphEditorTypes[graph.GetType().Name].displayName; + + if (GUILayout.Button(graph.name, level1LabelStyle)) { + graphEditor.fadeArea.open = graph.open = !graph.open; + if (!graph.open) { + graph.infoScreenOpen = false; + } + RepaintSceneView(); + } + } + + // The OnInspectorGUI method ensures that the scene view is repainted when gizmos are toggled on or off by checking for EndChangeCheck + graph.drawGizmos = GUILayout.Toggle(graph.drawGizmos, new GUIContent("Draw Gizmos", "Draw Gizmos"), graphGizmoButtonStyle); + + if (GUILayout.Button(new GUIContent("", "Edit Name"), graphEditNameButtonStyle)) { + graphNameFocused = graphEditor; + GUI.FocusControl(graphGUIDString); + } + + if (GUILayout.Toggle(graph.infoScreenOpen, new GUIContent("Info", "Info"), graphInfoButtonStyle)) { + if (!graph.infoScreenOpen) { + graphEditor.infoFadeArea.open = graph.infoScreenOpen = true; + graphEditor.fadeArea.open = graph.open = true; + } + } else { + graphEditor.infoFadeArea.open = graph.infoScreenOpen = false; + } + + if (GUILayout.Button(new GUIContent("Duplicate", "Duplicate"), graphDuplicateButtonStyle)) { + DuplidateGraph(graph); + } + + if (GUILayout.Button(new GUIContent("Delete", "Delete"), graphDeleteButtonStyle)) { + RemoveGraph(graph); + } + GUILayout.EndHorizontal(); + } + + void DrawGraphInfoArea (GraphEditor graphEditor) { + graphEditor.infoFadeArea.Begin(); + + if (graphEditor.infoFadeArea.BeginFade()) { + // Calculate number of nodes in the graph + (float, (int, int)) pair; + graphNodeCounts = graphNodeCounts ?? new Dictionary(); + + if (!graphNodeCounts.TryGetValue(graphEditor.target, out pair) || (Time.realtimeSinceStartup-pair.Item1) > 2) { + var counters = (0, 0); + graphEditor.target.GetNodes(static (GraphNode node, ref (int, int)counters) => { + // Guard against bad user-implemented graphs + if (node != null) { + counters.Item1++; + if (node.Walkable) counters.Item2++; + } + }, ref counters); + pair = (Time.realtimeSinceStartup, counters); + graphNodeCounts[graphEditor.target] = pair; + } + + var total = pair.Item2.Item1; + var numWalkable = pair.Item2.Item2; + + EditorGUI.indentLevel++; + + EditorGUILayout.LabelField("Nodes", total.ToString()); + EditorGUILayout.LabelField("Walkable", numWalkable.ToString()); + EditorGUILayout.LabelField("Unwalkable", (total-numWalkable).ToString()); + if (!graphEditor.target.isScanned) EditorGUILayout.HelpBox("The graph is not scanned", MessageType.Info); + + EditorGUILayout.LabelField("Graph Index", graphEditor.target.graphIndex.ToString()); + EditorGUI.indentLevel--; + } + + graphEditor.infoFadeArea.End(); + } + + /// Draws the inspector for the given graph with the given graph editor + void DrawGraph (GraphEditor graphEditor) { + graphEditor.fadeArea.Begin(); + DrawGraphHeader(graphEditor); + + if (graphEditor.fadeArea.BeginFade()) { + DrawGraphInfoArea(graphEditor); + graphEditor.OnInspectorGUI(graphEditor.target); + graphEditor.OnBaseInspectorGUI(graphEditor.target); + } + + graphEditor.fadeArea.End(); + } + + public void OnSceneGUI () { + script = target as AstarPath; + + DrawSceneGUISettings(); + + // OnSceneGUI may be called from EditorUtility.DisplayProgressBar + // which is called repeatedly while the graphs are scanned in the + // editor. However running the OnSceneGUI method while the graphs + // are being scanned is a bad idea since it can interfere with + // scanning, especially by serializing changes + if (script.isScanning) { + return; + } + + script.colorSettings.PushToStatic(); + EditorGUI.BeginChangeCheck(); + + if (!LoadStyles()) return; + + // Some GUI controls might change this to Used, so we need to grab it here + EventType et = Event.current.type; + + CheckGraphEditors(); + for (int i = 0; i < script.graphs.Length; i++) { + NavGraph graph = script.graphs[i]; + if (graph != null && graphEditors[i] != null) { + graphEditors[i].OnSceneGUI(graph); + } + } + + SaveGraphsAndUndo(et); + + if (EditorGUI.EndChangeCheck()) { + EditorUtility.SetDirty(target); + } + } + + void DrawSceneGUISettings () { + var darkSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene); + + Handles.BeginGUI(); + float width = 180; + float height = 76; + float margin = 10; + + var origWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 144; + + GUILayout.BeginArea(new Rect(Camera.current.pixelWidth/EditorGUIUtility.pixelsPerPoint - width, Camera.current.pixelHeight/EditorGUIUtility.pixelsPerPoint - height, width - margin, height - margin), "Graph Display", astarSkin.FindStyle("SceneBoxDark")); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Show Graphs", darkSkin.toggle, astarSkin.FindStyle("ScenePrefixLabel")); + script.showNavGraphs = EditorGUILayout.Toggle(script.showNavGraphs, darkSkin.toggle); + EditorGUILayout.EndHorizontal(); + + if (GUILayout.Button("Scan", darkSkin.button)) { + RunTask(MenuScan); + } + + // Invisible button to capture clicks. This prevents a click inside the box from causing some other GameObject to be selected. + GUI.Button(new Rect(0, 0, width - margin, height - margin), "", GUIStyle.none); + GUILayout.EndArea(); + + EditorGUIUtility.labelWidth = origWidth; + Handles.EndGUI(); + } + + + TextAsset SaveGraphData (byte[] bytes, TextAsset target = null) { + string projectPath = System.IO.Path.GetDirectoryName(Application.dataPath) + "/"; + + string path; + + if (target != null) { + path = AssetDatabase.GetAssetPath(target); + } else { + // Find a valid file name + int i = 0; + do { + path = "Assets/GraphCaches/GraphCache" + (i == 0 ? "" : i.ToString()) + ".bytes"; + i++; + } while (System.IO.File.Exists(projectPath+path)); + } + + string fullPath = projectPath + path; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fullPath)); + var fileInfo = new System.IO.FileInfo(fullPath); + // Make sure we can write to the file + if (fileInfo.Exists && fileInfo.IsReadOnly) + fileInfo.IsReadOnly = false; + System.IO.File.WriteAllBytes(fullPath, bytes); + + AssetDatabase.Refresh(); + return AssetDatabase.LoadAssetAtPath(path); + } + + void DrawSerializationSettings () { + serializationSettingsArea.Begin(); + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("Save & Load", level0LabelStyle)) { + serializationSettingsArea.open = !serializationSettingsArea.open; + } + + if (script.data.cacheStartup && script.data.file_cachedStartup != null) { + GUIUtilityx.PushTint(Color.yellow); + GUILayout.Label("Startup cached", thinHelpBox); + GUILayout.Space(20); + GUIUtilityx.PopTint(); + } + + GUILayout.EndHorizontal(); + + // This displays the serialization settings + if (serializationSettingsArea.BeginFade()) { + script.data.cacheStartup = EditorGUILayout.Toggle(new GUIContent("Cache startup", "If enabled, will cache the graphs so they don't have to be scanned at startup"), script.data.cacheStartup); + + script.data.file_cachedStartup = EditorGUILayout.ObjectField(script.data.file_cachedStartup, typeof(TextAsset), false) as TextAsset; + + if (script.data.cacheStartup && script.data.file_cachedStartup == null) { + EditorGUILayout.HelpBox("No cache has been generated", MessageType.Error); + } + + if (script.data.cacheStartup && script.data.file_cachedStartup != null) { + EditorGUILayout.HelpBox("All graph settings will be replaced with the ones from the cache when the game starts", MessageType.Info); + } + + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("Generate cache")) { + RunTask(() => { + var serializationSettings = new Pathfinding.Serialization.SerializeSettings(); + + if (isPrefab) { + if (!EditorUtility.DisplayDialog("Can only save settings", "Only graph settings can be saved when the AstarPath object is a prefab. Instantiate the prefab in a scene to be able to save node data as well.", "Save settings", "Cancel")) { + return; + } + } else { + serializationSettings.nodes = true; + + if (EditorUtility.DisplayDialog("Scan before generating cache?", "Do you want to scan the graphs before saving the cache.\n" + + "If the graphs have not been scanned then the cache may not contain node data and then the graphs will have to be scanned at startup anyway.", "Scan", "Don't scan")) { + MenuScan(); + } + } + + // Save graphs + var bytes = script.data.SerializeGraphs(serializationSettings); + + // Store it in a file + script.data.file_cachedStartup = SaveGraphData(bytes, script.data.file_cachedStartup); + script.data.cacheStartup = true; + }); + } + + if (GUILayout.Button("Load from cache")) { + RunTask(() => { + if (EditorUtility.DisplayDialog("Are you sure you want to load from cache?", "Are you sure you want to load graphs from the cache, this will replace your current graphs?", "Yes", "Cancel")) { + script.data.LoadFromCache(); + } + }); + } + + GUILayout.EndHorizontal(); + + GUILayout.Space(5); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Save to file")) { + RunTask(() => { + string path = EditorUtility.SaveFilePanel("Save Graphs", "", "graph.bytes", "bytes"); + + if (path != "") { + var serializationSettings = Pathfinding.Serialization.SerializeSettings.Settings; + if (isPrefab) { + if (!EditorUtility.DisplayDialog("Can only save settings", "Only graph settings can be saved when the AstarPath object is a prefab. Instantiate the prefab in a scene to be able to save node data as well.", "Save settings", "Cancel")) { + return; + } + } else { + if (EditorUtility.DisplayDialog("Include node data?", "Do you want to include node data in the save file. " + + "If node data is included the graph can be restored completely without having to scan it first.", "Include node data", "Only settings")) { + serializationSettings.nodes = true; + } + } + + if (serializationSettings.nodes && EditorUtility.DisplayDialog("Scan before saving?", "Do you want to scan the graphs before saving? " + + "\nNot scanning can cause node data to be omitted from the file if the graph is not yet scanned.", "Scan", "Don't scan")) { + MenuScan(); + } + + uint checksum; + var bytes = SerializeGraphs(serializationSettings, out checksum); + Pathfinding.Serialization.AstarSerializer.SaveToFile(path, bytes); + + EditorUtility.DisplayDialog("Done Saving", "Done saving graph data.", "Ok"); + } + }); + } + + if (GUILayout.Button("Load from file")) { + RunTask(() => { + string path = EditorUtility.OpenFilePanel("Load Graphs", "", ""); + + if (path != "") { + try { + byte[] bytes = Pathfinding.Serialization.AstarSerializer.LoadFromFile(path); + DeserializeGraphs(bytes); + } catch (System.Exception e) { + Debug.LogError("Could not load from file at '"+path+"'\n"+e); + } + } + }); + } + + GUILayout.EndHorizontal(); + } + + serializationSettingsArea.End(); + } + + public void RunTask (System.Action action) { + EditorApplication.CallbackFunction wrapper = null; + wrapper = () => { + EditorApplication.update -= wrapper; + // Run the callback only if the editor has not been disabled since the task was scheduled + if (script != null) action(); + }; + EditorApplication.update += wrapper; + } + + void DrawSettings () { + settingsArea.Begin(); + settingsArea.Header("Settings", ref showSettings); + + if (settingsArea.BeginFade()) { + DrawPathfindingSettings(); + DrawDebugSettings(); + DrawColorSettings(); + DrawTagSettings(); + DrawEditorSettings(); + } + + settingsArea.End(); + } + + void DrawPathfindingSettings () { + alwaysVisibleArea.Begin(); + alwaysVisibleArea.HeaderLabel("Pathfinding"); + alwaysVisibleArea.BeginFade(); + +#if !ASTAR_ATAVISM + EditorGUI.BeginDisabledGroup(Application.isPlaying); + + script.threadCount = (ThreadCount)EditorGUILayout.EnumPopup(new GUIContent("Thread Count", "Number of threads to run the pathfinding in (if any). More threads " + + "can boost performance on multi core systems. \n" + + "Use None for debugging or if you dont use pathfinding that much.\n " + + "See docs for more info"), script.threadCount); + + EditorGUI.EndDisabledGroup(); + + int threads = script.threadCount.ToConcreteThreadCount(); + if (threads > 0) EditorGUILayout.HelpBox("Using " + threads +" thread(s)" + (script.threadCount < 0 ? " on your machine" : ""), MessageType.None); + else EditorGUILayout.HelpBox("Using a single coroutine (no threads)" + (script.threadCount < 0 ? " on your machine" : ""), MessageType.None); + if (threads > SystemInfo.processorCount) EditorGUILayout.HelpBox("Using more threads than there are CPU cores may not have a positive effect on performance", MessageType.Warning); + + if (script.threadCount == ThreadCount.None) { + script.maxFrameTime = EditorGUILayout.FloatField(new GUIContent("Max Frame Time", "Max number of milliseconds to use for path calculation per frame"), script.maxFrameTime); + } else { + script.maxFrameTime = 10; + } + + script.maxNearestNodeDistance = EditorGUILayout.FloatField(new GUIContent("Max Nearest Node Distance", + "Normally, if the nearest node to e.g the start point of a path was not walkable" + + " a search will be done for the nearest node which is walkble. This is the maximum distance (world units) which it will search"), + script.maxNearestNodeDistance); + + script.heuristic = (Heuristic)EditorGUILayout.EnumPopup("Heuristic", script.heuristic); + + if (script.heuristic == Heuristic.Manhattan || script.heuristic == Heuristic.Euclidean || script.heuristic == Heuristic.DiagonalManhattan) { + EditorGUI.indentLevel++; + script.heuristicScale = EditorGUILayout.FloatField("Heuristic Scale", script.heuristicScale); + script.heuristicScale = Mathf.Clamp01(script.heuristicScale); + EditorGUI.indentLevel--; + } + + GUILayout.Label(new GUIContent("Advanced"), EditorStyles.boldLabel); + + DrawHeuristicOptimizationSettings(); + + script.batchGraphUpdates = EditorGUILayout.Toggle(new GUIContent("Batch Graph Updates", "Limit graph updates to only run every x seconds. Can have positive impact on performance if many graph updates are done"), script.batchGraphUpdates); + + if (script.batchGraphUpdates) { + EditorGUI.indentLevel++; + script.graphUpdateBatchingInterval = EditorGUILayout.FloatField(new GUIContent("Update Interval (s)", "Minimum number of seconds between each batch of graph updates"), script.graphUpdateBatchingInterval); + EditorGUI.indentLevel--; + } + + // Only show if there is actually a navmesh/recast graph in the scene + // to help reduce clutter for other users. + if (script.data.FindGraphWhichInheritsFrom(typeof(NavmeshBase)) != null) { + script.navmeshUpdates.updateInterval = EditorGUILayout.FloatField(new GUIContent("Navmesh Cutting Update Interval (s)", "How often to check if any navmesh cut has changed."), script.navmeshUpdates.updateInterval); + } +#endif + script.scanOnStartup = EditorGUILayout.Toggle(new GUIContent("Scan on Awake", "Scan all graphs on Awake. If this is false, you must call AstarPath.active.Scan () yourself. Useful if you want to make changes to the graphs with code."), script.scanOnStartup); + + alwaysVisibleArea.End(); + } + + readonly string[] heuristicOptimizationOptions = new [] { + "None", + "Random (low quality)", + "RandomSpreadOut (high quality)", + "Custom" + }; + + void DrawHeuristicOptimizationSettings () { + script.euclideanEmbedding.mode = (HeuristicOptimizationMode)EditorGUILayout.Popup(new GUIContent("Heuristic Optimization"), (int)script.euclideanEmbedding.mode, heuristicOptimizationOptions); + + EditorGUI.indentLevel++; + if (script.euclideanEmbedding.mode == HeuristicOptimizationMode.Random) { + script.euclideanEmbedding.spreadOutCount = EditorGUILayout.IntField(new GUIContent("Count", "Number of optimization points, higher numbers give better heuristics and could make it faster, " + + "but too many could make the overhead too great and slow it down. Try to find the optimal value for your map. Recommended value < 100"), script.euclideanEmbedding.spreadOutCount); + } else if (script.euclideanEmbedding.mode == HeuristicOptimizationMode.Custom) { + script.euclideanEmbedding.pivotPointRoot = EditorGUILayout.ObjectField(new GUIContent("Pivot point root", + "All children of this transform are going to be used as pivot points. " + + "Recommended count < 100"), script.euclideanEmbedding.pivotPointRoot, typeof(Transform), true) as Transform; + if (script.euclideanEmbedding.pivotPointRoot == null) { + EditorGUILayout.HelpBox("Please assign an object", MessageType.Error); + } + } else if (script.euclideanEmbedding.mode == HeuristicOptimizationMode.RandomSpreadOut) { + script.euclideanEmbedding.pivotPointRoot = EditorGUILayout.ObjectField(new GUIContent("Pivot point root", + "All children of this transform are going to be used as pivot points. " + + "They will seed the calculation of more pivot points. " + + "Recommended count < 100"), script.euclideanEmbedding.pivotPointRoot, typeof(Transform), true) as Transform; + + if (script.euclideanEmbedding.pivotPointRoot == null) { + EditorGUILayout.HelpBox("No root is assigned. A random node will be choosen as the seed.", MessageType.Info); + } + + script.euclideanEmbedding.spreadOutCount = EditorGUILayout.IntField(new GUIContent("Count", "Number of optimization points, higher numbers give better heuristics and could make it faster, " + + "but too many could make the overhead too great and slow it down. Try to find the optimal value for your map. Recommended value < 100"), script.euclideanEmbedding.spreadOutCount); + } + + if (script.euclideanEmbedding.mode != HeuristicOptimizationMode.None) { + EditorGUILayout.HelpBox("Heuristic optimization assumes the graph remains static. No graph updates, dynamic obstacles or similar should be applied to the graph " + + "when using heuristic optimization.", MessageType.Info); + } + + EditorGUI.indentLevel--; + } + + /// Opens the A* Inspector and shows the section for editing tags + public static void EditTags () { + AstarPath astar = UnityCompatibility.FindAnyObjectByType(); + + if (astar != null) { + showTagNames = true; + showSettings = true; + Selection.activeGameObject = astar.gameObject; + } else { + Debug.LogWarning("No AstarPath component in the scene"); + } + } + + void DrawTagSettings () { + tagsArea.Begin(); + tagsArea.Header("Tag Names", ref showTagNames); + + if (tagsArea.BeginFade()) { + string[] tagNames = script.GetTagNames(); + + for (int i = 0; i < tagNames.Length; i++) { + tagNames[i] = EditorGUILayout.TextField(new GUIContent("Tag "+i, "Name for tag "+i), tagNames[i]); + if (tagNames[i] == "") tagNames[i] = ""+i; + } + } + + tagsArea.End(); + } + + void DrawEditorSettings () { + editorSettingsArea.Begin(); + editorSettingsArea.Header("Editor"); + + if (editorSettingsArea.BeginFade()) { + FadeArea.fancyEffects = EditorGUILayout.Toggle("Smooth Transitions", FadeArea.fancyEffects); + } + + editorSettingsArea.End(); + } + + static void DrawColorSlider (ref float left, ref float right, bool editable) { + GUILayout.BeginHorizontal(); + GUILayout.Space(20); + GUILayout.BeginVertical(); + + GUILayout.Box("", astarSkin.GetStyle("ColorInterpolationBox")); + GUILayout.BeginHorizontal(); + if (editable) { + left = EditorGUILayout.IntField((int)left); + } else { + GUILayout.Label(left.ToString("0")); + } + GUILayout.FlexibleSpace(); + if (editable) { + right = EditorGUILayout.IntField((int)right); + } else { + GUILayout.Label(right.ToString("0")); + } + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + GUILayout.Space(4); + GUILayout.EndHorizontal(); + } + + void DrawDebugSettings () { + alwaysVisibleArea.Begin(); + alwaysVisibleArea.HeaderLabel("Debug"); + alwaysVisibleArea.BeginFade(); + + script.logPathResults = (PathLog)EditorGUILayout.EnumPopup("Path Logging", script.logPathResults); + script.debugMode = (GraphDebugMode)EditorGUILayout.EnumPopup("Graph Coloring", script.debugMode); + + if (script.debugMode == GraphDebugMode.SolidColor) { + EditorGUI.BeginChangeCheck(); + script.colorSettings._SolidColor = EditorGUILayout.ColorField(new GUIContent("Color", "Color used for the graph when 'Graph Coloring'='Solid Color'"), script.colorSettings._SolidColor); + if (EditorGUI.EndChangeCheck()) { + script.colorSettings.PushToStatic(); + } + } + + if (script.debugMode == GraphDebugMode.G || script.debugMode == GraphDebugMode.H || script.debugMode == GraphDebugMode.F || script.debugMode == GraphDebugMode.Penalty) { + script.manualDebugFloorRoof = !EditorGUILayout.Toggle("Automatic Limits", !script.manualDebugFloorRoof); + DrawColorSlider(ref script.debugFloor, ref script.debugRoof, script.manualDebugFloorRoof); + } + + script.showSearchTree = EditorGUILayout.Toggle("Show Search Tree", script.showSearchTree); + if (script.showSearchTree) { + EditorGUILayout.HelpBox("Show Search Tree is enabled, you may see rendering glitches in the graph rendering" + + " while the game is running. This is nothing to worry about and is simply due to the paths being calculated at the same time as the gizmos" + + " are being rendered. You can pause the game to see an accurate rendering.", MessageType.Info); + } + script.showUnwalkableNodes = EditorGUILayout.Toggle("Show Unwalkable Nodes", script.showUnwalkableNodes); + + if (script.showUnwalkableNodes) { + EditorGUI.indentLevel++; + script.unwalkableNodeDebugSize = EditorGUILayout.FloatField("Size", script.unwalkableNodeDebugSize); + EditorGUI.indentLevel--; + } + + script.graphUpdateDebugMode = (GraphUpdateDebugMode)EditorGUILayout.EnumFlagsField("Graph Update Debug Mode", script.graphUpdateDebugMode); + + script.showGraphsInStandalonePlayer = EditorGUILayout.Toggle(new GUIContent("Show Graphs in Standalone Player", "If true, graph visualizations will be rendered in standalone builds. Normally they are only visible in the editor."), script.showGraphsInStandalonePlayer); + + alwaysVisibleArea.End(); + } + + void DrawColorSettings () { + colorSettingsArea.Begin(); + colorSettingsArea.Header("Colors"); + + if (colorSettingsArea.BeginFade()) { + // Make sure the object is not null + AstarColor colors = script.colorSettings = script.colorSettings ?? new AstarColor(); + + colors._SolidColor = EditorGUILayout.ColorField(new GUIContent("Solid Color", "Color used for the graph when 'Graph Coloring'='Solid Color'"), colors._SolidColor); + colors._UnwalkableNode = EditorGUILayout.ColorField("Unwalkable Node", colors._UnwalkableNode); + colors._BoundsHandles = EditorGUILayout.ColorField("Bounds Handles", colors._BoundsHandles); + + colors._ConnectionLowLerp = EditorGUILayout.ColorField("Graph Debug Gradient (low)", colors._ConnectionLowLerp); + colors._ConnectionHighLerp = EditorGUILayout.ColorField("Graph Debug Gradient (high)", colors._ConnectionHighLerp); + + if (EditorResourceHelper.GizmoSurfaceMaterial != null && EditorResourceHelper.GizmoLineMaterial != null) { + EditorGUI.BeginChangeCheck(); + var current = Drawing.DrawingSettings.GetSettingsAsset(); + + Undo.RecordObject(current, "Change gizmo transparency"); + + current.settings.solidOpacity = EditorGUILayout.Slider("Gizmo Surface Opacity", current.settings.solidOpacity, 0, 1); + + current.settings.lineOpacity = EditorGUILayout.Slider("Gizmo Line Opacity", current.settings.lineOpacity, 0, 1); + + current.settings.solidOpacityBehindObjects = EditorGUILayout.Slider("Opacity Behind Objects", current.settings.solidOpacityBehindObjects, 0, 1); + + if (EditorGUI.EndChangeCheck()) { + // Technically a different setting, but for simplicity we just derive the line opacity behind objects from the solid opacity behind objects + current.settings.lineOpacityBehindObjects = current.settings.solidOpacityBehindObjects * 0.26f; + EditorUtility.SetDirty(current); + } + } + + colors._AreaColors = colors._AreaColors ?? new Color[0]; + + // Custom Area Colors + showCustomAreaColors = EditorGUILayout.Foldout(showCustomAreaColors, "Custom Area Colors"); + if (showCustomAreaColors) { + EditorGUI.indentLevel += 2; + + for (int i = 0; i < colors._AreaColors.Length; i++) { + GUILayout.BeginHorizontal(); + colors._AreaColors[i] = EditorGUILayout.ColorField("Area "+i+(i == 0 ? " (not used usually)" : ""), colors._AreaColors[i]); + if (GUILayout.Button(new GUIContent("", "Reset to the default color"), astarSkin.FindStyle("SmallReset"), GUILayout.Width(20))) { + colors._AreaColors[i] = AstarMath.IntToColor(i, 1F); + } + GUILayout.EndHorizontal(); + } + + GUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(colors._AreaColors.Length > 255); + + if (GUILayout.Button("Add New")) { + Memory.Realloc(ref colors._AreaColors, colors._AreaColors.Length+1); + colors._AreaColors[colors._AreaColors.Length-1] = AstarMath.IntToColor(colors._AreaColors.Length-1, 1F); + } + + EditorGUI.EndDisabledGroup(); + EditorGUI.BeginDisabledGroup(colors._AreaColors.Length == 0); + + if (GUILayout.Button("Remove last") && colors._AreaColors.Length > 0) { + colors._AreaColors = Memory.ShrinkArray(colors._AreaColors, colors._AreaColors.Length-1); + } + + EditorGUI.EndDisabledGroup(); + GUILayout.EndHorizontal(); + + EditorGUI.indentLevel -= 2; + } + + if (GUI.changed) { + colors.PushToStatic(); + } + } + + colorSettingsArea.End(); + } + + /// Make sure every graph has a graph editor + void CheckGraphEditors () { + var data = script.data; + data.graphs = data.graphs ?? new NavGraph[0]; + // Ensure graphEditors.Length >= data.graphs.Length + Memory.Realloc(ref graphEditors, data.graphs.Length); + + for (int i = 0; i < script.graphs.Length; i++) { + var graph = script.graphs[i]; + + if (graph != null && graph.guid == new Pathfinding.Util.Guid()) { + graph.guid = Pathfinding.Util.Guid.NewGuid(); + } + + if (graph == null || !graph.showInInspector) { + graphEditors[i] = null; + continue; + } + + if (graphEditors[i] == null || graphEditors[i].target != graph) { + graphEditors[i] = CreateGraphEditor(graph); + } + } + } + + void RemoveGraph (NavGraph graph) { + script.data.RemoveGraph(graph); + CheckGraphEditors(); + GUI.changed = true; + Repaint(); + } + + void DuplidateGraph (NavGraph graph) { + script.data.DuplicateGraph(graph); + CheckGraphEditors(); + GUI.changed = true; + Repaint(); + } + + void AddGraph (System.Type type) { + script.data.AddGraph(type); + CheckGraphEditors(); + GUI.changed = true; + } + + /// Creates a GraphEditor for a graph + GraphEditor CreateGraphEditor (NavGraph graph) { + var graphType = graph.GetType().Name; + GraphEditor result; + + if (graphEditorTypes.TryGetValue(graphType, out var graphEditorTypeAttr)) { + var graphEditorType = graphEditorTypeAttr.editorType; + result = System.Activator.CreateInstance(graphEditorType) as GraphEditor; + + // Deserialize editor settings + var editorData = (graph as IGraphInternals).SerializedEditorSettings; + if (editorData != null) Pathfinding.Serialization.TinyJsonDeserializer.Deserialize(editorData, graphEditorType, result, script.gameObject); + } else { + Debug.LogError("Couldn't find an editor for the graph type '" + graphType + "'. There are " + graphEditorTypes.Count + " available graph editors"); + result = new GraphEditor(); + graphEditorTypes[graphType] = new CustomGraphEditorAttribute(graph.GetType(), graphType) { + editorType = typeof(GraphEditor) + }; + } + + result.editor = this; + result.fadeArea = new FadeArea(graph.open, this, level1AreaStyle, level1LabelStyle); + result.infoFadeArea = new FadeArea(graph.infoScreenOpen, this, null, null); + result.target = graph; + + result.OnEnable(); + return result; + } + + void HandleUndo () { + // The user has tried to undo something, apply that + DeserializeGraphs(); + } + + void SerializeIfDataChanged () { + byte[] bytes = SerializeGraphs(out var checksum); + + uint byteHash = Checksum.GetChecksum(bytes); + uint dataHash = Checksum.GetChecksum(script.data.GetData()); + // Check if the data is different than the previous data, use checksums + bool isDifferent = checksum != ignoredChecksum && dataHash != byteHash; + + // nly save undo if the data was different from the last saved undo + if (isDifferent) { + Undo.RegisterCompleteObjectUndo(script, "A* Graph Settings"); + Undo.IncrementCurrentGroup(); + // Assign the new data + script.data.SetData(bytes); + EditorUtility.SetDirty(script); + } + } + + /// Called when an undo or redo operation has been performed + void OnUndoRedoPerformed () { + if (!this) return; + + byte[] bytes = SerializeGraphs(out var checksum); + + // Check if the data is different than the previous data, use checksums + bool isDifferent = Checksum.GetChecksum(script.data.GetData()) != Checksum.GetChecksum(bytes); + + if (isDifferent) { + HandleUndo(); + } + + CheckGraphEditors(); + // Deserializing a graph does not necessarily yield the same hash as the data loaded from + // this is (probably) because editor settings are not saved all the time + // so we explicitly ignore the new hash + SerializeGraphs(out checksum); + ignoredChecksum = checksum; + } + + public void SaveGraphsAndUndo (EventType et = EventType.Used, string eventCommand = "") { + // Serialize the settings of the graphs + + // Dont process undo events in editor, we don't want to reset graphs + // Also don't do this if the graph is being updated as serializing the graph + // might interfere with that (in particular it might unblock the path queue). + // Also don't do this if the AstarPath object is not the active one, since serialization uses the singleton in some ways. + if (Application.isPlaying || script.isScanning || script.IsAnyWorkItemInProgress) { + return; + } + + if ((Undo.GetCurrentGroup() != lastUndoGroup || et == EventType.MouseUp) && eventCommand != "UndoRedoPerformed") { + SerializeIfDataChanged(); + + lastUndoGroup = Undo.GetCurrentGroup(); + } + + if (Event.current == null || script.data.GetData() == null) { + SerializeIfDataChanged(); + } + } + + public byte[] SerializeGraphs (out uint checksum) { + return SerializeGraphs(Pathfinding.Serialization.SerializeSettings.Settings, out checksum); + } + + public byte[] SerializeGraphs (Pathfinding.Serialization.SerializeSettings settings, out uint checksum) { + CheckGraphEditors(); + // Serialize all graph editors + var output = new System.Text.StringBuilder(); + for (int i = 0; i < graphEditors.Length; i++) { + if (graphEditors[i] == null) continue; + output.Length = 0; + Pathfinding.Serialization.TinyJsonSerializer.Serialize(graphEditors[i], output); + (graphEditors[i].target as IGraphInternals).SerializedEditorSettings = output.ToString(); + } + // Serialize all graphs (including serialized editor data) + return script.data.SerializeGraphs(settings, out checksum); + } + + void DeserializeGraphs () { + // User has cleared the data field. Revert this. + if (script.data.GetData() == null) script.data.SetData(new byte[0]); + DeserializeGraphs(script.data.GetData()); + } + + void DeserializeGraphs (byte[] bytes) { + try { + if (bytes == null || bytes.Length == 0) { + script.data.graphs = new NavGraph[0]; + } else { + script.data.DeserializeGraphs(bytes); + } + // Make sure every graph has a graph editor + CheckGraphEditors(); + } catch (System.Exception e) { + Debug.LogError("Failed to deserialize graphs"); + Debug.LogException(e); + script.data.SetData(null); + } + } + + [MenuItem("Edit/Pathfinding/Scan All Graphs %&s")] + public static void MenuScan () { + if (AstarPath.active == null) { + AstarPath.FindAstarPath(); + if (AstarPath.active == null) { + return; + } + } + + try { + var lastMessageTime = Time.realtimeSinceStartup; + foreach (var p in AstarPath.active.ScanAsync()) { + // Displaying the progress bar is pretty slow, so don't do it too often + if (Time.realtimeSinceStartup - lastMessageTime > 0.2f) { + // Display a progress bar of the scan + UnityEditor.EditorUtility.DisplayProgressBar("Scanning", p.ToString(), p.progress); + lastMessageTime = Time.realtimeSinceStartup; + } + } + + // Repaint the game view in addition to just the scene view. + // In case the user only has the game view open it's nice to refresh it so they can see the graph. + UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); + } catch (System.Exception e) { + Debug.LogError("There was an error generating the graphs:\n"+e+"\n\nIf you think this is a bug, please contact me on forum.arongranberg.com (post a new thread)\n"); + EditorUtility.DisplayDialog("Error Generating Graphs", "There was an error when generating graphs, check the console for more info", "Ok"); + throw e; + } finally { + EditorUtility.ClearProgressBar(); + } + } + + /// Searches in the current assembly for GraphEditor and NavGraph types + void FindGraphTypes () { + if (graphEditorTypes.Count > 0) return; + + graphEditorTypes = new Dictionary(); + + var editorTypes = AssemblySearcher.FindTypesInheritingFrom(); + foreach (var type in editorTypes) { + // Loop through the attributes for the CustomGraphEditorAttribute attribute + foreach (var attribute in type.GetCustomAttributes(false)) { + if (attribute is CustomGraphEditorAttribute cge && !System.Type.Equals(cge.graphType, null)) { + cge.editorType = type; + graphEditorTypes.Add(cge.graphType.Name, cge); + } + } + } + + // Make sure graph types (not graph editor types) are also up to date + script.data.FindGraphTypes(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathEditor.cs.meta new file mode 100644 index 0000000..71b7d41 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: aa27fa41f8abe460a8b64e13d7be43ad +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef new file mode 100644 index 0000000..cb2690e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef @@ -0,0 +1,81 @@ +{ + "name": "AstarPathfindingProjectEditor", + "rootNamespace": "", + "references": [ + "GUID:efa45043feb7e4147a305b73b5cea642", + "GUID:774e21169c4ac4ec8a01db9cdb98d33b", + "GUID:f4059aaf6c60a4a58a177a2609feb769", + "GUID:de4e6084e6d474788bb8c799d6b461eb", + "GUID:734d92eba21c94caba915361bd5ac177", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:d8b63aba1907145bea998dd612889d6b" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "Pathfinding.Clipper2Lib.dll" + ], + "autoReferenced": true, + "defineConstraints": [ + "MODULE_BURST", + "MODULE_MATHEMATICS", + "MODULE_COLLECTIONS" + ], + "versionDefines": [ + { + "name": "com.unity.burst", + "expression": "1.8.7", + "define": "MODULE_BURST" + }, + { + "name": "com.unity.mathematics", + "expression": "1.2.6", + "define": "MODULE_MATHEMATICS" + }, + { + "name": "com.unity.collections", + "expression": "1.5.1", + "define": "MODULE_COLLECTIONS" + }, + { + "name": "com.unity.collections", + "expression": "0.11-preview", + "define": "MODULE_COLLECTIONS_0_11_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.0.0", + "define": "MODULE_COLLECTIONS_2_0_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.1.0", + "define": "MODULE_COLLECTIONS_2_1_0_OR_NEWER" + }, + { + "name": "com.unity.collections", + "expression": "2.2.0", + "define": "MODULE_COLLECTIONS_2_2_0_OR_NEWER" + }, + { + "name": "com.unity.entities", + "expression": "1.1.0-pre.3", + "define": "MODULE_ENTITIES" + }, + { + "name": "com.unity.entities", + "expression": "1.0.8", + "define": "MODULE_ENTITIES_1_0_8_OR_NEWER" + }, + { + "name": "com.unity.test-framework", + "expression": "1.5.1", + "define": "MODULE_TEST_FRAMEWORK" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef.meta new file mode 100644 index 0000000..d5c5cd0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 865575785c1524f888e47306de0c1246 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss new file mode 100644 index 0000000..f60b2ec --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss @@ -0,0 +1,112 @@ +.largeIcon { + width: 200px; + height: 200px; + background-image: url('EditorAssets/ComponentIcons/d_AstarPath@256.png?fileID=2800000&guid=1620f833be5302149a071c06944d3e9b&type=3#d_AstarPath@256'); + flex-shrink: 0; + margin-bottom: 50px; + margin-top: 50px; + transition-property: scale; + transition-duration: 0.6s; + scale: 1 1; + transition-timing-function: ease-out-back; +} + +.welcomeWindow { + flex-grow: 1; + flex-direction: column; + align-items: center; + justify-content: flex-start; + -unity-background-image-tint-color: rgb(34, 34, 34); + background-color: rgb(34, 34, 34); + position: relative; + flex-basis: 100%; +} + +.welcomeButton { + align-self: stretch; + opacity: 1; + margin-left: 20px; + margin-right: 20px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + height: 40px; + border-top-width: 0; + border-right-width: 0; + border-bottom-width: 0; + border-left-width: 0; + border-left-color: rgba(224, 224, 224, 0); + border-right-color: rgba(224, 224, 224, 0); + border-top-color: rgba(224, 224, 224, 0); + border-bottom-color: rgba(224, 224, 224, 0); + background-color: rgb(89, 89, 89); + margin-bottom: 10px; + color: rgb(255, 255, 255); + -unity-font-style: bold; + font-size: 14px; + -unity-text-align: middle-left; + padding-left: 30px; + transition-property: padding-left, background-color; + transition-duration: 0.1s, 0.1s; +} + +:root { + flex-basis: 100%; +} + +.welcomeButton:hover { + padding-left: 40px; + background-color: rgb(231, 122, 14); +} + +.buttonIcon { + flex-grow: 0; + right: auto; + position: relative; + height: 28px; + -unity-background-image-tint-color: rgb(255, 255, 255); + width: 28px; + flex-shrink: 0; + margin-right: 5px; + margin-left: 5px; +} + +.buttonIconRight { + flex-grow: 0; + right: auto; + position: relative; + background-image: url('EditorAssets/images/check_icon.png?fileID=2800000&guid=25a6bf057fb4ad5438e3fa0aa1b45789&type=3#check_icon'); + height: 28px; + -unity-background-image-tint-color: rgb(255, 255, 255); + width: 28px; + flex-shrink: 0; +} + +.welcomeButton Label { + padding-left: 8px; +} + +.largeIconEntry { + scale: 0.7 0.7; +} + +.buttonIcon.samples { + background-image: url('EditorAssets/images/samples_icon.png?fileID=2800000&guid=e10d07f341fb69b42acf8f2b2971270f&type=3#samples_icon'); +} + +.buttonIcon.education { + background-image: url('EditorAssets/images/educate_icon.png?fileID=2800000&guid=d49de811c1b5cbb449d74fda55ad6adc&type=3#educate_icon'); +} + +.buttonIcon.changelog { + background-image: url('EditorAssets/images/changelog_icon.png?fileID=2800000&guid=aac6bbff2ba09084f96968aa0649198f&type=3#changelog_icon'); +} + +.buttonIcon.documentation { + background-image: url('EditorAssets/images/documentation_icon.png?fileID=2800000&guid=2af1390027808444db42d140d1d437ae&type=3#documentation_icon'); +} + +#logo { + background-image: url('EditorAssets/images/astar_logo.png?fileID=2800000&guid=1603401e2a100ba44b299ce8f603b5eb&type=3#astar_logo'); +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss.meta new file mode 100644 index 0000000..d4e818a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarPathfindingProjectEditor.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f1e4e8798274ca7448321794e89178b9 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs new file mode 100644 index 0000000..9c0b15d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs @@ -0,0 +1,291 @@ +using UnityEngine; +using UnityEditor; +using Pathfinding.Util; +using UnityEngine.Networking; +using System.Collections.Generic; +using System.Linq; + +namespace Pathfinding { + /// Handles update checking for the A* Pathfinding Project + [InitializeOnLoad] + public static class AstarUpdateChecker { + /// Used for downloading new version information + static UnityWebRequest updateCheckDownload; + + static System.DateTime _lastUpdateCheck; + static bool _lastUpdateCheckRead; + + static System.Version _latestVersion; + + static System.Version _latestBetaVersion; + + /// Description of the latest update of the A* Pathfinding Project + static string _latestVersionDescription; + + static bool hasParsedServerMessage; + + /// Number of days between update checks + const double updateCheckRate = 1F; + + /// URL to the version file containing the latest version number. + const string updateURL = "https://www.arongranberg.com/astar/version.php"; + + /// Last time an update check was made + public static System.DateTime lastUpdateCheck { + get { + try { + // Reading from EditorPrefs is relatively slow, avoid it + if (_lastUpdateCheckRead) return _lastUpdateCheck; + + _lastUpdateCheck = System.DateTime.Parse(EditorPrefs.GetString("AstarLastUpdateCheck", "1/1/1971 00:00:01"), System.Globalization.CultureInfo.InvariantCulture); + _lastUpdateCheckRead = true; + } + catch (System.FormatException) { + lastUpdateCheck = System.DateTime.UtcNow; + Debug.LogWarning("Invalid DateTime string encountered when loading from preferences"); + } + return _lastUpdateCheck; + } + private set { + _lastUpdateCheck = value; + EditorPrefs.SetString("AstarLastUpdateCheck", _lastUpdateCheck.ToString(System.Globalization.CultureInfo.InvariantCulture)); + } + } + + /// Latest version of the A* Pathfinding Project + public static System.Version latestVersion { + get { + RefreshServerMessage(); + return _latestVersion ?? AstarPath.Version; + } + private set { + _latestVersion = value; + } + } + + /// Latest beta version of the A* Pathfinding Project + public static System.Version latestBetaVersion { + get { + RefreshServerMessage(); + return _latestBetaVersion ?? AstarPath.Version; + } + private set { + _latestBetaVersion = value; + } + } + + /// Summary of the latest update + public static string latestVersionDescription { + get { + RefreshServerMessage(); + return _latestVersionDescription ?? ""; + } + private set { + _latestVersionDescription = value; + } + } + + /// + /// Holds various URLs and text for the editor. + /// This info can be updated when a check for new versions is done to ensure that there are no invalid links. + /// + static Dictionary astarServerData = new Dictionary { + { "URL:modifiers", "https://www.arongranberg.com/astar/docs/modifiers.html" }, + { "URL:astarpro", "https://arongranberg.com/unity/a-pathfinding/astarpro/" }, + { "URL:documentation", "https://arongranberg.com/astar/docs/" }, + { "URL:findoutmore", "https://arongranberg.com/astar" }, + { "URL:download", "https://arongranberg.com/unity/a-pathfinding/download" }, + { "URL:changelog", "https://arongranberg.com/astar/docs/changelog.html" }, + { "URL:tags", "https://arongranberg.com/astar/docs/tags.html" }, + { "URL:homepage", "https://arongranberg.com/astar/" } + }; + + static AstarUpdateChecker() { + // Add a callback so that we can parse the message when it has been downloaded + EditorApplication.update += UpdateCheckLoop; + EditorBase.getDocumentationURL = () => GetURL("documentation"); + } + + + static void RefreshServerMessage () { + if (!hasParsedServerMessage) { + var serverMessage = EditorPrefs.GetString("AstarServerMessage"); + + if (!string.IsNullOrEmpty(serverMessage)) { + ParseServerMessage(serverMessage); + ShowUpdateWindowIfRelevant(); + } + } + } + + public static string GetURL (string tag) { + RefreshServerMessage(); + string url; + astarServerData.TryGetValue("URL:"+tag, out url); + return url ?? ""; + } + + /// Initiate a check for updates now, regardless of when the last check was done + public static void CheckForUpdatesNow () { + lastUpdateCheck = System.DateTime.UtcNow.AddDays(-5); + + // Remove the callback if it already exists + EditorApplication.update -= UpdateCheckLoop; + + // Add a callback so that we can parse the message when it has been downloaded + EditorApplication.update += UpdateCheckLoop; + } + + /// + /// Checking for updates... + /// Should be called from EditorApplication.update + /// + static void UpdateCheckLoop () { + // Go on until the update check has been completed + if (!CheckForUpdates()) { + EditorApplication.update -= UpdateCheckLoop; + } + } + + /// + /// Checks for updates if there was some time since last check. + /// It must be called repeatedly to ensure that the result is processed. + /// Returns: True if an update check is progressing (WWW request) + /// + static bool CheckForUpdates () { + if (updateCheckDownload != null) { + if (updateCheckDownload.isDone) { + if (!string.IsNullOrEmpty(updateCheckDownload.error)) { + Debug.LogWarning("There was an error checking for updates to the A* Pathfinding Project\n" + + "The error might disappear if you switch build target from Webplayer to Standalone because of the webplayer security emulation\nError: " + + updateCheckDownload.error); + updateCheckDownload = null; + return false; + } + lastUpdateCheck = System.DateTime.UtcNow; + UpdateCheckCompleted(updateCheckDownload.downloadHandler.text); + updateCheckDownload.Dispose(); + updateCheckDownload = null; + return false; + } else { + return true; + } + } else { + // Check if it is time to check for updates + // Check for updates a bit earlier if we are in play mode or have the AstarPath object in the scene + // as then the collected statistics will be a bit more accurate + var offsetMinutes = (Application.isPlaying && Time.time > 60) || AstarPath.active != null ? -20 : 20; + var minutesUntilUpdate = lastUpdateCheck.AddDays(updateCheckRate).AddMinutes(offsetMinutes).Subtract(System.DateTime.UtcNow).TotalMinutes; + if (minutesUntilUpdate < 0) { + DownloadVersionInfo(); + } + return updateCheckDownload != null || minutesUntilUpdate < 10; + } + } + + static void DownloadVersionInfo () { + if (!Application.isPlaying) AstarPath.FindAstarPath(); + var script = AstarPath.active != null ? AstarPath.active : UnityCompatibility.FindAnyObjectByType(); + + bool mecanim = UnityCompatibility.FindAnyObjectByType() != null; + string query = updateURL+ + "?v="+AstarPath.Version+ + "&pro=1"+ + "&check="+updateCheckRate+"&distr="+AstarPath.Distribution+ + "&unitypro="+(Application.HasProLicense() ? "1" : "0")+ + "&inscene="+(script != null ? "1" : "0")+ + "&targetplatform="+EditorUserBuildSettings.activeBuildTarget+ + "&devplatform="+Application.platform+ + "&mecanim="+(mecanim ? "1" : "0")+ + "&hasNavmesh=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "NavMeshGraph") ? 1 : 0) + + "&hasPoint=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "PointGraph") ? 1 : 0) + + "&hasGrid=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "GridGraph") ? 1 : 0) + + "&hasLayered=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "LayerGridGraph") ? 1 : 0) + + "&hasRecast=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "RecastGraph") ? 1 : 0) + + "&hasGrid=" + (script != null && script.data.graphs.Any(g => g != null && g.GetType().Name == "GridGraph") ? 1 : 0) + + "&hasCustom=" + (script != null && script.data.graphs.Any(g => g != null && !g.GetType().FullName.Contains("Pathfinding.")) ? 1 : 0) + + "&graphCount=" + (script != null ? script.data.graphs.Count(g => g != null) : 0) + + "&unityversion="+Application.unityVersion + + "&branch="+AstarPath.Branch; + + updateCheckDownload = UnityWebRequest.Get(query); + updateCheckDownload.SendWebRequest(); + } + + /// Handles the data from the update page + static void UpdateCheckCompleted (string result) { + EditorPrefs.SetString("AstarServerMessage", result); + ParseServerMessage(result); + ShowUpdateWindowIfRelevant(); + } + + static void ParseServerMessage (string result) { + if (string.IsNullOrEmpty(result)) { + return; + } + + hasParsedServerMessage = true; + +#if ASTARDEBUG + Debug.Log("Result from update check:\n"+result); +#endif + + string[] splits = result.Split('|'); + latestVersionDescription = splits.Length > 1 ? splits[1] : ""; + + if (splits.Length > 4) { + // First 4 are just compatibility fields + var fields = splits.Skip(4).ToArray(); + + // Take all pairs of fields + for (int i = 0; i < (fields.Length/2)*2; i += 2) { + string key = fields[i]; + string val = fields[i+1]; + astarServerData[key] = val; + } + } + + try { + latestVersion = new System.Version(astarServerData["VERSION:branch"]); + } catch (System.Exception ex) { + Debug.LogWarning("Could not parse version\n"+ex); + } + + try { + latestBetaVersion = new System.Version(astarServerData["VERSION:beta"]); + } catch (System.Exception ex) { + Debug.LogWarning("Could not parse version\n"+ex); + } + } + + static void ShowUpdateWindowIfRelevant () { +#if !ASTAR_ATAVISM + try { + System.DateTime remindDate; + var remindVersion = new System.Version(EditorPrefs.GetString("AstarRemindUpdateVersion", "0.0.0.0")); + if (latestVersion == remindVersion && System.DateTime.TryParse(EditorPrefs.GetString("AstarRemindUpdateDate", "1/1/1971 00:00:01"), out remindDate)) { + if (System.DateTime.UtcNow < remindDate) { + // Don't remind yet + return; + } + } else { + EditorPrefs.DeleteKey("AstarRemindUpdateDate"); + EditorPrefs.DeleteKey("AstarRemindUpdateVersion"); + } + } catch { + Debug.LogError("Invalid AstarRemindUpdateVersion or AstarRemindUpdateDate"); + } + + var skipVersion = new System.Version(EditorPrefs.GetString("AstarSkipUpToVersion", AstarPath.Version.ToString())); + + if (AstarPathEditor.FullyDefinedVersion(latestVersion) != AstarPathEditor.FullyDefinedVersion(skipVersion) && AstarPathEditor.FullyDefinedVersion(latestVersion) > AstarPathEditor.FullyDefinedVersion(AstarPath.Version)) { + EditorPrefs.DeleteKey("AstarSkipUpToVersion"); + EditorPrefs.DeleteKey("AstarRemindUpdateDate"); + EditorPrefs.DeleteKey("AstarRemindUpdateVersion"); + + AstarUpdateWindow.Init(latestVersion, latestVersionDescription); + } +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs.meta new file mode 100644 index 0000000..fd7da51 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateChecker.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8df9913c9ee004459b24d89644e573d7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs new file mode 100644 index 0000000..64e81c2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs @@ -0,0 +1,101 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + public class AstarUpdateWindow : EditorWindow { + static GUIStyle largeStyle; + static GUIStyle normalStyle; + Version version; + string summary; + bool setReminder; + + public static AstarUpdateWindow Init (Version version, string summary) { + // Get existing open window or if none, make a new one: + AstarUpdateWindow window = EditorWindow.GetWindow(true, "", true); + + window.position = new Rect(Screen.currentResolution.width/2 - 300, Mathf.Max(5, Screen.currentResolution.height/3 - 150), 600, 400); + window.version = version; + window.summary = summary; + window.titleContent = new GUIContent("New version of the A* Pathfinding Project"); + return window; + } + + public void OnDestroy () { + if (version != null && !setReminder) { + Debug.Log("Closed window, reminding again in a week"); + EditorPrefs.SetString("AstarRemindUpdateDate", DateTime.UtcNow.AddDays(7).ToString(System.Globalization.CultureInfo.InvariantCulture)); + EditorPrefs.SetString("AstarRemindUpdateVersion", version.ToString()); + } + } + + void OnGUI () { + if (largeStyle == null) { + largeStyle = new GUIStyle(EditorStyles.largeLabel) { + fontSize = 32, + alignment = TextAnchor.UpperCenter, + richText = true + }; + + normalStyle = new GUIStyle(EditorStyles.label) { + wordWrap = true, + richText = true + }; + } + + if (version == null) { + return; + } + + GUILayout.Label("New Update Available!", largeStyle); + GUILayout.Label("There is a new version of the A* Pathfinding Project available for download.\n" + + "The new version is " + version + ", you are using " + AstarPath.Version + "\n\n"+ + "Summary:\n"+summary, normalStyle + ); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + + Color col = GUI.color; + GUI.backgroundColor *= new Color(0.5f, 1f, 0.5f); + if (GUILayout.Button("Take me to the download page!", GUILayout.Height(30), GUILayout.MaxWidth(300))) { + Application.OpenURL(AstarUpdateChecker.GetURL("download")); + } + GUI.backgroundColor = col; + + + if (GUILayout.Button("What's new? (full changelog)")) { + Application.OpenURL(AstarUpdateChecker.GetURL("changelog")); + } + + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("Skip this version", GUILayout.MaxWidth(100))) { + EditorPrefs.SetString("AstarSkipUpToVersion", version.ToString()); + setReminder = true; + Close(); + } + + if (GUILayout.Button("Remind me later (1 month)", GUILayout.MaxWidth(200))) { + EditorPrefs.SetString("AstarRemindUpdateDate", DateTime.UtcNow.AddDays(30).ToString(System.Globalization.CultureInfo.InvariantCulture)); + EditorPrefs.SetString("AstarRemindUpdateVersion", version.ToString()); + setReminder = true; + Close(); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs.meta new file mode 100644 index 0000000..624c04d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/AstarUpdateWindow.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97d8d5fc46a644f22b0d66c6ee18e753 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs new file mode 100644 index 0000000..614c66c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs @@ -0,0 +1,19 @@ +namespace Pathfinding { + /// Added to editors of custom graph types + [System.AttributeUsage(System.AttributeTargets.All, Inherited = false, AllowMultiple = true)] + public class CustomGraphEditorAttribute : System.Attribute { + /// Graph type which this is an editor for + public System.Type graphType; + + /// Name displayed in the inpector + public string displayName; + + /// Type of the editor for the graph + public System.Type editorType; + + public CustomGraphEditorAttribute (System.Type t, string displayName) { + graphType = t; + this.displayName = displayName; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs.meta new file mode 100644 index 0000000..796cadc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/CustomGraphEditorAttribute.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 576dcf42aca804a48b5923974edaee01 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets.meta new file mode 100644 index 0000000..f628909 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b2dff938dc634365967d447c540e208 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin new file mode 100644 index 0000000..4755d7a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin @@ -0,0 +1,3198 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12001, guid: 0000000000000000e000000000000000, type: 0} + m_Name: AstarEditorSkinDark + m_EditorClassIdentifier: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_box: + m_Name: box + m_Normal: + m_Background: {fileID: 11001, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_button: + m_Name: button + m_Normal: + m_Background: {fileID: -573041650897247223, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: -1537457205435906773, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: -7527060558648309217, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: -8766172725880940643, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 7832598784815925287, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: -9059002882264723198, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.007843138, g: 0.007843138, b: 0.007843138, a: 1} + m_OnNormal: + m_Background: {fileID: -4454209017672384243, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: -4993635991501620529, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.007843138, g: 0.007843138, b: 0.007843138, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 4047951448802137905, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: 7382603045041641420, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.007843138, g: 0.007843138, b: 0.007843138, a: 1} + m_OnFocused: + m_Background: {fileID: 8556163245987529883, guid: 0000000000000000d000000000000000, + type: 0} + m_ScaledBackgrounds: + - {fileID: 8718812295543890339, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 3 + m_Bottom: 3 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 2 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_toggle: + m_Name: toggle + m_Normal: + m_Background: {fileID: 11018, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.89112896, g: 0.89112896, b: 0.89112896, a: 1} + m_Hover: + m_Background: {fileID: 11014, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11013, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11016, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8901961, g: 0.8901961, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 11015, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11017, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 14 + m_Right: 0 + m_Top: 14 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 15 + m_Right: 0 + m_Top: 3 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 0 + m_Top: -4 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_label: + m_Name: label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.86734694, g: 0.86734694, b: 0.86734694, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textField: + m_Name: textfield + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textArea: + m_Name: textarea + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_window: + m_Name: window + m_Normal: + m_Background: {fileID: 11023, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11022, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 18 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 10 + m_Right: 10 + m_Top: 20 + m_Bottom: 10 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: -18} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSlider: + m_Name: horizontalslider + m_Normal: + m_Background: {fileID: 11009, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: -3 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSliderThumb: + m_Name: horizontalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 7 + m_Right: 7 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalSlider: + m_Name: verticalslider + m_Normal: + m_Background: {fileID: 11021, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Overflow: + m_Left: -2 + m_Right: -3 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalSliderThumb: + m_Name: verticalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 7 + m_Bottom: 7 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_horizontalScrollbar: + m_Name: horizontalscrollbar + m_Normal: + m_Background: {fileID: 11008, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 9 + m_Right: 9 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 1 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarThumb: + m_Name: horizontalscrollbarthumb + m_Normal: + m_Background: {fileID: 11007, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 13 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarLeftButton: + m_Name: horizontalscrollbarleftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarRightButton: + m_Name: horizontalscrollbarrightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbar: + m_Name: verticalscrollbar + m_Normal: + m_Background: {fileID: 11020, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 9 + m_Bottom: 9 + m_Margin: + m_Left: 1 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarThumb: + m_Name: verticalscrollbarthumb + m_Normal: + m_Background: {fileID: 11019, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalScrollbarUpButton: + m_Name: verticalscrollbarupbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarDownButton: + m_Name: verticalscrollbardownbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_ScrollView: + m_Name: scrollview + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_CustomStyles: + - m_Name: PixelBox + m_Normal: + m_Background: {fileID: 2800000, guid: 5d951d0a838a34f40ac2b9ce8968a7d6, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 4 + m_Bottom: 6 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 1 + m_Right: 1 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: Banner + m_Normal: + m_Background: {fileID: 2800000, guid: 05f85867f5d8e5f4191acbb74d301608, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: d1f703801f463704e870485f44c3831c, type: 3} + m_TextColor: {r: 0.11320752, g: 0.11320752, b: 0.11320752, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 4 + m_Bottom: 0 + m_Padding: + m_Left: 8 + m_Right: 8 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 10 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 17 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: ColorInterpolationBox + m_Normal: + m_Background: {fileID: 2800000, guid: 0790ee8db18ed49ed8369be285199835, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 7 + m_Right: 7 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 6 + m_Bottom: 4 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 4 + m_Overflow: + m_Left: 1 + m_Right: 1 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: StretchWidth + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: BoxHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.6334038, g: 0.63352257, b: 0.6333745, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.7397959, g: 0.7397959, b: 0.7397959, a: 1} + m_Focused: + m_Background: {fileID: 2800000, guid: db5b85df63e094a1c96f11019bccc577, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 8 + m_Right: 6 + m_Top: 2 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 18 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: TopBoxHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.6334038, g: 0.63352257, b: 0.6333745, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.7397959, g: 0.7397959, b: 0.7397959, a: 1} + m_Focused: + m_Background: {fileID: 2800000, guid: db5b85df63e094a1c96f11019bccc577, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.63529414, g: 0.63529414, b: 0.63529414, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 3 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 5} + m_FixedWidth: 0 + m_FixedHeight: 24 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: PixelBox3 + m_Normal: + m_Background: {fileID: 2800000, guid: 7991166a167af4b4793b4fc8fcc3bfe2, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9043fef44c60647b7a763eb86f912ba1, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 5 + m_Bottom: 2 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 2 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: PixelButton + m_Normal: + m_Background: {fileID: 2800000, guid: b5a4a564ac2dc4261a13719673f157e1, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: c730485723dee4bcbad9d8a7593a8799, type: 3} + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 9129e8496f4a9d641a05733c3193a430, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: b6a8e8f0cd4624f4ca8807a46e426442, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: d0f7738d6b5b2420b9a77bfef3926e2a, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: b9d7d6d7befb34b9c889e6195746c452, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 1 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: LinkButton + m_Normal: + m_Background: {fileID: 2800000, guid: 53ee29e7cbeb545bcb1ca238a2736fb8, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 1066e2a70f10b481984312db993dadc8, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 34 + m_FixedHeight: 34 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: CloseButton + m_Normal: + m_Background: {fileID: 2800000, guid: 9496521dd08a64ed2a92bc4e90a3a271, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 921542314cf2f4a55952ad059454677e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -2 + m_Right: 2 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 13 + m_FixedHeight: 13 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GridPivotSelectButton + m_Normal: + m_Background: {fileID: 2800000, guid: 37972c32346084c13bfddb8c3dbed143, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 7ea4fc739ffbb4af1a466f655bf1d178, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 7ea4fc739ffbb4af1a466f655bf1d178, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 37972c32346084c13bfddb8c3dbed143, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GridPivotSelectBackground + m_Normal: + m_Background: {fileID: 2800000, guid: 11b8d44fcb3cf4c048a576df708e0ec6, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 7 + m_Right: 4 + m_Top: 14 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: CollisionHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.6334038, g: 0.63352257, b: 0.6333745, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: InfoButton + m_Normal: + m_Background: {fileID: 2800000, guid: 1986ec06d68774254928f44b896a3913, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 623f6adf01f324cb7bc6aefe7f87d623, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: d893792094267994782a0ed91c3d5748, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 623121c393230f34fa02152b638144e6, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: d008deade2077407fbf14446df2547c9, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9b266e42522c048f6b62832985be51ed, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: d008deade2077407fbf14446df2547c9, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9b266e42522c048f6b62832985be51ed, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 1986ec06d68774254928f44b896a3913, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 623f6adf01f324cb7bc6aefe7f87d623, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: DuplicateButton + m_Normal: + m_Background: {fileID: 2800000, guid: de7e8d0cdf11edc44aff580ed8ef73c7, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 92ab6339e9b49ac478155629453bd6ef, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 103e3480a7bea7c4c86e3496ef1676dd, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 17770c378744fa24d9bb3d8f2e3ff802, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 9db63e61a665ba74c96cbdbe5ce0f7a1, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 167d2120e461bd444a13ee562df6a3c5, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 1986ec06d68774254928f44b896a3913, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 623f6adf01f324cb7bc6aefe7f87d623, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: EditButton + m_Normal: + m_Background: {fileID: 2800000, guid: 705155b2c3fce47f0a818ac32d5abe3f, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: e8ebb72a41d314b288545fddfd20cafd, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 999625d84327cee438ace602df54e656, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: daae080bc5b18274f96637529e5c9d3e, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: e0cb3cc51f25a48bf8b0c647452b09b1, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: f6a4baf90a7bb4e4a92abe07f1a0a9d3, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GizmoButton + m_Normal: + m_Background: {fileID: 2800000, guid: b05124c2b9dc5429a84ef0195a916fce, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 3772050cdbd2a472789aa76d6de5b270, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 535ef458a8b49f343b5e68267c3ead65, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: abdefba70bd618341a0130e354c68094, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 81679c1d59c8a1c49910fd365416d017, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 0b711386fb087cf4da2d9d6de03cff58, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 54ad5f6eb8a9349568e58cd1204388a2, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: a454b67ef7bd04780aca99e364202679, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 2800000, guid: b75c2d9e5306a944d8f46b030d24f61c, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 2030b60f83d0f754694d7684af95b377, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 790d4032d74412f45a0851f8ba194517, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: d685901fd1a1f95498ebcd28f6b5b19e, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: PixelBox3Separator + m_Normal: + m_Background: {fileID: 2800000, guid: 3a08de11a34c44da28de4c086fd13461, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 8 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: 2 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 4 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: GridSizeLock + m_Normal: + m_Background: {fileID: 2800000, guid: c43744025f7024e20b5fe880600d19a7, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 957d0fd6ae3494da9aed50c5d7d7ce4e, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: aaae516f9c5404f77a3e586cdd59695b, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 6c38830c1556942fcb6ce893085d1daa, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: aaae516f9c5404f77a3e586cdd59695b, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 6c38830c1556942fcb6ce893085d1daa, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: c43744025f7024e20b5fe880600d19a7, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 957d0fd6ae3494da9aed50c5d7d7ce4e, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 5 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 27 + m_FixedHeight: 28 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: UpArrow + m_Normal: + m_Background: {fileID: 2800000, guid: b1722c752c8754c5281b2efd6cf8477c, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 4c914fdaf554243fe9fb26afc323caef, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 11 + m_FixedHeight: 10 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: DownArrow + m_Normal: + m_Background: {fileID: 2800000, guid: 12ea2fbedc5cc4bd191d795f44b2de75, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: d8951099054c64beaaa49d675d71cfcf, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 11 + m_FixedHeight: 10 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SmallReset + m_Normal: + m_Background: {fileID: 2800000, guid: fb3e1d793e4f0459f8b913d0d2d76e6c, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 990963b32226c440fb1a72e47e0ad7a2, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 12 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SceneBoxDark + m_Normal: + m_Background: {fileID: 2800000, guid: a4bb5812f98bf40a68d9c1a69ad860cb, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: f834e7df6f42446b59c83dca1e5bd098, type: 3} + m_TextColor: {r: 0.74509805, g: 0.74509805, b: 0.74509805, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 20 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: -16} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: ScenePrefixLabel + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 1 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: HexagonDiameter + m_Normal: + m_Background: {fileID: 2800000, guid: aec40eb6c450c4bdea484100ff9fb9a4, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 36094f49ffe31443eb5e8ea185115a1d, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 8 + m_Right: 8 + m_Top: 5 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 32 + m_FixedHeight: 32 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: HexagonWidth + m_Normal: + m_Background: {fileID: 2800000, guid: 43a4af66a8c594a2daa4744ed77da2dd, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 321a1fe2502bc493d87337a3a4429cf5, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 8 + m_Right: 8 + m_Top: 5 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 32 + m_FixedHeight: 32 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SimpleDeleteButton + m_Normal: + m_Background: {fileID: 2800000, guid: 5d7e9740bfd1d470180f5e4823171712, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: f88ac1d30fa7144dd94337fce44b9239, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: f6b7bcad2476249879798d0ee64ea2e2, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 14e87db4f581b4f8cb79da48d87e01ee, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + m_Settings: + m_DoubleClickSelectsWord: 1 + m_TripleClickSelectsLine: 1 + m_CursorColor: {r: 1, g: 1, b: 1, a: 1} + m_CursorFlashSpeed: -1 + m_SelectionColor: {r: 1, g: 0.38403907, b: 0, a: 0.7} +--- !u!1002 &11400001 +EditorExtensionImpl: + serializedVersion: 6 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin.meta new file mode 100644 index 0000000..052aa3f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinDark.guiskin.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8fc713511eb5d4fb9937ab63de6af346 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin new file mode 100644 index 0000000..412753e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin @@ -0,0 +1,3186 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12001, guid: 0000000000000000e000000000000000, type: 0} + m_Name: AstarEditorSkinLight + m_EditorClassIdentifier: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_box: + m_Name: box + m_Normal: + m_Background: {fileID: 11001, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_button: + m_Name: button + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: -1537457205435906773, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: -8766172725880940643, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: -9059002882264723198, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: -4993635991501620529, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 7382603045041641420, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 8718812295543890339, guid: 0000000000000000d000000000000000, type: 0} + m_TextColor: {r: 0.03529412, g: 0.03529412, b: 0.03529412, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 3 + m_Right: 3 + m_Top: 2 + m_Bottom: 2 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 2 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_toggle: + m_Name: toggle + m_Normal: + m_Background: {fileID: 11018, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.89112896, g: 0.89112896, b: 0.89112896, a: 1} + m_Hover: + m_Background: {fileID: 11014, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11013, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11016, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8901961, g: 0.8901961, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 11015, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11017, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 14 + m_Right: 0 + m_Top: 14 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 15 + m_Right: 0 + m_Top: 3 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 0 + m_Top: -4 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_label: + m_Name: label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textField: + m_Name: textfield + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textArea: + m_Name: textarea + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_window: + m_Name: window + m_Normal: + m_Background: {fileID: 11023, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11022, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 18 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 10 + m_Right: 10 + m_Top: 20 + m_Bottom: 10 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: -18} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSlider: + m_Name: horizontalslider + m_Normal: + m_Background: {fileID: 11009, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: -3 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSliderThumb: + m_Name: horizontalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 7 + m_Right: 7 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalSlider: + m_Name: verticalslider + m_Normal: + m_Background: {fileID: 11021, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Overflow: + m_Left: -2 + m_Right: -3 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalSliderThumb: + m_Name: verticalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 7 + m_Bottom: 7 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_horizontalScrollbar: + m_Name: horizontalscrollbar + m_Normal: + m_Background: {fileID: 11008, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 9 + m_Right: 9 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 1 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarThumb: + m_Name: horizontalscrollbarthumb + m_Normal: + m_Background: {fileID: 11007, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 13 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarLeftButton: + m_Name: horizontalscrollbarleftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarRightButton: + m_Name: horizontalscrollbarrightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbar: + m_Name: verticalscrollbar + m_Normal: + m_Background: {fileID: 11020, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 9 + m_Bottom: 9 + m_Margin: + m_Left: 1 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarThumb: + m_Name: verticalscrollbarthumb + m_Normal: + m_Background: {fileID: 11019, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalScrollbarUpButton: + m_Name: verticalscrollbarupbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarDownButton: + m_Name: verticalscrollbardownbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_ScrollView: + m_Name: scrollview + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_CustomStyles: + - m_Name: PixelBox + m_Normal: + m_Background: {fileID: 2800000, guid: 2fa6580cc9f7f40919411d1ed85c6fe1, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 4 + m_Bottom: 6 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 1 + m_Right: 1 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: Banner + m_Normal: + m_Background: {fileID: 2800000, guid: 77e0d25913a365544942b955bd37cb25, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 14af83638dbc46f45837bdbdf32d53a0, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 4 + m_Bottom: 0 + m_Padding: + m_Left: 8 + m_Right: 8 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 10 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 17 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: ColorInterpolationBox + m_Normal: + m_Background: {fileID: 2800000, guid: 9bf4f8f9d8795455bb87213dd56ed793, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 7 + m_Right: 7 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 6 + m_Bottom: 4 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 4 + m_Overflow: + m_Left: 1 + m_Right: 1 + m_Top: 0 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: StretchWidth + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: BoxHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.7397959, g: 0.7397959, b: 0.7397959, a: 1} + m_Focused: + m_Background: {fileID: 2800000, guid: 620f088d04c284b21939ce24996fdbee, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12156863, g: 0.12156863, b: 0.12156863, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 8 + m_Right: 6 + m_Top: 2 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 18 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: TopBoxHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.7397959, g: 0.7397959, b: 0.7397959, a: 1} + m_Focused: + m_Background: {fileID: 2800000, guid: 620f088d04c284b21939ce24996fdbee, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12156863, g: 0.12156863, b: 0.12156863, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 3 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 5} + m_FixedWidth: 0 + m_FixedHeight: 24 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: PixelBox3 + m_Normal: + m_Background: {fileID: 2800000, guid: 86cd0618b10f3494c831ac9215649110, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 5ed56a3e069cd4b5aa4360154acc026a, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 5 + m_Bottom: 2 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 2 + m_Bottom: 2 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: PixelButton + m_Normal: + m_Background: {fileID: 2800000, guid: 3240f334343ca4dee8239c16d2de57c6, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 6444e8eb74e6f40e0a966d1e26744d08, type: 3} + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: c051ffc737cd54d909137d2937e82930, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 19e49615b2c8540fd8a11acd2a26c335, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 1 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: LinkButton + m_Normal: + m_Background: {fileID: 2800000, guid: 53ee29e7cbeb545bcb1ca238a2736fb8, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 1066e2a70f10b481984312db993dadc8, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 34 + m_FixedHeight: 34 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: CloseButton + m_Normal: + m_Background: {fileID: 2800000, guid: 9496521dd08a64ed2a92bc4e90a3a271, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 921542314cf2f4a55952ad059454677e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -2 + m_Right: 2 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 13 + m_FixedHeight: 13 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GridPivotSelectButton + m_Normal: + m_Background: {fileID: 2800000, guid: 9de7a15ab4e1d48da9861247bc34226d, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 3d11311e97436432e897a4bd0b5fbfac, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 3d11311e97436432e897a4bd0b5fbfac, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 9de7a15ab4e1d48da9861247bc34226d, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GridPivotSelectBackground + m_Normal: + m_Background: {fileID: 2800000, guid: b593db43606434498a7200f8281297d8, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.34183675, g: 0.34183675, b: 0.34183675, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 7 + m_Right: 4 + m_Top: 14 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: CollisionHeader + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: InfoButton + m_Normal: + m_Background: {fileID: 2800000, guid: efb7b60811b9a4ec7b92ecfcaa9bcd0d, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7ef0e648811f14f65b69c2df3f27de11, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 4d454c89b74d19343a4b00cb2ee4768c, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: d525acbbcdaf79248881d59b575b9551, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 7604699b5bc8f4343b03cc4d0b603403, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 8acb97c3fcc694bf3b7d33b1ff321337, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 7604699b5bc8f4343b03cc4d0b603403, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 8acb97c3fcc694bf3b7d33b1ff321337, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: efb7b60811b9a4ec7b92ecfcaa9bcd0d, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7ef0e648811f14f65b69c2df3f27de11, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: DuplicateButton + m_Normal: + m_Background: {fileID: 2800000, guid: d1be04a9da3e14848842e6aa7f352ea7, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 80b63a82e14cef34980c746c6dfe7c65, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 018e807d0f1b11c4ebd192f0e8b8d479, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 9f5dbec10705c6d4b8cac5c6f51e22b9, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 74de4b3c714a76040a1d002d5608b867, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 1b546bc75c806c34cae9fb22ff12fdb6, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: efb7b60811b9a4ec7b92ecfcaa9bcd0d, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7ef0e648811f14f65b69c2df3f27de11, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: EditButton + m_Normal: + m_Background: {fileID: 2800000, guid: 71ef598858974450ead310c33f29d5d3, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7e276a83ed42e4e3584a50b6084eef8d, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: ad07ab75d7d383044af9d7a3f6a17674, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: eaff2ac91e2f95645a0c8088eacfa37a, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 3354fe2e1951d420fa49d8f4fec2954b, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: f7ae6f90d33d043c684812513307ec16, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: + - {fileID: 0} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: GizmoButton + m_Normal: + m_Background: {fileID: 2800000, guid: c780955fc9d0f458abc0de8f2f9cedb2, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: deb2afb942c3b42fbba8823da7f05894, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 47aed39adb8ef6f4c87bce105c0eee4c, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 37a97633b5fd10843a3477f6e76d1993, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 6ddc02fd0dc8cd741ac72887911a191e, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 61a822bba0c5859438ac4b28923286bc, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 455f4fe795672465c8a4e8db3c6b9ccb, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 731ce0beec302410299a12fa1da2dc54, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 2800000, guid: 464dd6a898617fd47a12b97466c54ae1, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: c94104aaad48ce34c9f205f6c26e4190, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: d3070b98e2ba5b8428937a801fed5125, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 61a822bba0c5859438ac4b28923286bc, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 1 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: PixelBox3Separator + m_Normal: + m_Background: {fileID: 2800000, guid: e101da2dcb7a14fa2aed1a65c384a99b, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 8 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: 2 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 4 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: GridSizeLock + m_Normal: + m_Background: {fileID: 2800000, guid: 3805c157b9b38472684befb652c5d213, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: d04d4a88875f842c88825d3d5b2b529c, type: 3} + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 5e0cd84e9d84642ac883c25ca8d667ac, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7b229d059f89d49918aec1466c0c5819, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 2800000, guid: 5e0cd84e9d84642ac883c25ca8d667ac, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 7b229d059f89d49918aec1466c0c5819, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 2800000, guid: 3805c157b9b38472684befb652c5d213, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: d04d4a88875f842c88825d3d5b2b529c, type: 3} + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 5 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 27 + m_FixedHeight: 28 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: UpArrow + m_Normal: + m_Background: {fileID: 2800000, guid: b1722c752c8754c5281b2efd6cf8477c, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 4c914fdaf554243fe9fb26afc323caef, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 11 + m_FixedHeight: 10 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: DownArrow + m_Normal: + m_Background: {fileID: 2800000, guid: 12ea2fbedc5cc4bd191d795f44b2de75, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: d8951099054c64beaaa49d675d71cfcf, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 11 + m_FixedHeight: 10 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SmallReset + m_Normal: + m_Background: {fileID: 2800000, guid: fb3e1d793e4f0459f8b913d0d2d76e6c, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.12244898, g: 0.12244898, b: 0.12244898, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 990963b32226c440fb1a72e47e0ad7a2, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 12 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SceneBoxDark + m_Normal: + m_Background: {fileID: 2800000, guid: a4bb5812f98bf40a68d9c1a69ad860cb, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: f834e7df6f42446b59c83dca1e5bd098, type: 3} + m_TextColor: {r: 0.745283, g: 0.745283, b: 0.745283, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 20 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: -16} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: ScenePrefixLabel + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 1 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: HexagonDiameter + m_Normal: + m_Background: {fileID: 2800000, guid: b1817d211ea3547f088cddf76f3e6647, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 5a358b45ec6c34f20b9f792e1f259b4f, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 8 + m_Right: 8 + m_Top: 5 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 32 + m_FixedHeight: 32 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: HexagonWidth + m_Normal: + m_Background: {fileID: 2800000, guid: c3f63c474ba404e88a6a16a074cced39, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 6010a63a238ef4899872fe5bd998dbb5, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 8 + m_Right: 8 + m_Top: 5 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 32 + m_FixedHeight: 32 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: SimpleDeleteButton + m_Normal: + m_Background: {fileID: 2800000, guid: 168346d70f1fe4211ab22c360451c602, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 003032829f3e34ba3bbf576dbdbf4961, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: 3d1e3e2345df94db3be6501151e8db26, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: e95696bf2c915483a965d5b8c7c41fb0, type: 3} + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.4745098, g: 0.70980394, b: 0.8901961, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + m_Settings: + m_DoubleClickSelectsWord: 1 + m_TripleClickSelectsLine: 1 + m_CursorColor: {r: 1, g: 1, b: 1, a: 1} + m_CursorFlashSpeed: -1 + m_SelectionColor: {r: 1, g: 0.38403907, b: 0, a: 0.7} +--- !u!1002 &11400001 +EditorExtensionImpl: + serializedVersion: 6 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin.meta new file mode 100644 index 0000000..89d157f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/AstarEditorSkinLight.guiskin.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66428235a3cda4584bd90de6998ad4d2 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons.meta new file mode 100644 index 0000000..41a5e6f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1088224cbcd07314797b5131074dcc61 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png new file mode 100644 index 0000000000000000000000000000000000000000..25317e332dee57e57183a1e2a5ef918fde209e60 GIT binary patch literal 5307 zcmeHL`%@EF6uz6y?m`Fz6k!ts0mEpjf-5L!tw0cYjE)07sI8_%bTC?}silsk3L)Bv zH9V@;irO}a9b>IZ9copE3KL&-Kw<~mB90L8`AnA^-sRrl%z@1OTD`MSvGh|195N&ZU36wP|mC0KoSXnLlL3k;y#( z1m>kDD;IA%m}7G|5-bW5t5)-QIL_Px4o8lpv_F#)q$t#CwNoOA1SyRQ@~E!3m~r56 zj0i^f7%7L5ZWtK`FPVio;!;~CwWsk;%4rAs*XFLFY~!5R0g@!257RuvsL!MpA}ETE z@b?n_Uaf~qj`%@gd0APgb@=WVQL5Ju_iUG8a+0E`SEX7qh|z@g8M-F!KD=IJ5WYg8 zP)0O}!e%Kqic}2^4bLfIwvh_F>B;$cnx^?ANSLp?IXfTjCoLG=eM5_aS^# zL7-Lv8T3N}-GH19)FLKcM|g;hcWUL*Ry*xl$Z=N#tdh#mts8tT-#lFn??p8TjU>o= zW=yaD+RMEFzNbZN*I~lJH3@+*{2JagT3&ba>tg63qUC!}>m420BwgyEj?zB-=xC*f z51sc*YL>nObEU;vAD$G|{gl8d9EPPD{$koq`wMRHR`c2Id5>B=ykOS-ABSW1Wit-G zTSTve&-X#LAGO4u)(*?L7lLQf5L(eD05|Mm^_E~)-O{UNQR2zpWLUMXt7EJGpFVoWNz51ycedq>|`$e#2p2 z?y<_mo5tH#<~KGrbxQySACw@;wY-$Yo)M>^XK4Mbm+130y?CG^=&d+np#2KIziapQ zp^*IF3C;0_D?E3~nSeeMR4hZ}xi>|a8#E&i@G%_+89%C%fAuV`5~OHsTjJ#c=E8t{ zaBvdMl?2BPm}SeKGTfuu2wGQ~D3%^=DfiLE`i?Q~YfOYwRfAcLq6c@{cXwRgNkj9K zAM$tE$Px43qt|`ix4I^y2p!6($1L$#ZmQUXpr-ahWo6t~f>o6N_BOH*TJcWk?!ZWn8J8po0@vK2zRBN?Ol`x zc&uu0%^&u=*}%;bdxqp+9iS-NYQ&9-l>_VCH+++u?l1BYI^>P;p$SJT`c|k1P6}2* zuPi3M^{ma?`q5x9usoiq6}mw(CQZD+sLrY&jWpm0FUB=**%LsMEHq8GC+2SU2P&`V z=+gwOq2oy+JHvx@k5MvUo~{w@Mx9Q%w@+CIl&SjClH3F-6d|HQepz1lEX-?gtJRp$ zc?|(5ls-fSz6|d3=gH&mZH49*(#&iW2Ia0kWopp`WA0}7{S0Yt08yPvLR2-~Ub3%! zf&}(@qfv87HV(px+-L|}T2G)gv+OCimSjgDFpi7ritlc(B%22!+^>dA_aSX+|KqpO z;vMoQ1yfGJ5Pw$Z2eeR#yCE3;>*{IuL@^Neh>|*2uzsJ1;RUSFgN`gl$OkYs0jatW zqjwJy#tmuAwFSWQUSHW*)6h=klChCa$Wy$`H9UCHVx-ajnN_j(W zis)Lxy^IVks2>+%w=jj))GJX)$WICGGwPKDSEv!D#Ml*NEjMu*zVo(-t7vwb42Mjb zotoj0Nd>=p=O%i)z_=x>AVjRAU0g>B0<9cmWBQw0V21H5Sqg${z!2n21?0+rvkaOc zq<9>|pl&1A6F$Y66d!I#xXhUpC?s6u6vCRJ#;8s)hQgYDbhl(uHC#X4B9`MuX!3Rw zZ0O6miW*UTQjC4u-N)B6sSDg(YY3+WUoKL!lROF6g4u^vtb#C^QPx!fS5ZNHN*8IM zFx}N1^w+7GR~s~4GHgx|w+H?gA7q#hY7UL9oPNI><*o|msru@^@S)&n>C}rS=7mKw OQz(7zg5(oPYW=?lsnY`h literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png.meta new file mode 100644 index 0000000..1ec46c3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AIDestinationSetter@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 8dca5cfad5de0f444847aaaaa7cf73b8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AstarPath@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/AstarPath@256.png new file mode 100644 index 0000000000000000000000000000000000000000..b850c2b8e60bf3ba57c14bfeab51d0eb1fbe0585 GIT binary patch literal 9036 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 AfdBvi literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/DynamicGridObstacle@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/DynamicGridObstacle@256.png.meta new file mode 100644 index 0000000..aa9d93f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/DynamicGridObstacle@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: edb61d815bfcfa548b5e947d759e9fb6 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png new file mode 100644 index 0000000000000000000000000000000000000000..757939960c2b20e8024fd8ea339d167c91fe82f9 GIT binary patch literal 7628 zcmeHMiCYs_*T0j=MhHnzgb4b3?Kn(-e_+NJXFmNgj2>h-{N{+U zJaJe!XdFDgF1zpJ%=OR4!f-8Z{2)a*1Q$2zmJGepDVne;ChYL;1UdFAB`8yf22d+F@ky42WtVJTx-xbjf z;b(FpzZK$KARTj*@{!ErdsJbc(2p>PHcl0y`?U ziX?o1xMdejKVhobF?2Dx`gf^U&eeisODy8+t`HCZuYgSgRea zS|!AE0WtYILHW;8C>4BjLhgbGCCmB@eJf!m$OULZKQ-AhK?;2dofLS}8E<2kaft3y zr=YCYbGeOuDjL@Li0wvaX*0MUF7o`{ zg#4DiAS%jG==*@;EuFA6{P zmZEKoVh+32iO}?)&>Bt>C3BH&79+Ft;t=V!89}mAd(q(|OnX9<^wtP%$1^tR=*G>;Jvna<@r&)9osfqgz{^#UnQdt0 z!*znHaID2baZ^-uanMeS^7~Cg-LTayxYg(HX{T@azdrvn>j$OYa^I_rOva~bs}W>) zN=VN|K7FE5D=uELzVy?kdv9~{K3ca(=;)jM+$}o`k%q0+F}nh0xSK*3L!TK0=92BG zg0Ae3ngX1$+we3XNuLC81^(InEC#HI^gVoLG?K!K^4;v)KIH;2s51JRR0y-qKyS~z zvt3{7o;!zMd=N;e5y*3rz2QCSESdUyn_z@RxPJf{-MUVakgh*6CInA>s%9U58pyE{ z<|DBbE06U+#sgL?)=yI)VpeOJM&BlOLmx|Hm&jq`k_-=t9l47Z<6_!~8_?2?dp`sW z{HD8;Ki?j4rH^_cmWnPg%r%jX#kkB3DQ=sgneZT-jZ{%FPYPBYDC0JdVjc`Mp^j?= zSj4=_+x^%PS8bvH>vM-$w!kV{&xOjXC|DpUPN3SFsNn{LI=bn~&5{kgmQ;A=&8TZ044;4Qtnxb`5~A6lq6wfF2VJT!&4p z#k1aayqeWx6$1)<^i$)KNcwNVCR>XsgsM$tF^_39QYZ}+1SQ##u;9STw+u5ksB;_} z`)g@P`>4u7Gzu8TV4E6tj3m4pFtQOioieVmxz-8kwU(`wb5tj}$`ur%A4W@-n$>5u z3=~8sxjP+EenBLIk>(ya|AZ5z)&YI8H)?xMcjBc~Q$@iIz<#W}4Ns*|EGYO|cBHK3 zbZl!mC!TOrGr|n%o?f*hC|H?P&{f}jatskB1=~xl?kn^K1#9&Om;Cw1LYxywnUZ zE!#Gq)n7nTfw`j%ds#=8Ld5NA+0x$LLedhHmk3w-?v;dW^&rGsi|+_`YXbHp5~TJN z6C;hkT8wKA#20RsG-nbStED-s9~pcYbV$J~?fyJ2<2YGNNF^<8SScEbq(qh8{6Sz= zVQA9D0|lfw$g_#@2gOw4@EQO+t677?uf*lDRx9i>AR@1Yr7MRPAg-9WQI>wzn`jJ{ z(uSnaF|q~n3K|*>w_@?&5<9j@ACqd=#^vT$wRxbt0H*7Bv|K+&_h?@zEzlKx9BeGw> zD(brvh;Ab$yA;A3X>0cMu3E&`P_9A4&DSOC{*yt^ndxt6)S#oxU)6GE+=}l8wqe9m zw~&layt%5m?mKU*ZRwRBR)R6AICN=?-FtcK}mwJqeiwnjo8PuYq2_+ zbN&zAk;gNK-7OhmYtLVuYp-BxcWFw3EF1+Jh z3j~7J1wWLf`IIH*zT5o;A)^FnQ_0MOCI54-c{IXq#l8)=0nqHA_d7U>sM5-z`RiWg z5w;^hd70CTqoJS_T-j1eXhCJ*x{~FDV$50W6~V|&&f45^I44^-@mf}mOZrUi&xtv% ziMt7UIP!5hAOAR(y1WZP@#M(**ld}HM%V>Dl#GYO1+v!uqvG;FV!hy?yw6_l@H@Zi zMN^qY_GL~aJ+)m%HLUSZP4Fj%8<(5bf1Q1|8sVY|71ydC^K5o_8$A5QN?E(%vzHH~ zjf5azVEu|u!LD4-r-di7mfnmFE$hT=&XwPcJCc8|HV5VX2sbxrs4^v{0MiK?9QqjH4rYs?m8;wKh^gcn^@GWTpXNxNpc z&`Wf3GA*ZP;B7>;HU}RowbNKNgk9-@swBaGOunRk$16 zsNVakddTZ^W`9`0T05s>o(CFaTRMc?_4s}rv}KhC8LSLk z<@QS+b4)9oU>%F6jlZ3B=L|{ej$WT}_Ty_q*iJezdn*7w7Fue>Z9mWN2-v?sbQlQV z9GlWfJ3esikeV30HE`0d3xmTs{b@$823R5O4-SPr4&2##D02it+#sy=wgn~4X+#_wu$^osEuZDUH2iR4wh1pm%R}yAKNr0W#F}!Ix?|!0>#zs zQ4O*0k6p~&V8e_8+rAErD7{9~pI+8l;6j{~=y1}hj`Ml$VjiWIgS781>7CS+w#z@jxLwf|VwqXW#!|cw-E^LXHLMCZQ$^N)c4eW@HVekHp!0&l zEEhVK64G(&YSF5OIujOdAQZM(_=bTZLq=Vj9@cwnx?j^(AS}9jo*(T&`ApfS^Zcczpemsqbj;ci3Tpx^Uh0f-rPY>L3cM{NDt=J9 zenmO~WL(l>(Cf4kdlI!eUUVt>TMo%}^4nkA>aKqZBH|=fcbbd?!ao zU%<0_o`|0+5*6}8Cz7;|LZu@6`?1~5o{M{3VA;xFpvtVT z@bh1dL6u*Iba)Qj)9Ltjbt3^97DUE<*K+;^I_l~0wSxGev*bHSoE#7%iCQsuEg)sl z_z`;G!q$4lS3)l!Idw9#B|12SfnIkIc166}yLKZe{y+=4Ok^8=GK-{(kSdB5JSD@| z5vUf}3EP`5UC+x5*!2Mq>;GRHwxnn|LbB_K~`K| zL$(8r6X$a~cygLP>ms^sLJtR~zO8X5@<>6)a&%P5d$*$MAY*7xR*~OI;BMfE&j>L0zqvql)3f98aKI32Beu3zuk*xjp zh45nAxJu8{u@`Kq`Z?^{?w@Wu+RFw;%`X8&Ka6y$d=1C0kFMN>GP2;Hv-^Sakq_c= zxfZNg&*c_pZ;B*N07fR(PO_v{%to!2Y__EfHpW)i()G;Mpz$>;dlHkjDT5Car(7z~ z_sJ}h&^-N^6j49=$ig8?Rx{O?*}Q>M7%397*O~~4pPx9$r;~=u!MQDME~@NX6rSJcMxXjEJolwWh9kgC$A{egM0zEY^ zTC_RoY7qk|7s$%}yBEipvJe-rN8f~g!3}(vRYXD>H|bp)9-na_;!kx!)oIi6(=4f0 zz_T}CgIn&e$HRy)Qm0_;k8L!-2PjXI-go6ahsuc_lTe5{F z1}=eQ5&X*4aq-C~5=Jr!zvUAkym4Hk=i6FH*AvQY@be?o1pKuKJ%tyb_LN1n7HHv@ z*{jsNeRZdZ2asF~)ct@PS2?DQQqh`TV{Dv_n>X{og*)?p91Z67+{bR1b*9|uh%3`k zDx3D$CAq3gaiq@dtSjZ9X`>x?bgoHqO5UtYbXGnZZ9$}q!}6c*P(^JW?z^*s=gmA7D9`#|JgL(> literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png.meta new file mode 100644 index 0000000..97567ec --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MoveInCircle@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 8cf8c12a4f0221b438c0d7ffa0eab6f4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MovementScripts@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/MovementScripts@256.png new file mode 100644 index 0000000000000000000000000000000000000000..4162d0caecf52cbae89d76f1c88eb61f8bebb017 GIT binary patch literal 6063 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 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Patrol@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Patrol@256.png.meta new file mode 100644 index 0000000..4f3a15c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Patrol@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: af22be3b7ab2b3b44afe7297460363ff +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/ProceduralGraphMover@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/ProceduralGraphMover@256.png new file mode 100644 index 0000000000000000000000000000000000000000..85f8c5c8955a25e375bfc0fa5466b008c9113ad2 GIT binary patch literal 5483 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 YutD4Ue6L-85vW4;boFyt=akR{001}q)Bpeg literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/RVOController@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/RVOController@256.png.meta new file mode 100644 index 0000000..8f658b1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/RVOController@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 23b90e39b0f33834bbdbf9ff000d14d5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Seeker@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/Seeker@256.png new file mode 100644 index 0000000000000000000000000000000000000000..720b23b25807025bb29ece5251df91edfe5c6a8c GIT binary patch literal 8563 zcmeHNX4H7?Kk4uDrMXx`Kwzn)OWJXj5<@R`il~>3G`!wX?^*uM10xv=`6Z>WzwMl;l9RCH%v$Gb}>z{n^B)`gRWlJ%H)bxeMuw| zKiKiV7t|OXHG0G5{YUj~wOani0a0Z1bPLq^oCTZx(LMj@2kkKT9hv*+>*$#5CqK$R z9krUxMg>Ry3e@e^!&qvXPH;po?)VNHoN{3|*=FU*p&32aBppj6qV+o$xMRe)CFUoP zYNJmPt z$swADby3)D&B&mPaYK$wekv&e#;(Q%O+DaS8{uKk38CdVPCx84lN7H~A$oD4m;xsS zqhCZ2dB-}J=g8ILi-p~)x}&qg*G9UQswB}VOuf{vu!a0a!lIgI)Osp2th5QTQrE7a z8?YVUw|~M)gRFt*s`w$Vw@-GcDjIl7$Ek z&=h0Tt=*F1b@5Oza9OGrZhu=Ly6`Cvg5K>OsffMePsLI&G?$-}U(Vg2FNP)TgDmrc z^29N$5SGZrPk8*8r#rj%DWazl@b&jM6z=csvA@+`U*Rjg_lWpa?vUG=P&PWg+{=IM zyv-i(QQg4`;n7UTY7!#?=P}H|p$PlZ#US+Yh9tC76%%lZz4G5&xZHvi#Wn^ZujRRY zZT21$Vt`0?cDlQJyj{s+;dg5!*t-`t|5>}lYx;!PTD~N>maRD9sbKRHpoi~}etCPWHbF8mF z_U&tbcYh%$FI|XiqxuAyudx$^01?oeM#C(cRbWnAVroMoD4&~Gq`V`#_?7Kr+22ay znTLLcz(8jJJ}4kjcw_GhhmIVGp?g#kO6Z+hs^ud)vbq z&A2;OeflRbk<@&i3b^zZ4?XYlNb2dH9_#aED)0Drr^b?P4=xjCb{b*c!m#Xyh^)lU zxMHTI6ebsPNS%iZ9)&y*XVtQd7CPS3;aAMt@G`~Slw^M6Tcf6kbr;-yS0u#9@-LX~ z#pMJZsI9Fnd62r+xO_qvFpi*dVHmAPosy4*v-$HE*9gijw$kxAsG6iEJC+nZp6G&m?-`y%0cB%bY?YJtLQt$Gyz57LO)DY*`WS>xgDBzQfkD<#u_vK)h%Mvl!bb0npc%;8tKrDDzNJ$!8VH}z8CMHJ_c)WL@pJ;$r za4Si@D_UdSAwLmqv-`JOkBB1AsiJeNjU7R%_=;J|jQq8pL;4Z86{wGSmf9!VpX5f_ zlZ69s0mHCJmT0qUa%S@GUn~vuQyQ0OOMpQ>7yBDIWVpvEbmq-4b9)M;L}P!Kt{w>~ zA%gg?c-DC5&;TdB_vv4MPTz^_2>ymMI+DY=U~R?K)eL+ ziDwDM7H>w^^&r%}uaj#dV|G%~R24^O?J?Tk6Q8RG;#S}|vAc)HZ8`oDefZE1=KjcX zO~m}@)X1qY0B78(+)mm56M=fC^ts$XwLVJ$iO=!Vb#`L|3v2Y=NcDWEJPxYfcWG<_^*}BhD*_G)T0d>TuVGrP7^Pk zw0FwFTY+pbKGDR$%{5cSAss$HCh45!CV%X6(Ww5&m4>L1hN0N*Cyd$%z8_iYMc8H5QSI|I&eWEuLku4j%L$JA zd^&xmVFUf0@;6alt))vvzs+L^t1?1D$3|R!*^1jn3jsqMZ}lyMSRVHW`^V|FP$W4!%t8v+u4@5|5L~;b9pV8Sz?#xfZ*utu~l2O7rIz6>KuEDqG zef!)Z&Np$&5~rfMm+vDHseIQ30Y;H{J`A1Neu-K*={o&jkPUrZzlPAJSvVFj7H@ms zoq2MEexR^W&JQV1a*Y~RTW8z|HW!6YLpfoSLja1;yDYTsdAUtt$%D<6EN+KA+(&Kd z7R98aKN0XayxF_T)}A+hUCe z49|vf>`KdI``As6uKlya>oec%(+hAO)ikm{zwUf@7D?RNxAk05b`d_jc&Es`IjQz# zW*kPJOs_)XQFFRx`2Q_5J(^_W@nE|NevdpG+To5j=d#Z`?3#Zuh|^`Cz{`@s8>``l3ecxJWzWVKBUHvdZDJ^&mtaG`TG7fsD6q|D;$8E@eO)?7~6foNa#hRJ}b!}{fse_#1APReyCz}~I+2nZWq+#>^ z(^ZhHyMxNK=O;;F=i0Of*4uox-vWg^chGe8lIACQ?K!!>#J!yFH?xLqrUOwVf)OGn z%sqN`-whBx!h!zdz$^2@?4Ep>)TWy330*9DlbM`j z8maLPx1lhdR6fYk-ke}|1R0KZHmRroQpMt$lFIJOW)37#9MfL}=Z(r2LRV}-O4AxCYtw*O@amCvu+GuaO{Y0L-Kd)DB@D3@ zi$rYR8vn@F(j1hH$)fCACfjyH3H=j?4~b$F79&wuWu1i5yv?A~!h0kJQ2C-y2YvpnKYKHjj zJ2~QVv!Ir-f^E0|=K!1=xI`6Edlj+tdsevez*D$|@Lwqd@Q_8~^64k8QiZOAq?x~U zJRz#*4GoB$jl_MK*4En?I<&&Ng{paTCkpG@2%N#U0n+V47-x3b*T+9_^R6*Xz1v-K zv)@2`=%n)1*-OO)9RMN`77!mWmY{R8`S2PYwPoI5CY4)pMjaxhEJ=zmHama^R9e4wQZhH@-L7|1DRqPb@~97&kPMs;-ZQLiSd zEK%&TrW-cTC?Bu$Ed& zn1^}WUw-LRbSXSy@l$4tktn{ZC5s$TlN7QliM7|W!9=B(_0%d@Y>K=WW&RE>?|m^& z&an9-Gh4o6MEcoq$AY}V#urzqeuju1XtIDy`|Yb^Z0y&|yiz+YOHYu$MGZ9#9#c%A zqUOzed%`3oETLY`z(B)`tLK(lX-4s~+-DlT_Byd+pk(2sDfK{;*;iO{X^)j4gr?Gk z0GE`S0$V-4x8K_g9#IMU(#D-Qu$JXMWy;SPClqcy=Azg+HOgd~j{+q=j5!>8B z-?6h7|4uDoGf^Z#x<^v~vqaweywdi1KV8Y!Iz{J~NJMA=&h#o>nV}Ze14mxkPM3bO z6yd*TsZE4^y(1~usksL1vVSIh^rW^_7|B>Pen9w|&|p1PUCxrAQL1pZVs_b=*!@*xAuRf4 zp@C_D7>C7FNWLys|L*ejQ=Z&3{bz4ojB5oKm;j=PCJ<&mAw(W*6uui7jV*|)Y8B3T zSxZPipvPPw1_JsLJ3zF_AOBjlx1o_FZGV7_U>={HY2@c;rvnkZNiPHf^#(~6JBC&M zo6WBmZA#jji6<+O+-qcvy&iTn1k#2diHc&y%O@hYl=YNJ>;^oh z_y7a(4)tXB39)4UeOK5&Pn$HFRzqV{oweOaYrLDD9qwlc&;8;21G`OGpkqpFr^~Nj zWU0StP@8#G2tra4SmitRWnJ_)MB?>|C^g9cQ8teB1FSo?2J!;>UT=AlF||H$#!e$= z3qGlthA%vdfpnGx88w;9o9w_U>S;ryS7=z(7*0;g4t2}5m8%uC-e{oCI(!fVp8Zt8 zb9rU!(jF8!idDCC^A<0`Qn=r}+lp43q?-}q^z^0y=^hMC|DJ#mJ;r9$JsV3E-hL<# z9f}lgiIvOM6?KN{byVrE=VMD)*d(20`2e;9s(ivqoleeP&kDAL_T?0>>&xoutUAUC zgY8lB{Ta<=2tDA&5$2<*{^)L)eK0%Sd}p#sbRAWqC#i4+6s#1=m%!>7|ctlk;B96Fgf(u-`K7F!*!i>!)@o>{-hk^*F z3zwI{BwAmqv1&vAxTWbhhLyFEvWe4(XLM;X9Dyf}tw9a&NGD-E+%WxV}(#a&zcpPy>6cN7iSiAlg~D=+(}8TVvy4E+M7VdIMZD z3K}Us{@rDyxc5o(wF-N4=uQ}mx^SEzN8?~G8njrE<+Su}T;`oUmcXbV=vPWs%8e7w zu!ghS>^tZP_0w`GW4d<>hpYs2Ma(SpOz?zZ1{X5uNS_|R@@DN+f*(R*r=)>`(aN7k z-5AcDVnZ2rytB^P#KeCt$(npt#d0L6_T@b}$I-o{#EnPt7i=QWFPqwG9BmJ2+$P- zmm2(xf8J+o33Q>Iu>Iz3eJAs5P1L7Z*etxD5B!KapM@>{oNlZxW?^eSrxVo}3rqN% zK2mg=#m@R{T?~lwS?uD^=`4|mMwfmj2P4=jS?t!&`Aux09MgSzmpFLU#zxy7X0cs9 zr#q{HX!N4bGTkEshBIp`6bm+|)d0DScd_k%O`_PY;2m)y~5 zy75bPW_x_TWT%#ql^dlJlH{a-&zG$(d6LP}dacoD^i$ORfxcSHd~Pl*GAYZmzze|HeoBzT&sG>9&yxZiZo=57S&|vt+X4AVClv_`LwX z|JUV`ts+-NyPh_x7{>bW*`MepCIUCH`lT-w1J#*wJtO8M-okea0~IQl%cH3(Wy}cO zB4t)pRn_zE;Z~7S^4z17Ni55bVLNS}rnI0bJ^BQAGT z;Q1ED=e{whlc^zY^48%Mf(Qpd1%UuiAOIqF?>ebSt%;n5pXbSH?;cbh|1Oz9JmN^C zIWm_PNP`r=of! z^xM$ZlNQu*J`{sqk?O(o6i;J<1ZCVohGg{yMMOgS%5WKePz=)!OZ4O02bw{Ai})wL zfM2rYp9c!cf(CfX;FJ+VW=)EEtP2yo;?lg4E}a53Iaif3oE{!x-wzB&jA>We*HLDa0F@qS{$pl&afdrS<2GF<2#|q}h~r zZq$F&aArL!V})i{SU^LzO8PEZ_RU|I&)Jkjo1s$XOqwVl#Cbo1@FpDUlM+AwwXUAs z!|pDY4h|HMyuW$lG)c*)eNtlW3r??vh7jYDBN9RUp2TK}4MF0;Deb1}zC)y%<$bFh>{LoGkr7bgz z?t}C!>|Mn!bshBtyOy>V0{h2B&IsI;UD9PhQ&~4tWcXf5ybxYUq5fxRlNe4XnyU& zf10}LSxiuCk;|6cbRGiHs7KN;3w+YOB}}48x{0fWHQX{`)op6gr5D86a#rB4X0WCT z3~16(%Ce!};;r>@YTWAOYSZcHp4fnZm#bnXRZ>L72(SLygz3GYFm^aUzBiVBd3`2R z`-F>x^OhuM>sqG>?`IJcLCKe=z2UUtl3NIpIa)kekYQY^p#Vdu-YKt?d+# z<%#X!n8*>O0QZe%vQEWYfVi$8W6SY`<1=6wlU12$!CcgGBpMaa-*X2=#PGBR44fd& z0?mp|ScfySR0SH--1ksCoy752`|uiqMDfUFV9)I)<-i_e&&gB7FJ-GRSt|dW4Knh+S-!>k+3#M_@#$zI%(!qNBt_lUyI~)iQ_0 zSu;uf(n>L-!>=+HQ|a(?#$qb9((?67&=8V|f!9(>-q0qoAuUC0Yvp5lAg(TJZyqcy zC01bV=1&D?%1ZJGIxtVkL=HjoBC#cMpg$=g;*iK_e^RiJNTr{Mb4J+)G^u0+&Z$7H zlF4H0M6;5|xEdS0%@iN{RIx)QWm0OqcXi_*7c<#Y;#`|3zlKn;rw%`OYOw}K2+vrC z`Q#vYQ_IARTIvH-q+R5r1Kq)WtMVB>JW==q-E2xO~6kNr2kSSk9#^zKdE ldY}m0x-b70|F5402~h5cTbQqMU*e|G=uuCk!FM3BqKeP(^DbOo9j`3__eBidBnZ10o_C1i=|4AqZ%r4ALq% zfQ2HoYS9ahRTJ^b@PS4_MUV=@#RLHdP^OT4JFWE`djEs(`@sjFeb#>WTJL)I+H0Sa zL;S!1V?zr=0D$o#p5F=p2=KoIppU{o>$aCf!aoMFyl>+HxHMb$j}Ufl&RYPOV3D8q z%H5~J)mkm5(vD6l6zCCAR5uG~wc!>=2A8u9{p|z-!Cxa5EZ{G-w z)KrhnW~&*m+yu4(ZJ8*ALixf3YU)NoI8syKI|bT&9`>9NC6FVK!r^IsX=%{hP$IJ~ z6s?rWWS=dB1d0`(j^^eQy2%pnwxi3rv+?|*RLZ&H{#a$L)B~Ht0=nU)PVQ9pWCDU9 z5DIc{jnLiLFNo_i#p3o#W^9!m(-dRob?J(76_gOZq!i{0MJZKvj?KpOXe^7p zL}*NHNKy(C++*?PLV|)pIj-U)u-&cQ8o`&PSwKlkjSt?FU?s2{hpro*VQx*PFUHWV zkpd3AkKhF%R$>qk+Dw{avAx`qjT?o)M*cos^j?%Q1&hCzs4$~ceRcz94KZkxXa7A8 zQON!)8g}d4g8_fmGYnogTug`V8IE(0LElX9gzhzAb;&kC4D;P>NL?e(_B={TFiG8v0mZEKov2gM$Z&2vRBbZlltfrCSEBW?g9C zX0w*CEYvyspQhTRvMHd*^eA(1P%DgB@a}5XGLe6b#dR!57>yTwDWO}x-tR40yjCD8 zQpxTd*;@8aT`iMk%GPPOAj?8sv+;5A%_vv0`Vd9k&RCOcB$OwdCQNy(hCBa_cHU(n zU1-cVz}SK=8==Adj6LtR+3i9Ut$noUCu(7Y<>~03C;!8$1u!X;;z*}bbF-5z-cE+2 z5N??c4q*I1I5OJO$LY-LCzjJZ>0nWVxbEYfsoZl;m&A#g9604jpnW#-j~Q#VTB1eL zwF;tun-6P;M^tU*_Dx6Fn4tf}U7J7o7N9Tf00y?d%)Hrr2(drOt}x$_AbnXWfaM@shcHqV<$Pqh?ALgs^;HObr2IY#V@(Pk^G6XKX zM-h6pit8hoo{-l5E@W{XF3L=D>Z<|9Ja*1nLgOhIM(XQV;}=1EZw=EB-%pP-39+N^ z@Zt3mGs$R2Ytpry;1wd-{0-A{7kPyt_YI3!;55HNv!}+y^%0Fi`r^4HiX# zWNL9~-Ps)jGAl@xKBWAK*)2L-F@9Y{;t*aM_xLSw_6ctmw|gfQZwak>pd_%u>drx z&;}hb1(ixSuuR_OF-|G)=?+0!2ua5Gnv+m=1Q8&TZSS!lU`D4>?u({xA~U_p2=3XQ zbQ%AQPId3mki{Nl4F@sV2ni;+;g^tUmWq@Jz>}TLO|v=ncdPh6Ub9vgQGOgrQ9AndimM zUJI&4Bjfpr)kRpvO(TlJOg0u}IYSpjca!&$u)~A}VkKG4+BC8Jan5aCZCpE_y>2am z$-T8Z3|Ai@PU87Vo*@9)(=)GRG)w>18<|K-@qBTgAZVKwKW5)Od<0i3p!wymsn&{q ztE9b=Mc*ry+UpDVvzWVnwuvA64Ut5#5EsIZjF=6$70M*?7wU5Xy)>jeX0arMeuU>T zMqJUQHV_p$$A6R&x=q0sU4)zaoH;j>URR&A_tUn`Pl%u*e1_s~qS9`@{=VfcJp`0o zdYg&gkY$obrhvG$D12AcG-aMJdRut%)QWGwk*jj|c;8tCnwt7A*sx(^%LQjNqr5`h z7AF`Qi82(;2Z&Qn4pI~g9SSJ<;Is|n0hpZHxa|nO2D0V6e2p);Amv$5s#nOyM5L(s zz_s(kGxg>vI)~W!V)xmyOvMdfyRf`=$iSxPN);LvA7}JsqqiET8=&zb#df$yxIKNc zgNo+d+2)MWZTalv=zR$wZ}eP!+$+p!A)z2yexULVw;a$5T0tU7<{jn#^Wo8gstr>p zuPW#RdgV8-;4{RR3t}|i6-6G)^Ar90Bvgl4tfp8wXl4-a)7b( z9U%m$Y-$2iB3B(Y{7M;hfG|0uG16;pS=NtfVpt;ozK^hgiGUrXb)du&WI#w#`Wht@ z7gtR$7#mIXPWYe@=#_^auK!ZEb=GBrTJqjB%k#4qFTcQPzBAJzG2)_jVat{lb6lGV zK?k+wsJBrY(YYg=RA7E5EG4ib3yHcj;d`9+pxHCtzzYKJ8@78{z&voFuz-af$SlAV zB#dO#3Y+igAbAhRD)DWg|2b_nQ6X`i@z>L!G5J59@`!~Edz?uH*ONw0 zSHh<6OJ|L0lXJWu5k-BK`Ru&+n+=sAqfQvlDw0gFP2)sKFcQ$`J|BaW%G~u|h>aCj z0T%FIMzkg>w`iIBMpUpR8VRgHUg98hu^X5&>>mXv39qm{W9j)0H$N$Z8x^3psnDo)=6=L z#9lf&To8PCV!<;>JF%ejidwv5h9Nz(8FrF?zZ9rROQ!D3U=(efMhca?hMhQl7r7C+ zmFQ5Rdg(teGf&>OOYwqJdX*7O#t1BBi6$%QTx{>c)fF++v5v3zZ&rtl#QygN7zqv! z4~G3XZ0}<>z|R@Zl5wvRl{em#5iE{FEYaCA_3<7(s&(gDw!Ya7zM%DGQo zJ^@3jtVP@oFfbRO3%hU}L(=A);Pq{^ti1M=Y?hvIYAUkv{&2!&BNR;>U{Ekd7Ht5M z%_Fpxm82D!!)Y77%O|)iHgUtcRx^M8ncTZxWN5utEI~ZSNqP}ps}D5`i~0&d1yMPr7`ZWEM#8w%nJctpmto`TIyI@X{3&_kDw3fiXY1Gf`-^US zl#W^{pUfmrNstt4A7VF%MK=lL$Ayfu_4~7zXv1!-W}tStEi=o1c%GGW&?}+YT0d`; zT>pte#b(T6iNsqqoRlkG)ir8@!i7-D>X%e;?W)!HtiJV+{yt->OMgMM2Dja3Z2-s< zHAkP7$jhrr?R#;OEe(9N5Y3T(IJP~)yfiNGjfxAU)^?*4D^2ii09Cf?V^-deGriaa0*yCyV4=lYx*;?=9m4hH5>59DQ)oLSW>^8OYt zLW2g;z`Hh|sNf$OHD0_RPVXs~0@0}>9_4G4w!Fo^D+KPbT-w+@+}nvh=$y0Ga#8CY zPTfg0CE~$bSA}x-2Sd9Vru==o+6qF3Bt%SK&^p9bJE^8xjY^b$A<>cyh1_e!T|a`w z)bblAWf^`S8_)b=>1>(A#u});P!ZWVkHFkjw$1I#ziCJ(<ILTHm$YwWf0>-qx7wglfyzbW4Y@DhJIY0-(m^kLN{z_B#N2BY|)f1lt+jJz)Ra@nZOV0xma3Mc8&Aw60 zXnx6I*UjzVqDO3dwn{3J(dgP>y>9d;@B`JzdH{b!UWAiQYK_iHzVVZU{qb@WGp;D9 ztZr{-X33ds3MFrpQT}~W8%6>&ugKFLDvCJ3PttC)X%XDqo0kW!968=}?=(BT?^VY1 z<(^+Ph<&H`<8}e;nbAW5sB@L?_7>Thl;g2 zP04S!U;92od~g1NHAzWj70FFXXKVc%?~ct_eKXh^+x>9DR~6a-uY+KWPfLbM*hO^r>cZD#Vp)AlcP(- zHSbMomh6M&mPIc<_elok^iOj7n9Byow`3YcU0-10Jjj_Glb?#KqA@J6otR%#N3&Kj zE(YwBgIT$E!t^&DKrMpE)KdJ}7efN2^-0-IobucS>sEhq$UHG@rie9TTmTn}5$|}d zZ}@wf6&7R-3#@R`#xhi!pbm^E?UE&%U98*m(FGPV2q7)CGz%It(!j?|JuiwZ`@sfl!8HU>~ zbP%h!-j00>=)19UGM5E>>LIX}f3kTxd7bU$C(c$*9K_u0YFji(6vFp)#EWPy3EWOu z8r{V>9K5!O7#=X1wxw$m^FL3Sb}}6k#TuMS=Z1oHX-V0~hSU0qjMPVUH1bP$Mqf++ zM%q!P@J>?BT#tD{Wr4(oB!j|S21y!uK(8%AAW+z>ayY7zO$)`>F)9gM)Fe&6G{HG< zcsb~KoZWtEIE;DF@1s(+x%#L^g7t^c@`{G@XmmJ$*QMok*4(PxcqV@hxBHI?okT(P z1ALPUyr^e(7r-r>njpe|wVOo3_14mo!8^CQy8eDY! z;+7QWQSXAx>l!nM{f${=FLLPx`k_l4FJtAlb^{>r%yt}w^seDWbh#GDxuzvwT{&4B z`50-WdbB;B%7=Sqk1J}ir3Q`rHEpN!}foj$cZ07k%CX2*}k|| zs+Pn>9O|!3$QicJW>bA)x?bw{A`GUh9 z&wt=5KRpK}2YkYUYAQky(&N;Eld}-4gm?c7xXGissthNlQ;ta#(%a%3oAN9f-|DN3 zNP*yli~b954ko9ggG6agrH6o-S*`Lyl&$J(vVf@KeD1*saa$L>Mi&g-hqj*gUMw}o zcGynX)BZSYbf`{E+`a0eXG-JqgG@WGM2^m{xesV^3ZSE=y#_ z12}Fzus~5AXCk#szzn;`xc>6agb=#3VphKsrJZPAemXG%R~sLbK*FY%y!p3M2{+_v zO?gx1d9~@EzQahDv%+H<&9@48t$j--V60pY6-~Hu;W)uv8L&0q`ift(=%|Yq^um7P zCAwZr0S&XPg~f4Ix$zEeZ2+&l|C$icw2q_i=J-r&QR`&o8&|4=Z7o^@za2DVpFJ&Bz^^>Z zZrRN0ccs3sdk9)rvl~VCOcI<_*m3Cym%EYiqtR;A(hXk9ehs4!Z7A7^q-wWfyJI2= zJWkj0a{KnVNhh}Pg>s74*9Y4W10AwnDB5z!CZO8&?6Lzj2i&YNV zNXWe7n<-y`W)GJldyTC=24P5&@j`N4){TrQ1?5xNbgWd(W{I+Z7qvsr6@D_rjW?2H z@Cuf=I*I1WKBb#h0^b|%yJY_5X59lPK33<$>gMG;h4P=138~vGFnRXC1*70)BBsIm z0odu+#Mi5IHO3UFd%Wf?=hR6}wLr(lHY1F&a>&^8x3Of{xV&X_3ra@fhXQe8u49SX z|4=h71|PU4Q@Y6nI4#zZWblPyqHVjyZ|%g?53h>bzct4$ra6$t6Phi8i&~e7zZFhB zya4eQ>^`~>GdK^CU#K~}yL&X1#?qY#Itdflc?d1kWZWy}IMQoLvUk7cVC2Gf-Avk8 z%%M>J6DMk4Jqtrj2uOw(DP*H&Od^}rh!()E=J_)bEs=hA?ae7^Pg=!Tjgi8|o7Ie( zWl0fin29x*I;3#v^Ius8>~3I~!ia%TukoW~%vyse7+R?JoKTdmZuTcfQx^_mZ<-CT<@02^g)Dp$A)qs(ONP>WG_6@mo2D4ZrZv z;FB!We=R_avGI4rwwD(#zYE(w@=$r0^H|2eD-gAO5mXs!J2goGRR zE`P3zs|iEIw&6Yfgk8wSwvMRYpn3GuR4X2qGPr`azn_ToqKiRc(WBHyV$Nkbe;a|) ztKwp;B+MipNru0uQ0ig689tL_=*FUO9R0ugl?4L&et-QFXW5&px^r#O!a%=^K7!=` E1$I=1DgXcg literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AstarPath@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AstarPath@256.png.meta new file mode 100644 index 0000000..2ba5a8d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_AstarPath@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 1620f833be5302149a071c06944d3e9b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_DynamicGridObstacle@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_DynamicGridObstacle@256.png new file mode 100644 index 0000000000000000000000000000000000000000..b76ac12818f0f87192631fc0e85559d85fdaa2d1 GIT binary patch literal 4507 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 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MoveInCircle@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MoveInCircle@256.png.meta new file mode 100644 index 0000000..1412a08 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MoveInCircle@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: b13bcad279d89724589e3b19297e7f7e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png new file mode 100644 index 0000000000000000000000000000000000000000..f908c62d6c88500329fb8c0eca54e24128542902 GIT binary patch literal 5995 zcmeI0dsGuw9>?#@WI_mmgoiLhkRW1HYS;lOQkQcGj|K!E0d0M<220aO53P@E)fRO^ zH0W*+wQNz>tug3AUlv_zwLNu%X)CoA|SALh9q~gf9xOoUr+l7 zC!D#tpZoiLe~v7YXXRurSigV8(6lJ2 zcNmk2Jz#9?_XocrJyt|kKaQ1-N}K$ss!VFfY=^_)PK$EPT3JXJVFW?^o)*9Psu<4` z&D~ou0+_ zf|S!znx?y@_$_OtiL|Ax(&dEX5{blJJ*!EF;bsiE$k94YKv9(Y>2?+4Z=QC4ET(IH z6VUDhhpTrHXtj_#R(l|0lPvAUFv(n*bWU6x0Z(!7P8w(CRPLHDLQjL)UB@dgv}K6% zfGtZ=a@mja*6c%Q=;UEP(|k>f3%_5rF&5SLCAfs&lHv{4%7gxvb-b11BrtC6V9l?^ zwXt;i!hmG24R5iY2T9mnU6}f1fn_;w<7*@+x_K)`a4wQ`5=?u0-%~u_pNG`=4hw6i z0jO7&Ih&TKTD{#Sl?3e}%Lg+%_ZQOXn6f(Y;OHSq~3eqwU5JSht678r1Fw*~A#j6!gaeWaZN`*3$#kzMYuv=V7T0$(kitb=JgZobg-I+iWo8N@CPQN6 zO-p8HExeEWFjra2!K0;nyq9o@o4Li|`j*G+hdL$vn?+j0%mm#0#zzXTjTguBBb$

    sVz?3PZxp9?tU{MkrI zUH}OK;rRzIAYKaV=ZmA`$~6lH&HArsugPI<;%P_cWu9?;c;~BeOSuY)lQCjUdnG0f z>FUD4C(G94T>WyUSc8QuqHP71NN?}wLq63<@XA?*emd1-9l;2{OLPRYCpXP;IzLkdN> zqC!b*U+iJM3$FAa7wxm$EL(zY4ysK1>yUM02FJ1`?omJBMb%!0G0mT^3xl`4x>!B3 zk?@AjzQl9LKH?`{bF=3|+R5e;PD}M#^=JG&CxCj%wWsE-{SfL*i`AgEL%BS;%_FpY zO^2CVXhfaCegf!@j6E>H)HlZ8b6!{<3B^l<8>4I+YAU>8x910dpJc8m+gIS83L(Bcgky8lm&4!*46H@={e8+_> z9rScJDCRS76?MXS-tt#t^Q+L%gCg|?LG40=6q&jd2DEfNCjs~)xDoj7v|r-I-mYoC zqqmK7_cOhF53sUx^PPS86?LOusHRTBRmg(kALgZarIqjOanuhBp=bDnH{KzJ_mwgKFl7&(b_Vocqmp#05V zE#+);Jq2BtIGFeEJ|Ra77iSQ$6Y%=?$lMk!(e&IVbLxF~UwCfv{)Oi1aJ*h8s*K&m zOEP)LI${TFUA9Q_XlRoi&ECm%<}4yb$Ib;-yaF_Tur9B36O4e+JsK2!Oib?>n@N9C zx;{*@0xY=}kYAt88y(oPW5MPy$-W08IRx6dh6IFNW{48Zjg&B}nP1=!LqpVGczGd* zS08amX5b`+HzOOVFAn;7(@lPi1WrzbKArZ6j|lA?J1tGLKUu5^znpe)Iw}qMJraZo zI9ZJXTXSlE&9N6eol?%;_fS@N-*?=OR%e%D)j1{gHmE&rnZpl093r4E zBF<6+5dM5GXdiE?g98X>r+OlN=#mfhHMp;gE8nLbO+Jx-iz9MCC25O4@wkWh z47K~t@OB}}m`mI~C95i#9S{~Yz^i-tL%4WIwucMWPM|KR$Xuyk&kISg2b)ga^Tj5? z6}Hca%)x)-=S?BhGK|x55m3&Ny!-N}2GsH7a293NWJt+-zOjenDBB4X=HS2cX>huQ zGE#*jpA|9_rdTiXY-e98z^Up>_O$}ji^}S#ih=d7NkIl zaFBR20A;NTvaz z#jJ0q&Rz$njdw6Uvr%C4U(+v~e8v3vg|>O|sP95#B#VqcH|RV&-|QzD{&c8YjJ85# zr1ejE`O{e>uei!Fmq;BUF%2-;fo=h0nd447 zubZmt_a4}RGOY)f0IGPVbpp$p!Do#>$28N&)ByE26%dNlp%p)F>rYQGM1Av^K!b-I zvS_NSpelFaK;RS~6iv0j$B@J;%W_N-()aPMz?AH!62Y`+;Rn3|aBlgtPIltOQr_3# zfx#3u?5C-F7{sQEyFY}^a50wB zLqj0yd4z(GpQNU4Wyj+-Buum*3fFB*jGk0{b;f0+J+vC51$99+`q6^`<$wxUM2x<1 zd$!}FQ0S)&L%j=Hnnbt49Y=a6<|94?CDGf)K|T&x6pX&vk$Frfx^8hm2L$MFh$^8) zZ*i!zKByAf!3nJOLkB~f($oop?vpxEX6ocZb-BSd|{8&z>WA)l}Q0zTfm`2>1d3Zr95V)V=MpAVe6 zLqA#~@zL)Yo9=dBcl-BUyjBal9)JgLTTJ(_hU2jeSqj6G$!vp`zBjB1;U8>Os38Yh zxe{3kF%-m1nmqWJwcmtRdT^_jX*TXDF`CYcNWOL&nt?MqbYP2*1ON3dGn!yu2p{@i iU-{K^?*HROH325yIW)5CVE8(=EN4ACH|I!}uJS*RUNE8n literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png.meta new file mode 100644 index 0000000..bfd3220 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_MovementScripts@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 3f255a09e18c8ca438c5c637954f306e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png new file mode 100644 index 0000000000000000000000000000000000000000..0047f80529500d9faf3ba87cd7aec5628cf7ce8c GIT binary patch literal 4521 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6%U;1OBOz_3vZgc%c;>%3=R z5RCS8aSW-Lll15RfBVB460D08SeV$@K)M+I|4)+Y{T^t*<))F4kN^>p(q(Wvs3Ebk zp|KHU!T}d)#2x4SrW*+{5ff3V- ze%LU0F)sZ*(;b*1P9->;zl=yGBRga;f@)M?1eF@2kxgo3CrS4H33O2WVSj{qLZQHp z!~_O-i8@-4jGXFrG~^;tR{{Clgl-6K$w*7OGori~KH1iDiejQnfC1byKXr08X@2C4S3f9LaEMY%* zfps*sKt=(8!w2;x*8hOx2g*VQ1_q-!2$+MI8Lq9DtoXN;>jNk_JYD@<);T3K0RW&{ B>P!Ft literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png.meta new file mode 100644 index 0000000..3941556 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshAdd@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 94cf7e2a5fd05ea45a3a78170941a51a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshCut@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_NavmeshCut@256.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf4fa9a21ba7e53128b6679753be8965d7c5f6c GIT binary patch literal 5246 zcmeI$VN4Tu6aer$u3W44w*?_^z*fWxbV&68C!3pq77uWn0@Fnkjg@4sA7oK9%uJ&k z4F@hd!Du!XCpfm)Wszk{w!}qkcaRVR3XCnrYzt<~!jf&0U^Z~f?mgYw`(MBKaS=a| zl3%ay?%ut7?|*l=a$VWHxv6s*hM8AUURupCEd0wd79Kvf)cJS7$DCc|oA)rxXF2%C zZaStZStogRR?M$2fn1^$^Xp3^=V-f} zt%{=TZvC;zx8PI@ACz|z?iH$Zc%FPS&>>mATV+{vBq^pevebZ^X51zh6#o|VW|R

    Rd1XpHS@VpdQLu|s1V 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 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Patrol@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Patrol@256.png.meta new file mode 100644 index 0000000..360aff9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Patrol@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: a778521cf2098e742b580bee948ea7a5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png new file mode 100644 index 0000000000000000000000000000000000000000..b2abdbd4d4793c54cedb12f6f42867de8c24e6de GIT binary patch literal 5410 zcmd^D|8G;*6@S++em4FZm`NU?*olC|W7xcgCZT}imo$9|qXnCGT3QzL5<2E>t?IRG zWnUn%Lms3x37}1Dr&fz`Tg(q2s6e800l%hILE0CGR%)j$B4KOgbrs(dEun;X_uU}p zp11u4?GKTo<9j~moX=Ae)r6D&$4pB)!?o-a@oa$HUJWN>LcWEBd``Is~- z_GI>sjUEaMafTu=ZMxO`jv*ztph6j9%W3Op{Uum>kpWVt*du15;~#l>g|5g5D|W}Z zq2cW*nFlZtc4`sNI}#dHh^3(-CdnEL3uozEMyylG5fmHy;!;>>0$(^MlD^n*qwKQz zapdN`ED$=0`WjlDChA| zM_fn)T*W~k(tsJI1VcCo#BTtE`xD{ZQ@dOtBh8NDo`nEWD3zlm3NL4~*$N3){Diab z)5WLM67`EFaIikR&tdC}hs83wmnx={B!(7<>Lds)EI=s5G1y5Ahk!Xxnn3I~fYVh3 z!VGF$S%o1GfpF7htJJj;8b*r%#B4I65F*&M&|p~_D3=fjMB@0tolm*sx!Iw$i`iU+ z=oCvO7$7CPFyT})klaLFq?036k`Qny6q*T!B!sV}Ih1PmYXroB6*YH0cE|!W(NLs9 zXauK_4d0IH_3r|+$fn(`AJQC4s9MYO0$>)c9o@Id*@rLEHF&mGIQOi)3^XbU`GqIV zwijsDB~(A)d~FGD9GVZPKowdt&O#>9{qpHzzOP`YBN@Li^VpHw+t$6`_Q;E0c#KVK zs3O{adZUn-3kHuT!xk~0J3Q821gSVC@4S+UB0Aj?dQhIp{YY(R;L*3`otJKeKru@Q z3$JAK!sYVB`B>5h)!z{JVEtkvG*y$q`AM4L(KB83r`O>LVu3W*#O~qUqrCmo2Ay+(j554@sJj@ZDxk%o>W&UF zdCX%2=!_BysJQ%Io=Xm{Wi&`UfI+At(^?@|xCOwHDx$mCJ`257#rW`UmYoM!P{r=4 z;deOB3;|^3c50(yVsuiJy2It{5I{BzG?vQ`NIA&4DzjGUx3&-G=^$7Q0!%kam5Wk6 zOaMfy%EsyJwM_1C`;%al{GWYNHhPjD#y!&Z{ZgUg$@(D|d6FOOZ{rUvDTDU|`<8m; z_uF)**?CyFNhdV)rY5bI;hj@u)SKGc{VyH8GV_h*)p-V9Wz266u4ZHEl)yk}G2WBf z+GQ$+{D`raQm=G&fySeXT`#33jSd5B2!m+lc)$PVk_Z$B)aG1o5Q7NN6Ew&?-Fv-z zxY)A{ID&9lemSEr%Yc}PeCF8;Pabc$nhSJwYwfr&)Fgy&2G)mU;7jxmtp87_?_(mp z&kjW{I-Y|rzB_(%Ac{{eg(pujr{83vj&-|x<$IO_n z;Z^f(SAvHj(YC~nnM1m!WOHHRe4TIzQ$HWhP8IVG-+Z;LYp^>4aL2D%y$lMylK<0m z3MY(yF1od2mhwUxJCcndVat&xj@=sS{Px6^AD0eX+_Qpt@$Z{|z-+y`&LU}m?7asT z9%=V!8l_~d%_nKCQz5 zU?;(ArbeS~&Gb(ZfQJFVY!bi?#XGL1orXKE+f3560%(_D3Ic?J5a|4P()SCC7UoJ8=anl-oVe~Wvhc0q!1pOEoCMn3N{tdZ zLtl)XQ~G$aKdvI$HhvVY&U^Xww06$q@7wmb8Vk3Ou1oyTBq2+=Pm&NlRv@|fN220E+E1QT9|f<8-G=2tJ{1a-VT5Qb=lD-qS78@UO&?)hnQvbfwtY+srXUC$?!$%*#J(e$zO4H?0?I)_&smuKJ{sjjA qYS$?|ebi&b531~vIU>K_tgsAsd3v+-)}JM9rP=WCrlu2(a{RxLDru1b literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png.meta new file mode 100644 index 0000000..8f2813f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_ProceduralGraphMover@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 1f4ed587d406e7f42ba6020f06750920 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png new file mode 100644 index 0000000000000000000000000000000000000000..b19c2a9fdb41983685dbc386f94c8cf4a41a2c4d GIT binary patch literal 4523 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$6%U;1OBOz_3vZgc%c;>%3=R z5RCP7aSW-Lll15RfBVB460D08SeV$@K)M+I|4)+Y{T^t*<))F4kN^>p(q(Wvs3Ebk zp|KHU!T?kLgcbKt-MXtKy<>}d)#2x4SrW*$uzBR*F^ z(uv#q=klyi7~!Rf)Cf-`w)_bX4*nN>_y6K&9yTrm1B21LGg^UQ)SpR`Y<2Q4et*^6 zdw*WgU+263r!-A`SN{o7vX1avG}-_`X@7x}%cbv|`N>TC9MI6`tpe zf8<&6Bs?5m)_q&JPrlCNANy#bJKAc;+G$CWWT{*JrS41PzWBNo_s`p3NEl5mqkX*5 y%z`zuU~RIb%K*z-R%Uj$$Ma)GyM>Uk5oQL?PkdskmwD7cRkEk6pUXO@geCx^5bHev literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png.meta new file mode 100644 index 0000000..e617dc5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_RVOController@256.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 754f3279748cb184bbf0724c070d7d18 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: 4 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Seeker@256.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/ComponentIcons/d_Seeker@256.png new file mode 100644 index 0000000000000000000000000000000000000000..665aa795566ac137dc0fbcd0d14eb9b322f3c493 GIT binary patch literal 8340 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 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@2x.png.meta new file mode 100644 index 0000000..c4b5f5d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/banner@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: d1f703801f463704e870485f44c3831c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png new file mode 100644 index 0000000000000000000000000000000000000000..d8d06e3f9bbd7c7d578b8922b008c5c462712375 GIT binary patch literal 3361 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 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png.meta new file mode 100644 index 0000000..269010f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/colorInterpolationBox.png.meta @@ -0,0 +1,47 @@ +fileFormatVersion: 2 +guid: 0790ee8db18ed49ed8369be285199835 +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png new file mode 100644 index 0000000000000000000000000000000000000000..b9beb3dee9f8a172f79bcf40d7de79b1822799e3 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBISx-3$B+ufxxt2f%?3Or0z$$ajlE0#HMpgP zrS%_3*Lu#sFm)ooLTFNQ=1+!Khgg`CnpZn9t-Q4AG=XG zOHI10!Bq2F+$5DsL!smk0fKciN>+R>Q3>zaf28lRg8<8%=aqc9xAndS=6qhavAI)d z-}}RYBCZE_UvNEC8f(GzD#`4`CZC&2R;`-(`jeYP+k&QLnF$hX=eDk0v}>KXIv1nk zvs+P{-dERoGNncyvgj+;`5fA8yQb{J=_3X`XJ@rGW}Mxm)3GSy+~udUmhH@I$iI2; z+)AVA`{zqPxc^0j(e}LDize0|0e`K1u2^Q-^tw&rPzwrG)GNO<;Tvz^p`Q3#b^a%S PVZ`9+>gTe~DWM4f`K*$O literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png.meta new file mode 100644 index 0000000..192131b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 345f29dfae2c0a942ab48105d0ab4be9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4aa1f196864818fa1b6635a5938f9f37f9b43f2 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBezm8IV@L(#*~x}N2NXD1EFWL5nf^=f{xkv2 z85=qJCW+j1>wX;2##}l>a;eA#w%GFw67!OlUORQdlHGekkk!+iwX)OH>_j8nPGw}2 z#j8f0(_=pPtL>`F8jf`RYTIUc>7K)Lc)#7Y6(}oL^iVkAxqh!n>m;uz-=%Jm@5~>I Wo8M^<{wWG{27{-opUXO@geCyDZ%e5F literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png.meta new file mode 100644 index 0000000..e70bfda --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@1x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: b5a4a564ac2dc4261a13719673f157e1 +timeCreated: 1498132652 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb92918cc819c7829d0b6776e582a9f4c13df3f GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=j_uA8nq3qKbmd$LO7}EbChTSN=+}Zd1 z$?vTbtgT+H`ueAcF?=(N4x_ijca=4nb9)x6Kl{ac{``Tv-K7Glp zBa#Zd;zegK{p&dyA@=-TITMS8)w-`M+>4*_U)J`0G{Yo72*2WgrPT_|I~G)>qG?1CG_aV%|86b0wXZ^CgLkU%#k4Kz&yUDv_$JRWyl zmz&HDC=LT`+`85QWyA0q5A$&q0Uvk-F4f=5L~$jNpqs3a{T`@6?H9Rak`od>lGgwL N002ovPDHLkV1h8vkQe{} literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active.png.meta new file mode 100644 index 0000000..d156843 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 0fc5b0e581e9e2c4b874e38c93ed27a6 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..5d8910c27713c8c5b5920ccce84b8262f5333427 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$fuT$B+ufvy%<^4lD4uzTM({yu+GdZ|s$g z(Z~8Moz`DUR@lL$rQR^hkCTgWB2T4|)20vGe{K@wP(0#s-C=pVRSd_&wOi{Hp4T`X zVZ0?VaaDcs)Tu%LqrG;{@{Qf0A#kZhsQ4gX*`^l@TC=4zYL&k(UR^oExG;FZG-2*D zXYUw&n_zjOuc*`F)W&8`sZQz2jo0cVl5KOJ%uU*UD(3m#ddcK@_G|u{NBy^dSO;`2 NgQu&X%Q~loCIGUCV^shE literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png.meta new file mode 100644 index 0000000..e496c38 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@1x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: d0f7738d6b5b2420b9a77bfef3926e2a +timeCreated: 1498133640 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d53340e7201936631e3353285813cac8c6db5448 GIT binary patch literal 375 zcmV--0f_#IP)DZkl+zzS$6#d7@a)Nv#P4Pkq!_;UKB-;BaHyZaUKE(?}|8R zwrz(v?IDQA)HLm-6?j{fk;IS(iR;%Lk%-LP)7^@(KFHgQW-Z*-Jpd7;E9_6FkX VSonD5G(-RZ002ovPDHLkV1i);mi+(# literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png.meta new file mode 100644 index 0000000..0411ce6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_active@2x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: b9d7d6d7befb34b9c889e6195746c452 +timeCreated: 1498133640 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..76a633444e94d861e558c230ab6cee2455be6d40 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{v1yi$B+ufvy%+@S``FbBLC~_thHzsWnXMK zt!&z~uq$y#wFUGXRn-~9lNYLJIC953ncXxvY!s=$k-X;kr3WiLq}a}@EX`p&`dIJW z!tZR}Z&DSHgjqf}WPZ#Wqp{<0M#O~nIcg^!%A390C-^LINz5LOkCkllW)^LgqHnaT z1!AAF^vq9CnQ8HS+5Br&%cg`e1SGHbxgwSQcEOZaVHrR44(#-2iUYcn!PC{xWt~$( F698L6S*QR2 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png.meta new file mode 100644 index 0000000..19c4336 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 9129e8496f4a9d641a05733c3193a430 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/deleteButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..61b52e9f68cb47b58ed7a491be70ba0576332a03 GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=U%?3PeveOwQ8d)7mnhvSW z*|}x$f!1nuO=TCI3q1!KosTlU+Oj1}kYlR*BCnq+E*u*F=1QDSyYTS$zPl!8I}OV+ zZksjmF4$_hcCLtPVcq%CW3ond3l}nH33NylojdB>@Vqj-^_acuoAPz9-L{wtt~kEz z@s7xpKODvY3aB!9a`ncaS4`KAaTqZ5G@SIgk*;HW?(B?b96q)UAyUs5@b`hp{+3``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBex9d`V@L(#+sTf62NXD1BEKfa{h7Y+^!BYH zd~s5<89ejPxjIkdRnhTCNc(cnkmF{Fa=?Q};jW+vYG=K$wPzmB?U&h*K57(Zn=8Xy0mZk8;qce$QZqTu?jZ@Xu4%QrUJ-hKY? Rb`#Ly44$rjF6*2UngIJ@VtoJr literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@2x.png.meta new file mode 100644 index 0000000..161968d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 92ab6339e9b49ac478155629453bd6ef +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d85bc6904a526fffc67e4f8f54a8428f1daf91e GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{sd1K$B+ufx09{;S`-A@(#tfrsoY__aa+>c z+S|MTL8uK|qqcx0qs5ecLRW75RBcI_SO1ZpY2lQtm#beCYA7$6!j_%e8+1xePUF{Fa=?Q~zRW&?p%b|UWYSHWNc-bRSkls3L;RJpqBbHB}Bu>~cDo4G}6<`rpJaSQ@ zxT#d5@M&r6{T#;&>YhS(zyFM2#; zHh*}00@ibGxzC_Hdna#b=s}@J7I9nRnT$8{MNGT*Ci>rT#?$Moo9;5|AK?DLw1)vm z&8X$QK8soB4c`X-r9$qHnSN+!ii;d@*E<&Qi=i!}O|ewnP=4Q%DVm}OWSuRR${Vij qvz)l-mRwlFuimihrQy3QuG(#0=qKKHvZE8|eFjfgKbLh*2~7Z?#)I7e literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@2x.png.meta new file mode 100644 index 0000000..80e136c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_active@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 167d2120e461bd444a13ee562df6a3c5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..d6fa55bcd350ea6361c74328387d69f46d10a830 GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBez&KKV@L(#+ewCe4GKK2D~&9S3fa{8jwQ+5 zm64r0V{ccuLQIpVKf|;mo`#H_v-LAQy#BwJ=VB3b>iJO;(X(V@vToFCxd5B_kIZ`x zCY-)e7M6 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png.meta new file mode 100644 index 0000000..7b59fa7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 103e3480a7bea7c4c86e3496ef1676dd +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e836910d8e6f3cba87049326fc7fad1b65b6737d GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=vB9U2AaI%StCdmLQuQ73e0lg|C`&sF}gMr_jQepYG6t=Jd! zTE?v5+q5m^cfbFAYMb7DJminpVTnHr1U60xTD82v#5Z5AyS~ff%_J4gle2{W2w!}@ zLs`_GQFz()3HKxo9NW)-_wK&6t<^LnbZeF^lW{kn#kF~FqW>LdJiflV>29Nf0tW|E zQv;BS_#>TH%3rX-T0uiNBZ1{*)*VLqOHZ2keyq@xO>|~v{THaVM2YoC;;dzdHglwA x@4K||@G}?Tt7e|w%kG~s?n_=gMe58Q=6UR)>Pm(_kAQ)|;OXk;vd$@?2>`M_hLr#S literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png.meta new file mode 100644 index 0000000..3fe625b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/duplicateButton_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 17770c378744fa24d9bb3d8f2e3ff802 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..64020a55f874b60a2c6ef0ac0c51f8f619e7ef37 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{sT`J$B+ufxsxyQHW>)Cy-v9qbM**;DRRL}j1Z#FN;TvER+5 z*E?ag&H1lgs~TFlni#In=?xUgT{%bQxaa9jHFfKK=jA2rTD|09d93Kq);JWVUVF-s9Hc61cvAp-2S~xEg3g8dLffYh=6k}j1oK3O4U@ORG{1z}9T^N{S`D;wZBAi$MLqG^w fYFzt0n)7@E(Mj@;>_@wQ00000NkvXXu0mjf1)#!> literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@2x.png.meta new file mode 100644 index 0000000..f84eba3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton@2x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: e8ebb72a41d314b288545fddfd20cafd +timeCreated: 1499339103 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a1de8a489a229c7f3a5f5c02a7e44e47c8bfea1f GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{%ube$B+ufwUZ6GnhiwS;&a;j91Rh})v1v$up(#^xCGZvJ7X6tzQNKm%Ir>@ly|;Bb#PHa{uSxaLQfA%5 zCcfu)t$TlF+uJFIVh1;>@a*1SUh5Lj}vW4^k@@js=ORI%`tMCO2jj(`#<*a&nafzn!DO1 z`h!!#EVYnO)^)F)<~?^fsp)Uo+C0(z)1~i!PaE*a-ZwqIS$d-Eu|g5w%Qkkp+j4ks T_y5}n^d^I+tDnm{r-UW|j`4ex literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png.meta new file mode 100644 index 0000000..037e5e2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@1x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: e0cb3cc51f25a48bf8b0c647452b09b1 +timeCreated: 1499339103 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..df1c4f72fa045e49ddf78f6a9a532db327e3cddf GIT binary patch literal 553 zcmV+^0@nSBP)yXk0A+>5(@0j16^brhF6=w6(Pi0Qo{ z5s~Jfxg-yq#xwf+<+w}kWzeH2N+J@8yfVg~R8@V3KUwnDTd&t2MhBypTrQ`aPNz@u zKrq=H4u|4~M-#|qv)=*2FC`x3OeRz3!(K=5NyxJND{=S>LJG|_#)n@;h>;*Tl}ZKO zZrAbocMuqYcRHQ(=XSeo`x`l#Ouj)tuHpH79_e%%)9Dnx3tYq9NO+zDwOZ|8xRG+X zY&j=9&4E^{g=Vv9d){`tHT~}95pW9c_xl(O1`sMB3VxG-NVsbO(QsD+e!v|I_ywof zE<;-9PT+LgolGWh8`}|}KV7X>@B?lu5R1jI-|tbaR!t?mz->FO(nd-Ed^4k zl=d(&lMn&76etu5+Iu`6$8b2*gbayBqag%tDNrhv&c_5do_HiNo6R69rpquIjbc0= zW3gChZMWOOkA%M|A7DIKuO rUcFAFQtbgggytKy``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{!LF8$B+ufwUZ3FnhiwS;&a;j7Cm^PRZ>*` zMPc5;v^URg`1UK@bNudS)$LwTc%h0ZS13xuGkg9c{{Hza`}7rC7`k8Y5?swKEn6UY zb4h7z@Sz7!So1xPu=b0pYnxv**pNBPK~q%M^}0cUMB4+4zKqC4i4V+=SaUGVacHaj zwXa?0^gr&pgM~7O4R}sBnSKaweE&NmLh!@%bDw2SZE`tibNqbk^U8$V+ZrrlLa!>V z;ov#kur0TBi3Zn!lvl&^TPo~yF6Vtq%JIxudA^rp<};KeKD1lz R$`14-gQu&X%Q~loCIA?_eb@j1 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@1x.png.meta new file mode 100644 index 0000000..0e31542 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 999625d84327cee438ace602df54e656 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a39e85338a360659d35f5c65665ca60a1285047a GIT binary patch literal 566 zcmV-60?GY}P)yBsjP@I1B!C)2WTCv!mi2G?&mjP%oi+3*uTg9TcLY4n2dbAR<^COuv&x zDbiySbCNs|j5(U;^*f0iot&;o=5y7uDm0rt;2>S6>0I5_e z6*QYo*PnlaM7X@wYF*zC1_Q_MNUOlDyjrcgs)UpPw{kBbd{2Rpri><*~{uu~;l1 z3*l{;NF=NhuU0Fp*Xz4aG(RZR6PSn)m6pb1M2Erqf-$0_OWl#$qaB9uVZ2FIsX^SG zPA4to5X^S|cE=a&K9RR-fpkm!dgI&SaOly0$&MfU0mvELI~{6~UjP6A07*qoM6N<$ Eg5`zsxc~qF literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png.meta new file mode 100644 index 0000000..d1c5066 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/editButton_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: daae080bc5b18274f96637529e5c9d3e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a69d11a8f572384821300d03454cbe57b40caca9 GIT binary patch literal 502 zcmVrL=_9ph6tw5_EPo zoKV5_p7$Z;cyO?bwXDa^TCiTt9-6$S&7KS33*{ymX(v~ zd%Orq@%!z&BuS?-=5HGt!5|K@Su87xaH-4x1+P;upRdsfBVC6?zWq!Fx1|!kP1BBZ zB?!mk*iIyzRczc>tN0iUrhKx~h^5omj7IU)YPrad%_d$t9c)qZKNIYwQt)`axG5CI zEj#f}RlAJ_O!BjcyUCOjoN(D17I$2aZt5+;ALv_u2^dl`oGZEvq_j7JHHEQf zbzC9>TFKw8uF~&D!#eh_O3)eG z_&D{k2p71JI|<0m&eBM+=uE89KQQ1luA=}%{b*r9r5w->9-f$>IG_|i*l=HWcUAOv z_(o4utOR(ImEV(*O4O|x#{Wd0$?}*HEbn3v2?Od zwKy`ZVG?9I`gd-4f)wO(Sx>ENzJGgoqq4MVK89DZV>47L_KQ%|_46{t<^*0hkmK z0f;=T)#$_4R@C8ieNEKZq)0hR=7QK(9Jj08VgX;8rb4tZ;`!-m*UiUCfXjaF8eunB zTQTbDx}X0mtQo+1#ew03qfC+M9Y<46QXvl+L!+?Qnq z`kfA*OC>%$yTgGanyV3DmD|lGKQjkXpNz~z<+#_wKF!t4fF(r}34VTSwXmGcP_I;Y zWr@t@;|m0Mt_1cDh!ik;OoZ*44eYg6!>-2zyVJ=XGTavmsuopSFj_49pRKChXuuy1 z!$!s42Ln7&CsK<_$PP?&fM=-`C$F#SFQ}d}Yhir@|A0PaLNZdMlMHG-yH#DS9uAF0 pk$#>B^v=8?clbfb)y;Hz`3cZ1pk0zKo5KJA002ovPDHLkV1l@+(Te~8 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@1x.png.meta new file mode 100644 index 0000000..5b27592 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 81679c1d59c8a1c49910fd365416d017 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..47e246cd8fe42ac12c448123c432d05245388348 GIT binary patch literal 850 zcmV-Y1FigtP){bDnh`UQ0TjF=Zz>9%QD3oOtS@Ij2 z7B-vBn$F~J>7HO}T0-aV=j0)V!(p17o>u;k%_km^hX8phlcC{}5o*-yRNmk3nxm6P zu#*2iJ*CRwp>m!l0_^0ul@+RU1XQ?H3W7sJ6d4<%hZ7Tm1O&kO*%_T12K_ibrVAds zC3h`A7E)|#ih{wQn^#p?5v9F7F@7A2bw!~|z_+a}@%@S;Aq!CA=-@!?b+_aI>IrK}Q}%7yB>?cw5>6csQA4*x*OYA<&gHgL!Lbv6C{rNztESOWvFIr|Fz_r8NN9>n$*+?WYBrnXF^W3h1q*b4 zSkn1=^qvP5@*8|L!zEQ;HVmUNI5_942qtgvdNUgTa{~ae=5T{s9T( z9aQaP8PSH<`!xt~>R5Peg4gH6aVCRxmrGNg)!&l&`};4qbf5e%Z=>Da{2?KTFz9wLdv&jt3ANvt`YXq8IV$Qs=B zso7LKo47~0wgN^dgx7vwonN|L>z*ub6^m9pqIipPZ60tEJ4~xlg;>B>;m%sj{_I4f zDp!LalGFi@$3^(hhVdkufq;sQaF||>#9g&Icg49aXy>nKRc|x}Z-d1wcx*Hj_Z_RV zV!da*s)u?Ve?jC7W29fB?zRNR4k2YhM@x#SudJ-xPbRkqZnuZr-zLtvpmt2oUtF2y Z`6rZOpQ(pOyTt$i002ovPDHLkV1lU7&;S4c literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@1x.png.meta new file mode 100644 index 0000000..c12eb56 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 535ef458a8b49f343b5e68267c3ead65 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..eb7690d372c58805955db8104e513ade54045a97 GIT binary patch literal 821 zcmV-51Iqk~P)-ic(b$xa7=GS6j??Y+_W1;dk^p0F%C^fNT-Ox>mUFqEiIFQJdRJGk?4?|; z!aT1+$zz12(|;^Z{*Imq-2Eksj|GYm!&1`a0d-+-T=MCRevZuXN9wVL`3s92?3qNmdiTJ)_99Vq|_2Yj#90)#RTOvu%D z*b8_uF%j@WK)~_tt{CA7nSc`Kyp&$_!(l=Wz?+4>;YfBQ+M13KX3%#0w%S~WS11OV8e z;7gpa3I{TXM%9#_g5~m9>JVB9VL70RP?HEQ&hf;Oec?Ip5fWC) z^1LFBagqb@K#c~7h?^XrY8D$HAR$z(ZYs6rd7euN1DvbP=B8D3-S%CtS58s{xWI{f zroLFpW-E*_e5%FuwN&GEzjoK@tX13XHXi%~y2Yq*HA{pj00000NkvXXu0mjfn!Ieq literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png.meta new file mode 100644 index 0000000..81f5682 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_off_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: abdefba70bd618341a0130e354c68094 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..50e1fdee3e214cf8d1a1ccc3b823de3df2478b99 GIT binary patch literal 444 zcmV;t0Ym2tiS@)}GU2_+4fcNfq3ill zS(eAxXDZ8b;yBK#lYpwK3xu1xu7{HJWe_>jG%Z1DyRQ4ZS--V~`F#F}aE^n0+7-s! zJ%lVQ@X#s%@*RryJo=$LOI0W;KcYL zP!z=sh1ZIrEaEs$^E^K#+iUA5K@hyt`Cxp7@vPx-(=@+D#^dqfU@!>aL=X{EE5spI zJwUvmmj@@8B-U`YZXt@I1YtCtPS0?41_<%t}LfPcS4i5i0S m1h{q6h18X-Ygaa{O!+6xP_7%5mizAj0000{R z8bKJYZX9tVc!LpgfX>#H!3TnyVRC>7bAU<60o0xo#9Sbl158L3g6{}|t1RXMq2~r} zMDf-6KBOVy<)L={%8Uc{2T`E;8CXE@2kQxrT}|3 zI>X`6o6Tk!l3=mU=kp(_R4QR4AoE6W58#)*UQZ95Ck(=lP;;FGBN30s%SHl_YiXz! z0H=3#VJ7fEha|A}g>yJ`5sbor1fX;yrxomaeW&zZ7TtMy%9V?EK9Bv%`a zM&pA708)b?8cATh4U(8lChxIc3jmL~hG!XloqJ_H(N+zI)Yl;1-nSu`f5YYv^7aOs zUHv&j8H>d>5^>a_15eqm-mY3O$x*LkiUb(Q>)G=9MP%bw%oiaEv2E?E=Hvow zpDFOeh=Ri0x_^p4V;!8{KOmrq^-{lU!C!OAKkRj9Om51puK)l507*qoM6N<$f}M9- AfdBvi literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@2x.png.meta new file mode 100644 index 0000000..89a0c91 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on@2x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: a454b67ef7bd04780aca99e364202679 +timeCreated: 1498131017 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..fa505b01d8c9946ae8898e3df72690308a9fc90d GIT binary patch literal 510 zcmV!g~@oV#ZF2kXeN^|pUw0< zgTO%|0f(s+SnPI#XgimK_rYK(Cj54e3kBE>hoRSK7|4(1GCa52n)sgr;Y-XWIScJ)F1ftUkAZ;a(34FrhHnJ90H}DTs0sE4< zXOhfjQrWsy)w_@+N%&{%Va&QAS4Hy8wW3`90O<*)6wQb)g#Z8m07*qoM6N<$g14gK A`v3p{ literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png.meta new file mode 100644 index 0000000..a61b3ab --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 790d4032d74412f45a0851f8ba194517 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2d88d69cfb9736aae5f7a00e16a475d28371421f GIT binary patch literal 883 zcmV-(1C0EMP)QHS;aqN_IP&+6T9FxT{v=wa!7qjW&A84J-mZhek zln!bftb$ONo}$o#LlP;CE%^HV9(Qs{&bte8@dtX(chC3xeShBf?o-IXYfTGWRn-^? z@r3^jkw4fx6=E{3>+|M>f?w}7O%sZ;EQmC!4)ni5JbkO{x)ks(82nL1a;u03HRGB# zNQyE@)ki=jDyU2zrAvaMC?OU>_sK^zw6xHpp`r6$hpv#%$hNY>i4j z!AkyfXNSHoE!nQ~ML;R}$G$!~IX<>sTTuvV>+9)OTN~Z(?4$s1-q74E=W~aLls!10 z{nb^<^4Kdmpo|BD!j0HWq7x)C^c;2%BYZ76@)W`_!udUImREpdHqR(Svatw0p z0|6iU`)x(oo`TXO0y*Fo22-qwLM}&tm>heB*_*X>b?yQXbJOzV@UYDAGXl%ljvQjG z>oLY0-r@9g$71dRI*hWp&N0lD5Sn@5h1@D)gZBdN_V$)}p-jM+L_$7$*b_HeTZ`Up zb1B_3!r_G+fH&b)Y1GV2*|97Wfb4~(I7Tm~r;9{jvw%^2ypWqoh};4B#@t-lnwn>g zjc-aPchhNEYsDU4NVxa6ZSMYx1h`$PNX~WRc)?m!xnI1_?eZyw zZ4Oj&pI;QVr5JIo2036Gf{fu>)B``s0fb1PO8u9--kKuq1N`3W`z6Ff#k&Miq2lOfISZ}S#x6!?B(@?w+SKbn!#%U@qu zmZhibI;g4&ilSJpTL{Rq463Ta@pzo&QN;Is2*Yre-`&3$h5@1|E{c#K2&gdDtO{tF z28Y7|T-T-3*03yVs$B@+A4O5XaU5zcg7;t;)z0TLn5H@XGfqP@F)v9H48t&qjm!i_c%H}W4IIbO z_0bdDaQwx4Rt25`O>AI0^eVfs5#q^hUzX+9<#IVO4D&{EuMhS%ecykkY5MU4+X;n> TV1$Y@00000NkvXXu0mjf!XBr1 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@1x.png.meta new file mode 100644 index 0000000..6adcd36 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: b75c2d9e5306a944d8f46b030d24f61c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gizmoButton_on_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fa712a8177e4e04d1e05dc7851e532c96cc13d76 GIT binary patch literal 786 zcmV+t1MU2YP)dI zVNn=<-PtKd!GwYY$&hFyKDI)&OUqx7OsA2opRoVHOd+9MS|v&*QBb0!a1&ZG5n&_3 zp69G{W4YX&FPeFhn|qym&ig*^=k@Ani$EZ7yI!x;I-Ty@X0!QD9{?u1&164xyWL;P z+E)1;Q(k9}cXS@ek|agi?Y7Jb_8I|vn_>i*Cff>&UToj)KzPx0!Uau#+-A-<|o6Kf2nM@|WKAB8tJRVc8*Q0*F{}Pd;V?AJ_ zwF%0i(I^FjK~CZn=JPqRPDhnWg_C+Sfmkd?kw`>Su^P(dGF7WpdKCd!olGXV%YkOI z$?xHC_@u!7eWTHc^7;HxZ(La{mY-_M)9Ex>tyX>z0_ycT#p7`@7!0I^Y&Oe@`2Bt^ z19yaFu~;1JRjmRj2MMsxWHL%(-Q3)e7DT9!m^cdqgg4dB9}qyyaR3K%xf~ZHu={)r zyRP0PflxxWCIKWIP^;Ar6pHwsLaWvK=Qm_P7JyLfd*Oh9$Xc)!3Aii#RGlMa`vl-k zVL6EG=fOb|4h93>U|cR2c|0Dja5|kT?>n6iU0+|5)9F;!Q2UKWg9?QLtyU{#9R{RQ zDKZ+3T)A3F9xcN}BB7L$T0O&+4u^w34~N48%MeOfj-*8Bip8Run$wL40)+dJNa$`T zCs?coC_}eI*M#cKrX%T* zJ;-!^$QV*3HXuGPm&?eRNyuBBf2U)C?VN~F?L(#|TNpfD{an^LB{Ts5D?Lv= literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gridPivotSelect.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gridPivotSelect.png.meta new file mode 100644 index 0000000..ec5d57a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/gridPivotSelect.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 11b8d44fcb3cf4c048a576df708e0ec6 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/headerBackground.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/headerBackground.png new file mode 100644 index 0000000000000000000000000000000000000000..bb4b1450677cbe71746511356630712c7fc3b523 GIT binary patch literal 1069 zcmah|Pe>F|7$4J0ML~)nJ=9}5c&MFuGqbz1JGklo&4t!vU1;gFvolYfbmmPn&vupm zSQHg>kvwz=LXzrK2!d1xt3zNIQm3d6-8xlJ2ZN~XjqO^5)xewgzQ^zPeZTL|yVT!z zptZT9nPHgLbZ;U{_cm`eZlV9(pDQZe+DS4`2Jj#$sSaYIIxZrRHq{}NMXEl2YzcKS zOg(Gl@+6-*sA$;aR1d?IO`EbArmMSbtJ(-6pooSHE5d$!u*d>KkFbXW8JMx-XxQkT zaL~X+Urw7C(ZV|0y&rUy73#o5L4u?G-qUfUtpF3_5wd}LpJv9#rTe{>CY5QJCj*= zY`N7!(US3H)#e2b@}}wKwT5;{7X6a3A==H2+lbF17mqm_?Z@Dr8kyGa-+??JRih-Z zHfAD=q!SUA-f+61D*-7EQ~ntA%Sl-fl2SMojm1JDghEJ61_GfPH*L8@wKP=Y8kCy| z$iZMNCQ@$juUsYWAeCSzhw*5wd;P-4m!sOJ_`<3&~LghL>+4=PQ%EG(3 zBX?hJE=rGD(3h{@_~*MW+(xP3!nWV`F(J-!hU*OIsG8tqFk9wkMEdE tx6f|9I^Di==HArfl3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~Nw~85sqF;2buK*#8Vq82HtVM4(417gAxE|3FH`l)%q^j-u^*zImLG7Jn%44y8IAr-go zOti+}TnT@D%Tr_9KC6;~0)d!0F)l4) zS7HT3Yk0y~)YBaud*dH)2u;3mXNk+64}}UZ|DNBue!n@R?`5X@mU>f79vkZ$zG`Q( z+rHuE;mJjjpSLzNdZ!t)ca9MLts;4LwpZE9=`Cg+;d?n(ZkxFve-5+DG24Ruu}%Bt z#J;c%F=jsfN$=^!!vB{}{@=wCa@&7K!;<&I>V> z{+B!|3&fwT{B`v7n?>gOzc^Pjr7b+v|2^yB??urQZI-dFdF_7eoJGvMwiz-fOII_Q zJv;;Csa*dW_R#6mADsrxD)Gk|TaEa{b#)idpJ=nqZHsfqOv&~~FQPfZH{_d`X)8yy z@Ebo4$f;~ryeaN5wZBERBlz!x(-&X=eH?a7?AKGd0`+5pVLozV$*UtIqfb0Lv+tDY z8tG-C>Iq)IqNSou!#6oQ$(LB{5i}LddoZOg&^&yTFMg{vd$@?2>{k(1@-^{ literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@1x.png.meta new file mode 100644 index 0000000..d1bfc7b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@1x.png.meta @@ -0,0 +1,103 @@ +fileFormatVersion: 2 +guid: aec40eb6c450c4bdea484100ff9fb9a4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 10 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5ee613a538a38c523038a70837238aba0652f2cd GIT binary patch literal 2094 zcmaJ?X;f2Z8oo)`1X&aWfrv4pP=RcakioW|sEK3($ym~oBcK2OT@{KC z-Wpg0{*mwhB-TWxs6nC#)TojnDKfa&jmA)FCIx1I@gS7~_pWHBeED2_m*snp zl`Wr(-?CUDk_^!}+CPnY$%N>~xcwkpUG~xwBJIJaJgu>>|pspyW$-M#Y3Vmx$Sl| zhYKw zCmv?4cvF&R5}L8@P2GwS+P9`*W-m%1^5B}*m>HTM(7kGOdbri` zc4y|o!N0Buo=*v@7^F@Xh+1=P-Y&{pj{GpQ`Nz(0>Ti;514k$C$9L#=*wD_+ny~(H zIziH>_Be`8pgXKeZTUNan}JXFmuxN{%L9ZJym?BE!7@=Bq}PgbL`E}0dmV-Y7A~fhY99Qv z@W%+SZ6#^Q5q17aoO@cS;fU}W=i7ny&HM98x9r|jf244oTRjHbIsb`oR-x4e(;<3* zag_4+e8<9ZA0uxW@~anF0)R1Z0a zIckc7ce>?bi8$Lg9rHPHUHAyJ+Tx|3m2CK@+j0d%w(p;RYu!Key!t8T>d^CeJInOPYrf!aI4$U{e{FBQ;Ls>u2yLWs z1(`&~E{{u~%d2$f+{8Ao**URoIn!m7-MaNawfUsZ&@p!ZTtktKyrNXl-SSX5bZKMt zwZgLUy)`=4Wy`pcUr{m7Z@oHTRrBmC)06g=vs))_V)h{BE;`pfz1+FFow@GVW7xGi zuK;FIcVE@gS9kUe%f6cmnaTAIyc#NX*B@9XaCo~bwvyM>tFyejDb~$qAsx6T_Bj=C zp!&h*gvtP1jqnZc<qmR0&P08`G26+|_yfh8nY7^7@-_SZ+%dHaF_BXCYGjO&;d91`)3W{9 zjOeRp*p?(zp-0FVYxi-7$JV2KpVa1VKFR{-^RHhU+=SPLUX6a)e0pDehq}?^spE8| zqf^!E#$-Fau%}>Et+=aZXV-6((d0Sw%4F1{1qnd89WOa+`%|~^1`iJraO;A_ng0b5 C+&RPm literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png.meta new file mode 100644 index 0000000..0a4642b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_diameter@2x.png.meta @@ -0,0 +1,103 @@ +fileFormatVersion: 2 +guid: 36094f49ffe31443eb5e8ea185115a1d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 10 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..466afa3206165aab218be7fc84ac3c69815d15a2 GIT binary patch literal 1349 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~NxV7`a)R8<@K|ni*Lb8oF9KIh(pzxVf1+o0uB7 znLC=p^t$9Hm*%GCmB93-AoRN8)C)=sxdlL*T~doO%TiO^it=+6z@E0s#Nrk+V`F1; zb3-$rdFI&N0@0g-+bzyG-J%b4j6Nu8k)j$V1WZ35COqK+Iq;;Png>kpMZknTxW0T0q@Ge$s z=~HQCn`WFmo|7TuWhN4N`}T_G<*#(&^v|BDe)q*~Z}|Rmug~rMU;J)o{ySUql*q6X z=R&P=EpPigy3*fLQ>Rs*z2{ttPCWAqUzI~ki(br`^WIxv8vnt^K3WEk4S07Kcs$#8 z_mzu{)%(Lw669B1d6v;X-Go8LW)NGcr0&OX@_o z75qzm9o#&#RW&!NIk#HgpHac4<7SJQMQf#t{iiR}Z>~SgvF8xe-{5u34l86gGyna- z63h|1D`MS_x;e51dXg!BiUaR6G^ZZxzrZ|Mp!9LfccGW}S%oj~eqR-B!MrPS&Or^Q z`D|wM+0F>^SeWmu`QNmL^Z6{^izlUSv-{XNGC*Letb@<@0^!ybFOGDr>soqa!ej9` zi9J2GAA;T+hJ87nz?v1hy@0J?+L4k&yl-9xN|nEi`E@^YZ^df&s!#tzTvCIDtyU-9 zesgc@8Lx|WM}D(yIn>EAOW(VG-;5V~9bO&P*yCtybD-z;rL*5>U+iaKW;pjsr*`v$ R3=vSl=IQF^vd$@?2>^wg?Z^NC literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png.meta new file mode 100644 index 0000000..525328e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@1x.png.meta @@ -0,0 +1,103 @@ +fileFormatVersion: 2 +guid: 43a4af66a8c594a2daa4744ed77da2dd +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 10 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/hex_width@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6d79614ccefce54f9f13598c6c963651348281db GIT binary patch literal 1943 zcmaJ?c~BE)6yJa-A`uaa2O>*2Jjo?VAlxP#X$b^~U=fj!Y+!|Clg&aBv3OXQM$9c@B7~Te!utL_ukC6QxF?4EEXY=GLOe7aWJgzqh*C4~Ngw3S~da$VOG=C=Dokk1rSj}N@-2(#|95)6vkmb*s z;Bpahstl526I{t>E`3U_VHjizJTezT;WS7Rh$1rZW6^AQ%3N51Q|gU#B~#}@osvtz zlc5+|`>$3{xbXQgEGGvS-%O4^B**6+#RogH#r+BZOqu`x-a&C;UWLU8qx9M$2}6()Q zoBAFG{2FU+WmHbcf!kuVyGQz(w)jRbCFbhB@o}>mTYN%%esArfZJqXn=d~LS&tIsv zwn*3%#{i6X45+#Usm*C!v%LuD0MSAJq14ti`H^yQrClfWyH!RNuRzKxFG2a5R-|SU|=_}vRn6NcGN}ZYqoKo8|F%7NzBovESId@nt49=UAxv@ zu2AW&a!g{p?9yC-g|w<=p-2zQ zY=u4!ndchgu030@W$U99+p{+P(N`o}Uh_im24({ehCWVZzygKlT6O-FQQe+do14Km zBDm_n%?CN#U-WohXXwc*-~0cVd!bi3V7<7yanK=0d!Cfh7;k;7y(!fC+oK(}&G#Ek zVl?wh27-j7JuUgg%PlK^+E9M6y+`?F-m{3|nRyhJg;}2ZxRnE12rBK|2Pv~*nuCbUO)h2?^7iFbnclti~FxX7#wUJh8&bay2 z$Igl^JzhHZ;F0|ymA^?3`9!uc@0RCBKG;&m%}+hv6s zaZ&g#j|QjkDz|Ed!;>)QC)2{v5q3clC)O>;;qMuy&YnRX^06ebeIlbi>`eRS_0kS) zcGuX7cen0akL0xKOQJ%Do0kwh?c+U>hiT5Ki(HB;a%g>m>h`ZZhh7Y2_`kCl)D3QX id7_E)Q!3HEdXMMW?q$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~-c6^VPgIEGZ*O8WEvzdf_wfg=s<7d(U%4lt}c!OSk`7t{w- O!r>$j+1|*LJg}1{rUgjo>}hzlag8Pt(Jv{&Y$m}$a*YtV-jnO vl9CdWkFntc#tPNQ3%yQkJwIZ&1riv3%;yuGdDmh(&>$j+1|*LJg}1{rUgjo>}hzlag8Pt(Jv{&Y$m}*vZJhV4{k$ e-E}4w2?m>6T%2nf7L)``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{!dRA$B+ufxxu@6n+*iM3btryY+`(=5ggno zq^NT21d}A=3auApjD3>|D=Jl_y(VuJOmz;GJs5rgJ=6KU0n3Wo6k1!1uXdzZOnP5tMd-{9p8{#dH?&=4t0@&*z)fG zImuCCt{2ydZP3a7?<=j;_(pK&fo2V^A2~CZt?XKKB24gy!}YJmc8hlCX_ds58~I*7 y_c`VVGtU_pf#Vxj%@n(LBF6Jysp@v4)AtvBk1lZVbGri!1O`u6KbLh*2~7aVa)xjK literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@1x.png.meta new file mode 100644 index 0000000..fa00722 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@1x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: 1986ec06d68774254928f44b896a3913 +timeCreated: 1498131401 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5be3b7bfa795896947ec9eabad1b537c8b770bad GIT binary patch literal 554 zcmV+_0@eMAP)Sjxc;Er6P!UMf^mLcnLt_Q7LYbBVGbf zbwcL?-pP?0@?g8&zErE#2G+lhFqU}0OTc=)-e7nn54;30gTbJdrfC!HuLtYEa5$_V z4u=suaL>sxRl{?PF^quyejj1oA5gVD{Ye5uU%_Ir;B}Afx#RKpO#;N9AzEIu zA^~CnXn)fRzetEk0G$74ygWuXMAeH0oKB}kX)3G$(6IvuN=S%QfPLs}U_j9qAQk`v z+8q!ZP$VETV1bVTMFPqONV6;h{3mFv@{$ⓈOezr-6%hm*8?n7#;1AGB3ri6jZB)=g z1;J|+E6;r=cDt+Iz<$8not>RG3>>DF$N*XtaDzAr%;A$=vvdFK-Aa}i5QRTvGzRvi zI`Q;y2R$Sf<^q43O7?T)5tZx@(!Za=I#(XlpulRq!C*W^tKE~HVWyRIRU}uJ0bKJj zUpT_LRDr?&UW|RCT24T}l{ZI9K+^Tl&3-?b;cNnuP3{vBd2u!YwU029{QW3_aUb^9 zZhr({1e5{7(DTBFi3fa`ugW^lL24;VBlSP*nJDi750FDqE{Tf!6v{7qufw%32AyWZ oczGiQ5k3Mq4bB!Q)a`tH2l?Ajdn5Y#QQUu+Jd^@%@eE zP4|wFUt{J%D2Lo3{U8Ki5S9UBWG}H~IRawl{GETne??^Q77j$sc~ihP;XlCuD!&X0 zK8-RVy;ecjUTDzW*^2It5FVe?I5;{}`mZPmVAJ2%g~h3fpK)V-5L>&4Dvy8x4I<&L zqH$YGGgQ$M@bvszG(I`KgeqDo*f}^sLwy~ZXz7nAxT*&vB4*CnC?i@akknLHIXfXb z5C#WU3Z&bm0VA{v+R1oQ1UCgN0h8mShz%Cc_~Pmg99St>UEjuWiQ{kYAHad_fm&L2 zFBjJqIIsn@dk&o&=Iq%5+6u;V;pU$$Kps3^d>0_!eqK^9X>ef)@aF&h^NV&s3S4+A zKqsGzjsaN$N?*A;`DY0zHK447B(gZ+pnMe9lpH|$_d%261x`JnyaMiwU3$n5DY~ax zin*}rfkrAqBH>PF*2ia;_@FD73W}Z-imliqm143Q(#Jlhz5q#-3xT+iL12xPSnwU& z+TU2&G?Iy>bOzZGe?{00fhdEP)YvBZ);FUgPKbhIq%hh3g{5su?PmSv3NxaWR3 zKp@-mJS0hiD2jL?+5rL#!+^T3k!2Y-hW=m?k*KN)MN!bU?XI7zfNGE97-^b9*L7YP zpMa!qiFCi4fMgqnfvT!_9S{?^uDjZ<>mUM`z_#txmSu^D2z=jPE!9t@+)c0{3_~mn zztc1af7qGE?Gr^EU^vq*&>p;Ad!}hZ-}jGw Z{|DvUQ!HJhb!Y$p002ovPDHLkV1mZJnlk_Z literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@1x.png.meta new file mode 100644 index 0000000..1719da7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: d893792094267994782a0ed91c3d5748 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/infoButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dee56bb2634d8fbc373de8199d61b86ab92250cb GIT binary patch literal 576 zcmV-G0>AxM}bGEcn3ycWDJZj^duMoG4N4kNNT4EI#N~Y z`PAemp?pZh|FeBQ+kz0=wsW56y;+v^?EC%;`~&!H2fsa7E|>4OccJkL8lU2A1(HDu zAv%uZ$Us1&(fCNvxGLi5pMi@qqv~&1Mt&{r>GapU=a1JO&*E7(fD4%H=ZXVkCgvuCiDxKo_GBOeT}S?Zsjd za8GOJ0AXPb@F7KvLO^7**UP{mU{z+F9C02Ln6dXh#$Eo3CI!8TdZeY8b3L@02sw_q%~iWv%cKedc(&4Kev6!9M*QnSM24?Jc@Vlkpd34H_ipIk8U}fi7AzZCsS=07?@QuLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ@J&q4%mWE% zf_3=%T6yLbmn7yTr+T{BDgn*V%gju%a(1$GHZ?PJF>y0DH8gZJcXBZ_baZodv2b;A zF)^}qgz0t3PcF?(%`1WFO+n~&#iP^Az7H6D#^?{Dj2SqJXRKtXT=?BDwCtM&0p7c}mfa$#mn6S%D9L_T^Fx~NVaSW-r z^=4Ll2XCOv@$-AdXHVJRJ$)G{*Rk@ww){TK`UiMDlYVM_o$ScHgYTBgy^VRR zYYY>Qsb1jN=Oy%d(Qa|6(qA95&Gz*(tUef-*`Jhg^=;XkdIjFxHRl7b&0*VE?p5$) zX8e7}54s3=0aL@a6 z@qzcDpV~{TzsUG|)<-YC64QndbS#^BN(%XS%#)%tuojKlDRkdRBo##b` zdU>lCuMxE|HQwv8kvn1T;u@_A&&63Ay_u_KR$Kjev%;E*&pYmO1)E&;E2fo6Rc%$X zZ%v!~`dbrUUQ*#Swr7`SHEEXxoL{NZ^|i`PHTpp1y1hxJhJU$M9yPxGcKV#-R$iGc(>gC%$Z$KU~6hyfCc%)trI12$G-8U>H3g;+n7NCTZ{xbclJL+E{?i%CjVkj z6|44(Co8d(f%~Pla61dJKGS`6v;8hC#Qp>r00y{53k}ve%K!iX07*qoM6N<$f?KDm Aa{vGU literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@1x.png.meta new file mode 100644 index 0000000..203cafa --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@1x.png.meta @@ -0,0 +1,76 @@ +fileFormatVersion: 2 +guid: c43744025f7024e20b5fe880600d19a7 +timeCreated: 1498138222 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 0 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 1024 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + - buildTarget: Standalone + maxTextureSize: 1024 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/lock_open@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..861f8fed35cdf24fec1bc093e14abd71048fc20b GIT binary patch literal 1618 zcmeAS@N?(olHy`uVBq!ia0vp^W8U}fi7AzZCsS=07?@QuLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ@J&q4%mWE% zf_3=%T6yLbmn7yTr+T{BDgn*V%gju%a(1$GHZ?P}G;uRGH8gZJcXBZ_baZodv2b;A zF)^}qgz0t3PcF?(%`1WFO+n~&#HkmQ7;+1MHoK%2WtOF;xE1B+Du6w0m5JLe<~YrR z>P^Az77Ltu^?{Dj2SqJXRKtXT=?BDwCtM&0p7c}mfa$#mn6R6-2YqK?U^?RI;uunK z>&>iK50OBTBjsKv6hn8d5GoeWa`(BqZ-(aw7o<6DNXzj1-O;R72OuY9b z)#}v?C*D#H;H>i&y5=0NpZw+J?e({Q?30jT`o83Dmt=UndGVj624^-V9t)RcOT0fN zp*z}cAEN=cjJW-Q-~e^s2-_cg=Nh?|)w5bKxE+k%&yw5yHm$4C@B_=4#Ne!O?jZT@q&E3}Vk&QE)5XmO_F)US(4R0;|^y}gJE$<($yPd^d z5jrR6%tpIo+jkx~L(uDlDKbXkeF80d8q9`;IaL3H49fo<;Y#W zeMkDB{<8O4Q=O88zli*Ja&h`3EuZxP_b=QjdcKQ$!DX{V$8BueF3e*}U#?lC)p>}` zcGaY?4+pNTc%klN@3boM&)H*A9w|HCC)K>q5f^p2Vc5S%t?K3`E}ichn_akPfBvAZ zsuNJmYjFMW7oRPr5As(gh3J`a{El435}Ok(d@VZA^^j8H#0?%1Dmp@8EQy^PxNaZ* zk#{)EyMBGQ jn_PGJO}|QFJ*xymmtp;y3$xa+f=YQ$S3j3^P6}1_4M?lD6kr6X=_hj(#Xo;arwZJBQBHJE`{`G yNtj4Q{QLXceO8-&{l5vz4xTyFqr%I;$i}eAmBaqZag$V_F$|urelF{r5}E*`@G$)V literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox2.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox2.png.meta new file mode 100644 index 0000000..7b8a559 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox2.png.meta @@ -0,0 +1,54 @@ +fileFormatVersion: 2 +guid: 5d951d0a838a34f40ac2b9ce8968a7d6 +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8149b8b882faadd8c7a7848d52bd3c970e139e6b GIT binary patch literal 1108 zcmaJ=TWHfz7|y8ZR0fK-IYC1dZ>+hk?b^7gZPRLp8MP}|hsv_#Yzs9xF*#ei`XKrs z-VnhX2!hH$gfT%GD8nl<9g28C#0x$tQxGN&Q9;y`ZJj<;Ly~jO&;Nb@_g~KbWW&0O zX;sq*f~ZKWmr{70;eO?l@qf|CF9k32Q7nTRVJpfhHXyel2fMa=?4wtzO>6sh6<8x*N)BDKjcv$7Qh?fUwH z4H^p#X|<45LmIVaHCdMzFo6M(LgtO_rX%DjGQE+&<3QR zuJNiY$B}^m&H4O+09Q@&EXOk}egj_4C-?&bUrUZZ6pm(VtwKtQkH^9%k!nZC5*Q|z z%h9xNoyA242{@{)TS$i{=`t!Uumgz{Ryvk~VNKAQ z&Ul${!I->aF&xdhDHR=M`TwqlG2!i?6!@dJB(an3vH+6;4(za1+_=`oZYWEL+CV|j zPD8l8SjA*JM9^u678z{}kjrI7)lJv1ViY0ELc(;AVyYk^i4^9bbzKueJR6nbVcr)H z#5pcj8<99J9FIxCSVZ#ip>Po^LAApGCMsgJKiHaoV%=ddEF4(^w%!S}xD5?*G-*LE zp(Wy%xQIW>VLq&)l$J59Rzk}-*3F|dmce2ex3zz@x@f}l<8CJg7at}@ADDRFZ9LdR z@0X6>yf`@B|wk%Y;k3H#b zs!!CfSb5;wyhvtn*Rp-l^2;rR`2NeAp+~pR47}~D+*<~wc&=%m&&!qdPfmVY@Z&)E zW>@dr>%N)r;Hzy1tM4aiHb_-AWt%QdEj-E|`fzs6aBx%kWM}%w)k^l_1JB*>KW8@+ X^{b_GyPHoRckg*3)*$spn)mz$zDROe literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png.meta new file mode 100644 index 0000000..7b98371 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@1x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: 7991166a167af4b4793b4fc8fcc3bfe2 +timeCreated: 1498136635 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9e8843aeb9a1410ac369ad6d7554932a422f1866 GIT binary patch literal 1331 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~NwK8W}jdxH+1cxS6>b8oHW0xfmKcy1BYoxH`F* z7+E^P^t$9Hm*%GCmB93-AoQB!)C)=sxdlL*T~doO%TiO^it=+6z@E0s#O)RnoaRCG zrr>sqDNeomK*#8Vq82HtVM4(417gAxE|3FH`l)%q^j-u^*c>OEWEmJ3mwLK5hE&{o zGu_bpaDYf_e%vgJEwWaTyS6T9=JRt(e|h0w%R8^aH}V%l0}k3NT6Fw(6mVbd&Au>L zSy9u|R(;P2={rtw)5{+UOq%k2?)M_@?k!q)(28FGjs^S?W8J8oOU7)+}oFgUix$pj+Fv&HB zygXj%+fF6$W;Fa~Nc?_Vt65a*W}zkXxd!70JQg6zf%#}jgXCGckiT!OgRD$M4li2! zXvVtN-Y<^?op}q?X?yvA?G#0m2uoIw$$@9vXTSGbzPVq{BG+uT(D6b^xva+W-FG*! z=S}F5llpa6#_CIL{gDd+ZpMD=L)$~m%nw@4ntJx%qM5t-m*2DhI4AI}6T4Vi+XAI8 l?7vbDzpVI|w(pN<0z+K;qC;)k^|hd4%+uA+Wt~$(69C#w;q(9i literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png.meta new file mode 100644 index 0000000..d102b63 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3@2x.png.meta @@ -0,0 +1,56 @@ +fileFormatVersion: 2 +guid: 9043fef44c60647b7a763eb86f912ba1 +timeCreated: 1498136635 +licenseType: Store +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + allowsAlphaSplitting: 0 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3_separator.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/pixelBox3_separator.png new file mode 100644 index 0000000000000000000000000000000000000000..5b8357f69cf888bc712b81dd9297d9a5a55a2e36 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~d!3HGTx9oodq$EpRBT9nv(@M${i&7aJQ}UBi z6+Ckj(^G>|6H_V+Po~-c6&ZNCIEGZ*O6uw9`E%ZZF=1!jUn}Ja|G1C%sJ!~ie&meK pnl)>9M0PgJoH?i<`+Jr!UWsorES)R)!8MjtJqn(!g>(dYx25=EgvRtHrqj* z4DkbDcyTD|6vTnz)RW*r!Fdow5D$~-LC}L<^x#EAm@nHpJq!gylK0*}|KIQboio{v z{SEb->KTS3^bDJ#8K$Mxvys}12`J(o-HfweA3bJ)uEp7|NQTeYGA`@w zgAVQ-?8vKwy=qKjTlaz%Poe|{CJ1;&pXo|ooSo&B=-wA|ESOaxy>WKVsX`_TWXr)I z5)1`Y9zr0BK^TsRBHRN69ts>!Z!rMFQbd%5W-x!T)S9D}q@0qPw?$8JwoHgEaa^TR z306Wu%jw}@EEe-Q1R+2*0`7oGkQXrB9aRPeyQ-txM7K=fGoqr^PvR`~bS?zL&SVyd zO?N&~G-aHJYz_u_&M^GAX0=_C!@u2FRNKuD*qF;<*XnmvT949>DwyW(?}mIu>Wy^B z(P>dopJJ)~1~y4riL>-eP}4O@fl4@)84`qk@o-laXYoIT4b1s8l%?!$w62 zQ=y2ggk=a}G8RpQWjPT`LP3B^GAdTFY11XhRB_d=PVLTP+y07`WCtT+IeE+Kt9C%P zY!S;XTQ-n8MbMN%s&4v%-F|`Q;#IJtAH!P8u?#SqVM$-a110>g$mg)y|74A$&TxKv z{LwB|5$!>LyU_gfurNN@q%-5t;kbSHS&0tTgS3*!d(*`qGc&CGARC!BG2`$ljX9vHFn)^yKEoE^uMls68w`xc2JlvWX?d@};fU z8*gp@w0sz?yf;-oHT+@K5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8KW+g=7RhMR$W{Yl!|Z$R@KEJl?AE#L8-<0 zrA5iW_()TRX$FQJev3c~fv&OgFUkZ)N@9*nesXDUYF>$_i>(q+MlU5Z#mdFd#l+3k zz|Gv*$iU3P(9puj$kEx=)YaJB(b(C*&;@1&HoYcp7N(9yCPpqM&PIlYu9g;#CeF@| zmac|IMusk~7G^NLo_WP3iFwJXFncqB_L>+wSr{0)n&8!IX_DaOC3g2lIH^0DeTR`XJM3ab9>)DS?H`^)T`SSbCnD+;= zZ-@OUuJAj+`GO_Rbt6}fysQ29nh!U5o432FDKd5T^!MCgs+;g}TH)MT5=W|m_;qy>pd-!+9g{W&xOM52wuuZveP&#VylV=WmUNav2oFgM} zib+c$py<%!LxGGgO|07m4LmvY?kp3SS=7Y)oI}ShhqdJ9x_Ny)&(k@iboFyt I=akR{08Hz$G5`Po literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@2x.png.meta new file mode 100644 index 0000000..cab4be2 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton@2x.png.meta @@ -0,0 +1,103 @@ +fileFormatVersion: 2 +guid: f88ac1d30fa7144dd94337fce44b9239 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 10 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/DarkSkin/simpleDeleteButton_on@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2b2ed9367c8f44da9d884c81646233c4d21a88 GIT binary patch literal 1085 zcmbVLJ8aWH7&b~lG-{y;ilC~*;Q}h6v12E3604z2Y?nqNp-O|uh+|(8liKInSK@XG zFrbzRBqU&D;IS}QVxU4SATco%vC#oG7}%gvIpf*;D7t~k^hMwTPhijW2J;=Q3W?t2U2Mrl_AKQY84issxQvJh2s-=U*WmdOL4@2z3#(qpa!0xfGITY^u(5$G;47+Sw187I@w6R+Y3Fj=!j{`f6iFHD zsW!_oVb(PLxLRlz7vLW^c0{|yMH{jO=%NKjBll4m>5@tAb~oe$i8taU#~_!Y&dEqy zFrkIBa*8G=jBecdd7G>MPu5uC4C|N2U**!>A~onw zw~L=Fw%Z3S*!6VDmNA z`*rhn^W5av$|)u|`0L>1!1^t#dF)>1!O6#^H}=q4QwikXul7yGd!AMXo+OSw`?hx_ saCf8D^SN($&zazT=}>j}=fhsGMtywueB;BTYhV0^&MJBNMds?w-XAu z)>2hjS4j{=RkBq~rz z*B*bgOHqV-(AzFFKRzss4>a)1*myWHFZTNIVBJfK@vQsm0J6X*nF$x0d?cV`LWH{n4b?vPFxyZIh5v~e%Ls@i>PW^$`E%~OqQN)rPIMftDm17 zlDbFAj$NFXY92f=x$esAE9~@i-SH!dx&ie0$pB$hjou%ARx{Sr^lm$`q;&ap!m74z z)Vy6*y1a#mR5G9f|#-lgYuV(7S(|6+i;Bw1E*jhQf kDw7M3md|`6R+N+zQ+*c>f2p~)-}^@-r8e``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{%%hf$B+ufx04OI4jG8FUDkTFAb{&FYm}G% zgGs_S&MH?he`L9+>3u-%AhQJrW4_QN#pGoyTbd_N)?ZNFlx<&OQ}N;dd*1i{0s+^* z3f_{_a!%w45MTQ>F)B2)-|(Knu{_tnJUwPfEjDH7CW7|B~k4A mhk>cIZJTpp;XB21%lVVGo1Y9=s#Xg027{-opUXO@geCxnCocNk1~0Ucm9Jb+AW4B}!K>jMz$3JNBg zkQfM_LukVT6zXula4a=&k?xHAi)*lAAEfx!| zEXyajYgd@`AQ8-@`Gg77(=PJhXkaU6%UZCgy!bia`}9zggd z93C*-^Ss9{g4-lX!pgFIjuLRZ9YXjuUTB*3(sek38M>}NH%Dm*8;_HsDDR{vL{a=` z3-nI=rFSCGh6+#tDnJFO02QDDRDcR>1n_s6>;M!9a0tWjkn{u=lxAFR1WePE`gnyn zj`u9f5^=_!$c)G1SB7CO@;slpu6x9?><`cLUu4T*u&LQBK!1 oJrS!KPr4z%RTRZNWY@3!2jyO((~n=E4*&oF07*qoM6N<$f*TC7GXMYp literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@2x.png.meta new file mode 100644 index 0000000..2527b6b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/banner@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 14af83638dbc46f45837bdbdf32d53a0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/colorInterpolationBox.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/colorInterpolationBox.png new file mode 100644 index 0000000000000000000000000000000000000000..2563ffad2668658481215242fe23dc74ef944ebe GIT binary patch literal 3362 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 sb0s&!KV%{jd7k&fb=~KV<81sH0LAV30^l``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBezm8IV@L(#*~x}N2NXD1EFWk8S@O%XezJh( zjEx+9lSFR1bw3VhV=kQ`xm4r=TkLrTiFrv&ubnz!$?kn2%KE8~rtCB|JJATYQyCd$ z@v2eh^q3F+YP+hkh9h0S+O}CU1{@_cd1+CJM+4^ WW}2xUYwiG@!QkoY=d#Wzp$Pzk4ocww literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@1x.png.meta new file mode 100644 index 0000000..584b58e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 3240f334343ca4dee8239c16d2de57c6 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..58eb0a480b08c1ece3f52483d579ec3457fa86e8 GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=hyEWfe%d+lk(Q1)pI%VsuC4C((6!)}yb?(BR1 zf}pef?9!7`~ZBhtb>NyULo(xjl>3pZ#JzfBwMT?@W{Pma%vh A*8l(j literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png.meta new file mode 100644 index 0000000..6cb43ef --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 6444e8eb74e6f40e0a966d1e26744d08 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..12f0e6f046be19ee20d1405019cbfc156fab134f GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{v1yi$B+ufvy%<^4lD4u=H6PE+<#HHVD3AE zEwZ~)-^^fr7N=_5&S~!PMx%=(NT@sI#AF_hA0IXvFeUb^Tu~i($I;E@$o}GY%_{5M z3puAQdbV?mmpRYIL$hT|Rys6oEtAr|aq^5K=k=PT*ODu1-8{{nDP3d?@~d%}b!O`k zxljC26T}1kQrJyJ4vAKBDw;%Pmwi~UgLCV|N6+{E|0Z`vb5~RDvJRj-89ZJ6T-G@y GGywqng$-h+YU$Ld(Z?WJHrt4y5IG1E<-!S zK{H0cU=FB4`wnc!KA3wN#_GfWB_Vm#=he{q;sTb&2rvSSfYD0WWvDX#S}{!s%zc34 z)i?uv``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{(Mgt$B+ufvy%_<9#-IS&0Wo0V$LUXoA*-Q z1it{c6QYcpr}Dm7-rnWrn>5jT9*acE?Eco74jhRh6BkY?Qe!-oWX+gM z8(bDA?9#EUcyFJ~sj)AVbt&(KgtdQfIa&V%i*Z-Y;B*llX}!jGM*oo7HPEVoAh+zFQ8KyJYD@< J);T3K0RX^%UWfny literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@1x.png.meta new file mode 100644 index 0000000..b665eb8 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 82893b2f476caa64192d60139825552d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5e02d31461ec4b1ec37b8cb10a8a70f5a1d90edc GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=7bGa3g>dm4Iq%i`3?=T$u+k5(_z(6dNnWjf$$e{XGuBl?$u66ce8vEKAf9(7J^^FDJAFeEEk@eXV+8qxW=-j?%5^gbV9arFl9-|AD zY%Yp>nY#CJChu?C_wPe}`-Jr?azkSuPnMn`mFgfFz*c3QIC+~3*IcSu{&WAJqKb6Mw<&;$TWjG6%e literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png.meta new file mode 100644 index 0000000..47825a4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/deleteButton_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 1106592e1cec55344a2548b8321964fb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5df283dccfdfe239031ef8be381fb3239b383b GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBex9d`V@L(#+sTf62NXD1BEOpMJGA_z`d?q~ zhYyx+%z6B?_V8N zHj}PU<9uJw^UvhNorX`9>|u+SG%irMRJ~p#OY7vUQ=+MV?cHCh6~2DO=K-{t!PC{x JWt~$(698L$OJ4v0 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png.meta new file mode 100644 index 0000000..3c327e4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: d1be04a9da3e14848842e6aa7f352ea7 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..742718f8696949958059c3c161203ef3b0d5d985 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=mF{Fa=?Q};jWWgXng#Kx>>Tc-sO5uiGu69zU`jHEuSB0apVeT S$`hc&89ZJ6T-G@yGywp9Phubd literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png.meta new file mode 100644 index 0000000..b7b3998 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 80b63a82e14cef34980c746c6dfe7c65 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..138fbe5af7c1ab46de6529739b295a737055ad3a GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBez&KKV@L(#+sTG}4GKK2(WwPrB<3}|-f?(A zUtV9Io-Ra;qp&9?udEa0`WbzS4CRudbgcgJRMdhWUVNN8GD zpWG=)R+eoi!?pxx%U0*Px)qCj{4(*?lBVf@nDbP0l+XkKc$!q` literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png.meta new file mode 100644 index 0000000..5719a5d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 74de4b3c714a76040a1d002d5608b867 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1022d2766ed89263cbf89999f60f916f82090c28 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=w_H=( zz_g>>nx}c)s#jKLFRGv36ya5QIFYB$K;ucs^{tiMCqDmqXZS-!scX?;w&vwOCO6fW z)!w&Py?0u2#(^81N1ARk3x~Y9z0)-{@{my1qKGYVOvalf3v_I6ZvW%YCbNGX=Qkb} zMn?w$0Tv*2;7#_9F!=`w=Nk@eyThb>Wt#Z`ckkRvW}k!{-HURu1vv-o+k94CiLyVT zQ6-jmY=(UK8;M$_$gQDwgksIF%B0Ta^DEYm(hvP}F|8sS7!C}cu6{1-oD!M<#3hMJ literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png.meta new file mode 100644 index 0000000..a0ee72a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_active@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 1b546bc75c806c34cae9fb22ff12fdb6 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..d7cd39561ecf0bea0128395d5aff19db3642a760 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBexIj{V@L(#+sPXRTNDIb%gZ$m+~eSQZ}njv z^I8WlgVcswOPK#G;X9|$H&eeM_QuaY4d+?*e4pIWWBI&MD&bj6?$xOOmMsYvd#*6H zs2%*Y(C@*I<$c@c7GF(F_IXF{Fa=?ev43%?2WE@$+me-f+k+oV%cm z#r4A~;Vp|_aL9=s6@0~UcY&m%ji|_5@9YKU+E-ef_H|3i{i&~G{cJpA#j32^-~YB6 zmMw8-4@;0aK07sXW*ea* zH>rZ-)y-Sm{yGTRH>-KvcHH&OQT|+7p3-Tf2qyuS?q`A~#IgU&K literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@2x.png.meta new file mode 100644 index 0000000..e56e953 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/duplicateButton_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 9f5dbec10705c6d4b8cac5c6f51e22b9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..8d33e46a01042c02abf6e91dd00df2917d9c76d4 GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{t-_X$B+ufxsx6FnhZo-{=2_djK1-LS0(01 z1><>!m?JM7cQjc?YzR1i@rYIZMb;@(?)w>K&i>{*dD&r>$n2e&Qp-3V?nzBMt+^ri z_O`Ni!^JM4p+~hUW-Y6cXp^(k`EJI{Vs`AyjCqOrD#3^NPCeYMmB1=i``~q&1H-!0 zoBpsdCq1~zaKK#m4QtP14gI?Z{dsk6>0cB7p(3#FrS$$Uf=zoF7%bbwA0!8g$ZDE! wAILRz)tc-Su;{?Q81AK0m*g&8z3I$sj!y85}Sb4q9e001y=UjP6A literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png.meta new file mode 100644 index 0000000..f656f32 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 71ef598858974450ead310c33f29d5d3 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..77c9988fa0cda036edd17cd0b87d78c16db7e30a GIT binary patch literal 482 zcmV<80UiE{P)(E8zsb+ z97)yzVTp)*pKfv!#7Kz{f@N98$Yb(67ZTO42Uwb>oE8j3xHCvGsbop#uCm1dU(G!m2e_(44)%mEl^d}-6^=Kgq1*9 zmML24QMZqC{xkX-S-|nQ*X=_E@;Htk$fbZQ_*nuj;j;x?!)FQjfEx?=g3Da!Hpo6V zfg@YMUukBv6+oZA;PI{5woQ^`He4%!(nomXKTJ0h5@*A;0?0>Q*Xs!hEBK^9(=;nQ z)@|F4Nm#=t1pvO``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{&i0m$B+ufxsw<2H5&-DtcFm>l++y0f+Y@(a6k2R}Uir(<);D1%{1&)p!G8hOut0ejS^2co?T QfL>(qboFyt=akR{0I5-Y1ONa4 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@1x.png.meta new file mode 100644 index 0000000..0f753c1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 3354fe2e1951d420fa49d8f4fec2954b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b041735a0b8856ee8007125acd4b6e755031eef3 GIT binary patch literal 560 zcmV-00?+-4P) zeeul_qQT@M>TozTZGnEjZ$ub=lSrjfF9{yrX=}$PD)_AUoWbKp5ek1;Pp^T&H29(eM;b z^L##^!!NECn9XL`Znp>{+*Kfx$>4A}K-YDlM38V-0lrX0u~-yJ1Pyl-$mjDY6beEK z8R0~tTCKkScc4``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{!>pE$B+ufxsx{vHXHD`rp8-o*f7~%`Eup} z!$~$3=?R>NIKCXDDpVxw^o_EFTS-km7x3;k!FFdeGN6cqejhOFchtN0@?kEP)0EOe=dzNabeNL1!o=FMHiEnN6`2H`ULHx=p(qA7#{%#Cnp9Sbb-aiMPg#osL`HF zrD;qqpj_ZzK)mpMoC6_8!7Y=?L_43)Uy7o<)6Wkikn8nosjAv>PQe~JBm|E3bWOYknzkCxBpzZt2e4A9pja%TR4N62(Gn5h zrA{Nf1yf`cKB_UAP^QQz#O~9G#s;+SApJOMWGg6jtZp`25*w1J*>QY7c osFu{UH03R)p>)ve^;~}72R9GcFcB_FyZ`_I07*qoM6N<$g0P$Kv;Y7A literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@2x.png.meta new file mode 100644 index 0000000..7255a13 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/editButton_hover@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: eaff2ac91e2f95645a0c8088eacfa37a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..bd18a92015979e3d34b79ecee38280fafc71c5e9 GIT binary patch literal 469 zcmV;`0V@89P)g01SKUUw9r5kg_4$%B9Wjc`2+F=;RldtC}}C7qp~PzDJgB`fk{HR>|Quy z$5CQuB+I_l&g|^2Pk=D1RC@I4(vw9rI0i7hw5&TZE0_BaqKkikM0y61uj7ys*`vul za7!YQZ6le_Bh%?Xn5GV8lfNY==#j3Az2m6ZMXQCU(Fk(8J?4EUnzb5E>vew>4=tBDRY~i0sbicv~)ULgGIPoHZJ%>f>PGS=QjH?sTf+iCj+Qh6GZj z65dxU_5Df?be~LSQU5tbE<-cmq)fa0l|({J;8wV@Ci8sEYE|WG2>J`9XNT;u2=AH) z#!ga&f{LdPRiDS>K#PGb7&HUHZfI5Pf(OPuf7muQfW{y1CBv!=<8EVGGoXs^L2iJls9y-qTLHA9m?%rS=Rk0aq^Y>U&x)800000 LNkvXXu0mjfY^%re literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png.meta new file mode 100644 index 0000000..7ebe1a5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: c780955fc9d0f458abc0de8f2f9cedb2 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7509266e5002a6a65c8ef9c9ab78042e63616bc3 GIT binary patch literal 814 zcmV+}1JV46P)eo}K~#7F#aBH~ z8$l4A+cWYxB9ZB!gOV<`Kmr=u`~^B_Eb#-9pF(~B1qCH^P+}@TNPs&^+)+XjgD?0# zGH-KBHuvR4$z7ggodkRH-pst2)mapw*KXHoIQ(o8wL+pc6#$Oiw}jYfU0&{Ke`E5M zm3i|09);mNl>y5dkZpH57Z-zH0+yHWN+Z`rjK>X|$-7M6rSef1Lb!s-Khb|ez+Myl z{741Do|&QA+FI7X+Z8I9{KNJ(iF%!a(TINB+$7EjDhN8ccYjYWx3{V13K0+||F*kJ zq3foeeNzi8$DzvH990$;WN;7x{=)+WkB>CI<7wunDY>x#5LH)K$!RvFh&ZNLP#h-Y zMgrcguamG@0n$a;EnQGr=boL(?D2lc6X0`e(jc

    Some editor gui helper methods + public static class EditorGUILayoutHelper { + /// + /// Tag names and an additional 'Edit Tags...' entry. + /// Used for SingleTagField + /// + static GUIContent[] tagNamesAndEditTagsButton; + static int[] tagValues = new [] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, -1 }; + + /// + /// Last time tagNamesAndEditTagsButton was updated. + /// Uses EditorApplication.timeSinceStartup + /// + static double timeLastUpdatedTagNames; + + static void FindTagNames () { + // Make sure the AstarPath object is initialized, this is required to be able to show tag names in the popup + AstarPath.FindAstarPath(); + + // Make sure the tagNamesAndEditTagsButton is relatively up to date + if (tagNamesAndEditTagsButton == null || EditorApplication.timeSinceStartup - timeLastUpdatedTagNames > 1) { + timeLastUpdatedTagNames = EditorApplication.timeSinceStartup; + tagNamesAndEditTagsButton = new GUIContent[GraphNode.MaxTagIndex + 2]; + if (AstarPath.active != null) { + var tagNames = AstarPath.active.GetTagNames(); + for (int i = 0; i <= GraphNode.MaxTagIndex; i++) { + if (AstarPath.active == null) tagNamesAndEditTagsButton[i] = new GUIContent("Tag " + i + (i == GraphNode.MaxTagIndex ? "+" : "")); + else { + var tagName = tagNames[i]; + if (tagName != i.ToString()) { + tagNamesAndEditTagsButton[i] = new GUIContent(tagName + " (tag " + i + ")"); + } else { + tagNamesAndEditTagsButton[i] = new GUIContent("Tag " + i); + } + } + } + } else { + for (int i = 0; i <= GraphNode.MaxTagIndex; i++) { + tagNamesAndEditTagsButton[i] = new GUIContent("Tag " + i + (i == GraphNode.MaxTagIndex ? "+" : "")); + } + } + tagNamesAndEditTagsButton[tagNamesAndEditTagsButton.Length-1] = new GUIContent("Edit Tags..."); + } + } + + public static int TagField (int value, System.Action editCallback) { + FindTagNames(); + value = Mathf.Clamp(value, 0, GraphNode.MaxTagIndex); + + var newValue = EditorGUILayout.IntPopup(value, tagNamesAndEditTagsButton, tagValues); + + // Last element corresponds to the 'Edit Tags...' entry. Open the tag editor + if (newValue == -1) { + editCallback(); + } else { + value = newValue; + } + + return value; + } + + public static int TagField (Rect rect, GUIContent label, int value, System.Action editCallback) { + FindTagNames(); + // Tags are between 0 and GraphNode.MaxTagIndex + value = Mathf.Clamp(value, 0, GraphNode.MaxTagIndex); + + var newValue = EditorGUI.IntPopup(rect, label, value, tagNamesAndEditTagsButton, tagValues); + + // Last element corresponds to the 'Edit Tags...' entry. Open the tag editor + if (newValue == -1) { + editCallback(); + } else { + value = newValue; + } + + return value; + } + + public static int TagField (GUIContent label, int value, System.Action editCallback) { + return TagField(GUILayoutUtility.GetRect(label, EditorStyles.popup), label, value, editCallback); + } + + public static void TagField (Rect position, GUIContent label, SerializedProperty property, System.Action editCallback) { + FindTagNames(); + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = property.hasMultipleDifferentValues; + property = property.FindPropertyRelative("value"); + var newValue = EditorGUI.IntPopup(position, label, (int)property.intValue, tagNamesAndEditTagsButton, tagValues); + + if (EditorGUI.EndChangeCheck() || property.intValue < 0 || property.intValue > GraphNode.MaxTagIndex) { + if (newValue == -1) { + editCallback(); + } else { + property.intValue = Mathf.Clamp(newValue, 0, GraphNode.MaxTagIndex); + } + } + EditorGUI.showMixedValue = false; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs.meta new file mode 100644 index 0000000..b8eeaec --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53446f7c411744bfdbf75bc347b42db8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors.meta new file mode 100644 index 0000000..b2e780c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b91087a27bf264aff8979e8f0ffa4de4 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs new file mode 100644 index 0000000..0b4b2cf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs @@ -0,0 +1,148 @@ +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + public class GraphEditor : GraphEditorBase { + public AstarPathEditor editor; + + /// Stores if the graph is visible or not in the inspector + public FadeArea fadeArea; + + /// Stores if the graph info box is visible or not in the inspector + public FadeArea infoFadeArea; + + public virtual void OnEnable () { + } + + /// Rounds a vector's components to multiples of 0.5 (i.e 0.5, 1.0, 1.5, etc.) if very close to them + public static Vector3 RoundVector3 (Vector3 v) { + const int Multiplier = 2; + + if (Mathf.Abs(Multiplier*v.x - Mathf.Round(Multiplier*v.x)) < 0.001f) v.x = Mathf.Round(Multiplier*v.x)/Multiplier; + if (Mathf.Abs(Multiplier*v.y - Mathf.Round(Multiplier*v.y)) < 0.001f) v.y = Mathf.Round(Multiplier*v.y)/Multiplier; + if (Mathf.Abs(Multiplier*v.z - Mathf.Round(Multiplier*v.z)) < 0.001f) v.z = Mathf.Round(Multiplier*v.z)/Multiplier; + return v; + } + + public static Object ObjectField (string label, Object obj, System.Type objType, bool allowSceneObjects, bool assetsMustBeInResourcesFolder) { + return ObjectField(new GUIContent(label), obj, objType, allowSceneObjects, assetsMustBeInResourcesFolder); + } + + public static Object ObjectField (GUIContent label, Object obj, System.Type objType, bool allowSceneObjects, bool assetsMustBeInResourcesFolder) { + obj = EditorGUILayout.ObjectField(label, obj, objType, allowSceneObjects); + + if (obj != null) { + if (allowSceneObjects && !EditorUtility.IsPersistent(obj)) { + // Object is in the scene + var com = obj as Component; + var go = obj as GameObject; + if (com != null) { + go = com.gameObject; + } + if (go != null && go.GetComponent() == null) { + if (FixLabel("Object's GameObject must have a UnityReferenceHelper component attached")) { + go.AddComponent(); + } + } + } else if (EditorUtility.IsPersistent(obj)) { + if (assetsMustBeInResourcesFolder) { + string path = AssetDatabase.GetAssetPath(obj).Replace("\\", "/"); + var rg = new System.Text.RegularExpressions.Regex(@"Resources/.*$"); + + if (!rg.IsMatch(path)) { + if (FixLabel("Object must be in the 'Resources' folder")) { + if (!System.IO.Directory.Exists(Application.dataPath+"/Resources")) { + System.IO.Directory.CreateDirectory(Application.dataPath+"/Resources"); + AssetDatabase.Refresh(); + } + + string ext = System.IO.Path.GetExtension(path); + string error = AssetDatabase.MoveAsset(path, "Assets/Resources/"+obj.name+ext); + + if (error == "") { + path = AssetDatabase.GetAssetPath(obj); + } else { + Debug.LogError("Couldn't move asset - "+error); + } + } + } + + if (!AssetDatabase.IsMainAsset(obj) && obj.name != AssetDatabase.LoadMainAssetAtPath(path).name) { + if (FixLabel("Due to technical reasons, the main asset must\nhave the same name as the referenced asset")) { + string error = AssetDatabase.RenameAsset(path, obj.name); + if (error != "") { + Debug.LogError("Couldn't rename asset - "+error); + } + } + } + } + } + } + + return obj; + } + + /// Draws common graph settings + public void OnBaseInspectorGUI (NavGraph target) { + int penalty = EditorGUILayout.IntField(new GUIContent("Initial Penalty", "Initial Penalty for nodes in this graph. Set during Scan."), (int)target.initialPenalty); + + if (penalty < 0) penalty = 0; + target.initialPenalty = (uint)penalty; + } + + /// Override to implement graph inspectors + public virtual void OnInspectorGUI (NavGraph target) { + } + + /// Override to implement scene GUI drawing for the graph + public virtual void OnSceneGUI (NavGraph target) { + } + + public static void Header (string title) { + EditorGUILayout.LabelField(new GUIContent(title), EditorStyles.boldLabel); + GUILayout.Space(4); + } + + /// Draws a thin separator line + public static void Separator () { + GUIStyle separator = AstarPathEditor.astarSkin.FindStyle("PixelBox3Separator") ?? new GUIStyle(); + + Rect r = GUILayoutUtility.GetRect(new GUIContent(), separator); + + if (Event.current.type == EventType.Repaint) { + separator.Draw(r, false, false, false, false); + } + } + + /// Draws a small help box with a 'Fix' button to the right. Returns: Boolean - Returns true if the button was clicked + public static bool FixLabel (string label, string buttonLabel = "Fix", int buttonWidth = 40) { + GUILayout.BeginHorizontal(); + GUILayout.Space(14*EditorGUI.indentLevel); + GUILayout.BeginHorizontal(AstarPathEditor.helpBox); + GUILayout.Label(label, EditorGUIUtility.isProSkin ? EditorStyles.whiteMiniLabel : EditorStyles.miniLabel, GUILayout.ExpandWidth(true)); + var returnValue = GUILayout.Button(buttonLabel, EditorStyles.miniButton, GUILayout.Width(buttonWidth)); + GUILayout.EndHorizontal(); + GUILayout.EndHorizontal(); + return returnValue; + } + + /// Draws a toggle with a bold label to the right. Does not enable or disable GUI + public bool ToggleGroup (string label, bool value) { + return ToggleGroup(new GUIContent(label), value); + } + + /// Draws a toggle with a bold label to the right. Does not enable or disable GUI + public static bool ToggleGroup (GUIContent label, bool value) { + GUILayout.BeginHorizontal(); + GUILayout.Space(13*EditorGUI.indentLevel); + value = GUILayout.Toggle(value, "", GUILayout.Width(10)); + GUIStyle boxHeader = AstarPathEditor.astarSkin.FindStyle("CollisionHeader"); + if (GUILayout.Button(label, boxHeader, GUILayout.Width(100))) { + value = !value; + } + + GUILayout.EndHorizontal(); + return value; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs.meta new file mode 100644 index 0000000..35c29f6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GraphEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b0bbfd9d85ec946b2a05cdeaab0d6f5f +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs new file mode 100644 index 0000000..cfcee9c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs @@ -0,0 +1,875 @@ +using UnityEngine; +using UnityEditor; +using Pathfinding.Serialization; +using System.Collections.Generic; + +namespace Pathfinding { + using Pathfinding.Graphs.Grid; + using Pathfinding.Graphs.Grid.Rules; + using Pathfinding.Util; + + [CustomGraphEditor(typeof(GridGraph), "Grid Graph")] + public class GridGraphEditor : GraphEditor { + [JsonMember] + public bool locked = true; + + [JsonMember] + public bool showExtra; + + GraphTransform savedTransform; + Vector2 savedDimensions; + float savedNodeSize; + + public bool isMouseDown; + + [JsonMember] + public GridPivot pivot; + + /// + /// Shows the preview for the collision testing options. + /// + /// [Open online documentation to see images] + /// + /// On the left you can see a top-down view of the graph with a grid of nodes. + /// On the right you can see a side view of the graph. The white line at the bottom is the base of the graph, with node positions indicated using small dots. + /// When using 2D physics, only the top-down view is visible. + /// + /// The green shape indicates the shape that will be used for collision checking. + /// + [JsonMember] + public bool collisionPreviewOpen; + + [JsonMember] + public int selectedTilemap; + + /// Cached gui style + static GUIStyle lockStyle; + + /// Cached gui style + static GUIStyle gridPivotSelectBackground; + + /// Cached gui style + static GUIStyle gridPivotSelectButton; + + public GridGraphEditor() { + // Default value depends on if the game is running or not. Make it hidden in play mode by default. + collisionPreviewOpen = !Application.isPlaying; + } + + public override void OnInspectorGUI (NavGraph target) { + var graph = target as GridGraph; + + DrawFirstSection(graph); + Separator(); + DrawMiddleSection(graph); + Separator(); + DrawCollisionEditor(graph, graph.collision); + DrawRules(graph); + + Separator(); + DrawLastSection(graph); + } + + bool IsHexagonal (GridGraph graph) { + return graph.neighbours == NumNeighbours.Six && graph.uniformEdgeCosts; + } + + bool IsIsometric (GridGraph graph) { + if (IsHexagonal(graph)) return false; + if (graph.aspectRatio != 1) return true; + return graph.isometricAngle != 0; + } + + bool IsAdvanced (GridGraph graph) { + if (graph.inspectorGridMode == InspectorGridMode.Advanced) return true; + // Weird configuration + return (graph.neighbours == NumNeighbours.Six) != graph.uniformEdgeCosts; + } + + InspectorGridMode DetermineGridType (GridGraph graph) { + bool hex = IsHexagonal(graph); + bool iso = IsIsometric(graph); + bool adv = IsAdvanced(graph); + + if (adv || (hex && iso)) return InspectorGridMode.Advanced; + if (hex) return InspectorGridMode.Hexagonal; + if (iso) return InspectorGridMode.IsometricGrid; + return graph.inspectorGridMode; + } + + void DrawInspectorMode (GridGraph graph) { + graph.inspectorGridMode = DetermineGridType(graph); + var newMode = (InspectorGridMode)EditorGUILayout.EnumPopup("Shape", (System.Enum)graph.inspectorGridMode); + if (newMode != graph.inspectorGridMode) graph.SetGridShape(newMode); + } + + protected virtual void Draw2DMode (GridGraph graph) { + graph.is2D = EditorGUILayout.Toggle(new GUIContent("2D"), graph.is2D); + } + + GUIContent[] hexagonSizeContents = { + new GUIContent("Hexagon Width", "Distance between two opposing sides on the hexagon"), + new GUIContent("Hexagon Diameter", "Distance between two opposing vertices on the hexagon"), + new GUIContent("Node Size", "Raw node size value, this doesn't correspond to anything particular on the hexagon."), + }; + + static List cachedSceneGridLayouts; + static float cachedSceneGridLayoutsTimestamp = -float.PositiveInfinity; + + static string GetPath (Transform current) { + if (current.parent == null) + return current.name; + return GetPath(current.parent) + "/" + current.name; + } + + void DrawTilemapAlignment (GridGraph graph) { + if (cachedSceneGridLayouts == null || Time.realtimeSinceStartup - cachedSceneGridLayoutsTimestamp > 5f) { + var tilemaps = UnityCompatibility.FindObjectsByTypeSorted(); + List layouts = new List(tilemaps); + for (int i = 0; i < tilemaps.Length; i++) { + if (tilemaps[i] is UnityEngine.Tilemaps.Tilemap tilemap) layouts.Remove(tilemap.layoutGrid); + } + cachedSceneGridLayouts = layouts; + cachedSceneGridLayoutsTimestamp = Time.realtimeSinceStartup; + } + for (int i = cachedSceneGridLayouts.Count - 1; i >= 0; i--) { + if (cachedSceneGridLayouts[i] == null) cachedSceneGridLayouts.RemoveAt(i); + } + + if (cachedSceneGridLayouts.Count > 0) { + GUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("Align to tilemap"); + + var tilemapNames = new GUIContent[cachedSceneGridLayouts.Count+1]; + tilemapNames[0] = new GUIContent("Select..."); + for (int i = 0; i < cachedSceneGridLayouts.Count; i++) tilemapNames[i+1] = new GUIContent(GetPath(cachedSceneGridLayouts[i].transform).Replace("/", "\u2215")); + + var originalIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + selectedTilemap = EditorGUILayout.Popup(selectedTilemap, tilemapNames); + selectedTilemap = Mathf.Clamp(selectedTilemap, 0, tilemapNames.Length - 1); + + EditorGUI.BeginDisabledGroup(selectedTilemap <= 0 || selectedTilemap - 1 >= cachedSceneGridLayouts.Count); + if (GUILayout.Button("Align To")) { + graph.AlignToTilemap(cachedSceneGridLayouts[selectedTilemap - 1]); + } + EditorGUI.EndDisabledGroup(); + EditorGUI.indentLevel = originalIndent; + + GUILayout.EndHorizontal(); + } + } + + void DrawFirstSection (GridGraph graph) { + float prevRatio = graph.aspectRatio; + + DrawInspectorMode(graph); + + Draw2DMode(graph); + DrawTilemapAlignment(graph); + + var normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); + var worldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); + int newWidth, newDepth; + + DrawWidthDepthFields(graph, out newWidth, out newDepth); + + EditorGUI.BeginChangeCheck(); + float newNodeSize; + if (graph.inspectorGridMode == InspectorGridMode.Hexagonal) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.BeginVertical(); + graph.inspectorHexagonSizeMode = (InspectorGridHexagonNodeSize)EditorGUILayout.EnumPopup(new GUIContent("Hexagon Dimension"), graph.inspectorHexagonSizeMode); + float hexagonSize = GridGraph.ConvertNodeSizeToHexagonSize(graph.inspectorHexagonSizeMode, graph.nodeSize); + hexagonSize = (float)System.Math.Round(hexagonSize, 5); + newNodeSize = GridGraph.ConvertHexagonSizeToNodeSize(graph.inspectorHexagonSizeMode, EditorGUILayout.FloatField(hexagonSizeContents[(int)graph.inspectorHexagonSizeMode], hexagonSize)); + EditorGUILayout.EndVertical(); + if (graph.inspectorHexagonSizeMode != InspectorGridHexagonNodeSize.NodeSize) GUILayout.Box("", AstarPathEditor.astarSkin.FindStyle(graph.inspectorHexagonSizeMode == InspectorGridHexagonNodeSize.Diameter ? "HexagonDiameter" : "HexagonWidth")); + EditorGUILayout.EndHorizontal(); + } else { + newNodeSize = EditorGUILayout.FloatField(new GUIContent("Node size", "The size of a single node. The size is the side of the node square in world units"), graph.nodeSize); + } + bool nodeSizeChanged = EditorGUI.EndChangeCheck(); + + newNodeSize = newNodeSize <= 0.01F ? 0.01F : newNodeSize; + + if (graph.inspectorGridMode == InspectorGridMode.IsometricGrid || graph.inspectorGridMode == InspectorGridMode.Hexagonal || graph.inspectorGridMode == InspectorGridMode.Advanced) { + graph.aspectRatio = EditorGUILayout.FloatField(new GUIContent("Aspect Ratio", "Ratio between a node's width and depth."), graph.aspectRatio); + } + + if (graph.inspectorGridMode == InspectorGridMode.IsometricGrid || graph.inspectorGridMode == InspectorGridMode.Advanced) { + DrawIsometricField(graph); + } + + if ((nodeSizeChanged && locked) || (newWidth != graph.width || newDepth != graph.depth) || prevRatio != graph.aspectRatio) { + graph.SetDimensions(newWidth, newDepth, newNodeSize); + + normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); + var newWorldPoint = graph.CalculateTransform().Transform(normalizedPivotPoint); + // Move the center so that the pivot point stays at the same point in the world + graph.center += worldPoint - newWorldPoint; + graph.center = RoundVector3(graph.center); + graph.UpdateTransform(); + } + + if ((nodeSizeChanged && !locked)) { + graph.nodeSize = newNodeSize; + graph.UpdateTransform(); + } + + DrawPositionField(graph); + + DrawRotationField(graph); + } + + void DrawRotationField (GridGraph graph) { + if (graph.is2D) { + var right = Quaternion.Euler(graph.rotation) * Vector3.right; + var angle = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; + if (angle < 0) angle += 360; + if (Mathf.Abs(angle - Mathf.Round(angle)) < 0.001f) angle = Mathf.Round(angle); + EditorGUI.BeginChangeCheck(); + angle = EditorGUILayout.FloatField("Rotation", angle); + if (EditorGUI.EndChangeCheck()) { + graph.rotation = RoundVector3(new Vector3(-90 + angle, 270, 90)); + } + } else { + graph.rotation = RoundVector3(EditorGUILayout.Vector3Field("Rotation", graph.rotation)); + } + } + + void DrawWidthDepthFields (GridGraph graph, out int newWidth, out int newDepth) { + lockStyle = lockStyle ?? AstarPathEditor.astarSkin.FindStyle("GridSizeLock") ?? new GUIStyle(); + + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(); + newWidth = EditorGUILayout.IntField(new GUIContent("Width (nodes)", "Width of the graph in nodes"), graph.width); + newDepth = EditorGUILayout.IntField(new GUIContent("Depth (nodes)", "Depth (or height you might also call it) of the graph in nodes"), graph.depth); + + // Clamping will be done elsewhere as well + // but this prevents negative widths from being converted to positive ones (since an absolute value will be taken) + newWidth = Mathf.Max(newWidth, 1); + newDepth = Mathf.Max(newDepth, 1); + + GUILayout.EndVertical(); + + Rect lockRect = GUILayoutUtility.GetRect(lockStyle.fixedWidth, lockStyle.fixedHeight); + + GUILayout.EndHorizontal(); + + // All the layouts mess up the margin to the next control, so add it manually + GUILayout.Space(2); + + // Add a small offset to make it better centred around the controls + lockRect.y += 3; + lockRect.width = lockStyle.fixedWidth; + lockRect.height = lockStyle.fixedHeight; + lockRect.x += lockStyle.margin.left; + lockRect.y += lockStyle.margin.top; + + locked = GUI.Toggle(lockRect, locked, + new GUIContent("", "If the width and depth values are locked, " + + "changing the node size will scale the grid while keeping the number of nodes consistent " + + "instead of keeping the size the same and changing the number of nodes in the graph"), lockStyle); + } + + void DrawIsometricField (GridGraph graph) { + var isometricGUIContent = new GUIContent("Isometric Angle", "For an isometric 2D game, you can use this parameter to scale the graph correctly.\nIt can also be used to create a hexagonal grid.\nYou may want to rotate the graph 45 degrees around the Y axis to make it line up better."); + var isometricOptions = new [] { new GUIContent("None (0°)"), new GUIContent("Isometric (≈54.74°)"), new GUIContent("Dimetric (60°)"), new GUIContent("Custom") }; + var isometricValues = new [] { 0f, GridGraph.StandardIsometricAngle, GridGraph.StandardDimetricAngle }; + var isometricOption = isometricValues.Length; + + for (int i = 0; i < isometricValues.Length; i++) { + if (Mathf.Approximately(graph.isometricAngle, isometricValues[i])) { + isometricOption = i; + } + } + + var prevIsometricOption = isometricOption; + isometricOption = EditorGUILayout.IntPopup(isometricGUIContent, isometricOption, isometricOptions, new [] { 0, 1, 2, 3 }); + if (prevIsometricOption != isometricOption) { + // Change to something that will not match the predefined values above + graph.isometricAngle = 45; + } + + if (isometricOption < isometricValues.Length) { + graph.isometricAngle = isometricValues[isometricOption]; + } else { + EditorGUI.indentLevel++; + // Custom + graph.isometricAngle = EditorGUILayout.FloatField(isometricGUIContent, graph.isometricAngle); + EditorGUI.indentLevel--; + } + } + + static Vector3 NormalizedPivotPoint (GridGraph graph, GridPivot pivot) { + switch (pivot) { + case GridPivot.Center: + default: + return new Vector3(graph.width/2f, 0, graph.depth/2f); + case GridPivot.TopLeft: + return new Vector3(0, 0, graph.depth); + case GridPivot.TopRight: + return new Vector3(graph.width, 0, graph.depth); + case GridPivot.BottomLeft: + return new Vector3(0, 0, 0); + case GridPivot.BottomRight: + return new Vector3(graph.width, 0, 0); + } + } + + void DrawPositionField (GridGraph graph) { + GUILayout.BeginHorizontal(); + var normalizedPivotPoint = NormalizedPivotPoint(graph, pivot); + var worldPoint = RoundVector3(graph.CalculateTransform().Transform(normalizedPivotPoint)); + var newWorldPoint = EditorGUILayout.Vector3Field(ObjectNames.NicifyVariableName(pivot.ToString()), worldPoint); + var delta = newWorldPoint - worldPoint; + if (delta.magnitude > 0.001f) { + graph.center += delta; + } + + pivot = PivotPointSelector(pivot); + GUILayout.EndHorizontal(); + } + + protected virtual void DrawMiddleSection (GridGraph graph) { + DrawNeighbours(graph); + DrawMaxClimb(graph); + DrawMaxSlope(graph); + DrawErosion(graph); + } + + protected virtual void DrawCutCorners (GridGraph graph) { + if (graph.inspectorGridMode == InspectorGridMode.Hexagonal) return; + + graph.cutCorners = EditorGUILayout.Toggle(new GUIContent("Cut Corners", "Enables or disables cutting corners. See docs for image example"), graph.cutCorners); + } + + protected virtual void DrawNeighbours (GridGraph graph) { + if (graph.inspectorGridMode == InspectorGridMode.Hexagonal) return; + + var neighboursGUIContent = new GUIContent("Connections", "Sets how many connections a node should have to it's neighbour nodes."); + GUIContent[] neighbourOptions; + if (graph.inspectorGridMode == InspectorGridMode.Advanced) { + neighbourOptions = new [] { new GUIContent("Four"), new GUIContent("Eight"), new GUIContent("Six") }; + } else { + neighbourOptions = new [] { new GUIContent("Four"), new GUIContent("Eight") }; + } + graph.neighbours = (NumNeighbours)EditorGUILayout.Popup(neighboursGUIContent, (int)graph.neighbours, neighbourOptions); + + EditorGUI.indentLevel++; + + if (graph.neighbours == NumNeighbours.Eight) { + DrawCutCorners(graph); + } + + if (graph.neighbours == NumNeighbours.Six) { + graph.uniformEdgeCosts = EditorGUILayout.Toggle(new GUIContent("Hexagon connection costs", "Tweak the edge costs in the graph to be more suitable for hexagon graphs"), graph.uniformEdgeCosts); + EditorGUILayout.HelpBox("You can set all settings to make this a hexagonal graph by changing the 'Shape' field above", MessageType.None); + } else { + graph.uniformEdgeCosts = false; + } + + EditorGUI.indentLevel--; + } + + protected virtual void DrawMaxClimb (GridGraph graph) { + if (!graph.collision.use2D) { + graph.maxStepHeight = EditorGUILayout.FloatField(new GUIContent("Max Step Height", "How high a step can be while still allowing the AI to go up/down it. A zero (0) indicates infinity. This affects for example how the graph is generated around ledges and stairs."), graph.maxStepHeight); + if (graph.maxStepHeight < 0) graph.maxStepHeight = 0; + if (graph.maxStepHeight > 0) { + EditorGUI.indentLevel++; + graph.maxStepUsesSlope = EditorGUILayout.Toggle(new GUIContent("Account for slopes", "Account for slopes when calculating the step sizes. See documentation for more info."), graph.maxStepUsesSlope); + EditorGUI.indentLevel--; + } + } + } + + protected void DrawMaxSlope (GridGraph graph) { + if (!graph.collision.use2D) { + graph.maxSlope = EditorGUILayout.Slider(new GUIContent("Max Slope", "Sets the max slope in degrees for a point to be walkable. Only enabled if Height Testing is enabled."), graph.maxSlope, 0, 90F); + } + } + + protected void DrawErosion (GridGraph graph) { + graph.erodeIterations = EditorGUILayout.IntField(new GUIContent("Erosion iterations", "Sets how many times the graph should be eroded. This adds extra margin to objects."), graph.erodeIterations); + graph.erodeIterations = graph.erodeIterations < 0 ? 0 : (graph.erodeIterations > 16 ? 16 : graph.erodeIterations); //Clamp iterations to [0,16] + + if (graph.erodeIterations > 0) { + EditorGUI.indentLevel++; + graph.erosionUseTags = EditorGUILayout.Toggle(new GUIContent("Erosion Uses Tags", "Instead of making nodes unwalkable, " + + "nodes will have their tag set to a value corresponding to their erosion level, " + + "which is a quite good measurement of their distance to the closest wall.\nSee online documentation for more info."), + graph.erosionUseTags); + if (graph.erosionUseTags) { + EditorGUI.indentLevel++; + graph.erosionFirstTag = EditorGUILayoutHelper.TagField(new GUIContent("First Tag"), graph.erosionFirstTag, AstarPathEditor.EditTags); + var tagNames = AstarPath.FindTagNames().Clone() as string[]; + var tagMsg = ""; + for (int i = graph.erosionFirstTag; i < graph.erosionFirstTag + graph.erodeIterations; i++) { + tagMsg += (i > graph.erosionFirstTag ? (i == graph.erosionFirstTag + graph.erodeIterations - 1 ? " or " : ", ") : "") + tagNames[i]; + } + EditorGUILayout.HelpBox("Tag " + tagMsg + " will be applied to nodes" + (graph.erodeIterations > 1 ? ", based on their distance to obstacles" : " when adjacent to obstacles"), MessageType.None); + for (int i = graph.erosionFirstTag; i < graph.erosionFirstTag + graph.erodeIterations; i++) tagNames[i] += " (used for erosion)"; + graph.erosionTagsPrecedenceMask = EditorGUILayout.MaskField( + new GUIContent("Overwritable tags", "Nodes near unwalkable nodes will be marked with tags. " + + "If these nodes already have tags, you may want the custom tag to take precedence. This mask controls which tags are allowed to be replaced by the new erosion tags."), + graph.erosionTagsPrecedenceMask, + tagNames + ); + if ((graph.erosionTagsPrecedenceMask & 0x1) == 0) { + EditorGUILayout.HelpBox("The " + tagNames[0] + " tag has been excluded. Since this is the default tag, erosion tags will likely be applied to very few, if any, nodes. This is likely not what you want", MessageType.Warning); + } + EditorGUI.indentLevel--; + } + EditorGUI.indentLevel--; + } + } + + void DrawLastSection (GridGraph graph) { + GUILayout.BeginHorizontal(); + GUILayout.Space(18); + graph.showMeshSurface = GUILayout.Toggle(graph.showMeshSurface, new GUIContent("Show surface", "Toggles gizmos for drawing the surface of the mesh"), EditorStyles.miniButtonLeft); + graph.showMeshOutline = GUILayout.Toggle(graph.showMeshOutline, new GUIContent("Show outline", "Toggles gizmos for drawing an outline of the nodes"), EditorStyles.miniButtonMid); + graph.showNodeConnections = GUILayout.Toggle(graph.showNodeConnections, new GUIContent("Show connections", "Toggles gizmos for drawing node connections"), EditorStyles.miniButtonRight); + GUILayout.EndHorizontal(); + } + + /// Draws the inspector for a class + protected virtual void DrawCollisionEditor (GridGraph graph, GraphCollision collision) { + collision = collision ?? new GraphCollision(); + + DrawUse2DPhysics(collision); + + collision.collisionCheck = ToggleGroup("Collision testing", collision.collisionCheck); + if (collision.collisionCheck) { + EditorGUI.indentLevel++; + string[] colliderOptions = collision.use2D ? new [] { "Circle", "Point" } : new [] { "Sphere", "Capsule", "Ray" }; + int[] colliderValues = collision.use2D ? new [] { 0, 2 } : new [] { 0, 1, 2 }; + // In 2D the Circle (Sphere) mode will replace both the Sphere and the Capsule modes + // However make sure that the original value is still stored in the grid graph in case the user changes back to the 3D mode in the inspector. + var tp = collision.type; + if (tp == ColliderType.Capsule && collision.use2D) tp = ColliderType.Sphere; + EditorGUI.BeginChangeCheck(); + tp = (ColliderType)EditorGUILayout.IntPopup("Collider type", (int)tp, colliderOptions, colliderValues); + if (EditorGUI.EndChangeCheck()) collision.type = tp; + + // Only spheres and capsules have a diameter + if (collision.type == ColliderType.Capsule || collision.type == ColliderType.Sphere) { + collision.diameter = EditorGUILayout.FloatField(new GUIContent("Diameter", "Diameter of the capsule or sphere. 1 equals one node width"), collision.diameter); + collision.diameter = Mathf.Max(collision.diameter, 0.01f); + } + + if (!collision.use2D) { + if (collision.type == ColliderType.Capsule || collision.type == ColliderType.Ray) { + collision.height = EditorGUILayout.FloatField(new GUIContent("Height/Length", "Height of cylinder or length of ray in world units"), collision.height); + collision.height = Mathf.Max(collision.height, 0.01f); + } + + collision.collisionOffset = EditorGUILayout.FloatField(new GUIContent("Offset", "Offset upwards from the node. Can be used so that obstacles can be used as ground and at the same time as obstacles for lower positioned nodes"), collision.collisionOffset); + } + + collision.mask = EditorGUILayoutx.LayerMaskField("Obstacle Layer Mask", collision.mask); + + DrawCollisionPreview(collision); + EditorGUI.indentLevel--; + } + + GUILayout.Space(2); + + if (collision.use2D) { + EditorGUI.BeginDisabledGroup(collision.use2D); + ToggleGroup("Height testing", false); + EditorGUI.EndDisabledGroup(); + } else { + collision.heightCheck = ToggleGroup("Height testing", collision.heightCheck); + if (collision.heightCheck) { + EditorGUI.indentLevel++; + collision.fromHeight = EditorGUILayout.FloatField(new GUIContent("Ray length", "The height from which to check for ground"), collision.fromHeight); + + collision.heightMask = EditorGUILayoutx.LayerMaskField("Mask", collision.heightMask); + + // Layered grid graphs don't support thick raycasts + if (graph.MaxLayers == 1) { + collision.thickRaycast = EditorGUILayout.Toggle(new GUIContent("Thick Raycast", "Use a thick line instead of a thin line"), collision.thickRaycast); + + if (collision.thickRaycast) { + EditorGUI.indentLevel++; + collision.thickRaycastDiameter = EditorGUILayout.FloatField(new GUIContent("Diameter", "Diameter of the thick raycast"), collision.thickRaycastDiameter); + EditorGUI.indentLevel--; + } + } else { + collision.thickRaycast = false; + } + + collision.unwalkableWhenNoGround = EditorGUILayout.Toggle(new GUIContent("Unwalkable when no ground", "Make nodes unwalkable when no ground was found with the height raycast. If height raycast is turned off, this doesn't affect anything"), collision.unwalkableWhenNoGround); + EditorGUI.indentLevel--; + } + } + } + + Vector3[] arcBuffer = new Vector3[21]; + Vector3[] lineBuffer = new Vector3[2]; + void DrawArc (Vector2 center, float radius, float startAngle, float endAngle) { + // The AA line doesn't always properly close the gap even for full circles + endAngle += 1*Mathf.Deg2Rad; + var width = 4; + // The DrawAAPolyLine method does not draw a centered line unfortunately + //radius -= width/2; + for (int i = 0; i < arcBuffer.Length; i++) { + float t = i * 1.0f / (arcBuffer.Length-1); + float angle = Mathf.Lerp(startAngle, endAngle, t); + arcBuffer[i] = new Vector3(center.x + radius * Mathf.Cos(angle), center.y + radius * Mathf.Sin(angle), 0); + } + Handles.DrawAAPolyLine(EditorResourceHelper.HandlesAALineTexture, width, arcBuffer); + } + + void DrawLine (Vector2 a, Vector2 b) { + lineBuffer[0] = a; + lineBuffer[1] = b; + Handles.DrawAAPolyLine(EditorResourceHelper.HandlesAALineTexture, 4, lineBuffer); + } + + void DrawDashedLine (Vector2 a, Vector2 b, float dashLength) { + if (dashLength == 0) { + DrawLine(a, b); + } else { + var dist = (b - a).magnitude; + int steps = Mathf.RoundToInt(dist / dashLength); + for (int i = 0; i < steps; i++) { + var t1 = i * 1.0f / (steps-1); + var t2 = (i + 0.5f) * 1.0f / (steps-1); + DrawLine(Vector2.Lerp(a, b, t1), Vector2.Lerp(a, b, t2)); + } + } + } + + static int RoundUpToNextOddNumber (float x) { + return Mathf.CeilToInt((x - 1)/2.0f)*2 + 1; + } + + float interpolatedGridWidthInNodes = -1; + float lastTime = 0; + + void DrawCollisionPreview (GraphCollision collision) { + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(2); + collisionPreviewOpen = EditorGUILayout.Foldout(collisionPreviewOpen, "Preview"); + EditorGUILayout.EndHorizontal(); + if (!collisionPreviewOpen) return; + + EditorGUILayout.Separator(); + var rect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(10, 100)); + var m = Handles.matrix; + Handles.matrix = Handles.matrix * Matrix4x4.Translate(new Vector3(rect.xMin, rect.yMin)); + + // Draw NxN grid with circle in the middle + // Draw Flat plane with capsule/sphere/line above + + Handles.color = Color.white; + int gridWidthInNodes = collision.type == ColliderType.Ray ? 3 : Mathf.Max(3, RoundUpToNextOddNumber(collision.diameter + 0.5f)); + if (interpolatedGridWidthInNodes == -1) interpolatedGridWidthInNodes = gridWidthInNodes; + if (Mathf.Abs(interpolatedGridWidthInNodes - gridWidthInNodes) < 0.01f) interpolatedGridWidthInNodes = gridWidthInNodes; + else editor.Repaint(); + + var dt = Time.realtimeSinceStartup - lastTime; + lastTime = Time.realtimeSinceStartup; + interpolatedGridWidthInNodes = Mathf.Lerp(interpolatedGridWidthInNodes, gridWidthInNodes, 5 * dt); + + var gridCenter = collision.use2D ? new Vector2(rect.width / 2.0f, rect.height * 0.5f) : new Vector2(rect.width / 3.0f, rect.height * 0.5f); + var gridWidth = Mathf.Min(rect.width / 3, rect.height); + var nodeSize = (this.target as GridGraph).nodeSize; + var scale = gridWidth / (nodeSize * interpolatedGridWidthInNodes); + var diameter = collision.type == ColliderType.Ray ? 0.05f : collision.diameter * nodeSize; + var interpolatedGridScale = gridWidthInNodes * nodeSize * scale; + for (int i = 0; i <= gridWidthInNodes; i++) { + var c = i*1.0f/gridWidthInNodes; + DrawLine(gridCenter + new Vector2(c - 0.5f, -0.5f) * interpolatedGridScale, gridCenter + new Vector2(c - 0.5f, 0.5f) * interpolatedGridScale); + DrawLine(gridCenter + new Vector2(-0.5f, c - 0.5f) * interpolatedGridScale, gridCenter + new Vector2(0.5f, c - 0.5f) * interpolatedGridScale); + } + + var sideBase = new Vector2(2*rect.width / 3f, rect.height); + float sideScale; + if (collision.type == ColliderType.Sphere) { + sideScale = scale; + // A high collision offset should not cause it to break + sideScale = Mathf.Min(sideScale, sideBase.y / (Mathf.Max(0, collision.collisionOffset) + diameter)); + } else { + sideScale = Mathf.Max(scale * 0.5f, Mathf.Min(scale, sideBase.y / (collision.height + collision.collisionOffset + diameter * 0.5f))); + // A high collision offset should not cause it to break + sideScale = Mathf.Min(sideScale, sideBase.y / (Mathf.Max(0, collision.collisionOffset) + diameter)); + } + + var darkGreen = new Color(9/255f, 150/255f, 23/255f); + var lightGreen = new Color(12/255f, 194/255f, 30/255f); + var green = EditorGUIUtility.isProSkin ? lightGreen : darkGreen; + + Handles.color = green; + DrawArc(gridCenter, diameter * 0.5f * scale, 0, Mathf.PI*2); + + if (!collision.use2D) { + Handles.color = Color.white; + var interpolatedGridSideScale = gridWidthInNodes * nodeSize * sideScale; + + DrawLine(sideBase + new Vector2(-interpolatedGridSideScale * 0.5f, 0), sideBase + new Vector2(interpolatedGridSideScale * 0.5f, 0)); + for (int i = 0; i <= gridWidthInNodes; i++) { + var c = i*1.0f/gridWidthInNodes; + DrawArc(sideBase + new Vector2(c - 0.5f, 0) * interpolatedGridSideScale, 2, 0, Mathf.PI*2); + } + + Handles.color = green; + + if (collision.type == ColliderType.Ray) { + var height = collision.height; + var maxHeight = sideBase.y / sideScale - (collision.collisionOffset + diameter*0.5f); + float dashLength = 0; + if (collision.height > maxHeight + 0.01f) { + height = maxHeight; + dashLength = 6; + } + + var offset = sideBase + new Vector2(0, -collision.collisionOffset) * sideScale; + DrawLine(offset + new Vector2(0, -height*0.75f) * sideScale, offset); + DrawDashedLine(offset + new Vector2(0, -height) * sideScale, offset + new Vector2(0, -height * 0.75f) * sideScale, dashLength); + DrawLine(offset, offset + new Vector2(6, -6)); + DrawLine(offset, offset + new Vector2(-6, -6)); + } else { + var height = collision.type == ColliderType.Capsule ? collision.height : 0; + // sideBase.y - (collision.collisionOffset + height + diameter * 0.5f) * scale < 0 + var maxHeight = sideBase.y / sideScale - (collision.collisionOffset + diameter*0.5f); + float dashLength = 0; + if (height > maxHeight + 0.01f) { + height = maxHeight; + dashLength = 6; + } + DrawArc(sideBase + new Vector2(0, -collision.collisionOffset * sideScale), diameter * 0.5f * sideScale, 0, Mathf.PI); + DrawArc(sideBase + new Vector2(0, -(height + collision.collisionOffset) * sideScale), diameter * 0.5f * sideScale, Mathf.PI, 2*Mathf.PI); + DrawDashedLine(sideBase + new Vector2(-diameter * 0.5f, -collision.collisionOffset) * sideScale, sideBase + new Vector2(-diameter * 0.5f, -(height + collision.collisionOffset)) * sideScale, dashLength); + DrawDashedLine(sideBase + new Vector2(diameter * 0.5f, -collision.collisionOffset) * sideScale, sideBase + new Vector2(diameter * 0.5f, -(height + collision.collisionOffset)) * sideScale, dashLength); + } + } + Handles.matrix = m; + EditorGUILayout.Separator(); + } + + protected virtual void DrawUse2DPhysics (GraphCollision collision) { + collision.use2D = EditorGUILayout.Toggle(new GUIContent("Use 2D Physics", "Use the Physics2D API for collision checking"), collision.use2D); + + if (collision.use2D) { + var graph = target as GridGraph; + if (Mathf.Abs(Vector3.Dot(Vector3.forward, Quaternion.Euler(graph.rotation) * Vector3.up)) < 0.9f) { + EditorGUILayout.HelpBox("When using 2D physics it is recommended to rotate the graph so that it aligns with the 2D plane.", MessageType.Warning); + } + } + } + + static Dictionary ruleEditors; + static Dictionary ruleHeaders; + static List ruleTypes; + Dictionary ruleEditorInstances = new Dictionary(); + + static void FindRuleEditors () { + ruleEditors = new Dictionary(); + ruleHeaders = new Dictionary(); + ruleTypes = new List(); + foreach (var type in TypeCache.GetTypesWithAttribute()) { + var attrs = type.GetCustomAttributes(typeof(CustomGridGraphRuleEditorAttribute), false); + foreach (CustomGridGraphRuleEditorAttribute attr in attrs) { + ruleEditors[attr.type] = type; + ruleHeaders[attr.type] = attr.name; + } + } + + foreach (var type in TypeCache.GetTypesDerivedFrom()) { + if (!type.IsAbstract) ruleTypes.Add(type); + } + } + + IGridGraphRuleEditor GetEditor (GridGraphRule rule) { + if (ruleEditors == null) FindRuleEditors(); + IGridGraphRuleEditor ruleEditor; + if (!ruleEditorInstances.TryGetValue(rule, out ruleEditor)) { + if (ruleEditors.ContainsKey(rule.GetType())) { + ruleEditor = ruleEditorInstances[rule] = (IGridGraphRuleEditor)System.Activator.CreateInstance(ruleEditors[rule.GetType()]); + } + } + return ruleEditor; + } + + protected virtual void DrawRules (GridGraph graph) { + var rules = graph.rules.GetRules(); + + for (int i = 0; i < rules.Count; i++) { + var rule = rules[i]; + if (rule != null) { + var ruleEditor = GetEditor(rule); + var ruleType = rule.GetType(); + GUILayout.BeginHorizontal(); + rule.enabled = ToggleGroup(ruleHeaders.TryGetValue(ruleType, out var header) ? header : ruleType.Name, rule.enabled); + if (GUILayout.Button("", AstarPathEditor.astarSkin.FindStyle("SimpleDeleteButton"))) { + graph.rules.RemoveRule(rule); + ruleEditorInstances.Remove(rule); + rule.enabled = false; + rule.DisposeUnmanagedData(); + } + GUILayout.EndHorizontal(); + + if (rule.enabled) { + if (ruleEditor != null) { + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + ruleEditor.OnInspectorGUI(graph, rule); + if (EditorGUI.EndChangeCheck()) rule.SetDirty(); + EditorGUI.indentLevel--; + } else { + EditorGUILayout.HelpBox("No editor found for " + rule.GetType().Name, MessageType.Error); + } + } + } + } + + EditorGUILayout.Separator(); + + GUILayout.BeginHorizontal(); + GUILayout.Space(10); + if (GUILayout.Button("Add Rule", GUILayout.Height(30))) { + if (ruleEditors == null) FindRuleEditors(); + GenericMenu menu = new GenericMenu(); + foreach (var type in ruleTypes) { + menu.AddItem(new GUIContent(ruleHeaders.TryGetValue(type, out var header) ? header : type.Name), false, ruleType => graph.rules.AddRule(System.Activator.CreateInstance((System.Type)ruleType) as GridGraphRule), type); + } + menu.ShowAsContext(); + } + GUILayout.Space(10); + GUILayout.EndHorizontal(); + } + + public static GridPivot PivotPointSelector (GridPivot pivot) { + // Find required styles + gridPivotSelectBackground = gridPivotSelectBackground ?? AstarPathEditor.astarSkin.FindStyle("GridPivotSelectBackground"); + gridPivotSelectButton = gridPivotSelectButton ?? AstarPathEditor.astarSkin.FindStyle("GridPivotSelectButton"); + + Rect r = GUILayoutUtility.GetRect(19, 19, gridPivotSelectBackground); + + // I have no idea why... but this is required for it to work well + r.y -= 14; + + r.width = 19; + r.height = 19; + + if (gridPivotSelectBackground == null) { + return pivot; + } + + if (Event.current.type == EventType.Repaint) { + gridPivotSelectBackground.Draw(r, false, false, false, false); + } + + if (GUI.Toggle(new Rect(r.x, r.y, 7, 7), pivot == GridPivot.TopLeft, "", gridPivotSelectButton)) + pivot = GridPivot.TopLeft; + + if (GUI.Toggle(new Rect(r.x+12, r.y, 7, 7), pivot == GridPivot.TopRight, "", gridPivotSelectButton)) + pivot = GridPivot.TopRight; + + if (GUI.Toggle(new Rect(r.x+12, r.y+12, 7, 7), pivot == GridPivot.BottomRight, "", gridPivotSelectButton)) + pivot = GridPivot.BottomRight; + + if (GUI.Toggle(new Rect(r.x, r.y+12, 7, 7), pivot == GridPivot.BottomLeft, "", gridPivotSelectButton)) + pivot = GridPivot.BottomLeft; + + if (GUI.Toggle(new Rect(r.x+6, r.y+6, 7, 7), pivot == GridPivot.Center, "", gridPivotSelectButton)) + pivot = GridPivot.Center; + + return pivot; + } + + static readonly Vector3[] handlePoints = new [] { new Vector3(0.0f, 0, 0.5f), new Vector3(1.0f, 0, 0.5f), new Vector3(0.5f, 0, 0.0f), new Vector3(0.5f, 0, 1.0f) }; + + public override void OnSceneGUI (NavGraph target) { + Event e = Event.current; + + var graph = target as GridGraph; + + graph.UpdateTransform(); + var currentTransform = graph.transform * Matrix4x4.Scale(new Vector3(graph.width, 1, graph.depth)); + + if (e.type == EventType.MouseDown) { + isMouseDown = true; + } else if (e.type == EventType.MouseUp) { + isMouseDown = false; + } + + if (!isMouseDown) { + savedTransform = currentTransform; + savedDimensions = new Vector2(graph.width, graph.depth); + savedNodeSize = graph.nodeSize; + } + + Handles.matrix = Matrix4x4.identity; + Handles.color = AstarColor.BoundsHandles; + Handles.CapFunction cap = Handles.CylinderHandleCap; + + var center = currentTransform.Transform(new Vector3(0.5f, 0, 0.5f)); + if (Tools.current == Tool.Scale) { + const float HandleScale = 0.1f; + + Vector3 mn = Vector3.zero; + Vector3 mx = Vector3.zero; + EditorGUI.BeginChangeCheck(); + for (int i = 0; i < handlePoints.Length; i++) { + var ps = currentTransform.Transform(handlePoints[i]); + Vector3 p = savedTransform.InverseTransform(Handles.Slider(ps, ps - center, HandleScale*HandleUtility.GetHandleSize(ps), cap, 0)); + + // Snap to increments of whole nodes + p.x = Mathf.Round(p.x * savedDimensions.x) / savedDimensions.x; + p.z = Mathf.Round(p.z * savedDimensions.y) / savedDimensions.y; + + if (i == 0) { + mn = mx = p; + } else { + mn = Vector3.Min(mn, p); + mx = Vector3.Max(mx, p); + } + } + + if (EditorGUI.EndChangeCheck()) { + graph.center = savedTransform.Transform((mn + mx) * 0.5f); + graph.unclampedSize = Vector2.Scale(new Vector2(mx.x - mn.x, mx.z - mn.z), savedDimensions) * savedNodeSize; + } + } else if (Tools.current == Tool.Move) { + EditorGUI.BeginChangeCheck(); + center = Handles.PositionHandle(graph.center, Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : Quaternion.Euler(graph.rotation)); + + if (EditorGUI.EndChangeCheck() && Tools.viewTool != ViewTool.Orbit) { + graph.center = center; + } + } else if (Tools.current == Tool.Rotate) { + EditorGUI.BeginChangeCheck(); + var rot = Handles.RotationHandle(Quaternion.Euler(graph.rotation), graph.center); + + if (EditorGUI.EndChangeCheck() && Tools.viewTool != ViewTool.Orbit) { + graph.rotation = rot.eulerAngles; + } + } + + var rules = graph.rules.GetRules(); + for (int i = 0; i < rules.Count; i++) { + var rule = rules[i]; + if (rule != null && rule.enabled) { + var ruleEditor = GetEditor(rule); + if (ruleEditor != null) { + ruleEditor.OnSceneGUI(graph, rule); + } + } + } + } + + public enum GridPivot { + Center, + TopLeft, + TopRight, + BottomLeft, + BottomRight + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs.meta new file mode 100644 index 0000000..495f7e9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/GridGeneratorEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 22fe5ef0fc7b34922bb13d1961c9f444 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs new file mode 100644 index 0000000..1fad006 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs @@ -0,0 +1,36 @@ +using UnityEngine; +using UnityEditor; +using Pathfinding.Graphs.Grid; + +namespace Pathfinding { + [CustomGraphEditor(typeof(LayerGridGraph), "Layered Grid Graph")] + public class LayerGridGraphEditor : GridGraphEditor { + protected override void DrawMiddleSection (GridGraph graph) { + var layerGridGraph = graph as LayerGridGraph; + + DrawNeighbours(graph); + + layerGridGraph.characterHeight = EditorGUILayout.DelayedFloatField("Character Height", layerGridGraph.characterHeight); + DrawMaxClimb(graph); + + DrawMaxSlope(graph); + DrawErosion(graph); + } + + protected override void DrawMaxClimb (GridGraph graph) { + var layerGridGraph = graph as LayerGridGraph; + + base.DrawMaxClimb(graph); + layerGridGraph.maxStepHeight = Mathf.Clamp(layerGridGraph.maxStepHeight, 0, layerGridGraph.characterHeight); + + if (layerGridGraph.maxStepHeight >= layerGridGraph.characterHeight) { + EditorGUILayout.HelpBox("Max step height needs to be smaller or equal to character height", MessageType.Info); + } + } + + protected override void DrawUse2DPhysics (GraphCollision collision) { + // 2D physics does not make sense for a layered grid graph + collision.use2D = false; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs.meta new file mode 100644 index 0000000..3b03a7e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/LayerGridGraphEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 889543b216eb24cca9740e3cca767f64 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs new file mode 100644 index 0000000..b9e6d51 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs @@ -0,0 +1,65 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomGraphEditor(typeof(NavMeshGraph), "Navmesh Graph")] + public class NavMeshGraphEditor : GraphEditor { + public override void OnInspectorGUI (NavGraph target) { + var graph = target as NavMeshGraph; + + graph.sourceMesh = ObjectField("Source Mesh", graph.sourceMesh, typeof(Mesh), false, true) as Mesh; + + if (graph.sourceMesh != null && !graph.sourceMesh.isReadable) { + if (FixLabel("The selected mesh is not readable. Scanning the graph at runtime will fail. Please enable read/write in the model import settings.")) { + // Try to enable Read/Write on the mesh import settings automatically + var path = AssetDatabase.GetAssetPath(graph.sourceMesh); + if (!string.IsNullOrEmpty(path)) { + var importer = AssetImporter.GetAtPath(path) as ModelImporter; + if (importer != null) { + importer.isReadable = true; + importer.SaveAndReimport(); + } else { + Debug.LogWarning("Could not find a ModelImporter for the selected mesh: " + path); + } + } + } + } + + graph.offset = EditorGUILayout.Vector3Field("Offset", graph.offset); + + graph.rotation = EditorGUILayout.Vector3Field("Rotation", graph.rotation); + + graph.scale = EditorGUILayout.FloatField(new GUIContent("Scale", "Scale of the mesh"), graph.scale); + graph.scale = Mathf.Abs(graph.scale) < 0.01F ? (graph.scale >= 0 ? 0.01F : -0.01F) : graph.scale; + + #pragma warning disable 618 + if (graph.nearestSearchOnlyXZ) { + graph.nearestSearchOnlyXZ = EditorGUILayout.Toggle(new GUIContent("Nearest node queries in XZ space", + "Recomended for single-layered environments.\nFaster but can be inacurate esp. in multilayered contexts."), graph.nearestSearchOnlyXZ); + + EditorGUILayout.HelpBox("The global toggle for node queries in XZ space has been deprecated. Use the NearestNodeConstraint settings instead.", MessageType.Warning); + } + #pragma warning restore 618 + + graph.recalculateNormals = EditorGUILayout.Toggle(new GUIContent("Recalculate Normals", "Disable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings. See docs for more info."), graph.recalculateNormals); + graph.enableNavmeshCutting = EditorGUILayout.Toggle(new GUIContent("Affected By Navmesh Cuts", "Makes this graph affected by NavmeshCut and NavmeshAdd components. See the documentation for more info."), graph.enableNavmeshCutting); + if (graph.enableNavmeshCutting) { + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + var newValue = EditorGUILayout.FloatField(new GUIContent("Agent Radius", "Navmesh cuts can optionally be expanded by the agent radius"), graph.navmeshCuttingCharacterRadius); + if (EditorGUI.EndChangeCheck()) { + graph.navmeshCuttingCharacterRadius = Mathf.Max(0, newValue); + graph.navmeshUpdateData.ReloadAllTiles(); + } + EditorGUI.indentLevel--; + } + + GUILayout.BeginHorizontal(); + GUILayout.Space(18); + graph.showMeshSurface = GUILayout.Toggle(graph.showMeshSurface, new GUIContent("Show surface", "Toggles gizmos for drawing the surface of the mesh"), EditorStyles.miniButtonLeft); + graph.showMeshOutline = GUILayout.Toggle(graph.showMeshOutline, new GUIContent("Show outline", "Toggles gizmos for drawing an outline of the nodes"), EditorStyles.miniButtonMid); + graph.showNodeConnections = GUILayout.Toggle(graph.showNodeConnections, new GUIContent("Show connections", "Toggles gizmos for drawing node connections"), EditorStyles.miniButtonRight); + GUILayout.EndHorizontal(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs.meta new file mode 100644 index 0000000..7e33f80 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/NavMeshGeneratorEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c4ad80e9395de47c78e55cbaae437660 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs new file mode 100644 index 0000000..7b8fb5b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomGraphEditor(typeof(PointGraph), "Point Graph")] + public class PointGraphEditor : GraphEditor { + static readonly GUIContent[] nearestNodeDistanceModeLabels = { + new GUIContent("Node"), + new GUIContent("Connection (slower)"), + }; + + public override void OnInspectorGUI (NavGraph target) { + var graph = target as PointGraph; + + graph.root = ObjectField(new GUIContent("Root", "All children of this object will be used as nodes, if it is not set, a tag search will be used instead (see below)"), graph.root, typeof(Transform), true, false) as Transform; + + graph.recursive = EditorGUILayout.Toggle(new GUIContent("Recursive", "Should children of the children in the root GameObject be searched"), graph.recursive); + graph.searchTag = EditorGUILayout.TagField(new GUIContent("Tag", "If root is not set, all objects with this tag will be used as nodes"), graph.searchTag); + + if (graph.root != null) { + EditorGUILayout.HelpBox("All children "+(graph.recursive ? "and sub-children " : "") +"of 'root' will be used as nodes\nSet root to null to use a tag search instead", MessageType.None); + } else { + EditorGUILayout.HelpBox("All object with the tag '"+graph.searchTag+"' will be used as nodes"+(graph.searchTag == "Untagged" ? "\nNote: the tag 'Untagged' cannot be used" : ""), MessageType.None); + } + + graph.maxDistance = EditorGUILayout.FloatField(new GUIContent("Max Distance", "The max distance in world space for a connection to be valid. A zero counts as infinity"), graph.maxDistance); + + graph.limits = EditorGUILayout.Vector3Field("Max Distance (axis aligned)", graph.limits); + + graph.raycast = EditorGUILayout.Toggle(new GUIContent("Raycast", "Use raycasting to check if connections are valid between each pair of nodes"), graph.raycast); + + if (graph.raycast) { + EditorGUI.indentLevel++; + + graph.use2DPhysics = EditorGUILayout.Toggle(new GUIContent("Use 2D Physics", "If enabled, all raycasts will use the Unity 2D Physics API instead of the 3D one."), graph.use2DPhysics); + graph.thickRaycast = EditorGUILayout.Toggle(new GUIContent("Thick Raycast", "A thick raycast checks along a thick line with radius instead of just along a line"), graph.thickRaycast); + + if (graph.thickRaycast) { + EditorGUI.indentLevel++; + graph.thickRaycastRadius = EditorGUILayout.FloatField(new GUIContent("Raycast Radius", "The radius in world units for the thick raycast"), graph.thickRaycastRadius); + EditorGUI.indentLevel--; + } + + graph.mask = EditorGUILayoutx.LayerMaskField("Mask", graph.mask); + EditorGUI.indentLevel--; + } + + graph.optimizeForSparseGraph = EditorGUILayout.Toggle(new GUIContent("Optimize For Sparse Graph", "Check online documentation for more information."), graph.optimizeForSparseGraph); + graph.nearestNodeDistanceMode = (PointGraph.NodeDistanceMode)EditorGUILayout.Popup(new GUIContent("Nearest Node Queries Find Closest"), (int)graph.nearestNodeDistanceMode, nearestNodeDistanceModeLabels); + + if (graph.nearestNodeDistanceMode == PointGraph.NodeDistanceMode.Connection && !graph.optimizeForSparseGraph) { + EditorGUILayout.HelpBox("Connection mode can only be used if Optimize For Sparse Graph is enabled", MessageType.Error); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs.meta new file mode 100644 index 0000000..cf96f37 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/PointGeneratorEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b563cffdf75ee4497b91d4ac8e106260 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs new file mode 100644 index 0000000..984b509 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs @@ -0,0 +1,590 @@ +using UnityEngine; +using UnityEditor; +using Pathfinding.Graphs.Navmesh; +using UnityEditorInternal; + +namespace Pathfinding { + /// Editor for the RecastGraph. + [CustomGraphEditor(typeof(RecastGraph), "Recast Graph")] + public class RecastGraphEditor : GraphEditor { + public static bool tagMaskFoldout; + public static bool meshesUnreadableAtRuntimeFoldout; + ReorderableList tagMaskList; + ReorderableList perLayerModificationsList; + ReorderableList perTerrainLayerModificationsList; + + public enum UseTiles { + UseTiles = 0, + DontUseTiles = 1 + } + + static readonly GUIContent[] DimensionModeLabels = new [] { + new GUIContent("2D"), + new GUIContent("3D"), + }; + + static Rect SliceColumn (ref Rect rect, float width, float spacing = 0) { + return GUIUtilityx.SliceColumn(ref rect, width, spacing); + } + + static void DrawIndentedList (ReorderableList list) { + GUILayout.BeginHorizontal(); + GUILayout.Space(EditorGUI.IndentedRect(default).xMin); + list.DoLayoutList(); + GUILayout.Space(3); + GUILayout.EndHorizontal(); + } + + static void DrawColliderDetail (RecastGraph.CollectionSettings settings) { + const float LowestApproximationError = 0.5f; + settings.colliderRasterizeDetail = EditorGUILayout.Slider(new GUIContent("Round Collider Detail", "Controls the detail of the generated sphere and capsule meshes. "+ + "Higher values may increase navmesh quality slightly, and lower values improve graph scanning performance."), Mathf.Round(10*settings.colliderRasterizeDetail)*0.1f, 0, 1.0f / LowestApproximationError); + } + + void DrawCollectionSettings (RecastGraph.CollectionSettings settings, RecastGraph.DimensionMode dimensionMode) { + settings.collectionMode = (RecastGraph.CollectionSettings.FilterMode)EditorGUILayout.EnumPopup("Filter Objects By", settings.collectionMode); + + if (settings.collectionMode == RecastGraph.CollectionSettings.FilterMode.Layers) { + settings.layerMask = EditorGUILayoutx.LayerMaskField("Layer Mask", settings.layerMask); + } else { + DrawIndentedList(tagMaskList); + } + + if (dimensionMode == RecastGraph.DimensionMode.Dimension3D) { + settings.rasterizeTerrain = EditorGUILayout.Toggle(new GUIContent("Rasterize Terrains", "Should a rasterized terrain be included"), settings.rasterizeTerrain); + if (settings.rasterizeTerrain) { + EditorGUI.indentLevel++; + settings.rasterizeTrees = EditorGUILayout.Toggle(new GUIContent("Rasterize Trees", "Rasterize tree colliders on terrains. " + + "If the tree prefab has a collider, that collider will be rasterized. " + + "Otherwise a simple box collider will be used and the script will " + + "try to adjust it to the tree's scale, it might not do a very good job though so " + + "an attached collider is preferable."), settings.rasterizeTrees); + settings.terrainHeightmapDownsamplingFactor = EditorGUILayout.IntField(new GUIContent("Heightmap Downsampling", "How much to downsample the terrain's heightmap. A lower value is better, but slower to scan"), settings.terrainHeightmapDownsamplingFactor); + settings.terrainHeightmapDownsamplingFactor = Mathf.Max(1, settings.terrainHeightmapDownsamplingFactor); + DrawIndentedList(perTerrainLayerModificationsList); + EditorGUI.indentLevel--; + } + + settings.rasterizeMeshes = EditorGUILayout.Toggle(new GUIContent("Rasterize Meshes", "Should meshes be rasterized and used for building the navmesh"), settings.rasterizeMeshes); + settings.rasterizeColliders = EditorGUILayout.Toggle(new GUIContent("Rasterize Colliders", "Should colliders be rasterized and used for building the navmesh"), settings.rasterizeColliders); + } else { + // Colliders are always rasterized in 2D mode + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Toggle(new GUIContent("Rasterize Colliders", "Should colliders be rasterized and used for building the navmesh. In 2D mode, this is always enabled."), true); + EditorGUI.EndDisabledGroup(); + } + + if (settings.rasterizeMeshes && settings.rasterizeColliders && dimensionMode == RecastGraph.DimensionMode.Dimension3D) { + EditorGUILayout.HelpBox("You are rasterizing both meshes and colliders. This is likely just duplicate work if the colliders and meshes are similar in shape. You can use the RecastNavmeshModifier component" + + " to always include some specific objects regardless of what the above settings are set to.", MessageType.Info); + } + } + + public override void OnEnable () { + base.OnEnable(); + var graph = target as RecastGraph; + tagMaskList = new ReorderableList(graph.collectionSettings.tagMask, typeof(string), true, true, true, true) { + drawElementCallback = (Rect rect, int index, bool active, bool isFocused) => { + graph.collectionSettings.tagMask[index] = EditorGUI.TagField(rect, graph.collectionSettings.tagMask[index]); + }, + drawHeaderCallback = (Rect rect) => { + GUI.Label(rect, "Tag mask"); + }, + elementHeight = EditorGUIUtility.singleLineHeight, + onAddCallback = (ReorderableList list) => { + graph.collectionSettings.tagMask.Add("Untagged"); + } + }; + + perLayerModificationsList = new ReorderableList(graph.perLayerModifications, typeof(RecastGraph.PerLayerModification), true, true, true, true) { + drawElementCallback = (Rect rect, int index, bool active, bool isFocused) => { + var element = graph.perLayerModifications[index]; + var w = rect.width; + var spacing = EditorGUIUtility.standardVerticalSpacing; + rect.yMin += 1; // Fix alignment + element.layer = EditorGUI.LayerField(SliceColumn(ref rect, w * 0.3f, spacing), element.layer); + + if (element.mode == RecastNavmeshModifier.Mode.WalkableSurfaceWithTag) { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(SliceColumn(ref rect, w * 0.4f, spacing), element.mode); + element.surfaceID = Util.EditorGUILayoutHelper.TagField(rect, GUIContent.none, element.surfaceID, AstarPathEditor.EditTags); + element.surfaceID = Mathf.Clamp(element.surfaceID, 0, GraphNode.MaxTagIndex); + } else if (element.mode == RecastNavmeshModifier.Mode.WalkableSurfaceWithSeam) { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(SliceColumn(ref rect, w * 0.4f, spacing), element.mode); + string helpTooltip = "All surfaces on this mesh will be walkable and a " + + "seam will be created between the surfaces on this mesh and the surfaces on other meshes (with a different surface id)"; + GUI.Label(SliceColumn(ref rect, 70, spacing), new GUIContent("Surface ID", helpTooltip)); + element.surfaceID = Mathf.Max(0, EditorGUI.IntField(rect, new GUIContent("", helpTooltip), element.surfaceID)); + } else { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(rect, element.mode); + } + + graph.perLayerModifications[index] = element; + }, + drawHeaderCallback = (Rect rect) => { + GUI.Label(rect, "Per Layer Modifications"); + }, + elementHeight = EditorGUIUtility.singleLineHeight, + onAddCallback = (ReorderableList list) => { + // Find the first layer that is not already modified + var availableLayers = graph.collectionSettings.layerMask; + foreach (var mod in graph.perLayerModifications) { + availableLayers &= ~(1 << mod.layer); + } + var newMod = RecastGraph.PerLayerModification.Default; + for (int i = 0; i < 32; i++) { + if ((availableLayers & (1 << i)) != 0) { + newMod.layer = i; + break; + } + } + graph.perLayerModifications.Add(newMod); + } + }; + + var terrain = Terrain.activeTerrain; + GUIContent[] terrainLayerOptions = null; + if (terrain != null && terrain.terrainData != null) { + var terrainLayers = terrain.terrainData.terrainLayers; + terrainLayerOptions = new GUIContent[terrainLayers.Length]; + for (int i = 0; i < terrainLayers.Length; i++) { + terrainLayerOptions[i] = new GUIContent(terrainLayers[i] != null ? terrainLayers[i].name : "Terrain Layer " + i); + terrainLayerOptions[i].image = terrainLayers[i]?.diffuseTexture; + } + } + + perTerrainLayerModificationsList = new ReorderableList(graph.perTerrainLayerModifications, typeof(RecastGraph.PerTerrainLayerModification), true, true, true, true) { + drawElementCallback = (Rect rect, int index, bool active, bool isFocused) => { + var element = graph.perTerrainLayerModifications[index]; + var w = rect.width; + + // Fix alignment + rect.yMin += EditorGUIUtility.standardVerticalSpacing/2; + rect.height -= EditorGUIUtility.standardVerticalSpacing; + + var spacing = EditorGUIUtility.standardVerticalSpacing; + var thresholdRect = GUIUtilityx.SliceColumnRight(ref rect, 50, 0); + var thresholdLabelRect = GUIUtilityx.SliceColumnRight(ref rect, 70, 10); + + if (terrainLayerOptions != null) { + element.layer = EditorGUI.Popup(SliceColumn(ref rect, w * 0.2f, spacing), element.layer, terrainLayerOptions); + } else { + element.layer = EditorGUI.IntField(SliceColumn(ref rect, w * 0.2f, spacing), element.layer); + element.layer = Mathf.Max(element.layer, 0); + } + + if (element.mode == RecastNavmeshModifier.Mode.WalkableSurfaceWithTag) { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(SliceColumn(ref rect, w * 0.3f, spacing), element.mode); + element.surfaceID = Util.EditorGUILayoutHelper.TagField(rect, GUIContent.none, element.surfaceID, AstarPathEditor.EditTags); + element.surfaceID = Mathf.Clamp(element.surfaceID, 0, GraphNode.MaxTagIndex); + } else if (element.mode == RecastNavmeshModifier.Mode.WalkableSurfaceWithSeam) { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(SliceColumn(ref rect, w * 0.3f, spacing), element.mode); + string helpTooltip = "All surfaces on this mesh will be walkable and a " + + "seam will be created between the surfaces on this mesh and the surfaces on other meshes (with a different surface id)"; + GUI.Label(SliceColumn(ref rect, 70, spacing), new GUIContent("Surface ID", helpTooltip)); + element.surfaceID = Mathf.Max(0, EditorGUI.IntField(rect, new GUIContent("", helpTooltip), element.surfaceID)); + } else { + element.mode = (RecastNavmeshModifier.Mode)EditorGUI.EnumPopup(rect, element.mode); + } + + GUI.Label(thresholdLabelRect, new GUIContent("Threshold", "How much the terrain layer must be above the base plane to be considered walkable. " + + "0 means that the terrain layer must be at least at the base plane level, 1 means that it must be at least one cell above the base plane level.")); + element.threshold = EditorGUI.FloatField(thresholdRect, element.threshold); + element.threshold = Mathf.Clamp(element.threshold, 0, 1); + + graph.perTerrainLayerModifications[index] = element; + }, + drawHeaderCallback = (Rect rect) => { + GUI.Label(rect, "Per Terrain Layer Modifications"); + }, + elementHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, + onAddCallback = (ReorderableList list) => { + // Find the first layer that is not already modified + var newMod = RecastGraph.PerTerrainLayerModification.Default; + while (graph.perTerrainLayerModifications.Exists(mod => mod.layer == newMod.layer)) { + newMod.layer++; + } + graph.perTerrainLayerModifications.Add(newMod); + } + }; + } + + public override void OnInspectorGUI (NavGraph target) { + var graph = target as RecastGraph; + + Header("Shape"); + + graph.dimensionMode = (RecastGraph.DimensionMode)EditorGUILayout.Popup(new GUIContent("Dimensions", "Should the graph be for a 2D or 3D world?"), (int)graph.dimensionMode, DimensionModeLabels); + if (graph.dimensionMode == RecastGraph.DimensionMode.Dimension2D && Mathf.Abs(Vector3.Dot(Quaternion.Euler(graph.rotation) * Vector3.up, Vector3.forward)) < 0.99999f) { + EditorGUI.indentLevel++; + EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); + GUILayout.Label(EditorGUIUtility.IconContent("console.warnicon"), GUILayout.ExpandWidth(false)); + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + GUILayout.BeginHorizontal(); + GUILayout.Label("Your graph is not in the XY plane"); + if (GUILayout.Button("Align")) { + graph.rotation = new Vector3(-90, 0, 0); + graph.forcedBoundsCenter = new Vector3(graph.forcedBoundsCenter.x, graph.forcedBoundsCenter.y, -graph.forcedBoundsSize.y * 0.5f); + } + GUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + EditorGUILayout.EndHorizontal(); + EditorGUI.indentLevel--; + } + + // In 3D mode, we use the graph's center as the pivot point, but in 2D mode, we use the center of the base plane of the graph as the pivot point. + // This makes sense because in 2D mode, you typically want to set the base plane's center to Z=0, and you don't care much about the height of the graph. + var pivot = graph.dimensionMode == RecastGraph.DimensionMode.Dimension2D ? new Vector3(0.0f, -0.5f, 0.0f) : Vector3.zero; + var centerOffset = Quaternion.Euler(graph.rotation) * Vector3.Scale(graph.forcedBoundsSize, pivot); + var newCenter = EditorGUILayout.Vector3Field("Center", graph.forcedBoundsCenter + centerOffset); + var newSize = EditorGUILayout.Vector3Field("Size", graph.forcedBoundsSize); + + // Make sure the bounding box is not infinitely thin along any axis + newSize = Vector3.Max(newSize, Vector3.one * 0.001f); + + // Recalculate the center offset with the new size, and then adjust the center so that the pivot point stays the same if the size changes + centerOffset = Quaternion.Euler(graph.rotation) * Vector3.Scale(newSize, pivot); + graph.forcedBoundsCenter = RoundVector3(newCenter) - centerOffset; + graph.forcedBoundsSize = RoundVector3(newSize); + + graph.rotation = RoundVector3(EditorGUILayout.Vector3Field("Rotation", graph.rotation)); + + long estWidth = Mathf.RoundToInt(Mathf.Ceil(graph.forcedBoundsSize.x / graph.cellSize)); + long estDepth = Mathf.RoundToInt(Mathf.Ceil(graph.forcedBoundsSize.z / graph.cellSize)); + + EditorGUI.BeginDisabledGroup(true); + var estTilesX = (estWidth + graph.editorTileSize - 1) / graph.editorTileSize; + var estTilesZ = (estDepth + graph.editorTileSize - 1) / graph.editorTileSize; + var label = estWidth.ToString() + " x " + estDepth.ToString() + " voxels"; + if (graph.useTiles) { + label += ", divided into " + (estTilesX*estTilesZ) + " tiles"; + } + EditorGUILayout.LabelField(new GUIContent("Size", "Based on the voxel size and the bounding box"), new GUIContent(label)); + EditorGUI.EndDisabledGroup(); + + // Show a warning if the number of voxels is too large + if (estWidth*estDepth >= 3000*3000) { + GUIStyle helpBox = GUI.skin.FindStyle("HelpBox") ?? GUI.skin.FindStyle("Box"); + + Color preColor = GUI.color; + if (estWidth*estDepth >= 8192*8192) { + GUI.color = Color.red; + } else { + GUI.color = Color.yellow; + } + + GUILayout.Label("Warning: Might take some time to calculate", helpBox); + GUI.color = preColor; + } + + if (!editor.isPrefab) { + if (GUILayout.Button(new GUIContent("Snap bounds to scene", "Will snap the bounds of the graph to exactly contain all meshes in the scene that matches the masks."))) { + graph.SnapBoundsToScene(); + GUI.changed = true; + } + } + + Separator(); + Header("Input Filtering"); + + DrawCollectionSettings(graph.collectionSettings, graph.dimensionMode); + + Separator(); + Header("Agent Characteristics"); + + graph.characterRadius = EditorGUILayout.FloatField(new GUIContent("Character Radius", "Radius of the character. It's good to add some margin.\nIn world units."), graph.characterRadius); + graph.characterRadius = Mathf.Max(graph.characterRadius, 0); + + if (graph.characterRadius < graph.cellSize * 2) { + EditorGUILayout.HelpBox("For best navmesh quality, it is recommended to keep the character radius at least 2 times as large as the voxel size. Smaller voxels will give you higher quality navmeshes, but it will take more time to scan the graph.", MessageType.Warning); + } + + if (graph.dimensionMode == RecastGraph.DimensionMode.Dimension3D) { + graph.walkableHeight = EditorGUILayout.DelayedFloatField(new GUIContent("Character Height", "Minimum distance to the roof for an area to be walkable"), graph.walkableHeight); + graph.walkableHeight = Mathf.Max(graph.walkableHeight, 0); + + graph.walkableClimb = EditorGUILayout.FloatField(new GUIContent("Max Step Height", "How high can the character step up vertically"), graph.walkableClimb); + + // A walkableClimb higher than this can cause issues when generating the navmesh since then it can in some cases + // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with a navmesh without links) + if (graph.walkableClimb >= graph.walkableHeight) { + graph.walkableClimb = graph.walkableHeight; + EditorGUILayout.HelpBox("Max Step Height should be less than Character Height. Clamping to " + graph.walkableHeight+".", MessageType.Warning); + } else if (graph.walkableClimb < 0) { + graph.walkableClimb = 0; + } + + graph.maxSlope = EditorGUILayout.Slider(new GUIContent("Max Slope", "Approximate maximum slope"), graph.maxSlope, 0F, 90F); + } + + if (graph.dimensionMode == RecastGraph.DimensionMode.Dimension2D) { + graph.backgroundTraversability = (RecastGraph.BackgroundTraversability)EditorGUILayout.EnumPopup("Background traversability", graph.backgroundTraversability); + } + + DrawIndentedList(perLayerModificationsList); + + int seenLayers = 0; + for (int i = 0; i < graph.perLayerModifications.Count; i++) { + if ((seenLayers & 1 << graph.perLayerModifications[i].layer) != 0) { + EditorGUILayout.HelpBox("Duplicate layers. Each layer can only be modified by a single rule.", MessageType.Error); + break; + } + seenLayers |= 1 << graph.perLayerModifications[i].layer; + } + + Separator(); + Header("Rasterization"); + + EditorGUI.BeginChangeCheck(); + graph.cellSize = Mathf.Max(0.001f, EditorGUILayout.FloatField(new GUIContent("Voxel Size", "Size of one voxel in world units"), graph.cellSize)); + if (EditorGUI.EndChangeCheck()) { + graph.cellSize = Mathf.Round(graph.cellSize * Int3.Precision) / (float)Int3.Precision; // Round to a multiple of Int3.Precision, to avoid precision issues + } + + graph.useTiles = (UseTiles)EditorGUILayout.EnumPopup("Use Tiles", graph.useTiles ? UseTiles.UseTiles : UseTiles.DontUseTiles) == UseTiles.UseTiles; + + if (graph.useTiles) { + EditorGUI.indentLevel++; + graph.editorTileSize = EditorGUILayout.IntField(new GUIContent("Tile Size (voxels)", "Size in voxels of a single tile.\n" + + "This is the width of the tile.\n" + + "\n" + + "A large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world)\n" + + "smaller tile sizes are (much) faster to update.\n" + + "\n" + + "Different tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for\n" + + "better quality paths, but too small tiles can lead to effects looking like invisible obstacles.\n\n" + + "Typical values are between 64 and 256"), graph.editorTileSize); + graph.editorTileSize = Mathf.Max(10, graph.editorTileSize); + EditorGUI.indentLevel--; + } + + graph.maxEdgeLength = EditorGUILayout.FloatField(new GUIContent("Max Border Edge Length", "Maximum length of one border edge in the completed navmesh before it is split. A lower value can often yield better quality graphs, but don't use so low values so that you get a lot of thin triangles."), graph.maxEdgeLength); + graph.maxEdgeLength = graph.maxEdgeLength < graph.cellSize ? graph.cellSize : graph.maxEdgeLength; + + // This is actually a float, but to make things easier for the user, we only allow picking integers. Small changes don't matter that much anyway. + graph.contourMaxError = EditorGUILayout.IntSlider(new GUIContent("Edge Simplification", "Simplifies the edges of the navmesh such that it is no more than this number of voxels away from the true value.\nIn voxels."), Mathf.RoundToInt(graph.contourMaxError), 0, 5); + graph.minRegionSize = EditorGUILayout.FloatField(new GUIContent("Min Region Size", "Small regions will be removed. In voxels. Only regions within single tiles can be removed. Regions that span multiple tiles will always be kept. If you don't use tiling, then all small regions will be removed."), graph.minRegionSize); + + var effectivelyRasterizingColliders = graph.collectionSettings.rasterizeColliders || (graph.dimensionMode == RecastGraph.DimensionMode.Dimension3D && graph.collectionSettings.rasterizeTerrain && graph.collectionSettings.rasterizeTrees) || graph.dimensionMode == RecastGraph.DimensionMode.Dimension2D; + if (effectivelyRasterizingColliders) { + DrawColliderDetail(graph.collectionSettings); + } + + var countStillUnreadable = 0; + for (int i = 0; graph.meshesUnreadableAtRuntime != null && i < graph.meshesUnreadableAtRuntime.Count; i++) { + countStillUnreadable += graph.meshesUnreadableAtRuntime[i].Item2.isReadable ? 0 : 1; + } + if (countStillUnreadable > 0) { + GUILayout.BeginHorizontal(); + GUILayout.Space(EditorGUI.IndentedRect(new Rect(0, 0, 0, 0)).xMin); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + meshesUnreadableAtRuntimeFoldout = GUILayout.Toggle(meshesUnreadableAtRuntimeFoldout, "", EditorStyles.foldout, GUILayout.Width(10)); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.Label(EditorGUIUtility.IconContent("console.warnicon"), GUILayout.ExpandWidth(false)); + GUILayout.Label(graph.meshesUnreadableAtRuntime.Count + " " + (graph.meshesUnreadableAtRuntime.Count > 1 ? "meshes" : "mesh") + " will be ignored if scanned in a standalone build, because they are marked as not readable." + + "If you plan to scan the graph in a standalone build, all included meshes must be marked as read/write in their import settings.", EditorStyles.wordWrappedMiniLabel); + // EditorGUI.DrawTextureTransparent() EditorGUIUtility.IconContent("console.warnicon") + GUILayout.EndHorizontal(); + + if (meshesUnreadableAtRuntimeFoldout) { + EditorGUILayout.Separator(); + for (int i = 0; i < graph.meshesUnreadableAtRuntime.Count; i++) { + var(source, mesh) = graph.meshesUnreadableAtRuntime[i]; + if (!mesh.isReadable) { + GUILayout.BeginHorizontal(); + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField(source, typeof(Mesh), true); + EditorGUILayout.ObjectField(mesh, typeof(Mesh), false); + EditorGUI.EndDisabledGroup(); + if (GUILayout.Button("Make readable")) { + var importer = ModelImporter.GetAtPath(AssetDatabase.GetAssetPath(mesh)) as ModelImporter; + if (importer != null) { + importer.isReadable = true; + importer.SaveAndReimport(); + } + } + GUILayout.EndHorizontal(); + } + } + } + EditorGUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + + Separator(); + Header("Runtime Settings"); + + graph.enableNavmeshCutting = EditorGUILayout.Toggle(new GUIContent("Affected by Navmesh Cuts", "Makes this graph affected by NavmeshCut and NavmeshAdd components. See the documentation for more info."), graph.enableNavmeshCutting); + + Separator(); + Header("Debug"); + GUILayout.BeginHorizontal(); + GUILayout.Space(18); + graph.showMeshSurface = GUILayout.Toggle(graph.showMeshSurface, new GUIContent("Show surface", "Toggles gizmos for drawing the surface of the mesh"), EditorStyles.miniButtonLeft); + graph.showMeshOutline = GUILayout.Toggle(graph.showMeshOutline, new GUIContent("Show outline", "Toggles gizmos for drawing an outline of the nodes"), EditorStyles.miniButtonMid); + graph.showNodeConnections = GUILayout.Toggle(graph.showNodeConnections, new GUIContent("Show connections", "Toggles gizmos for drawing node connections"), EditorStyles.miniButtonRight); + GUILayout.EndHorizontal(); + + + Separator(); + Header("Advanced"); + + graph.relevantGraphSurfaceMode = (RecastGraph.RelevantGraphSurfaceMode)EditorGUILayout.EnumPopup(new GUIContent("Relevant Graph Surface Mode", + "Require every region to have a RelevantGraphSurface component inside it.\n" + + "A RelevantGraphSurface component placed in the scene specifies that\n" + + "the navmesh region it is inside should be included in the navmesh.\n\n" + + "If this is set to OnlyForCompletelyInsideTile\n" + + "a navmesh region is included in the navmesh if it\n" + + "has a RelevantGraphSurface inside it, or if it\n" + + "is adjacent to a tile border. This can leave some small regions\n" + + "which you didn't want to have included because they are adjacent\n" + + "to tile borders, but it removes the need to place a component\n" + + "in every single tile, which can be tedious (see below).\n\n" + + "If this is set to RequireForAll\n" + + "a navmesh region is included only if it has a RelevantGraphSurface\n" + + "inside it. Note that even though the navmesh\n" + + "looks continous between tiles, the tiles are computed individually\n" + + "and therefore you need a RelevantGraphSurface component for each\n" + + "region and for each tile."), + graph.relevantGraphSurfaceMode); + + #pragma warning disable 618 + if (graph.nearestSearchOnlyXZ) { + graph.nearestSearchOnlyXZ = EditorGUILayout.Toggle(new GUIContent("Nearest node queries in XZ space", + "Recomended for single-layered environments.\nFaster but can be inacurate esp. in multilayered contexts."), graph.nearestSearchOnlyXZ); + + EditorGUILayout.HelpBox("The global toggle for node queries in XZ space has been deprecated. Use the NearestNodeConstraint settings instead.", MessageType.Warning); + } + #pragma warning restore 618 + + if (GUILayout.Button("Export to .obj file")) { + editor.RunTask(() => ExportToFile(graph)); + } + } + + static readonly Vector3[] handlePoints = new [] { new Vector3(-1, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, -1), new Vector3(0, 0, 1), new Vector3(0, 1, 0), new Vector3(0, -1, 0) }; + + public override void OnSceneGUI (NavGraph target) { + var graph = target as RecastGraph; + + Handles.matrix = Matrix4x4.identity; + Handles.color = AstarColor.BoundsHandles; + Handles.CapFunction cap = Handles.CylinderHandleCap; + + var center = graph.forcedBoundsCenter; + Matrix4x4 matrix = Matrix4x4.TRS(center, Quaternion.Euler(graph.rotation), graph.forcedBoundsSize * 0.5f); + + if (Tools.current == Tool.Scale) { + const float HandleScale = 0.1f; + + Vector3 mn = Vector3.zero; + Vector3 mx = Vector3.zero; + EditorGUI.BeginChangeCheck(); + for (int i = 0; i < handlePoints.Length; i++) { + var ps = matrix.MultiplyPoint3x4(handlePoints[i]); + Vector3 p = matrix.inverse.MultiplyPoint3x4(Handles.Slider(ps, ps - center, HandleScale*HandleUtility.GetHandleSize(ps), cap, 0)); + + if (i == 0) { + mn = mx = p; + } else { + mn = Vector3.Min(mn, p); + mx = Vector3.Max(mx, p); + } + } + + if (EditorGUI.EndChangeCheck()) { + graph.forcedBoundsCenter = matrix.MultiplyPoint3x4((mn + mx) * 0.5f); + graph.forcedBoundsSize = Vector3.Scale(graph.forcedBoundsSize, (mx - mn) * 0.5f); + } + } else if (Tools.current == Tool.Move) { + EditorGUI.BeginChangeCheck(); + center = Handles.PositionHandle(center, Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : Quaternion.Euler(graph.rotation)); + + if (EditorGUI.EndChangeCheck() && Tools.viewTool != ViewTool.Orbit) { + graph.forcedBoundsCenter = center; + } + } else if (Tools.current == Tool.Rotate) { + EditorGUI.BeginChangeCheck(); + var rot = Handles.RotationHandle(Quaternion.Euler(graph.rotation), graph.forcedBoundsCenter); + + if (EditorGUI.EndChangeCheck() && Tools.viewTool != ViewTool.Orbit) { + graph.rotation = rot.eulerAngles; + } + } + } + + /// Exports the INavmesh graph to a .obj file + public static void ExportToFile (NavmeshBase target) { + if (target == null) return; + + NavmeshTile[] tiles = target.GetTiles(); + + if (tiles == null) { + if (EditorUtility.DisplayDialog("Scan graph before exporting?", "The graph does not contain any mesh data. Do you want to scan it?", "Ok", "Cancel")) { + AstarPathEditor.MenuScan(); + tiles = target.GetTiles(); + if (tiles == null) return; + } else { + return; + } + } + + string path = EditorUtility.SaveFilePanel("Export .obj", "", "navmesh.obj", "obj"); + if (path == "") return; + + //Generate .obj + var sb = new System.Text.StringBuilder(); + + string name = System.IO.Path.GetFileNameWithoutExtension(path); + + sb.Append("g ").Append(name).AppendLine(); + + //Vertices start from 1 + int vCount = 1; + + //Define single texture coordinate to zero + sb.Append("vt 0 0\n"); + + for (int t = 0; t < tiles.Length; t++) { + NavmeshTile tile = tiles[t]; + + if (tile == null) continue; + + var vertices = tile.verts; + + //Write vertices + for (int i = 0; i < vertices.Length; i++) { + var v = (Vector3)vertices[i]; + sb.Append(string.Format("v {0} {1} {2}\n", -v.x, v.y, v.z)); + } + + //Write triangles + TriangleMeshNode[] nodes = tile.nodes; + for (int i = 0; i < nodes.Length; i++) { + TriangleMeshNode node = nodes[i]; + if (node == null) { + Debug.LogError("Node was null or no TriangleMeshNode. Critical error. Graph type " + target.GetType().Name); + return; + } + if (node.GetVertexArrayIndex(0) < 0 || node.GetVertexArrayIndex(0) >= vertices.Length) throw new System.Exception("ERR"); + + sb.Append(string.Format("f {0}/1 {1}/1 {2}/1\n", (node.GetVertexArrayIndex(0) + vCount), (node.GetVertexArrayIndex(1) + vCount), (node.GetVertexArrayIndex(2) + vCount))); + } + + vCount += vertices.Length; + } + + string obj = sb.ToString(); + + using (var sw = new System.IO.StreamWriter(path)) { + sw.Write(obj); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs.meta new file mode 100644 index 0000000..ed531b4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphEditors/RecastGraphEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1e335b837bf0e437f865b313cd5ac11c +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs new file mode 100644 index 0000000..bc0f456 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs @@ -0,0 +1,51 @@ +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + [CustomPropertyDrawer(typeof(GraphMask))] + public class GraphMaskDrawer : PropertyDrawer { + string[] graphLabels = new string[31]; + + public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + // Make sure the AstarPath object is initialized and the graphs are loaded, this is required to be able to show graph names in the mask popup + AstarPath.FindAstarPath(); + + for (int i = 0; i < graphLabels.Length; i++) { + if (AstarPath.active == null || AstarPath.active.data.graphs == null || i >= AstarPath.active.data.graphs.Length || AstarPath.active.data.graphs[i] == null) graphLabels[i] = "Graph " + i + (i == 30 ? "+" : ""); + else { + graphLabels[i] = AstarPath.active.data.graphs[i].name + " (graph " + i + ")"; + } + } + + EditorGUI.showMixedValue = property.hasMultipleDifferentValues; + var valueProp = property.FindPropertyRelative("value"); + var mask = new GraphMask((uint)valueProp.intValue); // Note: .uintValue is not supported in Unity 2021.3 + if (mask.isPureBitmask) { + EditorGUI.BeginChangeCheck(); + int newVal = EditorGUI.MaskField(position, label, valueProp.intValue, graphLabels); + if (EditorGUI.EndChangeCheck()) { + valueProp.intValue = newVal; + } + } else { + var tooltip = ""; + var cnt = 0; + for (uint i = 0; i <= GraphNode.MaxGraphIndex; i++) { + if (mask.Contains(i)) { + if (cnt > 0) tooltip += ", "; + if (AstarPath.active == null || AstarPath.active.data.graphs == null || i >= AstarPath.active.data.graphs.Length || AstarPath.active.data.graphs[i] == null) { + tooltip += "Graph " + i; + } else { + tooltip += AstarPath.active.data.graphs[i].name + " (graph " + i + ")"; + } + cnt++; + } + } + EditorGUI.LabelField(position, label, new GUIContent(cnt > 2 ? "Mixed..." : tooltip, tooltip + "\nCannot be edited in the inspector because the mask contains large graph indices")); + if (GUI.Button(new Rect(position.xMax - 60, position.y, 60, 16), new GUIContent("Reset"), EditorStyles.miniButton)) { + valueProp.intValue = -1; + } + } + EditorGUI.showMixedValue = false; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs.meta new file mode 100644 index 0000000..d01be4c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphMaskDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fcb22d8838af78c3b8b72d6cfbaa7f9f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs new file mode 100644 index 0000000..acbb671 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs @@ -0,0 +1,343 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +namespace Pathfinding { + /// Editor for GraphUpdateScene + [CustomEditor(typeof(GraphUpdateScene))] + [CanEditMultipleObjects] + public class GraphUpdateSceneEditor : EditorBase { + int selectedPoint = -1; + + const float pointGizmosRadius = 0.09F; + static Color PointColor = new Color(1, 0.36F, 0, 0.6F); + static Color PointSelectedColor = new Color(1, 0.24F, 0, 1.0F); + + GraphUpdateScene[] scripts; + + protected override void Inspector () { + // Find all properties + var points = FindProperty("points"); + var legacyMode = FindProperty("legacyMode"); + + // Get a list of inspected components + if (scripts == null || scripts.Length != targets.Length) scripts = new GraphUpdateScene[targets.Length]; + targets.CopyTo(scripts, 0); + + EditorGUI.BeginChangeCheck(); + + // Make sure no point arrays are null + for (int i = 0; i < scripts.Length; i++) { + scripts[i].points = scripts[i].points ?? new Vector3[0]; + } + + if (!points.hasMultipleDifferentValues && points.arraySize == 0) { + if (scripts[0].GetComponent() != null) { + EditorGUILayout.HelpBox("Using polygon collider shape", MessageType.Info); + } else if (scripts[0].GetComponent() != null || scripts[0].GetComponent() != null) { + EditorGUILayout.HelpBox("No points, using collider.bounds", MessageType.Info); + } else if (scripts[0].GetComponent() != null) { + EditorGUILayout.HelpBox("No points, using renderer.bounds", MessageType.Info); + } else { + EditorGUILayout.HelpBox("No points and no collider or renderer attached, will not affect anything\nPoints can be added using the transform tool and holding shift", MessageType.Warning); + } + } + + DrawPointsField(); + + EditorGUI.indentLevel = 0; + + DrawPhysicsField(); + + PropertyField("updateErosion", null, "Recalculate erosion for grid graphs.\nSee online documentation for more info"); + + DrawConvexField(); + + // Minimum bounds height is not applied when using the bounds from a collider or renderer + if (points.hasMultipleDifferentValues || points.arraySize > 0) { + FloatField("minBoundsHeight", min: 0.1f); + } + PropertyField("applyOnStart"); + PropertyField("applyOnScan"); + + DrawWalkableField(); + DrawPenaltyField(); + DrawTagField(); + + EditorGUILayout.Separator(); + + if (legacyMode.hasMultipleDifferentValues || legacyMode.boolValue) { + EditorGUILayout.HelpBox("Legacy mode is enabled because you have upgraded from an earlier version of the A* Pathfinding Project. " + + "Disabling legacy mode is recommended but you may have to tweak the point locations or object rotation in some cases", MessageType.Warning); + if (GUILayout.Button("Disable Legacy Mode")) { + for (int i = 0; i < scripts.Length; i++) { + Undo.RecordObject(scripts[i], "Disable Legacy Mode"); + scripts[i].DisableLegacyMode(); + } + } + } + + if (scripts.Length == 1 && scripts[0].points.Length >= 3) { + var size = scripts[0].GetBounds().size; + if (Mathf.Min(Mathf.Min(Mathf.Abs(size.x), Mathf.Abs(size.y)), Mathf.Abs(size.z)) < 0.05f) { + EditorGUILayout.HelpBox("The bounding box is very thin. Your shape might be oriented incorrectly. The shape will be projected down on the XZ plane in local space. Rotate this object " + + "so that the local XZ plane corresponds to the plane in which you want to create your shape. For example if you want to create your shape in the XY plane then " + + "this object should have the rotation (-90,0,0). You will need to recreate your shape after rotating this object.", MessageType.Warning); + } + } + + if (GUILayout.Button("Edit points")) { + Tools.current = Tool.Move; + } + + if (GUILayout.Button("Clear all points")) { + for (int i = 0; i < scripts.Length; i++) { + Undo.RegisterFullObjectHierarchyUndo(scripts[i], "Clear points"); + scripts[i].points = new Vector3[0]; + scripts[i].RecalcConvex(); + } + } + + if (EditorGUI.EndChangeCheck()) { + for (int i = 0; i < scripts.Length; i++) { + EditorUtility.SetDirty(scripts[i]); + } + + // Repaint the scene view if necessary + if (!Application.isPlaying || EditorApplication.isPaused) SceneView.RepaintAll(); + } + } + + void DrawPointsField () { + EditorGUI.BeginChangeCheck(); + PropertyField("points"); + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + for (int i = 0; i < scripts.Length; i++) { + scripts[i].RecalcConvex(); + } + HandleUtility.Repaint(); + } + } + + void DrawPhysicsField () { + if (PropertyField("updatePhysics", "Update Physics", "Perform similar calculations on the nodes as during scan.\n" + + "Grid Graphs will update the position of the nodes and also check walkability using collision.\nSee online documentation for more info.")) { + EditorGUI.indentLevel++; + PropertyField("resetPenaltyOnPhysics"); + EditorGUI.indentLevel--; + } + } + + void DrawConvexField () { + EditorGUI.BeginChangeCheck(); + PropertyField("convex"); + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + for (int i = 0; i < scripts.Length; i++) { + scripts[i].RecalcConvex(); + } + HandleUtility.Repaint(); + } + } + + void DrawWalkableField () { + if (PropertyField("modifyWalkability")) { + EditorGUI.indentLevel++; + PropertyField("setWalkability", "Walkability Value"); + EditorGUI.indentLevel--; + } + } + + void DrawPenaltyField () { + PropertyField("penaltyDelta", "Penalty Delta"); + + if (!FindProperty("penaltyDelta").hasMultipleDifferentValues && FindProperty("penaltyDelta").intValue < 0) { + EditorGUILayout.HelpBox("Be careful when lowering the penalty. Negative penalties are not supported and will instead underflow and get really high.\n" + + "You can set an initial penalty on graphs (see their settings) and then lower them like this to get regions which are easier to traverse.", MessageType.Warning); + } + } + + void DrawTagField () { + if (PropertyField("modifyTag")) { + EditorGUI.indentLevel++; + PropertyField("setTag"); + + if (GUILayout.Button("Tags can be used to restrict which units can walk on what ground. Click here for more info", "HelpBox")) { + Application.OpenURL(AstarUpdateChecker.GetURL("tags")); + } + EditorGUI.indentLevel--; + } + } + + static void SphereCap (int controlID, Vector3 position, Quaternion rotation, float size) { + Handles.SphereHandleCap(controlID, position, rotation, size, Event.current.type); + } + + public void OnSceneGUI () { + var script = target as GraphUpdateScene; + + // Don't allow editing unless it is the active object + if (Selection.activeGameObject != script.gameObject || script.legacyMode) return; + + // Make sure the points array is not null + if (script.points == null) { + script.points = new Vector3[0]; + EditorUtility.SetDirty(script); + } + + List pointsInWorldSpace = Pathfinding.Pooling.ListPool.Claim(); + pointsInWorldSpace.AddRange(script.points); + + Matrix4x4 invMatrix = script.transform.worldToLocalMatrix; + + Matrix4x4 matrix = script.transform.localToWorldMatrix; + for (int i = 0; i < pointsInWorldSpace.Count; i++) pointsInWorldSpace[i] = matrix.MultiplyPoint3x4(pointsInWorldSpace[i]); + + + float minScreenDist = float.PositiveInfinity; + if (Tools.current != Tool.View && (Event.current.type == EventType.Layout || Event.current.type == EventType.MouseMove)) { + for (int i = 0; i < script.points.Length; i++) { + float dist = HandleUtility.DistanceToLine(pointsInWorldSpace[i], (pointsInWorldSpace[i] + pointsInWorldSpace[(i+1) % script.points.Length])*0.5f); + dist = Mathf.Min(dist, HandleUtility.DistanceToLine(pointsInWorldSpace[i], (pointsInWorldSpace[i] + pointsInWorldSpace[(i-1 + script.points.Length) % script.points.Length])*0.5f)); + HandleUtility.AddControl(-i - 1, dist); + minScreenDist = Mathf.Min(dist, minScreenDist); + } + } + + // If there is a point sort of close to the cursor, but not close enough + // to receive a click event, then prevent the user from accidentally clicking + // which would deselect the current object and be kinda annoying. + // Also always add a default control when shift is pressed, to ensure we don't deselect anything. + // This is particularly important when the user is holding shift to add the first point. + if (Tools.current != Tool.View && (minScreenDist < 50 || Event.current.shift)) + HandleUtility.AddDefaultControl(0); + + for (int i = 0; i < pointsInWorldSpace.Count; i++) { + if (i == selectedPoint && Tools.current == Tool.Move) { + Handles.color = PointSelectedColor; + SphereCap(-i-1, pointsInWorldSpace[i], Quaternion.identity, HandleUtility.GetHandleSize(pointsInWorldSpace[i])*pointGizmosRadius*2); + + Vector3 pre = pointsInWorldSpace[i]; + Vector3 post = Handles.PositionHandle(pointsInWorldSpace[i], Quaternion.identity); + if (pre != post) { + Undo.RegisterFullObjectHierarchyUndo(script, "Moved Point"); + script.points[i] = invMatrix.MultiplyPoint3x4(post); + } + } else { + Handles.color = PointColor; + SphereCap(-i-1, pointsInWorldSpace[i], Quaternion.identity, HandleUtility.GetHandleSize(pointsInWorldSpace[i])*pointGizmosRadius); + } + } + + if (Event.current.type == EventType.MouseDown) { + int pre = selectedPoint; + selectedPoint = -(HandleUtility.nearestControl+1); + if (pre != selectedPoint) GUI.changed = true; + } + + if (Tools.current == Tool.Move) { + var darkSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene); + + Handles.BeginGUI(); + float width = 220; + float height = 76; + float margin = 10; + + AstarPathEditor.LoadStyles(); + GUILayout.BeginArea(new Rect(Camera.current.pixelWidth / EditorGUIUtility.pixelsPerPoint - width, Camera.current.pixelHeight / EditorGUIUtility.pixelsPerPoint - height, width - margin, height - margin), "Shortcuts", AstarPathEditor.astarSkin.FindStyle("SceneBoxDark")); + + GUILayout.Label("Shift+Click: Add new point", darkSkin.label); + GUILayout.Label("Backspace: Delete selected point", darkSkin.label); + + // Invisible button to capture clicks. This prevents a click inside the box from causing some other GameObject to be selected. + GUI.Button(new Rect(0, 0, width - margin, height - margin), "", GUIStyle.none); + GUILayout.EndArea(); + + Handles.EndGUI(); + } + + if (Tools.current == Tool.Move && Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Backspace && selectedPoint >= 0 && selectedPoint < pointsInWorldSpace.Count) { + Undo.RegisterCompleteObjectUndo(script, "Removed Point"); + var arr = new List(script.points); + arr.RemoveAt(selectedPoint); + pointsInWorldSpace.RemoveAt(selectedPoint); + script.points = arr.ToArray(); + GUI.changed = true; + } + + if (Event.current.shift && Tools.current == Tool.Move) { + HandleUtility.Repaint(); + + // Find the closest segment + int insertionIndex = pointsInWorldSpace.Count; + float minDist = float.PositiveInfinity; + for (int i = 0; i < pointsInWorldSpace.Count; i++) { + float dist = HandleUtility.DistanceToLine(pointsInWorldSpace[i], pointsInWorldSpace[(i+1)%pointsInWorldSpace.Count]); + if (dist < minDist) { + insertionIndex = i + 1; + minDist = dist; + } + } + + var ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); + System.Object hit = HandleUtility.RaySnap(ray); + + Vector3 rayhit = Vector3.zero; + bool didHit = false; + if (hit != null) { + rayhit = ((RaycastHit)hit).point; + didHit = true; + } else { + var plane = new Plane(script.transform.up, script.transform.position); + float distance; + plane.Raycast(ray, out distance); + if (distance > 0) { + rayhit = ray.GetPoint(distance); + didHit = true; + } + } + + if (!didHit && SceneView.currentDrawingSceneView.in2DMode) { + didHit = true; + rayhit = ray.origin; + rayhit.z = 0; + } + + if (didHit) { + if (Event.current.type == EventType.MouseDown) { + pointsInWorldSpace.Insert(insertionIndex, rayhit); + // For some reason, using Undo.RecordObject doesn't always work properly here. I guess Unity is bad at diffing arrays? + Undo.RegisterCompleteObjectUndo(script, "Added Point"); + var arr = new List(script.points); + arr.Insert(insertionIndex, invMatrix.MultiplyPoint3x4(rayhit)); + script.points = arr.ToArray(); + GUI.changed = true; + } else { + Handles.color = Color.green; + if (pointsInWorldSpace.Count > 0) { + Handles.DrawDottedLine(pointsInWorldSpace[(insertionIndex-1 + pointsInWorldSpace.Count) % pointsInWorldSpace.Count], rayhit, 8); + Handles.DrawDottedLine(pointsInWorldSpace[insertionIndex % pointsInWorldSpace.Count], rayhit, 8); + } + SphereCap(0, rayhit, Quaternion.identity, HandleUtility.GetHandleSize(rayhit)*pointGizmosRadius); + // Project point down onto a plane + var zeroed = invMatrix.MultiplyPoint3x4(rayhit); + zeroed.y = 0; + Handles.color = new Color(1, 1, 1, 0.5f); + Handles.DrawDottedLine(matrix.MultiplyPoint3x4(zeroed), rayhit, 4); + } + } + + if (Event.current.type == EventType.MouseDown) { + Event.current.Use(); + } + } + + // Make sure the convex hull stays up to date + script.RecalcConvex(); + Pathfinding.Pooling.ListPool.Release(ref pointsInWorldSpace); + + if (GUI.changed) HandleUtility.Repaint(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs.meta new file mode 100644 index 0000000..944f272 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/GraphUpdateSceneEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4c7bf5208cabd4676a4691ce4541bd62 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors.meta new file mode 100644 index 0000000..be5e2d6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: deea50c68b64af2428ff80a68ff6b8b5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs new file mode 100644 index 0000000..5dc52ef --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs @@ -0,0 +1,17 @@ +using Pathfinding; +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(AIDestinationSetter), true)] + [CanEditMultipleObjects] + public class AIDestinationSetterEditor : EditorBase { + protected override void Inspector () { + PropertyField("target"); +#if MODULE_ENTITIES + if ((target as AIDestinationSetter).GetComponent() != null) { + PropertyField("useRotation"); + } +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs.meta new file mode 100644 index 0000000..672094b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/AIDestinationSetterEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59db5c27342b40b5cbe5c5700c306651 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs new file mode 100644 index 0000000..081d668 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs @@ -0,0 +1,379 @@ +#if MODULE_ENTITIES +using UnityEditor; +using UnityEngine; +using Pathfinding.RVO; +using Pathfinding.ECS; +using System.Linq; +using Unity.Entities; +using Pathfinding.ECS.RVO; + +namespace Pathfinding { + [CustomEditor(typeof(FollowerEntity), true)] + [CanEditMultipleObjects] + public class FollowerEntityEditor : EditorBase { + bool debug = false; + bool tagPenaltiesOpen; + bool legend = false; + + static readonly GUIContent[] PositionSyncOptions = new [] { + new GUIContent("Move independently of transform"), + new GUIContent("Move agent with transform") + }; + + static readonly GUIContent[] RotationSyncOptions = new [] { + new GUIContent("Rotate independently of transform"), + new GUIContent("Rotate agent with transform") + }; + + protected override void OnDisable () { + base.OnDisable(); + EditorPrefs.SetBool("FollowerEntity.debug", debug); + EditorPrefs.SetBool("FollowerEntity.tagPenaltiesOpen", tagPenaltiesOpen); + } + + protected override void OnEnable () { + base.OnEnable(); + debug = EditorPrefs.GetBool("FollowerEntity.debug", false); + tagPenaltiesOpen = EditorPrefs.GetBool("FollowerEntity.tagPenaltiesOpen", false); + } + + public override bool RequiresConstantRepaint () { + // When the debug inspector is open we want to update it every frame + // as the agent can move + return debug && Application.isPlaying; + } + + protected void AutoRepathInspector () { + var mode = FindProperty("autoRepathBacking.mode"); + + PropertyField(mode, "Recalculate Paths Automatically"); + if (!mode.hasMultipleDifferentValues) { + var modeValue = (AutoRepathPolicy.Mode)mode.enumValueIndex; + EditorGUI.indentLevel++; + var period = FindProperty("autoRepathBacking.period"); + if (modeValue == AutoRepathPolicy.Mode.EveryNSeconds || modeValue == AutoRepathPolicy.Mode.Dynamic) { + FloatField(period, min: 0f); + } + if (modeValue == AutoRepathPolicy.Mode.Dynamic) { + EditorGUILayout.HelpBox("The path will be recalculated at least every " + period.floatValue.ToString("0.0") + " seconds, but more often if the destination changes quickly", MessageType.None); + } + EditorGUI.indentLevel--; + } + } + + protected void DebugInspector () { + debug = EditorGUILayout.Foldout(debug, "Debug info"); + if (debug) { + EditorGUI.indentLevel++; + DebugInspectorContents(); + EditorGUI.indentLevel--; + } + } + + void DebugInspectorContents () { + EditorGUI.BeginDisabledGroup(true); + + if (!Application.isPlaying) { + EditorGUILayout.HelpBox("Debug info is only available while playing.", MessageType.Info); + return; + } + + if (targets.Length == 1) { + var ai = target as FollowerEntity; + + if (!ai.enabled) { + EditorGUILayout.HelpBox("FollowerEntity is disabled.", MessageType.Info); + return; + } + + // The FollowerEntity could be baked, or have an internal entity + FollowerEntityProxy proxy; + if (ai.entityExists) { + proxy = new FollowerEntityProxy(ai.world, ai.entity); + } else { + // For a baked entity, try to get the entities created from this authoring object + var ls = new Unity.Collections.NativeList(Unity.Collections.Allocator.Temp); + var world = World.DefaultGameObjectInjectionWorld; // Guess the default world + world?.EntityManager.Debug.GetEntitiesForAuthoringObject(ai.gameObject, ls); + + if (ls.Length == 0) { + EditorGUILayout.HelpBox("No entities found for this object. Cannot display debug info.", MessageType.Warning); + return; + } else if (ls.Length != 1) { + EditorGUILayout.HelpBox("Multiple entities found for this object. Cannot display debug info.", MessageType.Warning); + return; + } + + proxy = new FollowerEntityProxy(world, ls[0]); + + if (!proxy.likelyHasReasonableComponents) { + EditorGUILayout.HelpBox("The entity is missing some required components.", MessageType.Warning); + return; + } + } + + EditorGUILayout.Toggle("Reached Destination", proxy.reachedDestination); + EditorGUILayout.Toggle("Reached End Of Path", proxy.reachedEndOfPath); + if (proxy.enableLocalAvoidance) { + EditorGUILayout.Toggle("Reached (maybe crowded) End Of Path", proxy.reachedCrowdedEndOfPath); + } + EditorGUILayout.Toggle("Has Path", proxy.hasPath); + EditorGUILayout.Toggle("Path Pending", proxy.pathPending); + if (proxy.isTraversingOffMeshLink) { + EditorGUILayout.Toggle("Traversing Off-Mesh Link", true); + } + if (proxy.isStopped) { + EditorGUILayout.Toggle("IsStopped (user controlled)", proxy.isStopped); + } + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginChangeCheck(); + var newDestination = EditorGUILayout.Vector3Field("Destination", proxy.destination); + if (EditorGUI.EndChangeCheck() && proxy.entityExists) proxy.SetDestination(newDestination, proxy.destinationFacingDirection); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.LabelField("Remaining Distance", proxy.remainingDistance.ToString("0.00")); + EditorGUILayout.LabelField("Speed", proxy.velocity.magnitude.ToString("0.00")); + } else { + var ls = new Unity.Collections.NativeList(Unity.Collections.Allocator.Temp); + var world = World.DefaultGameObjectInjectionWorld; // Guess the default world + + int nReachedDestination = 0; + int nReachedEndOfPath = 0; + int nReachedCrowdedEndOfPath = 0; + int nPending = 0; + int nBaked = 0; + for (int i = 0; i < targets.Length; i++) { + var ai = targets[i] as FollowerEntity; + + var origCount = ls.Length; + world?.EntityManager.Debug.GetEntitiesForAuthoringObject(targets[i], ls); + if (ls.Length == origCount) { + if (ai.reachedDestination) nReachedDestination++; + if (ai.reachedEndOfPath) nReachedEndOfPath++; + if (ai.reachedCrowdedEndOfPath) nReachedCrowdedEndOfPath++; + if (ai.pathPending) nPending++; + } else { + nBaked++; + } + } + if (nBaked == ls.Length) { + for (int i = 0; i < ls.Length; i++) { + var ai = new FollowerEntityProxy(world, ls[i]); + if (ai.reachedDestination) nReachedDestination++; + if (ai.reachedEndOfPath) nReachedEndOfPath++; + if (ai.reachedCrowdedEndOfPath) nReachedCrowdedEndOfPath++; + if (ai.pathPending) nPending++; + } + } else { + EditorGUILayout.HelpBox("Some authoring script baked multiple entities. Cannot determine which ones are relevant. Cannot display debug info.", MessageType.Warning); + return; + } + EditorGUILayout.LabelField("Reached Destination", nReachedDestination + " of " + targets.Length); + EditorGUILayout.LabelField("Reached End Of Path", nReachedEndOfPath + " of " + targets.Length); + EditorGUILayout.LabelField("Reached (maybe crowded) End Of Path", nReachedCrowdedEndOfPath + " of " + targets.Length); + EditorGUILayout.LabelField("Path Pending", nPending + " of " + targets.Length); + } + EditorGUI.EndDisabledGroup(); + + legend = EditorGUILayout.Foldout(legend, "Debug rendering legend"); + if (legend) { + EditorGUI.indentLevel++; + EditorGUI.BeginDisabledGroup(true); + Section("General"); + EditorGUILayout.ColorField("Destination", Color.blue); + EditorGUILayout.ColorField("Path", JobDrawFollowerGizmos.Path); + + var debugRendering = (Pathfinding.PID.PIDMovement.DebugFlags)FindProperty("movement.debugFlags").intValue; + if ((debugRendering & PID.PIDMovement.DebugFlags.Rotation) != 0) { + Section("Rotation"); + EditorGUILayout.ColorField("Visual Rotation", JobDrawFollowerGizmos.VisualRotationColor); + EditorGUILayout.ColorField("Unsmoothed Rotation", JobDrawFollowerGizmos.UnsmoothedRotation); + EditorGUILayout.ColorField("Internal Rotation", JobDrawFollowerGizmos.InternalRotation); + EditorGUILayout.ColorField("Target Internal Rotation", JobDrawFollowerGizmos.TargetInternalRotation); + EditorGUILayout.ColorField("Target Internal Rotation Hint", JobDrawFollowerGizmos.TargetInternalRotationHint); + } + EditorGUI.EndDisabledGroup(); + EditorGUI.indentLevel--; + } + } + + void PathfindingSettingsInspector () { + bool anyCustomTraversalProvider = this.targets.Any(s => (s as FollowerEntity).pathfindingSettings.traversalProvider != null); + if (anyCustomTraversalProvider) { + EditorGUILayout.HelpBox("Custom traversal provider active", MessageType.None); + } + + PropertyField("managedSettings.pathfindingSettings.graphMask", "Traversable Graphs"); + + tagPenaltiesOpen = EditorGUILayout.Foldout(tagPenaltiesOpen, new GUIContent("Tags", "Settings for each tag")); + if (tagPenaltiesOpen) { + EditorGUI.indentLevel++; + var traversableTags = this.targets.Select(s => (s as FollowerEntity).pathfindingSettings.traversableTags).ToArray(); + SeekerEditor.TagsEditor(FindProperty("managedSettings.pathfindingSettings.tagCostMultipliers"), FindProperty("managedSettings.pathfindingSettings.tagEntryCosts"), traversableTags); + for (int i = 0; i < targets.Length; i++) { + (targets[i] as FollowerEntity).pathfindingSettings.traversableTags = traversableTags[i]; + } + EditorGUI.indentLevel--; + } + } + + protected override void Inspector () { + Undo.RecordObjects(targets, "Modify FollowerEntity settings"); + EditorGUI.BeginChangeCheck(); + Section("Shape"); + FloatField("shape.radius", min: 0.01f); + FloatField("shape.height", min: 0.01f); + Popup("orientationBacking", new [] { new GUIContent("ZAxisForward (for 3D games)"), new GUIContent("YAxisForward (for 2D games)") }, "Orientation"); + var orientationProperty = FindProperty("orientationBacking"); + bool is2D = (OrientationMode)orientationProperty.enumValueIndex == OrientationMode.YAxisForward; + + Section("Movement"); + FloatField("movement.follower.speed", min: 0f); + FloatField("movement.follower.rotationSpeed", min: 0f); + var maxRotationSpeed = FindProperty("movement.follower.rotationSpeed"); + FloatField("movement.follower.maxRotationSpeed", min: maxRotationSpeed.hasMultipleDifferentValues ? 0f : maxRotationSpeed.floatValue); + if (ByteAsToggle("movement.follower.allowRotatingOnSpotBacking", "Allow Rotating On The Spot")) { + EditorGUI.indentLevel++; + FloatField("movement.follower.maxOnSpotRotationSpeed", min: 0f); + FloatField("movement.follower.slowdownTimeWhenTurningOnSpot", min: 0f); + EditorGUI.indentLevel--; + } + Slider("movement.positionSmoothing", left: 0f, right: 0.5f); + Slider("movement.rotationSmoothing", left: 0f, right: 0.5f); + FloatField("movement.follower.slowdownTime", min: 0f); + FloatField("movement.stopDistance", min: 0f); + FloatField("movement.follower.leadInRadiusWhenApproachingDestination", min: 0f); + FloatField("movement.follower.desiredWallDistance", min: 0f); + + if (!is2D && PropertyField("enableGravityBacking", "Gravity")) { + EditorGUI.indentLevel++; + PropertyField("movement.groundMask", "Raycast Ground Mask"); + EditorGUI.indentLevel--; + } + var movementPlaneSource = FindProperty("movementPlaneSourceBacking"); + PropertyField(movementPlaneSource, "Movement Plane Source"); + if (AstarPath.active != null && AstarPath.active.data.graphs != null) { + var possiblySpherical = AstarPath.active.data.navmeshGraph != null && !AstarPath.active.data.navmeshGraph.RecalculateNormals; + if (!possiblySpherical && !movementPlaneSource.hasMultipleDifferentValues && (MovementPlaneSource)movementPlaneSource.intValue == MovementPlaneSource.Raycast) { + EditorGUILayout.HelpBox("Using raycasts as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead.", MessageType.Info); + } + if (!possiblySpherical && !movementPlaneSource.hasMultipleDifferentValues && (MovementPlaneSource)movementPlaneSource.intValue == MovementPlaneSource.NavmeshNormal) { + EditorGUILayout.HelpBox("Using the navmesh normal as the movement plane source is only recommended if you have a spherical or otherwise non-planar world. It has a performance overhead.", MessageType.Info); + } + } + + if ((target as MonoBehaviour).gameObject.scene.isSubScene) { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Popup(new GUIContent("Position Sync", "Position sync cannot be changed when the FollowerEntity is in a sub scene"), 0, PositionSyncOptions); + EditorGUILayout.Popup(new GUIContent("Rotation Sync", "Rotation sync cannot be changed when the FollowerEntity is in a sub scene"), 0, RotationSyncOptions); + EditorGUI.EndDisabledGroup(); + } else { + this.Popup("syncPosition", PositionSyncOptions, "Position Sync"); + this.Popup("syncRotation", RotationSyncOptions, "Rotation Sync"); + } + + Section("Pathfinding"); + PathfindingSettingsInspector(); + AutoRepathInspector(); + + + if (SectionEnableable("Local Avoidance", "enableLocalAvoidanceBacking")) { + if (Application.isPlaying && RVOSimulator.active == null && !EditorUtility.IsPersistent(target)) { + EditorGUILayout.HelpBox("There is no enabled RVOSimulator component in the scene. A single global RVOSimulator component is required for local avoidance.", MessageType.Warning); + } + if (targets.Length == 1) { + var ai = target as FollowerEntity; + if (ai.localAvoidanceTemporarilyDisabled) { + EditorGUILayout.HelpBox("Local avoidance is temporarily disabled while traversing an off-mesh link.", MessageType.Warning); + } + } + FloatField("rvoSettingsBacking.agentTimeHorizon", min: 0f, max: 20.0f); + FloatField("rvoSettingsBacking.obstacleTimeHorizon", min: 0f, max: 20.0f); + PropertyField("rvoSettingsBacking.maxNeighbours"); + ClampInt("rvoSettingsBacking.maxNeighbours", min: 0, max: SimulatorBurst.MaxNeighbourCount); + PropertyField("rvoSettingsBacking.layer"); + PropertyField("rvoSettingsBacking.collidesWith"); + Slider("rvoSettingsBacking.priority", left: 0f, right: 1.0f); + PropertyField("rvoSettingsBacking.locked"); + + if (targets.Length == 1) { + var ai = target as FollowerEntity; + var simulator = RVOSimulator.active?.GetSimulator(); + if (simulator != null && ai.entityExists && World.DefaultGameObjectInjectionWorld.EntityManager.HasComponent(ai.entity)) { + var agentIndex = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData(ai.entity); + simulator.BlockUntilSimulationStepDone(); + if (agentIndex.TryGetIndex(ref simulator.simulationData, out var index)) { + if (simulator.outputData.numNeighbours[index] >= simulator.simulationData.maxNeighbours[index]) { + EditorGUILayout.HelpBox("Limit of how many neighbours to consider (Max Neighbours) has been reached. Some nearby agents may have been ignored. " + + "To ensure all agents are taken into account you can raise the 'Max Neighbours' value at a cost to performance.", MessageType.Warning); + } + } + } + } + } + + Section("Debug"); + PropertyField("movement.debugFlags", "Movement Debug Rendering"); + PropertyField("rvoSettingsBacking.debug", "Local Avoidance Debug Rendering"); + DebugInspector(); + + if (EditorGUI.EndChangeCheck()) { + for (int i = 0; i < targets.Length; i++) { + var script = targets[i] as FollowerEntity; + script.SyncWithEntity(); + } + } + } + + public void OnSceneGUI () { + if (EditorApplication.isPaused) { + var script = target as FollowerEntity; + if (script.position != script.transform.position && script.updatePosition) { + // Force sync the position with the entity. + // The user may have moved the entity in the scene view. + // If the game is paused, we still want the entity to be in the correct position immediately, + // in order to draw gizmos correctly. + script.position = script.transform.position; + } + } + } + } +} +#else +using UnityEditor; +using UnityEngine; +using Pathfinding.ECS; +using UnityEditor.PackageManager; +using UnityEditor.PackageManager.Requests; + +namespace Pathfinding { + // This inspector is only used if the Entities package is not installed + [CustomEditor(typeof(FollowerEntity), true)] + [CanEditMultipleObjects] + public class FollowerEntityEditor : EditorBase { + static AddRequest addRequest; + + protected override void Inspector () { + if (addRequest != null) { + if (addRequest.Status == StatusCode.Success) { + addRequest = null; + + // If we get this far, unity did not successfully reload the assemblies. + // Who knows what went wrong. Quite possibly restarting Unity will resolve the issue. + EditorUtility.DisplayDialog("Installed Entities package", "The entities package has been installed. You may have to restart the editor for changes to take effect.", "Ok"); + } else if (addRequest.Status == StatusCode.Failure) { + EditorGUILayout.HelpBox("Failed to install the Entities package. Please install it manually using the Package Manager." + (addRequest.Error != null ? "\n" + addRequest.Error.message : ""), MessageType.Error); + } else { + EditorGUILayout.HelpBox("Installing entities package...", MessageType.None); + } + } else { + EditorGUILayout.HelpBox("This component requires the Entities package (1.1.0+) to be installed. Please install it using the Package Manager.", MessageType.Error); + if (GUILayout.Button("Install entities package")) { + addRequest = Client.Add("com.unity.entities"); + } + } + } + } +} +#endif diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs.meta new file mode 100644 index 0000000..9375e37 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/FollowerEntityEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 058264d1741a8df4bb39dd131d7fd2f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs new file mode 100644 index 0000000..13a5a8c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs @@ -0,0 +1,29 @@ +using Pathfinding; +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + [CustomEditor(typeof(NodeLink2), true)] + [CanEditMultipleObjects] + public class NodeLink2Editor : EditorBase { + GUIContent HandlerContent = new GUIContent("Handler", "The object that handles movement when traversing the link"); + + protected override void Inspector () { + base.Inspector(); + + var target = this.target as NodeLink2; + if (target.onTraverseOffMeshLink != null) { + var name = target.onTraverseOffMeshLink.name; + if (name == null || name == "") name = target.onTraverseOffMeshLink.GetType().Name; + else name += " → " + target.onTraverseOffMeshLink.GetType().Name; + if (target.onTraverseOffMeshLink is UnityEngine.Component) { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.ObjectField(HandlerContent, target.onTraverseOffMeshLink as UnityEngine.Object, typeof(UnityEngine.Component), true); + EditorGUI.EndDisabledGroup(); + } else { + EditorGUILayout.LabelField(HandlerContent, name); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs.meta new file mode 100644 index 0000000..d0aea6e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/Inspectors/NodeLink2Editor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d111ee9f291ca0b4aa6c075a119df78a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs new file mode 100644 index 0000000..26484c3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs @@ -0,0 +1,15 @@ +using UnityEditor; +using UnityEngine; +using Pathfinding; + +namespace Pathfinding.Examples { + [CustomEditor(typeof(LocalSpaceRichAI), true)] + [CanEditMultipleObjects] + public class LocalSpaceRichAIEditor : BaseAIEditor { + protected override void Inspector () { + Section("Local Movement"); + PropertyField("graph"); + base.Inspector(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs.meta new file mode 100644 index 0000000..31d8b18 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/LocalSpaceRichAIEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2bf40a141e29dc484a3219b4121ab13f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors.meta new file mode 100644 index 0000000..7c00efc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f756ea48002c49fda0cdfc09b7da69b diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs new file mode 100644 index 0000000..88e1798 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs @@ -0,0 +1,16 @@ +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(FunnelModifier))] + [CanEditMultipleObjects] + public class FunnelModifierEditor : EditorBase { + protected override void Inspector () { + Section("Settings for navmeshes"); + PropertyField("quality"); + PropertyField("splitAtEveryPortal"); + + Section("Settings for grids"); + PropertyField("accountForGridPenalties"); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs.meta new file mode 100644 index 0000000..ddc0b33 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/FunnelModifierEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e6d301cc4df3beab9b5c57be031e267 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs new file mode 100644 index 0000000..3a1f678 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs @@ -0,0 +1,38 @@ +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(RaycastModifier))] + [CanEditMultipleObjects] + public class RaycastModifierEditor : EditorBase { + protected override void Inspector () { + PropertyField("quality"); + + if (PropertyField("useRaycasting", "Use Physics Raycasting")) { + EditorGUI.indentLevel++; + + PropertyField("use2DPhysics"); + if (PropertyField("thickRaycast")) { + EditorGUI.indentLevel++; + FloatField("thickRaycastRadius", min: 0f); + EditorGUI.indentLevel--; + } + + PropertyField("raycastOffset"); + PropertyField("mask", "Layer Mask"); + EditorGUI.indentLevel--; + } + + PropertyField("useGraphRaycasting"); + if (!FindProperty("useGraphRaycasting").boolValue && !FindProperty("useRaycasting").boolValue) { + EditorGUILayout.HelpBox("You should use either raycasting, graph raycasting or both, otherwise this modifier will not do anything", MessageType.Warning); + } + + if (FindProperty("useGraphRaycasting").boolValue && !FindProperty("useRaycasting").boolValue) { + AstarPath.FindAstarPath(); + if (AstarPath.active != null && AstarPath.active.data.gridGraph != null) { + EditorGUILayout.HelpBox("For grid graphs, when using only graph raycasting the funnel modifier has superceded this modifier. Try using that instead. It's faster and more accurate.", MessageType.Warning); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs.meta new file mode 100644 index 0000000..c985a7b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/RaycastModifierEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ec73ac2d3bd6342faace3242ff3e6f79 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs new file mode 100644 index 0000000..0059533 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs @@ -0,0 +1,45 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(SimpleSmoothModifier))] + [CanEditMultipleObjects] + public class SmoothModifierEditor : EditorBase { + protected override void Inspector () { + var smoothType = FindProperty("smoothType"); + + PropertyField("smoothType"); + + if (!smoothType.hasMultipleDifferentValues) { + switch ((SimpleSmoothModifier.SmoothType)smoothType.enumValueIndex) { + case SimpleSmoothModifier.SmoothType.Simple: + if (PropertyField("uniformLength")) { + FloatField("maxSegmentLength", min: 0.005f); + } else { + IntSlider("subdivisions", 0, 6); + } + + PropertyField("iterations"); + ClampInt("iterations", 0); + + PropertyField("strength"); + break; + case SimpleSmoothModifier.SmoothType.OffsetSimple: + PropertyField("iterations"); + ClampInt("iterations", 0); + + FloatField("offset", min: 0f); + break; + case SimpleSmoothModifier.SmoothType.Bezier: + IntSlider("subdivisions", 0, 6); + PropertyField("bezierTangentLength"); + break; + case SimpleSmoothModifier.SmoothType.CurvedNonuniform: + FloatField("maxSegmentLength", min: 0.005f); + PropertyField("factor"); + break; + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs.meta new file mode 100644 index 0000000..57d277a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ModifierEditors/SmoothModifierEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1b6748a8c87024ddd9cc5f3c5e19ab60 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs new file mode 100644 index 0000000..ef2b625 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs @@ -0,0 +1,57 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(NavmeshAdd))] + [CanEditMultipleObjects] + public class NavmeshAddEditor : EditorBase { + protected override void Inspector () { + EditorGUI.BeginChangeCheck(); + var type = FindProperty("type"); + PropertyField("type", "Shape"); + EditorGUI.indentLevel++; + + if (!type.hasMultipleDifferentValues) { + switch ((NavmeshAdd.MeshType)type.intValue) { + case NavmeshAdd.MeshType.Rectangle: + PropertyField("rectangleSize"); + break; + case NavmeshAdd.MeshType.CustomMesh: + PropertyField("mesh"); + PropertyField("meshScale"); + break; + } + } + + PropertyField("center"); + EditorGUI.indentLevel--; + + EditorGUILayout.Separator(); + PropertyField("updateDistance"); + if (PropertyField("useRotationAndScale")) { + EditorGUI.indentLevel++; + FloatField("updateRotationDistance", min: 0f, max: 180f); + EditorGUI.indentLevel--; + } + + EditorGUI.BeginChangeCheck(); + PropertyField("graphMask", "Traversable Graphs"); + bool changedMask = EditorGUI.EndChangeCheck(); + + serializedObject.ApplyModifiedProperties(); + + if (EditorGUI.EndChangeCheck()) { + foreach (NavmeshAdd tg in targets) { + tg.RebuildMesh(); + tg.ForceUpdate(); + // If the mask is changed we disable and then enable the component + // to make sure it is removed from the right graphs and then added back + if (changedMask && tg.enabled) { + tg.enabled = false; + tg.enabled = true; + } + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs.meta new file mode 100644 index 0000000..530f383 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshAddEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 551f2013671c4d8db91cf1d1afbde58b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs new file mode 100644 index 0000000..e3e2157 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs @@ -0,0 +1,106 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(NavmeshCut))] + [CanEditMultipleObjects] + public class NavmeshCutEditor : EditorBase { + GUIContent[] MeshTypeOptions = new [] { + new GUIContent("Rectangle (legacy)"), + new GUIContent("Circle (legacy)"), + new GUIContent("Custom Mesh"), + new GUIContent("Box"), + new GUIContent("Sphere"), + new GUIContent("Capsule"), + }; + + protected override void Inspector () { + // Make sure graphs are deserialized. + // The gizmos on the navmesh cut uses the graph information to visualize the character radius + AstarPath.FindAstarPath(); + + EditorGUI.BeginChangeCheck(); + var type = FindProperty("type"); + var circleResolution = FindProperty("circleResolution"); + Popup("type", MeshTypeOptions, label: "Shape"); + EditorGUI.indentLevel++; + + if (!type.hasMultipleDifferentValues) { + switch ((NavmeshCut.MeshType)type.intValue) { + case NavmeshCut.MeshType.Circle: + case NavmeshCut.MeshType.Capsule: + FloatField("circleRadius", "Radius", min: 0.01f); + PropertyField("circleResolution", "Resolution"); + FloatField("height", min: 0f); + + if (circleResolution.intValue >= 20) { + EditorGUILayout.HelpBox("Be careful with large resolutions. It is often better with a relatively low resolution since it generates cleaner navmeshes with fewer nodes.", MessageType.Warning); + } + break; + case NavmeshCut.MeshType.Sphere: + FloatField("circleRadius", "Radius", min: 0.01f); + PropertyField("circleResolution", "Resolution"); + + if (circleResolution.intValue >= 20) { + EditorGUILayout.HelpBox("Be careful with large resolutions. It is often better with a relatively low resolution since it generates cleaner navmeshes with fewer nodes.", MessageType.Warning); + } + break; + case NavmeshCut.MeshType.Rectangle: + PropertyField("rectangleSize"); + FloatField("height", min: 0f); + break; + case NavmeshCut.MeshType.Box: + PropertyField("rectangleSize.x", "Width"); + PropertyField("height", "Height"); + PropertyField("rectangleSize.y", "Depth"); + break; + case NavmeshCut.MeshType.CustomMesh: + PropertyField("mesh"); + PropertyField("meshScale"); + FloatField("height", min: 0f); + EditorGUILayout.HelpBox("This mesh should be a planar surface. Take a look at the documentation for an example.", MessageType.Info); + break; + } + } + + PropertyField("center"); + EditorGUI.indentLevel--; + + EditorGUILayout.Separator(); + PropertyField("updateDistance"); + if (PropertyField("useRotationAndScale")) { + EditorGUI.indentLevel++; + FloatField("updateRotationDistance", min: 0f, max: 180f); + EditorGUI.indentLevel--; + } + + PropertyField("isDual"); + PropertyField("cutsAddedGeom", "Cuts Added Geometry"); + PropertyField("radiusExpansionMode", "Radius Expansion"); + + EditorGUI.BeginChangeCheck(); + PropertyField("graphMask", "Affected Graphs"); + bool changedMask = EditorGUI.EndChangeCheck(); + + serializedObject.ApplyModifiedProperties(); + + if (EditorGUI.EndChangeCheck()) { + foreach (NavmeshCut tg in targets) { + tg.ForceUpdate(); + // If the mask is changed we disable and then enable the component + // to make sure it is removed from the right graphs and then added back + if (changedMask && tg.enabled) { + tg.enabled = false; + tg.enabled = true; + } + } + } + +#if !UNITY_2022_3_OR_NEWER + EditorGUILayout.HelpBox("The NavmeshCut component requires Unity 2022.3 or newer to work, due to Unity bugs in earlier versions. Please update Unity to 2022.3.21 or later, if you want to use navmesh cutting.", MessageType.Error); +#elif !MODULE_COLLECTIONS_2_2_0_OR_NEWER + EditorGUILayout.HelpBox("The NavmeshCut component requires the com.unity.collections package version 2.2.0 or newer. Please install it using the Package Manager.", MessageType.Error); +#endif + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs.meta new file mode 100644 index 0000000..a26bfce --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshCutEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3b9f4072976bf44aea89e602ff53292f +timeCreated: 1459017463 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs new file mode 100644 index 0000000..ff3f92a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs @@ -0,0 +1,168 @@ +using UnityEditor; +using UnityEngine; +using System.Linq; + +namespace Pathfinding { + using Pathfinding.Sync; + + /// Editor for the component + [CustomEditor(typeof(NavmeshPrefab), true)] + [CanEditMultipleObjects] + public class NavmeshPrefabEditor : EditorBase { + protected override void OnEnable () { + base.OnEnable(); + AstarPathEditor.LoadStyles(); + EditorApplication.update += OnUpdate; + } + + protected override void OnDisable () { + base.OnDisable(); + EditorApplication.update -= OnUpdate; + } + + void OnUpdate () { + } + + static int pendingScanProgressId; + + static int PendingScan (Promise[] pendingScanProgress, NavmeshPrefab[] pendingScanTargets) { + var progressId = UnityEditor.Progress.Start("Scanning Navmesh Prefab", "Scanning Navmesh Prefab", UnityEditor.Progress.Options.None, -1); + EditorApplication.CallbackFunction cb = null; + cb = () => { + if (UnityEditor.Progress.Exists(progressId)) { + bool allDone = true; + var avg = 0.0f; + for (int i = 0; i < pendingScanProgress.Length; i++) { + if (pendingScanTargets[i] == null) { + avg += 1.0f; + } else { + avg += pendingScanProgress[i].Progress; + + if (pendingScanProgress[i].IsCompleted) { + var data = pendingScanProgress[i].Complete().data; + // Data can be null if some exception has been thrown during the scan + if (data != null) { + pendingScanTargets[i].SaveToFile(data); + } + pendingScanProgress[i].Dispose(); + pendingScanTargets[i] = null; + pendingScanProgress[i] = default; + } else { + allDone = false; + } + } + } + avg /= pendingScanProgress.Length; + UnityEditor.Progress.Report(progressId, avg); + + if (allDone) { + UnityEditor.Progress.Finish(progressId); + } + } else { + EditorApplication.update -= cb; + } + }; + EditorApplication.update += cb; + return progressId; + } + + protected override void Inspector () { + AstarPath.FindAstarPath(); + Section("Shape"); + BoundsField("bounds"); + + bool needsRounding = false; + RecastGraph graph = null; + if (AstarPath.active != null && AstarPath.active.data.recastGraph != null) { + graph = AstarPath.active.data.recastGraph; + } + bool isPrefab = EditorUtility.IsPersistent(this.target); + + if (graph != null && !isPrefab) { + if (!graph.useTiles) { + EditorGUILayout.HelpBox("The recast graph in the scene doesn't use tiles. It needs to use tiles to be used with this component.", MessageType.Warning); + if (GUILayout.Button("Enable tiling on recast graph")) { + graph.useTiles = true; + } + } + + var roundedTiles = this.targets.Cast().Select(target => { + var navmeshPrefab = target as NavmeshPrefab; + var bounds = navmeshPrefab.bounds; + var desiredBounds = NavmeshPrefab.SnapSizeToClosestTileMultiple(graph, bounds); + return new { + needsRounding = !Mathf.Approximately(bounds.extents.x, desiredBounds.extents.x) || !Mathf.Approximately(bounds.extents.z, desiredBounds.extents.z), + desiredBounds = desiredBounds + }; + }).ToArray(); + needsRounding = roundedTiles.Any(x => x.needsRounding); + + if (needsRounding) { + EditorGUILayout.HelpBox("Bounds size is not a multiple of the recast graph's tile size (" + (graph.editorTileSize * graph.cellSize).ToString("0.0") + ").\nThe tile size is voxel size * tile size (voxels) (set in recast graph settings)", MessageType.Warning); + if (GUILayout.Button("Round to nearest multiple")) { + UnityEditor.Undo.RecordObjects(targets, "Snap to nearest tile multiple"); + for (int i = 0; i < targets.Length; i++) { + (targets[i] as NavmeshPrefab).bounds = roundedTiles[i].desiredBounds; + EditorUtility.SetDirty(targets[i]); + } + } + } + + if (GUILayout.Button("Snap position to nearest tile")) { + for (int i = 0; i < targets.Length; i++) { + var navmeshPrefab = targets[i] as NavmeshPrefab; + navmeshPrefab.SnapToClosestTileAlignment(); + } + } + } + Section("Settings"); + PropertyField("applyOnStart"); + PropertyField("removeTilesWhenDisabled"); + Section("Serialized Data"); + EditorGUILayout.BeginHorizontal(); + PropertyField("serializedNavmesh"); + +#if UNITY_2021_3_OR_NEWER + EditorGUI.showMixedValue = targets.Length > 1; + var target = this.target as NavmeshPrefab; + GUILayout.Label(EditorUtility.FormatBytes(target.serializedNavmesh != null ? target.serializedNavmesh.dataSize : 0), GUILayout.ExpandWidth(false)); + EditorGUI.showMixedValue = false; +#endif + EditorGUILayout.EndHorizontal(); + + if (UnityEditor.Progress.Exists(pendingScanProgressId)) { + var r = EditorGUILayout.GetControlRect(); + EditorGUI.ProgressBar(r, UnityEditor.Progress.GetProgress(pendingScanProgressId), "Scanning..."); + Repaint(); + } else { + EditorGUI.BeginDisabledGroup(needsRounding || isPrefab || graph == null); + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Scan & Save")) { + var pendingScanProgress = new Promise[targets.Length]; + var pendingScanTargets = new NavmeshPrefab[targets.Length]; + for (int i = 0; i < targets.Length; i++) { + var navmeshPrefab = targets[i] as NavmeshPrefab; + pendingScanProgress[i] = navmeshPrefab.ScanAsync(graph); + // this.pendingScanProgress[i].Complete(); + pendingScanTargets[i] = navmeshPrefab; + } + + pendingScanProgressId = PendingScan(pendingScanProgress, pendingScanTargets); + } + EditorGUI.EndDisabledGroup(); + EditorGUI.BeginDisabledGroup(isPrefab || graph == null); + if (GUILayout.Button("Edit graph", GUILayout.ExpandWidth(false))) { + Selection.activeGameObject = AstarPath.active.gameObject; + AstarPath.active.showGraphs = true; + } + EditorGUI.EndDisabledGroup(); + EditorGUILayout.EndHorizontal(); + if (isPrefab) { + EditorGUILayout.HelpBox("Open the prefab or add it to a scene to scan it.", MessageType.Info); + } else if (graph == null) { + EditorGUILayout.HelpBox("No recast graph was found in the scene. Add one if you want to scan this navmesh prefab.", MessageType.Info); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs.meta new file mode 100644 index 0000000..26c484a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/NavmeshPrefabEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cfd3f05fe0624efab757dfc144f5928 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs new file mode 100644 index 0000000..d993fca --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs @@ -0,0 +1,13 @@ +using System.Diagnostics; +using UnityEditor; + +namespace Pathfinding { + [FilePath("ProjectSettings/com.arongranberg.astar/settings.asset", FilePathAttribute.Location.ProjectFolder)] + internal class PathfindingEditorSettings : ScriptableSingleton { + public bool hasShownWelcomeScreen = false; + + public void Save () { + Save(true); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs.meta new file mode 100644 index 0000000..7edc8a6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingEditorSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21f007d651ef4ab42b597f1da9ad75cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs new file mode 100644 index 0000000..6b52bf3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs @@ -0,0 +1,11 @@ +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + [CustomPropertyDrawer(typeof(PathfindingTag))] + public class PathfindingTagDrawer : PropertyDrawer { + public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + Pathfinding.Util.EditorGUILayoutHelper.TagField(position, label, property, AstarPathEditor.EditTags); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs.meta new file mode 100644 index 0000000..bc094fc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/PathfindingTagDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 276fb1a9d0bb895e8af74a9cc9908a5f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs new file mode 100644 index 0000000..4d125ef --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs @@ -0,0 +1,47 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +namespace Pathfinding { + [CustomEditor(typeof(ProceduralGraphMover))] + [CanEditMultipleObjects] + public class ProceduralGridMoverEditor : EditorBase { + GUIContent[] graphLabels = new GUIContent[32]; + + protected override void Inspector () { + // Make sure the AstarPath object is initialized and the graphs are loaded, this is required to be able to show graph names in the mask popup + AstarPath.FindAstarPath(); + + for (int i = 0; i < graphLabels.Length; i++) { + if (AstarPath.active == null || AstarPath.active.data.graphs == null || i >= AstarPath.active.data.graphs.Length || AstarPath.active.data.graphs[i] == null) { + graphLabels[i] = new GUIContent("Graph " + i + (i == 31 ? "+" : "")); + } else { + graphLabels[i] = new GUIContent(AstarPath.active.data.graphs[i].name + " (graph " + i + ")"); + } + } + + Popup("graphIndex", graphLabels, "Graph"); + PropertyField("target"); + + // Only show the update distance field if the graph is a grid graph, or if we are not sure which graph type it is + var graphIndexProp = FindProperty("graphIndex"); + bool showField = true; + if (!graphIndexProp.hasMultipleDifferentValues && AstarPath.active != null) { + var graphIndex = graphIndexProp.intValue; + if (graphIndex >= 0 && graphIndex < AstarPath.active.data.graphs.Length) { + var graph = AstarPath.active.data.graphs[graphIndex]; + if (graph is GridGraph) { + // NOOP + } else if (graph is RecastGraph) { + showField = false; + } else { + EditorGUILayout.HelpBox("The selected graph is not a grid, layered grid or recast graph", MessageType.Warning); + } + } + } + if (showField) { + PropertyField("updateDistance"); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs.meta new file mode 100644 index 0000000..4d81ba1 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/ProceduralGridMoverEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b50f1b778f5176f18b2305552cc4639c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs new file mode 100644 index 0000000..1a79c29 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs @@ -0,0 +1,67 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding.RVO { + [CustomEditor(typeof(RVOController))] + [CanEditMultipleObjects] + public class RVOControllerEditor : EditorBase { + protected override void Inspector () { + Section("Shape"); + var ai = (target as MonoBehaviour).GetComponent(); + if (ai != null) { + var drivenStr = "Driven by " + ai.GetType().Name + " component"; + EditorGUILayout.LabelField("Radius", drivenStr); + if ((target as RVOController).movementPlaneMode == MovementPlane.XZ) { + EditorGUILayout.LabelField("Height", drivenStr); + EditorGUILayout.LabelField("Center", drivenStr); + } + } else { + FloatField("radiusBackingField", label: "Radius", min: 0.01f); + + if ((target as RVOController).movementPlaneMode == MovementPlane.XZ) { + FloatField("heightBackingField", label: "Height", min: 0.01f); + PropertyField("centerBackingField", label: "Center"); + } + } + + Section("Avoidance"); + FloatField("agentTimeHorizon", min: 0f); + FloatField("obstacleTimeHorizon", min: 0f); + PropertyField("maxNeighbours"); + PropertyField("layer"); + PropertyField("collidesWith"); + PropertyField("priority"); + var rvoController = target as RVOController; + if (!Mathf.Approximately(rvoController.priorityMultiplier, 1.0f)) { + EditorGUILayout.HelpBox("Another script is applying an additional multiplier to the priority of " + rvoController.priorityMultiplier.ToString("0.00"), MessageType.None); + } + + if (rvoController.flowFollowingStrength > 0.01f) { + EditorGUILayout.HelpBox("Another script is adding flow following behavior. Strength: " + (rvoController.flowFollowingStrength*100).ToString("0") + "%", MessageType.None); + } + + EditorGUILayout.Separator(); + EditorGUI.BeginDisabledGroup(PropertyField("lockWhenNotMoving")); + PropertyField("locked"); + EditorGUI.EndDisabledGroup(); + EditorGUILayout.Separator(); + PropertyField("debug"); + + bool maxNeighboursLimit = false; + + for (int i = 0; i < targets.Length; i++) { + var controller = targets[i] as RVOController; + maxNeighboursLimit |= controller.rvoAgent != null && controller.rvoAgent.NeighbourCount >= controller.rvoAgent.MaxNeighbours; + } + + if (maxNeighboursLimit) { + EditorGUILayout.HelpBox("Limit of how many neighbours to consider (Max Neighbours) has been reached. Some nearby agents may have been ignored. " + + "To ensure all agents are taken into account you can raise the 'Max Neighbours' value at a cost to performance.", MessageType.Warning); + } + + if (RVOSimulator.active == null && !EditorUtility.IsPersistent(target)) { + EditorGUILayout.HelpBox("There is no enabled RVOSimulator component in the scene. A single RVOSimulator component is required for local avoidance.", MessageType.Warning); + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs.meta new file mode 100644 index 0000000..6f96fe8 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOControllerEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9af3f5ba99e534f21bb7566d10310a0c +timeCreated: 1467044398 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs new file mode 100644 index 0000000..0f1fb06 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs @@ -0,0 +1,11 @@ +using UnityEditor; +using Pathfinding.RVO; + +namespace Pathfinding { + [CustomEditor(typeof(RVONavmesh))] + public class RVONavmeshEditor : EditorBase { + protected override void Inspector () { + EditorGUILayout.HelpBox("This component is deprecated. The RVOSimulator now has an option to take the navmesh into account automatically.", MessageType.Warning); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs.meta new file mode 100644 index 0000000..f8312a0 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVONavmeshEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4eff877e8158f41ae99f5cf271cb7646 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs new file mode 100644 index 0000000..115f9ec --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs @@ -0,0 +1,21 @@ +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + [CustomEditor(typeof(Pathfinding.RVO.RVOSimulator))] + public class RVOSimulatorEditor : EditorBase { + static readonly GUIContent[] movementPlaneOptions = new [] { new GUIContent("XZ (for 3D games)"), new GUIContent("XY (for 2D games)"), new GUIContent("Arbitrary (for non-planar worlds)") }; + + protected override void Inspector () { + Section("Movement"); + Popup("movementPlane", movementPlaneOptions); + + PropertyField("symmetryBreakingBias"); + PropertyField("hardCollisions"); + PropertyField("useNavmeshAsObstacle"); + + Section("Debugging"); + PropertyField("drawQuadtree"); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs.meta new file mode 100644 index 0000000..d6620d4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSimulatorEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5d2e069b218f84e61b4ba29c4c958613 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs new file mode 100644 index 0000000..77f0c11 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs @@ -0,0 +1,12 @@ +using UnityEditor; +using Pathfinding.RVO; + +namespace Pathfinding { + [CustomEditor(typeof(RVOSquareObstacle))] + [CanEditMultipleObjects] + public class RVOSquareObstacleEditor : EditorBase { + protected override void Inspector () { + EditorGUILayout.HelpBox("This component is deprecated. Local avoidance colliders never worked particularly well and the performance was poor. Modify the graphs instead so that pathfinding takes obstacles into account.", MessageType.Warning); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs.meta new file mode 100644 index 0000000..0c70199 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RVOSquareObstacleEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 426beb6f8d635447fadad7ee0a592fe0 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs new file mode 100644 index 0000000..14dc7ee --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs @@ -0,0 +1,3 @@ +// This file has been removed from the package. Since UnityPackages cannot +// delete files, only replace them, this message is left here to prevent old +// files from causing compiler errors. diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs.meta new file mode 100644 index 0000000..774e965 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastMeshObjEditor.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7c4297fc55c734c9bad31126dc81e49b +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs new file mode 100644 index 0000000..197e0ce --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs @@ -0,0 +1,129 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding { + [CustomEditor(typeof(RecastNavmeshModifier))] + [CanEditMultipleObjects] + public class RecastNavmeshModifierEditor : EditorBase { + protected override void Inspector () { + var modeProp = FindProperty("mode"); + var areaProp = FindProperty("surfaceID"); + var geometrySource = FindProperty("geometrySource"); + var includeInScan = FindProperty("includeInScan"); + var script = target as RecastNavmeshModifier; + + if (areaProp.intValue < 0) { + areaProp.intValue = 0; + } + + PropertyField(includeInScan); + if (!includeInScan.hasMultipleDifferentValues && script.includeInScan == RecastNavmeshModifier.ScanInclusion.AlwaysExclude) { + EditorGUILayout.HelpBox("This object will be completely ignored by the graph. Even if it would otherwise be included due to its layer or tag.", MessageType.None); + return; + } + + PropertyField(geometrySource, "Geometry Source"); + if (!geometrySource.hasMultipleDifferentValues) { + var geometrySourceValue = (RecastNavmeshModifier.GeometrySource)geometrySource.intValue; + script.ResolveMeshSource(out var filter, out var coll, out var coll2D); + switch (geometrySourceValue) { + case RecastNavmeshModifier.GeometrySource.Auto: + if (filter != null) { + EditorGUILayout.HelpBox("Using the attached MeshFilter as a source", MessageType.None); + if (script.GetComponent() == null) { + EditorGUILayout.HelpBox("When a MeshFilter is used as a geometry source, a MeshRenderer must also be attached", MessageType.Error); + } + } else if (coll != null) { + EditorGUILayout.HelpBox("Using the attached collider as a source", MessageType.None); + } else if (coll2D != null) { + EditorGUILayout.HelpBox("Using the attached 2D collider as a source", MessageType.None); + } else { + EditorGUILayout.HelpBox("No MeshFilter or MeshCollider found on this GameObject", MessageType.Error); + } + break; + case RecastNavmeshModifier.GeometrySource.MeshFilter: + if (filter == null) { + EditorGUILayout.HelpBox("No MeshFilter found on this GameObject", MessageType.Error); + } else if (script.GetComponent() == null) { + EditorGUILayout.HelpBox("When a MeshFilter is used as a geometry source, a MeshRenderer must also be attached", MessageType.Error); + } + break; + case RecastNavmeshModifier.GeometrySource.Collider: + if (coll == null && coll2D == null) { + EditorGUILayout.HelpBox("No collider found on this GameObject", MessageType.Error); + } + break; + } + } + + PropertyField(modeProp, "Surface Type"); + // Note: uses intValue instead of enumValueIndex because the enum does not start from 0. + var mode = (RecastNavmeshModifier.Mode)modeProp.intValue; + if (!modeProp.hasMultipleDifferentValues) { + switch (mode) { + case RecastNavmeshModifier.Mode.UnwalkableSurface: + EditorGUILayout.HelpBox("All surfaces on this mesh will be made unwalkable", MessageType.None); + break; + case RecastNavmeshModifier.Mode.WalkableSurface: + EditorGUILayout.HelpBox("All surfaces on this mesh will be walkable", MessageType.None); + break; + case RecastNavmeshModifier.Mode.WalkableSurfaceWithSeam: + EditorGUILayout.HelpBox("All surfaces on this mesh will be walkable and a " + + "seam will be created between the surfaces on this mesh and the surfaces on other meshes (with a different surface id)", MessageType.None); + EditorGUI.indentLevel++; + PropertyField(areaProp, "Surface ID"); + if (!areaProp.hasMultipleDifferentValues && areaProp.intValue < 0) { + areaProp.intValue = 0; + } + EditorGUI.indentLevel--; + break; + case RecastNavmeshModifier.Mode.WalkableSurfaceWithTag: + EditorGUILayout.HelpBox("All surfaces on this mesh will be walkable and the given tag will be applied to them", MessageType.None); + EditorGUI.indentLevel++; + + EditorGUI.showMixedValue = areaProp.hasMultipleDifferentValues; + EditorGUI.BeginChangeCheck(); + var newTag = Util.EditorGUILayoutHelper.TagField(new GUIContent("Tag Value"), areaProp.intValue, AstarPathEditor.EditTags); + if (EditorGUI.EndChangeCheck()) { + areaProp.intValue = newTag; + } + if (!areaProp.hasMultipleDifferentValues && (areaProp.intValue < 0 || areaProp.intValue > GraphNode.MaxTagIndex)) { + areaProp.intValue = Mathf.Clamp(areaProp.intValue, 0, GraphNode.MaxTagIndex); + } + + EditorGUI.indentLevel--; + break; + } + } + + var dynamicProp = FindProperty("dynamic"); + PropertyField(dynamicProp, "Dynamic", "Setting this value to false will give better scanning performance, but you will not be able to move the object during runtime"); + if (!dynamicProp.hasMultipleDifferentValues && !dynamicProp.boolValue) { + EditorGUILayout.HelpBox("This object must not be moved during runtime since 'dynamic' is set to false", MessageType.Info); + } + + bool solidAlwaysEnabled = true; + bool solidRelevant = false; + for (int i = 0; i < targets.Length; i++) { + script.ResolveMeshSource(out var meshSource, out var collider, out var collider2D); + bool usesConvexCollider = collider != null && (collider is BoxCollider || collider is SphereCollider || collider is CapsuleCollider || (collider is MeshCollider mc && mc.convex)); + solidAlwaysEnabled &= usesConvexCollider; + + // If the object only has a 2D collider, the solid field doesn't affect anything + solidRelevant |= meshSource != null || collider != null; + } + + if (solidRelevant) { + if (solidAlwaysEnabled) { + // Forced solid + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Toggle("Solid", true); + EditorGUILayout.HelpBox("Convex colliders are always treated as solid", MessageType.Info); + EditorGUI.EndDisabledGroup(); + } else { + PropertyField("solid"); + } + } + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs.meta new file mode 100644 index 0000000..75f8b86 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RecastNavmeshModifierEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e17ef9873f904adda7b90d280bd3a07e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors.meta new file mode 100644 index 0000000..bd57d90 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c5bd303177e2641e39d7786d6633315a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs new file mode 100644 index 0000000..a4f941c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs @@ -0,0 +1,7 @@ +namespace Pathfinding.Graphs.Grid.Rules { + /// Common interface for all grid graph rule editors + public interface IGridGraphRuleEditor { + void OnInspectorGUI(GridGraph graph, GridGraphRule rule); + void OnSceneGUI(GridGraph graph, GridGraphRule rule); + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs.meta new file mode 100644 index 0000000..f05cd6d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/GridGraphRuleEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cb8fa407b3b048b0976573f12ca770e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs new file mode 100644 index 0000000..ba3b721 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs @@ -0,0 +1,22 @@ +using Pathfinding.Graphs.Grid.Rules; +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + /// Editor for the rule + [CustomGridGraphRuleEditor(typeof(RuleAnglePenalty), "Penalty from Slope Angle")] + public class RuleAnglePenaltyEditor : IGridGraphRuleEditor { + public void OnInspectorGUI (GridGraph graph, GridGraphRule rule) { + var target = rule as RuleAnglePenalty; + + if (target.curve == null || target.curve.length == 0) target.curve = AnimationCurve.Linear(0, 0, 90, 1); + target.penaltyScale = EditorGUILayout.FloatField("Penalty Scale", target.penaltyScale); + if (target.penaltyScale < 1) target.penaltyScale = 1; + target.curve = EditorGUILayout.CurveField(target.curve, Color.red, new Rect(0, 0, 90, 1)); + + EditorGUILayout.HelpBox("Nodes will get a penalty between 0 and " + target.penaltyScale.ToString("0") + " depending on the slope angle", MessageType.None); + } + + public void OnSceneGUI (GridGraph graph, GridGraphRule rule) { } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs.meta new file mode 100644 index 0000000..130c50d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleAnglePenaltyEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b43f06d7cb7e494b901c3ae81026332 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs new file mode 100644 index 0000000..30eac85 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs @@ -0,0 +1,61 @@ +using Pathfinding.Graphs.Grid.Rules; +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + /// Editor for the rule + [CustomGridGraphRuleEditor(typeof(RuleElevationPenalty), "Penalty from Elevation")] + public class RuleElevationPenaltyEditor : IGridGraphRuleEditor { + float lastChangedTime = -10000; + + public void OnInspectorGUI (GridGraph graph, GridGraphRule rule) { + var target = rule as RuleElevationPenalty; + + if (target.curve == null || target.curve.length == 0) target.curve = AnimationCurve.Linear(0, 0, 1, 1); + target.penaltyScale = EditorGUILayout.FloatField("Penalty Scale", target.penaltyScale); + target.penaltyScale = Mathf.Max(target.penaltyScale, 1.0f); + + EditorGUILayout.LabelField("Elevation Range", ""); + EditorGUI.BeginChangeCheck(); + EditorGUI.indentLevel++; + target.elevationRange.x = EditorGUILayout.FloatField("Min", target.elevationRange.x); + target.elevationRange.y = EditorGUILayout.FloatField("Max", target.elevationRange.y); + target.elevationRange.x = Mathf.Max(target.elevationRange.x, 0); + target.elevationRange.y = Mathf.Max(target.elevationRange.y, target.elevationRange.x + 1.0f); + EditorGUI.indentLevel--; + if (EditorGUI.EndChangeCheck()) lastChangedTime = Time.realtimeSinceStartup; + + target.curve = EditorGUILayout.CurveField(target.curve, Color.red, new Rect(0, 0, 1, 1)); + + EditorGUILayout.HelpBox("Nodes will get a penalty between 0 and " + target.penaltyScale.ToString("0") + " depending on their elevation above the grid graph plane", MessageType.None); + } + + protected static readonly Color GizmoColorMax = new Color(222.0f/255, 113.0f/255, 33.0f/255, 0.5f); + protected static readonly Color GizmoColorMin = new Color(33.0f/255, 104.0f/255, 222.0f/255, 0.5f); + + public void OnSceneGUI (GridGraph graph, GridGraphRule rule) { + var target = rule as RuleElevationPenalty; + + // Draw some helpful gizmos in the scene view for a few seconds whenever the settings change + const float FullAlphaTime = 2.0f; + const float FadeoutTime = 0.5f; + float alpha = Mathf.SmoothStep(1, 0, (Time.realtimeSinceStartup - lastChangedTime - FullAlphaTime)/FadeoutTime); + + if (alpha <= 0) return; + + var currentTransform = graph.transform * Matrix4x4.Scale(new Vector3(graph.width, 1, graph.depth)); + Handles.matrix = currentTransform.matrix; + var zTest = Handles.zTest; + Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; + Handles.color = GizmoColorMin * new Color(1.0f, 1.0f, 1.0f, alpha); + Handles.DrawAAConvexPolygon(new Vector3[] { new Vector3(0, target.elevationRange.x, 0), new Vector3(1, target.elevationRange.x, 0), new Vector3(1, target.elevationRange.x, 1), new Vector3(0, target.elevationRange.x, 1) }); + Handles.color = GizmoColorMax * new Color(1.0f, 1.0f, 1.0f, alpha); + Handles.DrawAAConvexPolygon(new Vector3[] { new Vector3(0, target.elevationRange.y, 0), new Vector3(1, target.elevationRange.y, 0), new Vector3(1, target.elevationRange.y, 1), new Vector3(0, target.elevationRange.y, 1) }); + Handles.zTest = zTest; + Handles.matrix = Matrix4x4.identity; + + // Repaint the scene view until the alpha goes to zero + SceneView.RepaintAll(); + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs.meta new file mode 100644 index 0000000..4778db3 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleElevationPenaltyEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb9fce5dbc8cd4af78db90d7e4f661ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs new file mode 100644 index 0000000..ac6d6cd --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs @@ -0,0 +1,49 @@ +using UnityEditor; +using UnityEngine; +using System.Linq; +using Pathfinding.Graphs.Grid.Rules; + +namespace Pathfinding { + /// Editor for the rule + [CustomGridGraphRuleEditor(typeof(RulePerLayerModifications), "Per Layer Modifications")] + public class RulePerLayerModificationsEditor : IGridGraphRuleEditor { + public void OnInspectorGUI (GridGraph graph, GridGraphRule rule) { + var target = rule as RulePerLayerModifications; + + for (int i = 0; i < target.layerRules.Length; i++) { + GUILayout.BeginHorizontal(); + target.layerRules[i].layer = EditorGUILayout.LayerField((int)target.layerRules[i].layer); + target.layerRules[i].action = (RulePerLayerModifications.RuleAction)EditorGUILayout.EnumPopup(target.layerRules[i].action); + if (target.layerRules[i].action == RulePerLayerModifications.RuleAction.SetTag) { + target.layerRules[i].tag = Pathfinding.Util.EditorGUILayoutHelper.TagField(target.layerRules[i].tag, AstarPathEditor.EditTags); + } else { + EditorGUILayout.LabelField(""); + } + if (GUILayout.Button("", AstarPathEditor.astarSkin.FindStyle("SimpleDeleteButton"))) { + var ls = target.layerRules.ToList(); + ls.RemoveAt(i); + target.layerRules = ls.ToArray(); + } + GUILayout.Space(5); + GUILayout.EndHorizontal(); + } + + GUILayout.BeginHorizontal(); + GUILayout.Space(32); + if (GUILayout.Button("Add per layer rule", GUILayout.MaxWidth(160))) { + var ls = target.layerRules.ToList(); + ls.Add(new RulePerLayerModifications.PerLayerRule()); + target.layerRules = ls.ToArray(); + } + GUILayout.EndHorizontal(); + + if (graph.collision.use2D) { + EditorGUILayout.HelpBox("This rule does not work with 2D physics, since it requires height testing information", MessageType.Error); + } else if (!graph.collision.heightCheck) { + EditorGUILayout.HelpBox("This rule requires height testing to be enabled on the grid graph", MessageType.Error); + } + } + + public void OnSceneGUI (GridGraph graph, GridGraphRule rule) { } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs.meta new file mode 100644 index 0000000..bc4a0be --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RulePerLayerModificationsEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e94be22ba4fbd41daac01121acf6110a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs new file mode 100644 index 0000000..3081eb7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs @@ -0,0 +1,143 @@ +using Pathfinding.Graphs.Grid.Rules; +using UnityEditor; +using UnityEngine; + +namespace Pathfinding { + /// Editor for the rule + [CustomGridGraphRuleEditor(typeof(RuleTexture), "Texture")] + public class RuleTextureEditor : IGridGraphRuleEditor { + protected static readonly string[] ChannelUseNames = { "None", "Penalty", "Height", "Walkability and Penalty", "Walkability" }; + + public void OnInspectorGUI (GridGraph graph, GridGraphRule rule) { + var target = rule as RuleTexture; + + target.texture = GraphEditor.ObjectField(new GUIContent("Texture"), target.texture, typeof(Texture2D), false, true) as Texture2D; + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Generate Reference")) { + SaveReferenceTexture(graph); + EditorUtility.DisplayDialog("Reference texture saved", "A texture has been saved in which every pixel corresponds to one node. The red channel represents if a node is walkable or not. The green channel represents the (normalized) Y coordinate of the nodes.", "Ok"); + } + GUILayout.EndHorizontal(); + + if (target.texture != null) { + string path = AssetDatabase.GetAssetPath(target.texture); + + if (path != "") { + var importer = AssetImporter.GetAtPath(path) as TextureImporter; + if (importer != null && !importer.isReadable) { + if (GraphEditor.FixLabel("Texture is not readable")) { + importer.isReadable = true; + EditorUtility.SetDirty(importer); + AssetDatabase.ImportAsset(path); + } + } + } + } + + target.scalingMode = (RuleTexture.ScalingMode)EditorGUILayout.EnumPopup("Scaling Mode", target.scalingMode); + if (target.scalingMode == RuleTexture.ScalingMode.FixedScale) { + EditorGUI.indentLevel++; + target.nodesPerPixel = EditorGUILayout.FloatField("Nodes Per Pixel", target.nodesPerPixel); + EditorGUI.indentLevel--; + } + + for (int i = 0; i < 4; i++) { + char channelName = "RGBA"[i]; + target.channels[i] = (RuleTexture.ChannelUse)EditorGUILayout.Popup("" + channelName, (int)target.channels[i], ChannelUseNames); + + if (target.channels[i] != RuleTexture.ChannelUse.None) { + EditorGUI.indentLevel++; + if (target.channels[i] != RuleTexture.ChannelUse.Walkable) { + target.channelScales[i] = EditorGUILayout.FloatField("Scale", target.channelScales[i]); + } + + string help = ""; + switch (target.channels[i]) { + case RuleTexture.ChannelUse.Penalty: + help = "Penalty goes from 0 to " + target.channelScales[i].ToString("0") + " depending on the " + channelName + " channel value"; + break; + case RuleTexture.ChannelUse.Position: + help = "Nodes will have their Y coordinate set to a value between 0 and " + target.channelScales[i].ToString("0") + " depending on the "+channelName+" channel"; + + if (graph.collision.heightCheck) { + EditorGUILayout.HelpBox("Height testing is enabled but the node positions will be overwritten by the texture data. You should disable either height testing or this feature.", MessageType.Error); + } + break; + case RuleTexture.ChannelUse.WalkablePenalty: + help = "If the "+channelName+" channel is 0, the node is made unwalkable. Otherwise the penalty goes from 0 to " + target.channelScales[i].ToString("0") + " depending on the " + channelName + " channel value"; + break; + case RuleTexture.ChannelUse.Walkable: + help = "If the "+channelName+" channel is 0, the node is made unwalkable."; + break; + } + + EditorGUILayout.HelpBox(help, MessageType.None); + + if ((target.channels[i] == RuleTexture.ChannelUse.Penalty || target.channels[i] == RuleTexture.ChannelUse.WalkablePenalty) && target.channelScales[i] < 0) { + EditorGUILayout.HelpBox("Negative penalties are not supported. You can instead raise the penalty of other nodes.", MessageType.Error); + } + + EditorGUI.indentLevel--; + } + } + } + + static void SaveReferenceTexture (GridGraph graph) { + if (graph.nodes == null || graph.nodes.Length != graph.width * graph.depth * graph.LayerCount) { + AstarPath.active.Scan(); + } + + if (graph.nodes.Length < graph.width * graph.depth) { + Debug.LogError("Couldn't create reference image since nodes.Length < width*depth"); + return; + } + + if (graph.nodes.Length == 0) { + Debug.LogError("Couldn't create reference image since the graph is too small (0*0)"); + return; + } + + if (graph.LayerCount > 1) { + Debug.LogWarning("Creating reference image for a layered grid graph. Only the first layer will be included in the image."); + } + + var tex = new Texture2D(graph.width, graph.depth); + + float maxY = float.NegativeInfinity; + for (int i = 0; i < graph.nodes.Length; i++) { + Vector3 p = graph.nodes[i] != null? graph.transform.InverseTransform((Vector3)graph.nodes[i].position) : Vector3.zero; + maxY = p.y > maxY ? p.y : maxY; + } + + var cols = new Color[graph.width*graph.depth]; + + for (int z = 0; z < graph.depth; z++) { + for (int x = 0; x < graph.width; x++) { + GraphNode node = graph.nodes[z*graph.width+x]; + if (node != null) { + float v = node.Walkable ? 1F : 0.0F; + Vector3 p = graph.transform.InverseTransform((Vector3)node.position); + float q = p.y / maxY; + cols[z*graph.width+x] = new Color(v, q, 0); + } else { + cols[z*graph.width+x] = new Color(0, 0, 0); + } + } + } + tex.SetPixels(cols); + tex.Apply(); + + string path = AssetDatabase.GenerateUniqueAssetPath("Assets/gridReference.png"); + System.IO.File.WriteAllBytes(path, tex.EncodeToPNG()); + + AssetDatabase.Refresh(); + Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Texture)); + + EditorGUIUtility.PingObject(obj); + } + + public void OnSceneGUI (GridGraph graph, GridGraphRule rule) { } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs.meta new file mode 100644 index 0000000..3bd7887 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/RuleEditors/RuleTextureEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50639ba8f5afa4626889b9ef9d6e5ab4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs new file mode 100644 index 0000000..433c39a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs @@ -0,0 +1,298 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Linq; + +namespace Pathfinding { + [CustomEditor(typeof(Seeker))] + [CanEditMultipleObjects] + public class SeekerEditor : EditorBase { + static bool tagPenaltiesOpen; + static List scripts = new List(); + + GUIContent[] exactnessLabels = new [] { new GUIContent("Node Center (Snap To Node)"), new GUIContent("Original"), new GUIContent("Interpolate (deprecated)"), new GUIContent("Closest On Node Surface"), new GUIContent("Node Connection") }; + + protected override void Inspector () { + scripts.Clear(); + foreach (var script in targets) scripts.Add(script as Seeker); + + Undo.RecordObjects(targets, "Modify settings on Seeker"); + + PropertyField("drawGizmos", "Draw Gizmos"); + PropertyField("detailedGizmos", "Detailed Gizmos"); + + var startEndModifierProp = FindProperty("startEndModifier"); + startEndModifierProp.isExpanded = EditorGUILayout.Foldout(startEndModifierProp.isExpanded, startEndModifierProp.displayName); + if (startEndModifierProp.isExpanded) { + EditorGUI.indentLevel++; + Popup("startEndModifier.exactStartPoint", exactnessLabels, "Start Point Snapping"); + Popup("startEndModifier.exactEndPoint", exactnessLabels, "End Point Snapping"); + PropertyField("startEndModifier.addPoints", "Add Points"); + + if (FindProperty("startEndModifier.exactStartPoint").enumValueIndex == (int)StartEndModifier.Exactness.Original || FindProperty("startEndModifier.exactEndPoint").enumValueIndex == (int)StartEndModifier.Exactness.Original) { + if (PropertyField("startEndModifier.useRaycasting", "Physics Raycasting")) { + EditorGUI.indentLevel++; + PropertyField("startEndModifier.mask", "Layer Mask"); + EditorGUI.indentLevel--; + EditorGUILayout.HelpBox("Using raycasting to snap the start/end points has largely been superseded by the 'ClosestOnNode' snapping option. It is both faster and usually closer to what you want to achieve.", MessageType.Info); + } + + if (PropertyField("startEndModifier.useGraphRaycasting", "Graph Raycasting")) { + EditorGUILayout.HelpBox("Using raycasting to snap the start/end points has largely been superseded by the 'ClosestOnNode' snapping option. It is both faster and usually closer to what you want to achieve.", MessageType.Info); + } + } + + EditorGUI.indentLevel--; + } + + PropertyField("graphMask", "Traversable Graphs"); + + tagPenaltiesOpen = EditorGUILayout.Foldout(tagPenaltiesOpen, new GUIContent("Tags", "Settings for each tag")); + if (tagPenaltiesOpen) { + var traversableTags = scripts.Select(s => s.traversableTags).ToArray(); + EditorGUI.indentLevel++; + TagsEditor(FindProperty("tagCostMultipliers"), FindProperty("tagEntryCosts"), traversableTags); + for (int i = 0; i < scripts.Count; i++) { + scripts[i].traversableTags = traversableTags[i]; + } + EditorGUI.indentLevel--; + } + + if (scripts.Count > 0 && scripts[0].traversalProvider != null) { + EditorGUILayout.HelpBox("A custom traversal provider has been set", MessageType.None); + } + + // Make sure we don't leak any memory + scripts.Clear(); + } + + public static void TagsEditor (SerializedProperty costByDistanceProp, SerializedProperty entryCostsProp, int[] traversableTags) { + string[] tagNames = AstarPath.FindTagNames(); + if (tagNames.Length != 32) { + tagNames = new string[32]; + for (int i = 0; i < tagNames.Length; i++) tagNames[i] = "" + i; + } + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField("Tag", EditorStyles.boldLabel, GUILayout.MaxWidth(120)); + for (int i = 0; i < tagNames.Length; i++) { + EditorGUILayout.LabelField(tagNames[i], GUILayout.MaxWidth(120)); + } + + if (GUILayout.Button("Edit names", EditorStyles.miniButton)) { + AstarPathEditor.EditTags(); + } + EditorGUILayout.EndVertical(); + + // Prevent indent from affecting the other columns + var originalIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + +#if !ASTAR_NoTagPenalty + // If navmesh/recast graph only, then only show traversal scale unless an entry cost is non zero + bool hasNavmesh = false; + bool hasGrid = false; + bool hasOther = false; + AstarPath.FindAstarPath(); + if (AstarPath.active != null && AstarPath.active.graphs != null) { + for (int i = 0; i < AstarPath.active.data.graphs.Length; i++) { + var graph = AstarPath.active.data.graphs[i]; + if (graph is NavmeshBase) { + hasNavmesh = true; + } else if (graph is GridGraph) { + hasGrid = true; + } else if (graph != null && !(graph is LinkGraph)) { + hasOther = true; + } + } + } + bool anyEntryCosts = false; + for (int i = 0; i < tagNames.Length; i++) { + if (entryCostsProp.GetArrayElementAtIndex(i).intValue != 0) { + anyEntryCosts = true; + break; + } + } + bool hideEntryCosts = hasNavmesh && !(hasGrid || hasOther) && !anyEntryCosts; + + static float LinearToSemiLog (float value) { + if (value < 0) return -1; + if (value < 1) return value - 1; + return Mathf.Log10(value); + } + + static float SemiLogToLinear (float value) { + if (value < 0) return value + 1; + return Mathf.Pow(10, value); + } + + static bool? TagTraversable (int[] traversableTags, int i) { + var anyFalse = false; + var anyTrue = false; + for (int j = 0; j < traversableTags.Length; j++) { + var prevTraversable = ((traversableTags[j] >> i) & 0x1) != 0; + anyTrue |= prevTraversable; + anyFalse |= !prevTraversable; + } + if (anyTrue == anyFalse) return null; + else return anyTrue; + } + + static bool HerusticIsAdmissable (float costPerDistanceFactor, uint entryCost) { + bool anyNotOk = false; + if (AstarPath.active != null && AstarPath.active.graphs != null) { + if (costPerDistanceFactor >= AstarPath.active.effectiveHeuristicScale) return true; + + for (int j = 0; j < AstarPath.active.data.graphs.Length; j++) { + var graph = AstarPath.active.data.graphs[j]; + if (graph == null || graph is LinkGraph) continue; + if (graph is GridGraph gridGraph) { + // A too low cost per distance can be compensated by a high entry cost + var cost = gridGraph.nodeSize * costPerDistanceFactor * Int3.Precision + entryCost; + var heuristic = Int3.Precision*gridGraph.nodeSize*AstarPath.active.effectiveHeuristicScale; + if (cost >= heuristic) { + // Ok + } else { + anyNotOk = true; + } + } else { + anyNotOk = true; + } + } + } + return !anyNotOk; + } + + GUILayout.Space(5); + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField("Traversable", EditorStyles.boldLabel, GUILayout.MaxWidth(90)); + for (int i = 0; i < tagNames.Length; i++) { + EditorGUI.BeginChangeCheck(); + var v = TagTraversable(traversableTags, i); + EditorGUI.showMixedValue = v == null; + var newTraversable = EditorGUILayout.Toggle(v.HasValue ? v.Value : false, GUILayout.MaxWidth(90)); + EditorGUI.showMixedValue = false; + if (EditorGUI.EndChangeCheck()) { + for (int j = 0; j < traversableTags.Length; j++) { + traversableTags[j] = (traversableTags[j] & ~(1 << i)) | ((newTraversable ? 1 : 0) << i); + } + } + } + + if (GUILayout.Button("Toggle all", EditorStyles.miniButton, GUILayout.MaxWidth(90))) { + for (int j = traversableTags.Length - 1; j >= 0; j--) { + traversableTags[j] = (traversableTags[0] & 0x1) == 0 ? -1 : 0; + } + } + EditorGUILayout.EndVertical(); + + GUILayout.Space(5); + + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField("Cost per world unit", EditorStyles.boldLabel, GUILayout.MaxWidth(130)); + var prop = costByDistanceProp; + float lowestTraversalCostWarning = 1f; + bool warnAboutEntryCostsOnNavmeshGraphs = hasNavmesh && !(hasOther || hasGrid) && anyEntryCosts; + + // On grid graphs we can check if the entry cost of a node is high enough to compensate for a low cost per distance + // Other other graphs it is hard, because the distance an agent moves between nodes can vary a lot + bool entryCostCanCompensateForTraversalCost = hasGrid && !(hasNavmesh || hasOther); + + if (costByDistanceProp.arraySize != 32) costByDistanceProp.arraySize = 32; + for (int i = 0; i < tagNames.Length; i++) { + var element = costByDistanceProp.GetArrayElementAtIndex(i); + EditorGUI.BeginDisabledGroup(TagTraversable(traversableTags, i) == false); + + EditorGUILayout.BeginHorizontal(); + + // Note: uintValue is not supported in Unity 2021.3, so we use intValue and cast instead + bool heuristicIsAdmissable = EditorGUI.showMixedValue || HerusticIsAdmissable(element.floatValue, (uint)entryCostsProp.GetArrayElementAtIndex(i).intValue); + if (!heuristicIsAdmissable) GUIUtilityx.PushTint(new Color(1, 0.9f, 0.5f)); + + var r = EditorGUILayout.GetControlRect(false); + EditorGUI.BeginProperty(r, GUIContent.none, element); + EditorGUI.showMixedValue = element.hasMultipleDifferentValues; + EditorGUI.BeginChangeCheck(); + var res = GUI.HorizontalSlider(r, LinearToSemiLog(element.floatValue), -1, 3); + if (EditorGUI.EndChangeCheck()) { + if (Mathf.Abs(res - Mathf.Round(res)) < 0.05f) res = Mathf.Round(res); + var v = SemiLogToLinear(res); + // Round to 1 decimal place + v = Mathf.Round(v * 10) / 10; + element.floatValue = v; + } + + r = EditorGUILayout.GetControlRect(false, GUILayout.MinWidth(60), GUILayout.MaxWidth(70)); + EditorGUI.BeginChangeCheck(); + res = EditorGUI.FloatField(r, GUIContent.none, element.floatValue * Int3.Precision); + if (EditorGUI.EndChangeCheck()) { + element.floatValue = Mathf.Max(0, res / Int3.Precision); + } + EditorGUI.EndProperty(); + EditorGUI.showMixedValue = false; + EditorGUILayout.EndHorizontal(); + + EditorGUI.EndDisabledGroup(); + + if (!heuristicIsAdmissable) GUIUtilityx.PopTint(); + + if (!heuristicIsAdmissable && element.floatValue < lowestTraversalCostWarning) { + lowestTraversalCostWarning = element.floatValue; + } + } + if (GUILayout.Button("Reset all", EditorStyles.miniButton)) { + for (int i = 0; i < tagNames.Length; i++) { + costByDistanceProp.GetArrayElementAtIndex(i).floatValue = 1; + } + } + EditorGUILayout.EndVertical(); + GUILayout.Space(5); + + if (!hideEntryCosts) { + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField("Cost per node", EditorStyles.boldLabel, GUILayout.MaxWidth(100)); + if (entryCostsProp.arraySize != 32) entryCostsProp.arraySize = 32; + for (int i = 0; i < tagNames.Length; i++) { + EditorGUI.BeginDisabledGroup(TagTraversable(traversableTags, i) == false); + var element = entryCostsProp.GetArrayElementAtIndex(i); + + // Note: uintValue is not supported in Unity 2021.3, so we use intValue and cast instead + bool heuristicIsAdmissable = EditorGUI.showMixedValue || HerusticIsAdmissable(costByDistanceProp.GetArrayElementAtIndex(i).floatValue, (uint)element.intValue); + bool warn = (!heuristicIsAdmissable && entryCostCanCompensateForTraversalCost) || (warnAboutEntryCostsOnNavmeshGraphs && element.intValue != 0); + if (warn) GUIUtilityx.PushTint(new Color(1, 0.9f, 0.5f)); + + EditorGUILayout.PropertyField(element, GUIContent.none, false, GUILayout.MinWidth(100)); + // Penalties should not be negative + if (!element.hasMultipleDifferentValues && element.intValue < 0) element.intValue = 0; + + if (warn) GUIUtilityx.PopTint(); + + EditorGUI.EndDisabledGroup(); + } + if (GUILayout.Button("Reset all", EditorStyles.miniButton)) { + for (int i = 0; i < tagNames.Length; i++) { + entryCostsProp.GetArrayElementAtIndex(i).intValue = 0; + } + } + EditorGUILayout.EndVertical(); + } +#endif + + EditorGUILayout.EndHorizontal(); + + if (AstarPath.active != null && lowestTraversalCostWarning < AstarPath.active.effectiveHeuristicScale) { + var msg = "The cost per world unit for some tags is lower than the default. This may cause suboptimal paths to be calculated. You must reduce A* Inspector -> Pathfinding -> Heuristic Scale to at most " + lowestTraversalCostWarning; + if (entryCostCanCompensateForTraversalCost) { + msg += ", or increase the cost per node,"; + } + msg += " to guarantee optimal paths.\n\nIn most cases, it is recommended to only make tags costlier to traverse than the default, not cheaper."; + EditorGUILayout.HelpBox(msg, MessageType.Warning); + } + if (warnAboutEntryCostsOnNavmeshGraphs) { + EditorGUILayout.HelpBox("It is recommended to only use costs that scale with distance on navmesh/recast graphs, instead of costs per node, since nodes sizes can vary significantly.\nBefore version 5.4 only costs per node were supported.", MessageType.Warning); + } + EditorGUI.indentLevel = originalIndent; + } + } +} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs.meta new file mode 100644 index 0000000..4d38d9c --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/SeekerEditor.cs.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 95febb4c5ed8c46939368f03ecf4b3b0 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/UI.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/UI.meta new file mode 100644 index 0000000..c01b6c6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3dc0946757e6754459cc262a46ce7891 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.cs new file mode 100644 index 0000000..c817bf6 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/UI/WelcomeScreen.cs @@ -0,0 +1,208 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.Compilation; +using UnityEditor.Scripting.ScriptCompilation; + +namespace Pathfinding { + internal class WelcomeScreen : UnityEditor.EditorWindow { + [SerializeField] + private VisualTreeAsset m_VisualTreeAsset = default; + + public bool isImportingSamples; + private bool askedAboutQuitting; + + [InitializeOnLoadMethod] + public static void TryCreate () { + if (!PathfindingEditorSettings.instance.hasShownWelcomeScreen) { + // Wait a bit before showing the window to avoid stuttering + // as all the other windows in Unity load. + // This makes the animation smoother. + var delay = 0.5f; + var t0 = Time.realtimeSinceStartup; + EditorApplication.CallbackFunction create = null; + create = () => { + if (Time.realtimeSinceStartup - t0 > delay) { + EditorApplication.update -= create; + PathfindingEditorSettings.instance.hasShownWelcomeScreen = true; + PathfindingEditorSettings.instance.Save(); + Create(); + } + }; + EditorApplication.update += create; + } + } + + public static void Create () { + var window = GetWindow( + true, + "A* Pathfinding Project", + true + ); + window.minSize = window.maxSize = new Vector2(400, 400*1.618f); + window.ShowUtility(); + } + + + public void CreateGUI () { + VisualElement root = rootVisualElement; + + VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate(); + root.Add(labelFromUXML); + + var sampleButton = root.Query

    i+(aImOCGM(C=U=jJUI9*hAV)d zKsC3vQrnUcfS!XK!M^YB%S?2FJaUND&j-=p{HPj;JyC!Ytxfm*{EuKj+9t~)*G0e& zz|`Vmq6!HC&nGAH3AM_AuL9VJR zRe4+D`W4aGA#;pn7IHVC@`ElyHxWtqV|sUQp)`+RQFM9Y<46QXvl+L!+?Qnq z`kfA*OC>%$yTgGanyV3DmD|lGKQjkXpNz~z<+#_wKF!t4fF(r}34VTSwXmGcP_I;Y zWr@t@;|m0Mt_1cDh!ik;OoZ*44eYg6!>-2zyVJ=XGTavmsuopSFj_49pRKChXuuy1 z!$!s42Ln7&CsK<_$PP?&fM=-`C$F#SFQ}d}Yhir@|A0PaLNZdMlMHG-yH#DS9uAF0 pk$#>B^v=8?clbfb)y;Hz`3cZ1pk0zKo5KJA002ovPDHLkV1l@+(Te~8 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@1x.png.meta new file mode 100644 index 0000000..0cc89f9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 6ddc02fd0dc8cd741ac72887911a191e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..47e246cd8fe42ac12c448123c432d05245388348 GIT binary patch literal 850 zcmV-Y1FigtP){bDnh`UQ0TjF=Zz>9%QD3oOtS@Ij2 z7B-vBn$F~J>7HO}T0-aV=j0)V!(p17o>u;k%_km^hX8phlcC{}5o*-yRNmk3nxm6P zu#*2iJ*CRwp>m!l0_^0ul@+RU1XQ?H3W7sJ6d4<%hZ7Tm1O&kO*%_T12K_ibrVAds zC3h`A7E)|#ih{wQn^#p?5v9F7F@7A2bw!~|z_+a}@%@S;Aq!CA=-@!?b+_aI>IrK}Q}%7yB>?cw5>6csQA4*x*OYA<&gHgL!Lbv6C{rNztESOWvFIr|Fz_r8NN9>n$*+?WYBrnXF^W3h1q*b4 zSkn1=^qvP5@*8|L!zEQ;HVmUNI5_942qtgvGsnS@@Y0;|Qs$g>dm zqER@C#lEX-*h8%b^YPdelQ*5yL;{ph2%cLl3;3zogx7W(0!aQV!AU&Mt9FY;c-M7P z(%WbR!$yP0T^Rm!y_W9 zEWk7$UnByZLct)AN~+3)vxoACgk;jdUXb80800=xWsJd~T7~6Ci>56&za~$sqIPH- zL`rKM*6Uo^PcsN;fMU;LRgb9RKcg1tmnfnI@o8)sW4er2Uie#q*92w9-DGlq?)8!a k4UgLz1ZJC}(NRslhX4Qo07*qoM6N<$g2*?{bN~PV literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@1x.png.meta new file mode 100644 index 0000000..ff683db --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 47aed39adb8ef6f4c87bce105c0eee4c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_off_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a4319204ec60b45662cd30e86833bedd9e371fea GIT binary patch literal 872 zcmV-u1DE`XP)S#5O`m z(2}Z|Nv`K%Pvc=oNoIRjYJ;dfK)|C68bx zzrVbs^4Xc?I!^?&l4sY}L0fv!! zM@GoUBHF-$S|LL&B_KLAMSWvq*2E6*1p-p*Yy}|df!SG+a^U*p1dU6*!^7kRl%m3h z`+0OEq7U}>)6>HP)hZRs^{O2?>Iiadm|b0^+}4)Z%nevy*x4bC39&x8xF|l|p^&_Q zCKE9^%muim738_i&BpIX&cGu_kG35o77q@@@G-mB+=@tXh>c~GrpGjrjg~Z8_@4w|Q=<%s9Mi0@z1^5_7%;7_ z{>>(Zhi+^-Mp}T|D>c9ZFm3@vi*~!ex^jK_NC}X6pUXz*2Fp<#8XS~n}lk7JA zp$Vm5vYBCazHeri9nJuy6p!PWvm=0f#W`2J57|Hnp)A+|>xm!;cyF@7*`bX*&#!R2 z>!xW+%d({Fy7Nu~S(d$k_y%`qVs%}!G)*O3;rss2X8zU@kn{!e1KjUjVchrxCQg#% zvsC~aA28>raF1lkT7bO~_BaZ??+G9tV*QhkE~w3NgIgl@tMg_s;hunP+hZiX(jA~u ziSJv4Cwko=w$S(UjPLQfa1=#rk)kLrtE&2=CiI!-APnJOz&=tO>xsk+XX`IGjw4}4 zsB{NHs9Rmamk<%2=S|rTTHk?AoU#r$`6Ui$AXra;+n6b&sbpQJ(u^|sD^E_04FS}g Q!vFvP07*qoM6N<$f|@(B8~^|S literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@1x.png.meta new file mode 100644 index 0000000..fc1406f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 455f4fe795672465c8a4e8db3c6b9ccb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cab64eeab14fa92d971e204aa99ef2f8be9ad64 GIT binary patch literal 712 zcmV;(0yq7MP)q@B;410&ep@DIO^C+fRYaL@a zZMWM~c>ziZSg+TO-EQ|C!vVhXKfMF+4}B ziyw^A0R0{B^%>_zz^wWbLx!ATju2+{11W%GH-d;$^k4I$p$o{b4S9?e^}F5f5Kcdf z2doj)!LU}VeZuL+__&6UkEI0|a^5gQ>sy6YDg`_=o6Q&^`bIce@GuO8#d9SG_u6hFBzg@oE z-4LPrgYL|nUO;*1Tu7O zJwN23kO_wUXC8<5dtkX-4)ER&EQiF@djE^HFZ3)M7T?gfH+c ulrvlYG5(Bga<2Z701N7^e%IVzbK)Lq=_c%X$}0~50000!g~@oV#ZF2kXeN^|pUw0< zgTO%|0f(s+SnPI#XgimK_rYK(Cj54e3kBE>hoRSK7|4(1GCa52n)sgr;Y-XWIScJ)F1ftUkAZ;a(34FrhHnJ90H}DTs0sE4< zXOhfjQrWsy)w_@+N%&{%Va&QAS4Hy8wW3`90O<*)6wQb)g#Z8m07*qoM6N<$g14gK A`v3p{ literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@1x.png.meta new file mode 100644 index 0000000..e3f9c19 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: d3070b98e2ba5b8428937a801fed5125 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/gizmoButton_on_active@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2d88d69cfb9736aae5f7a00e16a475d28371421f GIT binary patch literal 883 zcmV-(1C0EMP)QHS;aqN_IP&+6T9FxT{v=wa!7qjW&A84J-mZhek zln!bftb$ONo}$o#LlP;CE%^HV9(Qs{&bte8@dtX(chC3xeShBf?o-IXYfTGWRn-^? z@r3^jkw4fx6=E{3>+|M>f?w}7O%sZ;EQmC!4)ni5JbkO{x)ks(82nL1a;u03HRGB# zNQyE@)ki=jDyU2zrAvaMC?OU>_sK^zw6xHpp`r6$hpv#%$hNY>i4j z!AkyfXNSHoE!nQ~ML;R}$G$!~IX<>sTTuvV>+9)OTN~Z(?4$s1-q74E=W~aLls!10 z{nb^<^4Kdmpo|BD!j0HWq7x)C^c;2%BYZ76@)W`_!udUImREpdHqR(Svatw0p z0|6iU`)x(oo`TXO0y*Fo22-qwLM}&tm>heB*_*X>b?yQXbJOzV@UYDAGXl%ljvQjG z>oLY0-r@9g$71dRI*hWp&N0lD5Sn@5h1@D)gZBdN_V$)}p-jM+L_$7$*b_HeTZ`Up zb1B_3!r_G+fH&b)Y1GV2*|97Wfb4~(I7Tm~r;9{jvw%^2ypWqoh};4B#@t-lnwn>g zjc-aPchhNEYsDU4NVxa6ZSMYx1h`$PNX~WRc)?m!xnI1_?eZyw zZ4Oj&pI;QVr5JIo2036Gf{fu>)B``s0fbEqo@-}lgU9h}c+5JeFriJxa9P!t8^c@EPwy^N!XX_}yE+RHe3c9JB4rfD`oh+!B^ zI89T&K$c}Jt8LqGxm$=!D7P}Fs>lzeAVK!E9I-S-av{hAQHueHz7uFtN<~WYm zavjHkvMg60dw~6^s*1%f;6<7jXB@{>MAvnYWtnYj7zUP=-Wjkgi-~as7TUID3-Bnh zd|ZHAShn~K_a0y%FPH~;JdC6CcnUrogdXW#KqHExKk9r@bML$8Y2`kB&cFY(^Lu5c SiS0cA0000do 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 RZJFtRBNjyptN4ip{{WcGn+pH{ literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@2x.png.meta new file mode 100644 index 0000000..075b54e --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_diameter@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 5a358b45ec6c34f20b9f792e1f259b4f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dd1fbe4ab730f163b086b142295bbd2a036be822 GIT binary patch literal 1205 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~Nw~85dUTd8XLi0@0g-#VugHCI$vL_38s1qYsK&q^O1o0n-nN2~W5{4m|0n<^j`t5inu9 zynMvOz`&^P>EaktaVzQ1|Nr*PJO)NaO&Svzls8#yWib03$)T~#^1#!E|2!p3J7gTZ z-!HD{>r8B56lUF*&h|)0VISwx$PVA`6%QGGdJTM-Hn2Y`zVKiV`_l8w8<{VDWY|2} zh5ZE6M{~4awD=aicaUY8G=1?lW{ZX$QXTfpAI%lkF(&z$n9d0>jbE@ss#|K4 z%me--+ZXPO_H23Y@agozSNY1VLMQnyFg$u2(5yFEXnz1hP`%(P`IJ5}O}-b5kLEl4 zk?Yv(>GAN!Daio7N_Nxr?Gfw^7SW9nEnYWcS+efcm#{z3?%-qoD6M7rfcJ{cBCbV@ z`kd~v2Co_d*?X)KE?69xcB-^d_g>qu#1_2 aiHG6h7TtRmTf*Of3KCCOKbLh*2~7YxXOJ)e literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png.meta new file mode 100644 index 0000000..d62c493 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: c3f63c474ba404e88a6a16a074cced39 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b0660d19209988b4e7675b1cc31160123f315da GIT binary patch literal 1577 zcmaJ<3rrJt82|GqprRnKP0`7v;Lr$duhK$KN?ZDnq6KU#;4>WU2^Z*HxjWWUmr(Js z5T|pZbCD2ippe~CNYIE~${poJ*t^Hf=R%_{Oo5d9 zMpU*g15%O{28no!V@(1WhQwkX948Ws;TT9LfQ5Vk`-x-WI7B2ygz?bW;Rm$mB)uA5{ZXHD2!!2V(AisLG7^w9XZaR!e|qPTN#`r zAP*y&OBORyF01rS3Kr`GEkTc!i7go4j#~LJPvA*u+)=0de^-lT!kcFF*jv5-NlY6{ ztQcR9(PS}YVjH(1(i6&xC@BnONXkf(h2vGs$R`<+&L^#q(jbNw=ui_*cnk|i5jq{B zC1?gEOqfma{K4=6G;RYx2y}js&03fQq}%cK6qB zYzs_`vYo8#c5SRUefQLn%%ckpsuf#G`}|eknUtR-xFeiRuIIrZ*GpSQ zaRQtV&VF+(D(Ev#{U$=b=&3{!(gb&bs6aH_SUZxjZe`bR*uV%UCycq|ceU6%{E--N zI%YiFKK=RZ6^R@+(OXb2X4dyl;D z3VKql%SZ|X4_)&+YA(6o8MH-jdi+8m`)b7hWzUsT?*$dIp<0Jmxb1Z)q8RQQRJO%C z54N};b`SZIRgrxM-2P{f8IR6{aCSwHoNWlT7u;|^)-?Afit%OxC_W%Np4_7hd-STb zFzetnu+Q&6y6@o4tj7D?XkdT7E^uJx9cFL%8Qp3=4Jtt&oo&U5b`<~byx+l%K2Gap&W!?w0pb*;1r`-Y(u&pB?_kg^bNE z-Noyj=Z@9*Z>F0DFE^>uPi+23QzO5Xqg literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png.meta new file mode 100644 index 0000000..57dccff --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/hex_width@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 6010a63a238ef4899872fe5bd998dbb5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..cafc1ade109effc00a91f1ad0b91d756e541ad32 GIT binary patch literal 328 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{#{QO$B+ufxxtQHhYSSHsL9z>z1+E1wCbfW z&ktey2g0}Pj`DNVIn*?6nLbT!uUN~k1}XIkO&qbacv@s{+&!6hXaYlzVaPO9PW}b= z1ST$=@Juo^RI*iL=c0Y@KPO6T3l!P=zc?y8Y4+}QxBV}FVc4s4bpun-DZz!u)?HU< zH1U;gVZQhBjc!7D{_SsUzYei2z`|HH=5GEXyoK1V;MNKx}bo!jh=M3>b^-XET7F zBNJNwq9{^?6c4L6o6Q?}az_E2&*weDyD+m{uh%c+K~VO_n7tgM3On*7wEOGH_=7wN zS^{MJiYhD-?-6s9KW-18=^BJ*3LwY;gGNk{{g$f`Z5Ke4OTLHb0Yer~5Oe(E7=U;N z(WlAWCn-?v0yG=1dp?=|Aq64ZFp&e8`XU7(2Dak=Hz`pWD9e(?Wd&5(s||Erx13DH zH2_jMSS%JbDbX5`ADw2kTB&wGhyhsvbpxslTpDl$FCg{$s|-vVkWWG!FNYIvykEuE z+NAG$Idy>e4wc|9V@{rUS769kkV3rnJ&Fx@SAbu+RaHH-ZTkq}UsQt+A`K}qF|K3R zfUdL1g60C+j(fnd6N=2BIi??TDOjAd|7Sp8ZgpLoUd@?5M{J)ky%n_g00000NkvXX Hu0mjfdP2-L literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@2x.png.meta new file mode 100644 index 0000000..d288c89 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 7ef0e648811f14f65b69c2df3f27de11 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..747e9de4ec1b37d14e623868af7daddaabe0408d GIT binary patch literal 384 zcmV-`0e}99P)ugy`7f$K>m`>l<`&Otcx78utm z;NCm66s%$l_G+$6(-W|C=Tyk|n+f(i+n@d`74s}^Cg2trPcKnOxJVHF{J~uWGx7j~ z`xlG^(FcrCujM;N2i}EskQ;f34!tb>056q663n3B*@Y662g?`N=(|;7OVWy7x6U3m e)Rvj~3zd)XC|OgQlO-Jh0000)qwz9oMhZ5{xgBro+nmU5d1fWT(@6e=F!roqqW;#4k&0L{UFdl0);?f-Im;4 zklkAY2UZA5(&Z`vbMh(5#Ix>7NHRmf5|CM6LUy-y#fiUN;J^xjK0M>D*h}-Df8fB* z0p)ftp?W}3^c*_Bn5$j!doQXIhAO~YbiTym>>a=s z;PpTd<17L7B{xU{vINv8V6`Nx%LxbHM{&9C05YZF!dVtyzs1qQxDWXDKz+vNPc!%- zz2NsMR)uvRP+>5Sw(vRj-K_~~Cvc&~^uy){wqoxh?H;HQq|hK_Pcp0B+tE1)G7&aH zn5s$DrVP5WoQM&s2x;7M97BDQcmBCgbX&DR-O`(OpTnIhORuYitGOrTy}d}`{>H8V O0000U)3 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@2x.png.meta new file mode 100644 index 0000000..af7665b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_active@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 8acb97c3fcc694bf3b7d33b1ff321337 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..540c8ab4eec2b983179604a5a665bf74f4b5a9a3 GIT binary patch literal 355 zcmV-p0i6DcP)gr@18B*|Umd7jeZiUS~`hos?bE7DqH9LLohDZWXY?6z%DmL;mH zGL80?+dh$1&!4-cQi?J?gqf*L8@Z2(Ig{)=AsxKl3;?!AQWe zr)m0nz+UVo;OAqQr2i#g4;a4h;}8Lx$KWfNK9ZH-ho)()`4czB+HVZQ5W_H7Ux1)l zLAsV<=l^WxUU&-*ilTVY;zHr3cIJ~wc}ng4`~XU#TbL|)xJUp1002ovPDHLkV1nCS Bl1cyo literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png.meta new file mode 100644 index 0000000..70eb389 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 4d454c89b74d19343a4b00cb2ee4768c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/infoButton_hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e0bc13e6d39193cf19cbba2322e8550f765d09 GIT binary patch literal 588 zcmV-S0<-;zP)9-sqzZh!>_A_S-cL~5rT zN-|VBpL%=}B90>YKilU!TX17KojRAx<-@Y9H#8gY5BR?Sl}IG|?RI-8-&r^3&)4hq z6F+zc1K=WsZQIROt0e*flgY$2|qLp0;r;u zf^xYWg#CCt2F@8ZpeG=eNCTq3mjMxps-T`(k`IFBN0YZuvjcW z2oZUg&*y;yD+QGIc*Q%8102{ja5|kLw$TU>;ikh~JzIb*JPCL8YytcIK4Rx`IpD$) zu-R-Pb~c*@E-V4t?e_jm-OzXqgd?EioFyQBg6*vU0000l2?mnzs#$eUg}%!%Z`V8 zcMxIg^4!#&W$S7ykmFDV@}<(ZW($fES*k!AOHdPRuEw9SkC>?YX3;@TgPmt+w$DRG zCjYQMg%uqKVQ*NOV literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@1x.png.meta new file mode 100644 index 0000000..03b487b --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 5e0cd84e9d84642ac883c25ca8d667ac +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 1024 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 1024 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e1bdcae237b521cd8d564dc6aa389d5825078a GIT binary patch literal 1640 zcmeAS@N?(olHy`uVBq!ia0vp^W8U}fi7AzZCsS=07?@QuLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ@J&q4%mWE% zf_3=%T6yLbmn7yTr+T{BDgn*V%gju%a&mF8v~V#tF>y0DH8gZJcXBZ_baZodv2b;A zF)^}qgz0t3PcF?(%`1WFO+n~2#iP^Az7Gs=x^?{Dj2SqJXRKtXT=?BDwCtM&0p7c}mfa$#mn6NLsK9I-2z;w&g#WAGf z)|*-J9lU`u$ItH-pFL%N_w=p&i$aeu-*ggmigwy@WVg*k_Kyv*uN?*ZPtC8m5X4nE zVXI)*o?Gm$aufb&ewrWi?A(ue-^yQ?e4A69ZhHOi-(^3qF1P+ZXJ(pR`Lj9qEz|S# zx|FtVi|D!XK%+UXfb~nmBq5Q*Iun~d#^#@AzI?#DAbL{7KDWOq7ufO|`8vzj+}4Ws znNcWe(wsl(h}Fw?t!{Jc{zUb@S3Zzc;CO4XL{#Z-ncwn$&eE?-r)*v3_-3z=Oy#rd zXP!&hH@EaEWHGj7paNTh6#P0S3b$$Iw^FC#&u4^>qTY2ikdX6-S)ZGUx z`jU3tU9;dVLr#C;nc#wX+e)MF91C1?I77;P<@0Xg=RI#rWsXMm9Dnmhpl^5blRIL4 zN%b#2>;AAflKlHcv5@=5_%D`|c73hyd>CWRFYD`h=9*=v?}BflDuuCEg-Q+v+ey{< zTGY3e9KRRvo9m^Gs!*=d%Z_jV-_A%oDyCh1%Dg$4)9dE*nNDwY4eIX5c#3B1_Sw{R zY|7bVANxz{PG8iT`u@oKjJt>Bwx9L-(C6AaV_S*!sWdg6YZHFn;PmG&ZTlUgckXB| zFwC0M)0R!wD3e@MKE+_ki-zv{H?(N&eerM^U zj@%DVOjRRuY$P5_rge&a_C4?2^?z3VRqi!rw){)CZ~QOlXzQjYbHvVE%kjFk+On^K zAu}V?rgf$!0jV(djZRTVr&*qgTkd$~>-W9QK}WQbkLsmdKI;Vst E066D`Z2$lO literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png.meta new file mode 100644 index 0000000..4d26244 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_closed@2x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 7b229d059f89d49918aec1466c0c5819 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..53e32e73d5182c1af1ec8f31abc1942c6afc44ea GIT binary patch literal 406 zcmV;H0crk;P)`d7Gm8{15M`h9xg|4tseX3 zNiWWQS2=q}sNh=fLEm&KD^%e9Fr>3J2aZb@zy%^1JKRrT69p3Uj&ddRP;>5=ztJ+7 zGYmSbd7ZPQ5=o1wb2ehVo0?<7hNv6If46!T(=}f)x|&eDXe_Jxsml~e8)p1=f(WC_ z&$iEi-dT#tD{B?1K~AcCTSkzHvsel3Lkd-iE!6#|^%i5gZXpun*4loCTpYCNO#a3G zRIEHNUaiDp2JV+S!W}Hcx=i=!)A2jE5W5R70BTZ32m_;~HUIzs07*qoM6N<$f&&<+ A$^ZZW literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png.meta new file mode 100644 index 0000000..79681c4 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 3805c157b9b38472684befb652c5d213 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 1024 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 1024 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/lock_open@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5645369f4d3315556eb743748565feb717d4f698 GIT binary patch literal 1617 zcmeAS@N?(olHy`uVBq!ia0vp^W8U}fi7AzZCsS=07?@QuLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ@J&q4%mWE% zf_3=%T6yLbmn7yTr+T{BDgn*V%gju%ax^uzuy8anHgPjIH8gZJcXBZ_baZodv2b;A zF)^}qgz0t3PcF?(%`1WFO+n~2!KoLN7;+1MHoK%2WtOF;xE1B+Du6w0m5JLehB(cG z>P^Az79*T`^?{Dj2SqJXRKtXT=?BDwCtM&0p7c}mfa$#mn6T}ZeQaW2U^?vS;uunK z>&-0R3}Hu+lCJ zUlc+GR#gbB)>xbHa;qH6QKzXv0wN(>S{){Rwg16%_*-rnv!`+XU3Pn;%FW)-=HIP+ zfA-BWnd5=(lRe$fZs(C;>p!UAq3O8ogsMtn&Golc8w6_@*Q;`N|NgR`dFunwjENOT zt78AD9onM4fa}jRl{MY#;;y~8ygk16V=bozkDSMD(ar1j@jd&Ioj4=;aL;-*o#o>`vd(e^cX)zz&bbM|q6T zdnGK(_pW`okN-;JNv8AFp7XB;36(6|X0hhbm3m#BhY#a7HauRvpipP=wm4xu-Xh0O zp>mS^|1}F%chu{Ax7IwBy3^j=?Pz|}ElHtOE0(P>Iyd*iPhGd&A#wJ9rhc=%QyLvB zxV|g6&+^*oAKOGCBO;rQH$3_-{QibqMHA0Ep2yh?^UwI`2i_2{IQPT6A#>HLqt6WY zp54p&C+XL(l_Em37c4)(-oI~4NMcp%ueN8?tgpv1zTL*jy~%XL+;1Wl*6tI7Jw?o3 z1Wuj9)}PbcRd?u%@{*`y_mkE%y0ra1-IIT&EYPuKYt<9ei>IfVCMuBEedhr!0efxENBf=Y!zqe{?y*j$APOeO-$6M&xcbCu0etox_ jTz5FfZ_c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`kBtHuNWFoz#!AFNG#Ad)HBe}%?0@jth%@)C>7xhtg4GcDhpEegHnt0 zON)|$@sXws(+mtd{1$-}0$pR}Uz7=ql*AmD{N&Qy)VvZ;7h5Huj9yA+ij|9@fwQr* ziL0Tdk%5_op`nGbi=m~Pxr>2?v$>OtkvYr^YFoU|7FQs_&(Y3#xZtXxsju>m=4nx1tIt)-iT>co`~Ra^|ATDJG25Cu aED{Wj{6*20r+zI36*!)*elF{r5}E)71gMMv literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@1x.png.meta new file mode 100644 index 0000000..bae1e67 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 86cd0618b10f3494c831ac9215649110 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 1 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/pixelBox3@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0528ee8c506d00ef92f3da4cc536f58eb9716f50 GIT binary patch literal 1606 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 U^XpK){eJjw$ZCD+?aaWbKPFXqN&o-= literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@1x.png.meta new file mode 100644 index 0000000..1119f4d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 168346d70f1fe4211ab22c360451c602 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef035a0be09c0732373d13a1e82a085267a76752 GIT binary patch literal 1266 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%o&*>5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8KW+g=7RhMR$W{Yl!|Z$R@KEJl?AE#L8-<0 zrA5iW_()TRX$FQJev3c~fv&OgFUkZ)N@9*nesXDUYF>$_i>(q+MlU5Z#mdFd#l+3k zz|Gv*$iU3P(9puj$kEx=)YaJB(b(C*&;@1&HoX>RhK6p&rY0^X&W47Du9g;#CeF@| zmac|IMusk~7G^NLo_WP3iFwJXFncqB_Coa<ylcOS(cjOR+OKs01jcROe`*N0lFK%&mnqK$O$!l zpkwqw$pb05z=VKl5X6Khejo>)eNyv)d8P=MIToh6yk}rwRPc0h45_&FW_m1FlY>CR z_4$`W%3mBcmbel+iD!3L4)nb*7gcEs!} z*ki`{oN3{!!^t9RnX0F&{$?rTxmUL`dV*7pViNNY&PU9J$#O0T(8e$)6VhX z_upN`eh(N5r`}`N5EOoR*sDN4xsmG;(?ucGkPl+tSM0D15Kt}oa7c~4WlJLu$C5c8 zb`-2)aBX5W7rfxfp*QDpSE%K|w*OBJf>L+K$Xdp2?m7^%a;urpKGi=q7u8P)EmVz} z$Nl)#3HPgqtr)fTJeYUnj=`b>nnG_9zbG6k6`GkR5jc;XrSGZbj#bHNyV4o6gqCsS z@mQz*U;TRNsSSxHJ7*P5-2T_}HcJfm9~KFQZ69M59*BL_1C>Iau6{1-oD!MO<3F4ZC%%F;%GZr!Vtz1b~1GB8>_5+wSBO`NlDPa zNyVEt^r8pv>OmwPH1S|Ons|VNF)`5?y{W<1{Xq`|6Pvd0z3+X$-}irgYVtx)*FYCV zQ9YS)HAmLo;5%`g{7-$`*U8e4wE~_&C0x}#NTp0vgmlK%=V1=&=JJ(SFhNo6Wh-C6 zh3tfEAe+?#9kyn>1Wi$i(VD9p3lP&qIBz*g=F8J(3~iZ7W;UATv#tWm*7%ACXI3Wj z#>#>bH<{6MbfP8`0vlqTuGtmGmupF;&MT9BAm$jlu7VemOv9-{c8XSz2k9sq4jDWE zv?#J55*0--LJK?)IG(&>2t?$lC<`&V`7p$qXO`rgnr_-6rzBIx*p)f1TCK9xFpIo- z4#eZ}fI|>ML?h%cJ6NxU9Dk_AphDm9EEikIp#w&}h!$~@A)Youu-$C-fY|Yyi6SZE zYP!n-mgj6ch^wyc;~f0s#-ZANe%Xaw4*F=(Ge|v3LoG1L-O+{uMdFQo(X&WV^ooj% zMH@Oeqb3=0!Ut3ZvUH9j3v1YVQ?kOU#6Xi+U3ONA94s4b4eu#yNsIvkbMhyno6 z;!-N2D53W7`>ktpr$iE_QV9o!@8b_Srg7)~UU0Nd2gTeNo z`N`p6e4s;S#v{Y=ws?7s4AxRcP33Fvia+=FN83+dUB5QA-DdEDaQlAW2d~1uq>9rc zds|cSiOrikgPp@2FJ|9#_pNRGy74geQ3;>#&J4cpogOJlJ#*VzyItb!ZkT%CTe-CQ z>7F$3;PbJK_FH@9VR`WRcd!%LynCm=GZAa+I7$6@viY@i-FY0$c1D|2AC1ke{su~_ BTL1t6 literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@1x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@1x.png.meta new file mode 100644 index 0000000..ceadbcc --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@1x.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 3d1e3e2345df94db3be6501151e8db26 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 1 + swizzle: 50462976 + cookieLightType: 1 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@2x.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/LightSkin/simpleDeleteButton_on@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3ea35410c1db7003a16469abf5aab5d3641bbbda GIT binary patch literal 1162 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 bG%zwu?vUsRE?aC0bP|K7tDnm{r-UW|{18;& literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown_on.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown_on.png.meta new file mode 100644 index 0000000..65ed2f7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowDown_on.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: d8951099054c64beaaa49d675d71cfcf +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/ArrowUp.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd0ad0180cde2391192d7a7afce2cbfaaa53c14 GIT binary patch literal 201 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 BXfgl* literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/astar_logo.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/astar_logo.png.meta new file mode 100644 index 0000000..46287c9 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/astar_logo.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 1603401e2a100ba44b299ce8f603b5eb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 2 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/changelog_icon.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/changelog_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d179343024894c55d82fe8da6f4812db6ba4d19f GIT binary patch literal 833 zcmV-H1HSx;P)+6mGPpc6nR2uu(*2%R9c?$m4Q+D>fCu|J|aANU}WpDf9eoW)|MnP!^F zBPG5CbX}M6j~vHS?Qj+X7cnT_JRwjXIm-~W#`H4r! z$(E70d(4s#H9(oE{;`VX2qq9jQXyS>jiS~|lCw~90lgRj%pd;HNzg843j z9*VewxirXNA^C2?m_PHnh?rzibSDYDs_J?j369HXfatWF5&pQ2<+!j~ha$Ols=*}3 zL`zYPU^mY^WmBAswZlRxADsY6$~GEcDkQY76+otGCN;JPKUjfv(k&F2RL!J4rTI(v zF;fO9bu($8V^$jSszY`eh7>2;P390_Iv<@q%H1^bb4j$X7qJooHSL5qu6?4I6;hI# zXYUq?g|>4=Bdl^|+RsGlkRTOFNao6Px(g6<(_|EkD=&=Q!PS&@--H6G0N3IrESV71 z1xV%7CpFbg=|fnd5LQ#)G-0N4n*7R6b6@wfn;C&SFpZ3I3ZKX|33G>`d}U&~`2R`+ z%&17Fc*_D7568uj&@=W@w8jroWJ_@r&6;H`##joL z`25az00<6{gJocA992+Hup8P1>>S7Old5+Lupic4e2#(=SP6D#{fp08jtQ0z1SSK` zeZ%dUf9-vMC16)Jp02K0ICg;J*|5@NL|@`rb_3b=)*B!pIA_S%CM;pN6M}0(E=~L9 z!eXL|AY6oNs!V^hFl9?TiBMH$%C50)pR@Ueh=@OrXIDV}+@*m4J^*lO9in(d3I&cU zRUPZ%33yNI#f7XY2oUb&#lj{;w}ck`#S;jQHri@K6gTyn3LJ#ouCcb-azB1+ZvLVN zbE0sG!(k)Wh3J|noXdWk=>RjLa4zdw>yBpih_&9ar`ogDB^eg5=l-BTF4crdC$Js1 z{ziv0wIiHc2#x1LQ@^^=jRXr+(GqTLK~uuP`yTU-!R<(Zlx{b zENiRjz5vAuHaHKaK~&}m)-63d{;UgeS;>!x=ZPf;wByG}{v qLhJ=cx))*{vTzd3nq{qD0R{jsX@&P)X0SN`0000TVgAdP}UZGXBe*Pq+vv?14q@_0bX8` zbC=vvAK1SY>>Av+AmXOe@kELk04Dxo_lwAs8_o+c6g$B}=LPd#?xyx}}lf~6$USXLR)g=nx+A+odg6x$yP z@Qn7Hsaub>2XU;K)yRx^u84*p@eGi;)}%fYQm$niB$}M>cDo1`u!9v?uNjN_!7vu+$EU4yHe@2;iOg-0V(`Jm|+VE)OHz2nZZrv-19Lm0KB$NN1@xP@|pF=FuMBkFl$c_1K z_s|JG3QM-eRnf%a!Ljc>1bf>X6~U&*(y1hgEe?V{&(K;G-bRt@FR@fI)H4bz8dKev z4--^i!Tg4xnNPqfP(`$!dC*wUCwEO#dFI|xQ-MUIkkCF5&Tr;R{t<%81xzjKpAMIGvys<2% oc^3ie7ZnK!2?+`R8ovY>0Law;=f8>OD*ylh07*qoM6N<$f-KJB%>V!Z literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/documentation_icon.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/documentation_icon.png.meta new file mode 100644 index 0000000..75b60af --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/documentation_icon.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 2af1390027808444db42d140d1d437ae +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5c9f2575aa97ad7abd0bab1947f09ec0634cb132 GIT binary patch literal 786 zcmV+t1MU2YP)~kZeinW*9a8H<>e`h-6K& zBYA7=q1Wcn6-^qVbby#X+Unm(;v3nn92?4^<((=>)CUKOCiqB1&nAoREH;WJAQxay z<9&?@k_9VmNs=EV)dvteW1mOtz1Rf9v4c)Ul93d1%BZP9iQuI|XV0`l-#iF{n39>6 zWJ7Z7Aa%syW0}OX_t@__B1270uox=bXaEvf<6wv_H$;ctWG)0R6>cT&ZOXj!4#IcO@&Vk9)kX^&BpZL^z9rj+XdW)_5QAed$^sUQl>s z%C4Q;+Jlx|BMm!fFkqO38OEfz>pdR^$0=4=T{O$lm7(1>>tnIK#)E4ycdf?EV>)z5 zeo2qPqd##KY!cc@glHQXFJmYAnB9q$K^Xlfil0r0l_1gH`N}xSU`+Ymld}IZt5q`2 z(Fi{wv5f8d^&Wfu7DM}#9d|Tl(RAOFz@BOS-nNf?!Wjah3 zt{dfdi3JlwpYDPWxsDS~E~jRf*olKDf@r}#4@2x6pCOae_$^R#uOfV7;jQ{cd#Alw zR21g6i&$Bfi!(~aZ|L42lSUSLDwg<4Hg%NFUbaQY;UaSmWz++*_bHeHcqkg%X!Ee% zO#aB!*9cg)d%1jJn>FCo{7PafSSlFNy5pgDxx_p?JUl!+JUl$c;-3Hm0D5(!Gw-|+ QBLDyZ07*qoM6N<$f}6}}zyJUM literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png.meta new file mode 100644 index 0000000..e2fc5e7 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/educate_icon.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: d49de811c1b5cbb449d74fda55ad6adc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png new file mode 100644 index 0000000000000000000000000000000000000000..67c91da461a52194b91d3ad17dbf7b20bb3f1dbe GIT binary patch literal 247 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ&H|6fVg?31We{epSZZGe6jVs| z2=ZlMs8VHMXlQ0&`1v15zhq!2HDF+PmB7GYHG_dcJbzOBQ8%F4T2B|pkcwMxt{8G1 zGLUF{sJ?V}`N=nhF>e;Rzg!|-bS}1vq0>7mpe2iA*Oyj#qrEltKYlu$i`1^_Fg(;z zyjElB67Bc4ekZ0DA5_|%=(J)6=iyDycAvDw%_}1zp8aoxYUE_ pck55Co7yW_Fe7zg>Sa4Nh6&xuUB_-WPXaoG!PC{xWt~$(697*gTkik> literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png.meta new file mode 100644 index 0000000..db6c14f --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: b593db43606434498a7200f8281297d8 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png new file mode 100644 index 0000000000000000000000000000000000000000..d7bb55d2d648095a02f548aff34bc97d0a6cecee GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJg}1{rUgjo>}hzlahp_q+tkyso^Gt!UsYQM-ygs pGV&kLKG~EfVw7;?c~k=fGsCKUUIEKLs(L`}44$rjF6*2UngHX+Dy#qi literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png.meta new file mode 100644 index 0000000..77004bb --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_Normal.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 9de7a15ab4e1d48da9861247bc34226d +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd6f53dd8bd41b058debeb559a975f5405cd3d1 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJg}1{rUgjo>}hzlTuGlk3q;B>+-&dos9eoCaNgg dU1wsEV351T#ra$9$2*`F22WQ%mvv4FO#mG#C;0#X literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png.meta new file mode 100644 index 0000000..9730cdf --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/gridPivotSelect_On.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 3d11311e97436432e897a4bd0b5fbfac +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: 0 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3854469579e47cf905206a611caff25aabc11fe5 GIT binary patch literal 827 zcmV-B1H}A^P)4JArZsyc7gWiARd6WrGJCpbvTC{87-FJkqEeyHsBlm@kbF6WV@eVTB-=sTuV%On~58PXFTkbQ)SmtBC zr|{~Y^qx-Nv|H?@d)zyZIfnUvT$lcI9A~!z@FJ1`CV&ax^)dA>iquPOjMT1(Z9fK~ z&5=O6qyZQu1&E}(@UWjpA;h3CRU0nV7j?1$jA`Gu0_K{{zf{Ph{lQ@QjU)?pH_7T)G+N>mm)*!^b_PNBd zkCuy)u_Ng#DVHIhjWPs>6PF#9Xmbo;|GNF1QM}@3J%`v2O19|%9P{rwK*?-+*xwrf z-|@`ECKA8|Faf*{fGXxD0oc0JF5Qh_UwHu}ZOm*T;K5sDMGexjOiR&X+gB3yE>41b zQadI1W83vdSh-R!VN$>GE%?}S%+hjA^3PprU$<38{ii=L3APP5w`@YlBiaw&l`;0d zmr3ob5m2Y#*%U_2phd~I@;2Juvzy%8mLy4%+mufM1^@u?KKVN%Bai?9002ovPDHLk FV1htOfK~ti literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png.meta new file mode 100644 index 0000000..dc1a4a5 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/samples_icon.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: e10d07f341fb69b42acf8f2b2971270f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_01.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/images/upDownArrow_01.png new file mode 100644 index 0000000000000000000000000000000000000000..bb43e9f5fd3036098b58a2bd61345dce5bffc3e4 GIT binary patch literal 249 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 FV1l7!{X_r& literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_normal.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_normal.png.meta new file mode 100644 index 0000000..7db2609 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/linkButton_normal.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 53ee29e7cbeb545bcb1ca238a2736fb8 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png new file mode 100644 index 0000000000000000000000000000000000000000..a87ee575558bc287c8636f235642be0e4391c74f GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx;}1{rUgj-r3Qy(S(`VSzq9`fx`ho1-*F&iHCxZ z{rOW~J1(UQJBiOpaosLF1aK$XN;&O0nGMF%zTeLXU$>@_l zv)BQqBflFg1Ul52WurMn-?v}d?Z}nYa6_)+@Ir~36HR>_tk*M2Rypip>xg$S>{!b_ diJ8NIp{z^ow(y;-Z$M`;c)I$ztaD0e0sz3QP00WN literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png.meta new file mode 100644 index 0000000..5482a1d --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: bdec0523f27864115b7b727c8f5d90e2 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + linearTexture: 1 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapMode: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 1 + textureType: 2 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png new file mode 100644 index 0000000000000000000000000000000000000000..9bb3204fdf5642a8b84ece8a2c95ff5884d0b818 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XtUX;ELn>}1m6VmGDX?y+sHm8*ta1MQ`6j$FGBPTe2}ufq yJy{YmQW153emKr*>zB8mw4`z3l_$Ic2@H$>aZEnA;>;qTF$|urelF{r5}E*W6fpS! literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png.meta new file mode 100644 index 0000000..eaaf91a --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox2.png.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: 2fa6580cc9f7f40919411d1ed85c6fe1 +TextureImporter: + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + linearTexture: 0 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + seamlessCubemap: 0 + textureFormat: -3 + maxTextureSize: 1024 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + alphaIsTransparency: 0 + textureType: 0 + buildTargetSettings: [] + userData: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox3_separator.png b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/pixelBox3_separator.png new file mode 100644 index 0000000000000000000000000000000000000000..29367605733a96c7ebc7d7304ce283ca9c96f931 GIT binary patch literal 2851 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 H@;3bgc|CQj literal 0 HcmV?d00001 diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@2x.png.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@2x.png.meta new file mode 100644 index 0000000..6515302 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/scene_box@2x.png.meta @@ -0,0 +1,103 @@ +fileFormatVersion: 2 +guid: f834e7df6f42446b59c83dca1e5bd098 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 10 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv new file mode 100644 index 0000000..1d98f33 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv @@ -0,0 +1,4323 @@ +AstarPath.AstarDistribution astarpath.html#AstarDistribution Information about where the package was downloaded. +AstarPath.Branch astarpath.html#Branch Which branch of the A* Pathfinding Project is this release. \n\nUsed when checking for updates so that users of the development versions can get notifications of development updates. +AstarPath.DebugLimitsCtx.debugFloor debuglimitsctx.html#debugFloor +AstarPath.DebugLimitsCtx.debugMode debuglimitsctx.html#debugMode +AstarPath.DebugLimitsCtx.debugPathID debuglimitsctx.html#debugPathID +AstarPath.DebugLimitsCtx.debugPathNodes debuglimitsctx.html#debugPathNodes +AstarPath.DebugLimitsCtx.debugRoof debuglimitsctx.html#debugRoof +AstarPath.DestroyGraphPromise.graph destroygraphpromise.html#graph +AstarPath.Distribution astarpath.html#Distribution Used by the editor to guide the user to the correct place to download updates. +AstarPath.IsAnyGraphUpdateInProgress astarpath.html#IsAnyGraphUpdateInProgress Returns if any graph updates are being calculated right now. \n\n[more in online documentation] +AstarPath.IsAnyGraphUpdateQueued astarpath.html#IsAnyGraphUpdateQueued Returns if any graph updates are waiting to be applied. \n\n[more in online documentation] +AstarPath.IsAnyWorkItemInProgress astarpath.html#IsAnyWorkItemInProgress Returns if any work items are in progress right now. \n\n[more in online documentation] +AstarPath.IsInsideWorkItem astarpath.html#IsInsideWorkItem Returns if this code is currently being exectuted inside a work item. \n\n[more in online documentation]\nIn contrast to IsAnyWorkItemInProgress this is only true when work item code is being executed, it is not true in-between the updates to a work item that takes several frames to complete. +AstarPath.IsUsingMultithreading astarpath.html#IsUsingMultithreading Returns whether or not multithreading is used. \n\n\n[more in online documentation] +AstarPath.NumParallelThreads astarpath.html#NumParallelThreads Number of parallel pathfinders. \n\nReturns the number of concurrent processes which can calculate paths at once. When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used). \n\n[more in online documentation] +AstarPath.On65KOverflow astarpath.html#On65KOverflow Called when pathID overflows 65536 and resets back to zero. \n\n[more in online documentation] +AstarPath.OnAwakeSettings astarpath.html#OnAwakeSettings Called on Awake before anything else is done. \n\nThis is called at the start of the Awake call, right after active has been set, but this is the only thing that has been done.\n\nUse this when you want to set up default settings for an AstarPath component created during runtime since some settings can only be changed in Awake (such as multithreading related stuff) [code in online documentation] +AstarPath.OnGraphPostScan astarpath.html#OnGraphPostScan Called for each graph after they have been scanned. \n\nAll other graphs might not have been scanned yet. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.OnGraphPreScan astarpath.html#OnGraphPreScan Called for each graph before they are scanned. \n\nIn most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.OnGraphsUpdated astarpath.html#OnGraphsUpdated Called when any graphs are updated. \n\nRegister to for example recalculate the path whenever a graph changes. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.OnLatePostScan astarpath.html#OnLatePostScan Called after scanning has completed fully. \n\nThis is called as the last thing in the Scan function. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.OnPathPostSearch astarpath.html#OnPathPostSearch Called for each path after searching. \n\nBe careful when using multithreading since this will be called from a different thread. +AstarPath.OnPathPreSearch astarpath.html#OnPathPreSearch Called for each path before searching. \n\nBe careful when using multithreading since this will be called from a different thread. +AstarPath.OnPathsCalculated astarpath.html#OnPathsCalculated Called right after callbacks on paths have been called. \n\nA path's callback function runs on the main thread when the path has been calculated. This is done in batches for all paths that have finished their calculation since the last frame. This event will trigger right after a batch of callbacks have been called.\n\nIf you do not want to use individual path callbacks, you can use this instead to poll all pending paths and see which ones have completed. This is better than doing it in e.g. the Update loop, because here you will have a guarantee that all calculated paths are still valid. Immediately after this callback has finished, other things may invalidate calculated paths, like for example graph updates.\n\nThis is used by the ECS integration to update all entities' pending paths, without having to store a callback for each agent, and also to avoid the ECS synchronization overhead that having individual callbacks would entail. +AstarPath.OnPostScan astarpath.html#OnPostScan Called after scanning. \n\nThis is called before applying links, flood-filling the graphs and other post processing. In most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.OnPreScan astarpath.html#OnPreScan Called before starting the scanning. \n\nIn most cases it is recommended to create a custom class which inherits from Pathfinding.GraphModifier instead. +AstarPath.Version astarpath.html#Version The version number for the A* Pathfinding Project. +AstarPath.active astarpath.html#active Returns the active AstarPath object in the scene. \n\n[more in online documentation] +AstarPath.asyncScanTask astarpath.html#asyncScanTask If an async scan is running, this will be set to the coroutine. \n\nThis primarily used to be able to force the async scan to complete immediately, if the AstarPath component should happen to be destroyed while an async scan is running. +AstarPath.batchGraphUpdates astarpath.html#batchGraphUpdates Throttle graph updates and batch them to improve performance. \n\nIf toggled, graph updates will batched and executed less often (specified by graphUpdateBatchingInterval).\n\nThis can have a positive impact on pathfinding throughput since the pathfinding threads do not need to be stopped as often, and it reduces the overhead per graph update. All graph updates are still applied, they are just batched together so that more of them are applied at the same time.\n\nDo not use this if you want minimal latency between a graph update being requested and it being applied.\n\nThis only applies to graph updates requested using the UpdateGraphs method. Not those requested using AddWorkItem.\n\nIf you want to apply graph updates immediately at some point, you can call FlushGraphUpdates.\n\n[more in online documentation] +AstarPath.colorSettings astarpath.html#colorSettings Reference to the color settings for this AstarPath object. \n\nColor settings include for example which color the nodes should be in, in the sceneview. +AstarPath.data astarpath.html#data Holds all graph data. +AstarPath.debugFloor astarpath.html#debugFloor Low value to use for certain debugMode modes. \n\nFor example if debugMode is set to G, this value will determine when the node will be completely red.\n\n[more in online documentation] +AstarPath.debugMode astarpath.html#debugMode The mode to use for drawing nodes in the sceneview. \n\n[more in online documentation] +AstarPath.debugPathData astarpath.html#debugPathData The path to debug using gizmos. \n\nThis is the path handler used to calculate the last path. It is used in the editor to draw debug information using gizmos. +AstarPath.debugPathID astarpath.html#debugPathID The path ID to debug using gizmos. +AstarPath.debugRoof astarpath.html#debugRoof High value to use for certain debugMode modes. \n\nFor example if debugMode is set to G, this value will determine when the node will be completely green.\n\nFor the penalty debug mode, the nodes will be colored green when they have a penalty less than debugFloor and red when their penalty is greater or equal to this value and something between red and green otherwise.\n\n[more in online documentation] +AstarPath.effectiveHeuristicScale astarpath.html#effectiveHeuristicScale The effective heuristic scale. \n\nThis is the heuristic scale used for pathfinding. Zero is returned if heuristic is set to None, otherwise heuristicScale is returned. \n\n[more in online documentation] +AstarPath.euclideanEmbedding astarpath.html#euclideanEmbedding Holds settings for heuristic optimization. \n\n[more in online documentation]\n [more in online documentation] +AstarPath.fullGetNearestSearch astarpath.html#fullGetNearestSearch Do a full GetNearest search for all graphs. \n\nAdditional searches will normally only be done on the graph which in the first fast search seemed to have the closest node. With this setting on, additional searches will be done on all graphs since the first check is not always completely accurate.\n\nMore technically: GetNearestForce on all graphs will be called if true, otherwise only on the one graph which's GetNearest search returned the best node.\n\nUsually faster when disabled, but higher quality searches when enabled. \n\n[more in online documentation] +AstarPath.graphDataLock astarpath.html#graphDataLock Global read-write lock for graph data. \n\nGraph data is always consistent from the main-thread's perspective, but if you are using jobs to read from graph data, you may need this.\n\nA write lock is held automatically...\n\n- During graph updates. During async graph updates, the lock is only held once per frame while the graph update is actually running, not for the whole duration.\n\n- During work items. Async work items work similarly to graph updates, the lock is only held once per frame while the work item is actually running.\n\n- When GraphModifier events run.\n\n- When graph related callbacks, such as OnGraphsUpdated, run.\n\n- During the last step of a graph's scanning process, when the new graph data is being made visible to the rest of the game. See ScanningStage.\n\n\nTo use e.g. AstarPath.active.GetNearest from an ECS job, you'll need to acquire a read lock first, and make sure the lock is only released when the job is finished.\n\n[code in online documentation]\n\n[more in online documentation] +AstarPath.graphUpdateBatchingInterval astarpath.html#graphUpdateBatchingInterval Minimum number of seconds between each batch of graph updates. \n\nIf batchGraphUpdates is true, this defines the minimum number of seconds between each batch of graph updates.\n\nThis can have a positive impact on pathfinding throughput since the pathfinding threads do not need to be stopped as often, and it reduces the overhead per graph update. All graph updates are still applied however, they are just batched together so that more of them are applied at the same time.\n\nDo not use this if you want minimal latency between a graph update being requested and it being applied.\n\nThis only applies to graph updates requested using the UpdateGraphs method. Not those requested using AddWorkItem.\n\n[more in online documentation] +AstarPath.graphUpdateDebugMode astarpath.html#graphUpdateDebugMode How to visualize graph updates in the editor. \n\nThis is used to visualize the bounds of graph updates, and how they affect the graph.\n\nIf showGraphsInStandalonePlayer is enabled, the visualization will also be shown in a standalone player.\n\n [image in online documentation][image in online documentation]\n\n[more in online documentation] +AstarPath.graphUpdates astarpath.html#graphUpdates Processes graph updates. +AstarPath.graphUpdatesWorkItemAdded astarpath.html#graphUpdatesWorkItemAdded Makes sure QueueGraphUpdates will not queue multiple graph update orders. +AstarPath.graphs astarpath.html#graphs Shortcut to AstarData.graphs. +AstarPath.hasScannedGraphAtStartup astarpath.html#hasScannedGraphAtStartup +AstarPath.heuristic astarpath.html#heuristic The distance function to use as a heuristic. \n\nThe heuristic, often referred to as just 'H' is the estimated cost from a node to the target. Different heuristics affect how the path picks which one to follow from multiple possible with the same length \n\n[more in online documentation] +AstarPath.heuristicScale astarpath.html#heuristicScale The scale of the heuristic. \n\nIf a value lower than 1 is used, the pathfinder will search more nodes (slower). If 0 is used, the pathfinding algorithm will be reduced to dijkstra's algorithm. This is equivalent to setting heuristic to None. If a value larger than 1 is used the pathfinding will (usually) be faster because it expands fewer nodes, but the paths may no longer be the optimal (i.e the shortest possible paths).\n\nUsually you should leave this to the default value of 1.\n\n[more in online documentation] +AstarPath.hierarchicalGraph astarpath.html#hierarchicalGraph Holds a hierarchical graph to speed up some queries like if there is a path between two nodes. +AstarPath.inGameDebugPath astarpath.html#inGameDebugPath Debug string from the last completed path. \n\nWill be updated if logPathResults == PathLog.InGame +AstarPath.isScanning astarpath.html#isScanning True while any graphs are being scanned. \n\nThis is primarily relevant when scanning graph asynchronously.\n\n[more in online documentation] +AstarPath.lastGraphUpdate astarpath.html#lastGraphUpdate Time the last graph update was done. \n\nUsed to group together frequent graph updates to batches +AstarPath.lastScanTime astarpath.html#lastScanTime The time it took for the last call to Scan to complete. +AstarPath.logPathResults astarpath.html#logPathResults The amount of debugging messages. \n\nUse less debugging to improve performance (a bit) or just to get rid of the Console spamming. Use more debugging (heavy) if you want more information about what the pathfinding scripts are doing. The InGame option will display the latest path log using in-game GUI.\n\n [image in online documentation] +AstarPath.manualDebugFloorRoof astarpath.html#manualDebugFloorRoof If set, the debugFloor and debugRoof values will not be automatically recalculated. \n\n[more in online documentation] +AstarPath.maxFrameTime astarpath.html#maxFrameTime Max number of milliseconds to spend on pathfinding during each frame. \n\nAt least 500 nodes will be searched each frame (if there are that many to search). When using multithreading this value is irrelevant. +AstarPath.maxNearestNodeDistance astarpath.html#maxNearestNodeDistance Maximum distance to search for nodes. \n\nWhen searching for the nearest node to a point, this is the limit (in world units) for how far away it is allowed to be.\n\nThis is relevant if you try to request a path to a point that cannot be reached and it thus has to search for the closest node to that point which can be reached (which might be far away). If it cannot find a node within this distance then the path will fail.\n\n [image in online documentation]\n\n[more in online documentation] +AstarPath.maxNearestNodeDistanceSqr astarpath.html#maxNearestNodeDistanceSqr Max Nearest Node Distance Squared. \n\n[more in online documentation] +AstarPath.navmeshUpdates astarpath.html#navmeshUpdates Handles navmesh cuts. \n\n[more in online documentation] +AstarPath.nextFreePathID astarpath.html#nextFreePathID The next unused Path ID. \n\nIncremented for every call to GetNextPathID +AstarPath.nodeStorage astarpath.html#nodeStorage Holds global node data that cannot be stored in individual graphs. +AstarPath.offMeshLinks astarpath.html#offMeshLinks Holds all active off-mesh links. +AstarPath.pathProcessor astarpath.html#pathProcessor Holds all paths waiting to be calculated and calculates them. +AstarPath.pathReturnQueue astarpath.html#pathReturnQueue Holds all completed paths waiting to be returned to where they were requested. +AstarPath.prioritizeGraphs astarpath.html#prioritizeGraphs Prioritize graphs. \n\nGraphs will be prioritized based on their order in the inspector. The first graph which has a node closer than prioritizeGraphsLimit will be chosen instead of searching all graphs.\n\n[more in online documentation] +AstarPath.prioritizeGraphsLimit astarpath.html#prioritizeGraphsLimit Distance limit for prioritizeGraphs. \n\n[more in online documentation] +AstarPath.scanOnStartup astarpath.html#scanOnStartup If true, all graphs will be scanned when the game starts, during OnEnable. \n\nIf you disable this, you will have to call AstarPath.active.Scan yourself to enable pathfinding. Alternatively you could load a saved graph from a file.\n\nIf a startup cache has been generated (see Saving and Loading Graphs), it always takes priority, and the graphs will be loaded from the cache instead of scanned.\n\nThis can be useful to disable if you want to scan your graphs asynchronously, or if you have a procedural world which has not been created yet at the start of the game.\n\n[more in online documentation] +AstarPath.showGraphs astarpath.html#showGraphs Shows or hides graph inspectors. \n\nUsed internally by the editor.\n\n[more in online documentation] +AstarPath.showGraphsInStandalonePlayer astarpath.html#showGraphsInStandalonePlayer Shows or hides the graph visualizations in standalone games. \n\nNormally, graph visualizations are only shown in the editor. If this is set to true, the graph visualizations will also be shown in standalone games.\n\n[more in online documentation] +AstarPath.showNavGraphs astarpath.html#showNavGraphs Visualize graphs in the scene view (editor only). \n\n [image in online documentation]\n\n[more in online documentation] +AstarPath.showSearchTree astarpath.html#showSearchTree If enabled, nodes will draw a line to their 'parent'. \n\nThis will show the search tree for the latest path.\n\n[more in online documentation] +AstarPath.showUnwalkableNodes astarpath.html#showUnwalkableNodes Toggle to show unwalkable nodes. \n\n[more in online documentation] +AstarPath.tagNames astarpath.html#tagNames Stored tag names. \n\n[more in online documentation] +AstarPath.threadCount astarpath.html#threadCount Number of pathfinding threads to use. \n\nMultithreading puts pathfinding in another thread, this is great for performance on 2+ core computers since the framerate will barely be affected by the pathfinding at all.\n- None indicates that the pathfinding is run in the Unity thread as a coroutine\n\n- Automatic will try to adjust the number of threads to the number of cores and memory on the computer. Less than 512mb of memory or a single core computer will make it revert to using no multithreading.\n\n\nIt is recommended that you use one of the "Auto" settings that are available. The reason is that even if your computer might be beefy and have 8 cores. Other computers might only be quad core or dual core in which case they will not benefit from more than 1 or 3 threads respectively (you usually want to leave one core for the unity thread). If you use more threads than the number of cores on the computer it is mostly just wasting memory, it will not run any faster. The extra memory usage is not trivially small. Each thread needs to keep a small amount of data for each node in all the graphs. It is not the full graph data but it is proportional to the number of nodes. The automatic settings will inspect the machine it is running on and use that to determine the number of threads so that no memory is wasted.\n\nThe exception is if you only have one (or maybe two characters) active at time. Then you should probably just go with one thread always since it is very unlikely that you will need the extra throughput given by more threads. Keep in mind that more threads primarily increases throughput by calculating different paths on different threads, it will not calculate individual paths any faster.\n\n[more in online documentation]\n\n\n\n[more in online documentation]\n [more in online documentation] +AstarPath.unwalkableNodeDebugSize astarpath.html#unwalkableNodeDebugSize Size of the red cubes shown in place of unwalkable nodes. \n\n[more in online documentation] +AstarPath.waitForPathDepth astarpath.html#waitForPathDepth +AstarPath.workItemLock astarpath.html#workItemLock Held if any work items are currently queued. +AstarPath.workItems astarpath.html#workItems Processes work items. +Pathfinding.ABPath.calculatePartial abpath.html#calculatePartial Calculate partial path if the target node cannot be reached. \n\nIf the target node cannot be reached, the node which was closest (given by heuristic) will be chosen as target node and a partial path will be returned. This only works if a heuristic is used (which is the default). If a partial path is found, CompleteState is set to Partial. \n\n[more in online documentation]\nThe endNode and endPoint will be modified and be set to the node which ends up being closest to the target.\n\n[more in online documentation] +Pathfinding.ABPath.cost abpath.html#cost Total cost of this path as used by the pathfinding algorithm. \n\nThe cost is influenced by both the length of the path, as well as any tags or penalties on the nodes. By default, the cost to move 1 world unit is Int3.Precision.\n\nIf the path failed, the cost will be set to zero.\n\n[more in online documentation] +Pathfinding.ABPath.endNode abpath.html#endNode End node of the path. +Pathfinding.ABPath.endPoint abpath.html#endPoint End point of the path. \n\nThis is the closest point on the endNode to originalEndPoint +Pathfinding.ABPath.endPointKnownBeforeCalculation abpath.html#endPointKnownBeforeCalculation True if this path type has a well defined end point, even before calculation starts. \n\nThis is for example true for the ABPath type, but false for the RandomPath type. +Pathfinding.ABPath.endingCondition abpath.html#endingCondition Optional ending condition for the path. \n\nThe ending condition determines when the path has been completed. Can be used to for example mark a path as complete when it is within a specific distance from the target.\n\nIf ending conditions are used that are not centered around the endpoint of the path, then you should also set the heuristic to None to ensure the path is still optimal. The performance impact of setting the heuristic to None is quite large, so you might want to try to run it with the default heuristic to see if the path is good enough for your use case anyway.\n\nIf null, no custom ending condition will be used. This means that the path will end when the target node has been reached.\n\n[more in online documentation] +Pathfinding.ABPath.hasEndPoint abpath.html#hasEndPoint Determines if a search for an end node should be done. \n\nSet by different path types. \n\n[more in online documentation] +Pathfinding.ABPath.originalEndPoint abpath.html#originalEndPoint End Point exactly as in the path request. +Pathfinding.ABPath.originalStartPoint abpath.html#originalStartPoint Start Point exactly as in the path request. +Pathfinding.ABPath.partialBestTargetGScore abpath.html#partialBestTargetGScore +Pathfinding.ABPath.partialBestTargetHScore abpath.html#partialBestTargetHScore +Pathfinding.ABPath.partialBestTargetPathNodeIndex abpath.html#partialBestTargetPathNodeIndex Current best target for the partial path. \n\nThis is the node with the lowest H score. +Pathfinding.ABPath.startNode abpath.html#startNode Start node of the path. +Pathfinding.ABPath.startPoint abpath.html#startPoint Start point of the path. \n\nThis is the closest point on the startNode to originalStartPoint +Pathfinding.ABPathEndingCondition.abPath abpathendingcondition.html#abPath Path which this ending condition is used on. \n\nSame as path but downcasted to ABPath +Pathfinding.AIBase.ShapeGizmoColor aibase.html#ShapeGizmoColor +Pathfinding.AIBase.accumulatedMovementDelta aibase.html#accumulatedMovementDelta Accumulated movement deltas from the Move method. +Pathfinding.AIBase.autoRepath aibase.html#autoRepath Determines how the agent recalculates its path automatically. \n\nThis corresponds to the settings under the "Recalculate Paths Automatically" field in the inspector. +Pathfinding.AIBase.canMove aibase.html#canMove Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nThis is also useful if you want to have full control over when the movement calculations run. Take a look at MovementUpdate\n\n[more in online documentation] +Pathfinding.AIBase.canSearch aibase.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.AIBase.canSearchCompability aibase.html#canSearchCompability +Pathfinding.AIBase.controller aibase.html#controller Cached CharacterController component. +Pathfinding.AIBase.desiredVelocity aibase.html#desiredVelocity Velocity that this agent wants to move with. \n\nIncludes gravity and local avoidance if applicable. In world units per second.\n\n[more in online documentation] +Pathfinding.AIBase.desiredVelocityWithoutLocalAvoidance aibase.html#desiredVelocityWithoutLocalAvoidance Velocity that this agent wants to move with before taking local avoidance into account. \n\nIncludes gravity. In world units per second.\n\nSetting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.\n\n[code in online documentation]\n\n[more in online documentation]\n\n\nIf you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.\n\n[more in online documentation] +Pathfinding.AIBase.destination aibase.html#destination Position in the world that this agent should move to. \n\nIf no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.\n\nNote that setting this property does not immediately cause the agent to recalculate its path. So it may take some time before the agent starts to move towards this point. Most movement scripts have a repathRate field which indicates how often the agent looks for a new path. You can also call the SearchPath method to immediately start to search for a new path. Paths are calculated asynchronously so when an agent starts to search for path it may take a few frames (usually 1 or 2) until the result is available. During this time the pathPending property will return true.\n\nIf you are setting a destination and then want to know when the agent has reached that destination then you could either use reachedDestination (recommended) or check both pathPending and reachedEndOfPath. Check the documentation for the respective fields to learn about their differences.\n\n[code in online documentation][code in online documentation] +Pathfinding.AIBase.destinationBackingField aibase.html#destinationBackingField Backing field for destination. +Pathfinding.AIBase.enableRotation aibase.html#enableRotation If true, the AI will rotate to face the movement direction. \n\n[more in online documentation] +Pathfinding.AIBase.endOfPath aibase.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.\n\nThis is only updated when the path is recalculated. +Pathfinding.AIBase.endReachedDistance aibase.html#endReachedDistance Distance to the end point to consider the end of path to be reached. \n\nWhen the end of the path is within this distance then IAstarAI.reachedEndOfPath will return true. When the destination is within this distance then IAstarAI.reachedDestination will return true.\n\nNote that the destination may not be reached just because the end of the path was reached. The destination may not be reachable at all.\n\n[more in online documentation] +Pathfinding.AIBase.gravity aibase.html#gravity Gravity to use. \n\nIf set to (NaN,NaN,NaN) then Physics.Gravity (configured in the Unity project settings) will be used. If set to (0,0,0) then no gravity will be used and no raycast to check for ground penetration will be performed. +Pathfinding.AIBase.groundMask aibase.html#groundMask Layer mask to use for ground placement. \n\nMake sure this does not include the layer of any colliders attached to this gameobject.\n\n[more in online documentation] +Pathfinding.AIBase.height aibase.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.\n\n[more in online documentation] +Pathfinding.AIBase.isStopped aibase.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.AIBase.lastDeltaPosition aibase.html#lastDeltaPosition Amount which the character wants or tried to move with during the last frame. +Pathfinding.AIBase.lastDeltaTime aibase.html#lastDeltaTime Delta time used for movement during the last frame. +Pathfinding.AIBase.lastRaycastHit aibase.html#lastRaycastHit Hit info from the last raycast done for ground placement. \n\nWill not update unless gravity is used (if no gravity is used, then raycasts are disabled).\n\n[more in online documentation] +Pathfinding.AIBase.lastRepath aibase.html#lastRepath Time when the last path request was started. +Pathfinding.AIBase.maxSpeed aibase.html#maxSpeed Max speed in world units per second. +Pathfinding.AIBase.movementPlane aibase.html#movementPlane Plane which this agent is moving in. \n\nThis is used to convert between world space and a movement plane to make it possible to use this script in both 2D games and 3D games. +Pathfinding.AIBase.onPathComplete aibase.html#onPathComplete Cached delegate for the OnPathComplete method. \n\nCaching this avoids allocating a new one every time a path is calculated, which reduces GC pressure. +Pathfinding.AIBase.onSearchPath aibase.html#onSearchPath Called when the agent recalculates its path. \n\nThis is called both for automatic path recalculations (see canSearch) and manual ones (see SearchPath).\n\n[more in online documentation] +Pathfinding.AIBase.orientation aibase.html#orientation Determines which direction the agent moves in. \n\nFor 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.\n\nUsing the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph) will all tell the agent which movement plane it is supposed to use.\n\n [image in online documentation] +Pathfinding.AIBase.position aibase.html#position Position of the agent. \n\nIn world space. If updatePosition is true then this value is idential to transform.position. \n\n[more in online documentation] +Pathfinding.AIBase.prevPosition1 aibase.html#prevPosition1 Position of the character at the end of the last frame. +Pathfinding.AIBase.prevPosition2 aibase.html#prevPosition2 Position of the character at the end of the frame before the last frame. +Pathfinding.AIBase.radius aibase.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.AIBase.reachedDestination aibase.html#reachedDestination True if the ai has reached the destination. \n\nThis is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within AIPath.endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).\n\nThis value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than AIPath.endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.\n\nFurthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).\n\nThe cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.\n\nIn contrast to reachedEndOfPath, this property is immediately updated when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.AIBase.repathRate aibase.html#repathRate Determines how often the agent will search for new paths (in seconds). \n\nThe agent will plan a new path to the target every N seconds.\n\nIf you have fast moving targets or AIs, you might want to set it to a lower value.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.AIBase.repathRateCompatibility aibase.html#repathRateCompatibility +Pathfinding.AIBase.rigid aibase.html#rigid Cached Rigidbody component. +Pathfinding.AIBase.rigid2D aibase.html#rigid2D Cached Rigidbody component. +Pathfinding.AIBase.rotation aibase.html#rotation Rotation of the agent. \n\nIf updateRotation is true then this value is identical to transform.rotation. +Pathfinding.AIBase.rvoController aibase.html#rvoController Cached RVOController component. +Pathfinding.AIBase.rvoDensityBehavior aibase.html#rvoDensityBehavior Controls if the agent slows down to a stop if the area around the destination is crowded. \n\nUsing this module requires that local avoidance is used: i.e. that an RVOController is attached to the GameObject.\n\n[more in online documentation]\n [more in online documentation] +Pathfinding.AIBase.seeker aibase.html#seeker Cached Seeker component. +Pathfinding.AIBase.shouldRecalculatePath aibase.html#shouldRecalculatePath True if the path should be automatically recalculated as soon as possible. +Pathfinding.AIBase.simulateMovement aibase.html#simulateMovement Enables or disables movement simulations completely. \n\nThis is primarily useful if you want to override the default movement behavior. If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. reachedDestination). Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement.\n\nIf you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use isStopped instead. If you want to make the agent stop immediately, disable the whole component instead.\n\n[more in online documentation] +Pathfinding.AIBase.simulatedPosition aibase.html#simulatedPosition Position of the agent. \n\nIf updatePosition is true then this value will be synchronized every frame with Transform.position. +Pathfinding.AIBase.simulatedRotation aibase.html#simulatedRotation Rotation of the agent. \n\nIf updateRotation is true then this value will be synchronized every frame with Transform.rotation. +Pathfinding.AIBase.startHasRun aibase.html#startHasRun True if the Start method has been executed. \n\nUsed to test if coroutines should be started in OnEnable to prevent calculating paths in the awake stage (or rather before start on frame 0). +Pathfinding.AIBase.tr aibase.html#tr Cached Transform component. +Pathfinding.AIBase.updatePosition aibase.html#updatePosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the object that this component is attached to will not move instead only the position property will change.\n\nThis is useful if you want to control the movement of the character using some other means such as for example root motion but still want the AI to move freely. \n\n[more in online documentation] +Pathfinding.AIBase.updateRotation aibase.html#updateRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate instead only the rotation property will change.\n\n[more in online documentation] +Pathfinding.AIBase.usingGravity aibase.html#usingGravity Indicates if gravity is used during this frame. +Pathfinding.AIBase.velocity aibase.html#velocity Actual velocity that the agent is moving with. \n\nIn world units per second.\n\n[more in online documentation] +Pathfinding.AIBase.velocity2D aibase.html#velocity2D Current desired velocity of the agent (does not include local avoidance and physics). \n\nLies in the movement plane. +Pathfinding.AIBase.verticalVelocity aibase.html#verticalVelocity Velocity due to gravity. \n\nPerpendicular to the movement plane.\n\nWhen the agent is grounded this may not accurately reflect the velocity of the agent. It may be non-zero even though the agent is not moving. +Pathfinding.AIBase.waitingForPathCalculation aibase.html#waitingForPathCalculation Only when the previous path has been calculated should the script consider searching for a new path. +Pathfinding.AIBase.whenCloseToDestination aibase.html#whenCloseToDestination What to do when within endReachedDistance units from the destination. \n\nThe character can either stop immediately when it comes within that distance, which is useful for e.g archers or other ranged units that want to fire on a target. Or the character can continue to try to reach the exact destination point and come to a full stop there. This is useful if you want the character to reach the exact point that you specified.\n\n[more in online documentation] +Pathfinding.AIDestinationSetter.ai aidestinationsetter.html#ai +Pathfinding.AIDestinationSetter.entity aidestinationsetter.html#entity +Pathfinding.AIDestinationSetter.target aidestinationsetter.html#target The object that the AI should move to. +Pathfinding.AIDestinationSetter.useRotation aidestinationsetter.html#useRotation If true, the agent will try to align itself with the rotation of the target. \n\nThis can only be used together with the FollowerEntity movement script. Other movement scripts will ignore it.\n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.AIDestinationSetter.world aidestinationsetter.html#world +Pathfinding.AILerp.autoRepath ailerp.html#autoRepath Determines how the agent recalculates its path automatically. \n\nThis corresponds to the settings under the "Recalculate Paths Automatically" field in the inspector. +Pathfinding.AILerp.canMove ailerp.html#canMove Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nThis is also useful if you want to have full control over when the movement calculations run. Take a look at MovementUpdate\n\n[more in online documentation] +Pathfinding.AILerp.canSearch ailerp.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.AILerp.canSearchAgain ailerp.html#canSearchAgain Only when the previous path has been returned should a search for a new path be done. +Pathfinding.AILerp.canSearchCompability ailerp.html#canSearchCompability +Pathfinding.AILerp.desiredVelocity ailerp.html#desiredVelocity Velocity that this agent wants to move with. \n\nIncludes gravity and local avoidance if applicable. In world units per second.\n\n[more in online documentation] +Pathfinding.AILerp.desiredVelocityWithoutLocalAvoidance ailerp.html#desiredVelocityWithoutLocalAvoidance Velocity that this agent wants to move with before taking local avoidance into account. \n\nIncludes gravity. In world units per second.\n\nSetting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.\n\n[code in online documentation]\n\n[more in online documentation]\n\n\nIf you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.\n\n[more in online documentation] +Pathfinding.AILerp.destination ailerp.html#destination Position in the world that this agent should move to. \n\nIf no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.\n\nNote that setting this property does not immediately cause the agent to recalculate its path. So it may take some time before the agent starts to move towards this point. Most movement scripts have a repathRate field which indicates how often the agent looks for a new path. You can also call the SearchPath method to immediately start to search for a new path. Paths are calculated asynchronously so when an agent starts to search for path it may take a few frames (usually 1 or 2) until the result is available. During this time the pathPending property will return true.\n\nIf you are setting a destination and then want to know when the agent has reached that destination then you could either use reachedDestination (recommended) or check both pathPending and reachedEndOfPath. Check the documentation for the respective fields to learn about their differences.\n\n[code in online documentation][code in online documentation] +Pathfinding.AILerp.enableRotation ailerp.html#enableRotation If true, the AI will rotate to face the movement direction. \n\n[more in online documentation] +Pathfinding.AILerp.endOfPath ailerp.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.\n\nThis is only updated when the path is recalculated. +Pathfinding.AILerp.hasPath ailerp.html#hasPath True if this agent currently has a path that it follows. +Pathfinding.AILerp.height ailerp.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.\n\n[more in online documentation] +Pathfinding.AILerp.interpolatePathSwitches ailerp.html#interpolatePathSwitches If true, some interpolation will be done when a new path has been calculated. \n\nThis is used to avoid short distance teleportation. \n\n[more in online documentation] +Pathfinding.AILerp.interpolator ailerp.html#interpolator +Pathfinding.AILerp.interpolatorPath ailerp.html#interpolatorPath +Pathfinding.AILerp.isStopped ailerp.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.AILerp.maxSpeed ailerp.html#maxSpeed Max speed in world units per second. +Pathfinding.AILerp.movementPlane ailerp.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.AILerp.onPathComplete ailerp.html#onPathComplete Cached delegate for the OnPathComplete method. \n\nCaching this avoids allocating a new one every time a path is calculated, which reduces GC pressure. +Pathfinding.AILerp.onSearchPath ailerp.html#onSearchPath Called when the agent recalculates its path. \n\nThis is called both for automatic path recalculations (see canSearch) and manual ones (see SearchPath).\n\n[more in online documentation] +Pathfinding.AILerp.orientation ailerp.html#orientation Determines which direction the agent moves in. \n\nFor 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.\n\nUsing the YAxisForward option will also allow the agent to assume that the movement will happen in the 2D (XY) plane instead of the XZ plane if it does not know. This is important only for the point graph which does not have a well defined up direction. The other built-in graphs (e.g the grid graph) will all tell the agent which movement plane it is supposed to use.\n\n [image in online documentation] +Pathfinding.AILerp.path ailerp.html#path Current path which is followed. +Pathfinding.AILerp.pathPending ailerp.html#pathPending True if a path is currently being calculated. +Pathfinding.AILerp.pathSwitchInterpolationTime ailerp.html#pathSwitchInterpolationTime Time since the path was replaced by a new path. \n\n[more in online documentation] +Pathfinding.AILerp.position ailerp.html#position Position of the agent. \n\nIn world space. \n\n[more in online documentation]\nIf you want to move the agent you may use Teleport or Move. +Pathfinding.AILerp.previousMovementDirection ailerp.html#previousMovementDirection +Pathfinding.AILerp.previousMovementOrigin ailerp.html#previousMovementOrigin When a new path was returned, the AI was moving along this ray. \n\nUsed to smoothly interpolate between the previous movement and the movement along the new path. The speed is equal to movement direction. +Pathfinding.AILerp.previousPosition1 ailerp.html#previousPosition1 +Pathfinding.AILerp.previousPosition2 ailerp.html#previousPosition2 +Pathfinding.AILerp.radius ailerp.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.AILerp.reachedDestination ailerp.html#reachedDestination True if the ai has reached the destination. \n\nThis is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within AIPath.endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).\n\nThis value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than AIPath.endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.\n\nFurthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).\n\nThe cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.\n\nIn contrast to reachedEndOfPath, this property is immediately updated when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.AILerp.reachedEndOfPath ailerp.html#reachedEndOfPath True if the end of the current path has been reached. +Pathfinding.AILerp.remainingDistance ailerp.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThe RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. However it will be accurate when the agent is within 1 corner of the destination. You can use GetRemainingPath to calculate the actual remaining path more precisely.\n\nThe AIPath and AILerp scripts use a more accurate distance calculation at all times.\n\nIf the agent does not currently have a path, then positive infinity will be returned.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.AILerp.repathRate ailerp.html#repathRate Determines how often it will search for new paths. \n\nIf you have fast moving targets or AIs, you might want to set it to a lower value. The value is in seconds between path requests.\n\n[more in online documentation] +Pathfinding.AILerp.repathRateCompatibility ailerp.html#repathRateCompatibility +Pathfinding.AILerp.rotation ailerp.html#rotation Rotation of the agent. \n\nIn world space. \n\n[more in online documentation] +Pathfinding.AILerp.rotationSpeed ailerp.html#rotationSpeed How quickly to rotate. +Pathfinding.AILerp.seeker ailerp.html#seeker Cached Seeker component. +Pathfinding.AILerp.shouldRecalculatePath ailerp.html#shouldRecalculatePath True if the path should be automatically recalculated as soon as possible. +Pathfinding.AILerp.simulateMovement ailerp.html#simulateMovement Enables or disables movement simulations completely. \n\nThis is primarily useful if you want to override the default movement behavior. If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. reachedDestination). Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement.\n\nIf you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use isStopped instead. If you want to make the agent stop immediately, disable the whole component instead.\n\n[more in online documentation] +Pathfinding.AILerp.simulatedPosition ailerp.html#simulatedPosition +Pathfinding.AILerp.simulatedRotation ailerp.html#simulatedRotation +Pathfinding.AILerp.speed ailerp.html#speed Speed in world units. +Pathfinding.AILerp.startHasRun ailerp.html#startHasRun Holds if the Start function has been run. \n\nUsed to test if coroutines should be started in OnEnable to prevent calculating paths in the awake stage (or rather before start on frame 0). +Pathfinding.AILerp.steeringTarget ailerp.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.AILerp.switchPathInterpolationSpeed ailerp.html#switchPathInterpolationSpeed How quickly to interpolate to the new path. \n\n[more in online documentation] +Pathfinding.AILerp.tr ailerp.html#tr Cached Transform component. +Pathfinding.AILerp.updatePosition ailerp.html#updatePosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the object that this component is attached to will not move instead only the position property will change.\n\n[more in online documentation] +Pathfinding.AILerp.updateRotation ailerp.html#updateRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the object that this component is attached to will not rotate instead only the rotation property will change.\n\n[more in online documentation] +Pathfinding.AILerp.velocity ailerp.html#velocity Actual velocity that the agent is moving with. \n\nIn world units per second.\n\n[more in online documentation] +Pathfinding.AIPath.GizmoColor aipath.html#GizmoColor +Pathfinding.AIPath.alwaysDrawGizmos aipath.html#alwaysDrawGizmos Draws detailed gizmos constantly in the scene view instead of only when the agent is selected and settings are being modified. +Pathfinding.AIPath.canSearch aipath.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.AIPath.constrainInsideGraph aipath.html#constrainInsideGraph Ensure that the character is always on the traversable surface of the navmesh. \n\nWhen this option is enabled a GetNearest query will be done every frame to find the closest node that the agent can walk on and if the agent is not inside that node, then the agent will be moved to it.\n\nThis is especially useful together with local avoidance in order to avoid agents pushing each other into walls. \n\n[more in online documentation]\nThis option also integrates with local avoidance so that if the agent is say forced into a wall by other agents the local avoidance system will be informed about that wall and can take that into account.\n\nEnabling this has some performance impact depending on the graph type (pretty fast for grid graphs, slightly slower for navmesh/recast graphs). If you are using a navmesh/recast graph you may want to switch to the RichAI movement script which is specifically written for navmesh/recast graphs and does this kind of clamping out of the box. In many cases it can also follow the path more smoothly around sharp bends in the path.\n\nIt is not recommended that you use this option together with the funnel modifier on grid graphs because the funnel modifier will make the path go very close to the border of the graph and this script has a tendency to try to cut corners a bit. This may cause it to try to go slightly outside the traversable surface near corners and that will look bad if this option is enabled.\n\n[more in online documentation]\nBelow you can see an image where several agents using local avoidance were ordered to go to the same point in a corner. When not constraining the agents to the graph they are easily pushed inside obstacles. [image in online documentation] +Pathfinding.AIPath.endOfPath aipath.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.\n\nThis is only updated when the path is recalculated. +Pathfinding.AIPath.gizmoHash aipath.html#gizmoHash +Pathfinding.AIPath.hasPath aipath.html#hasPath True if this agent currently has a path that it follows. +Pathfinding.AIPath.height aipath.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.\n\n[more in online documentation] +Pathfinding.AIPath.interpolator aipath.html#interpolator Represents the current steering target for the agent. +Pathfinding.AIPath.interpolatorPath aipath.html#interpolatorPath Helper which calculates points along the current path. +Pathfinding.AIPath.lastChangedTime aipath.html#lastChangedTime +Pathfinding.AIPath.maxAcceleration aipath.html#maxAcceleration How quickly the agent accelerates. \n\nPositive values represent an acceleration in world units per second squared. Negative values are interpreted as an inverse time of how long it should take for the agent to reach its max speed. For example if it should take roughly 0.4 seconds for the agent to reach its max speed then this field should be set to -1/0.4 = -2.5. For a negative value the final acceleration will be: -acceleration*maxSpeed. This behaviour exists mostly for compatibility reasons.\n\nIn the Unity inspector there are two modes: Default and Custom. In the Default mode this field is set to -2.5 which means that it takes about 0.4 seconds for the agent to reach its top speed. In the Custom mode you can set the acceleration to any positive value. +Pathfinding.AIPath.maxSpeed aipath.html#maxSpeed Max speed in world units per second. +Pathfinding.AIPath.movementPlane aipath.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.AIPath.path aipath.html#path Current path which is followed. +Pathfinding.AIPath.pathPending aipath.html#pathPending True if a path is currently being calculated. +Pathfinding.AIPath.pickNextWaypointDist aipath.html#pickNextWaypointDist How far the AI looks ahead along the path to determine the point it moves to. \n\nIn world units. If you enable the alwaysDrawGizmos toggle this value will be visualized in the scene view as a blue circle around the agent. [image in online documentation]\n\nHere are a few example videos showing some typical outcomes with good values as well as how it looks when this value is too low and too high. [more in online documentation] +Pathfinding.AIPath.preventMovingBackwards aipath.html#preventMovingBackwards Prevent the velocity from being too far away from the forward direction of the character. \n\nIf the character is ordered to move in the opposite direction from where it is facing then enabling this will cause it to make a small loop instead of turning on the spot.\n\nThis setting only has an effect if slowWhenNotFacingTarget is enabled. +Pathfinding.AIPath.radius aipath.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.AIPath.reachedDestination aipath.html#reachedDestination True if the ai has reached the destination. \n\nThis is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within AIPath.endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).\n\nThis value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than AIPath.endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.\n\nFurthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).\n\nThe cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.\n\nIn contrast to reachedEndOfPath, this property is immediately updated when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.AIPath.reachedEndOfPath aipath.html#reachedEndOfPath True if the agent has reached the end of the current path. \n\nNote that setting the destination does not immediately update the path, nor is there any guarantee that the AI will actually be able to reach the destination that you set. The AI will try to get as close as possible. Often you want to use reachedDestination instead which is easier to work with.\n\nIt is very hard to provide a method for detecting if the AI has reached the destination that works across all different games because the destination may not even lie on the navmesh and how that is handled differs from game to game (see also the code snippet in the docs for destination).\n\n[more in online documentation] +Pathfinding.AIPath.remainingDistance aipath.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThe RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. However it will be accurate when the agent is within 1 corner of the destination. You can use GetRemainingPath to calculate the actual remaining path more precisely.\n\nThe AIPath and AILerp scripts use a more accurate distance calculation at all times.\n\nIf the agent does not currently have a path, then positive infinity will be returned.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.AIPath.rotationFilterState aipath.html#rotationFilterState +Pathfinding.AIPath.rotationFilterState2 aipath.html#rotationFilterState2 +Pathfinding.AIPath.rotationSpeed aipath.html#rotationSpeed Rotation speed in degrees per second. \n\nRotation is calculated using Quaternion.RotateTowards. This variable represents the rotation speed in degrees per second. The higher it is, the faster the character will be able to rotate. +Pathfinding.AIPath.simulateMovement aipath.html#simulateMovement Enables or disables movement simulations completely. \n\nThis is primarily useful if you want to override the default movement behavior. If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. reachedDestination). Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement.\n\nIf you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use isStopped instead. If you want to make the agent stop immediately, disable the whole component instead.\n\n[more in online documentation] +Pathfinding.AIPath.slowWhenNotFacingTarget aipath.html#slowWhenNotFacingTarget Slow down when not facing the target direction. \n\nIncurs at a small performance overhead.\n\nThis setting only has an effect if enableRotation is enabled. +Pathfinding.AIPath.slowdownDistance aipath.html#slowdownDistance Distance from the end of the path where the AI will start to slow down. +Pathfinding.AIPath.steeringTarget aipath.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.AIPathAlignedToSurface.scratchDictionary aipathalignedtosurface.html#scratchDictionary Scratch dictionary used to avoid allocations every frame. +Pathfinding.AdvancedSmooth.ConstantTurn.circleCenter constantturn.html#circleCenter +Pathfinding.AdvancedSmooth.ConstantTurn.clockwise constantturn.html#clockwise +Pathfinding.AdvancedSmooth.ConstantTurn.gamma1 constantturn.html#gamma1 +Pathfinding.AdvancedSmooth.ConstantTurn.gamma2 constantturn.html#gamma2 +Pathfinding.AdvancedSmooth.MaxTurn.alfaLeftLeft maxturn.html#alfaLeftLeft +Pathfinding.AdvancedSmooth.MaxTurn.alfaLeftRight maxturn.html#alfaLeftRight +Pathfinding.AdvancedSmooth.MaxTurn.alfaRightLeft maxturn.html#alfaRightLeft +Pathfinding.AdvancedSmooth.MaxTurn.alfaRightRight maxturn.html#alfaRightRight +Pathfinding.AdvancedSmooth.MaxTurn.betaLeftLeft maxturn.html#betaLeftLeft +Pathfinding.AdvancedSmooth.MaxTurn.betaLeftRight maxturn.html#betaLeftRight +Pathfinding.AdvancedSmooth.MaxTurn.betaRightLeft maxturn.html#betaRightLeft +Pathfinding.AdvancedSmooth.MaxTurn.betaRightRight maxturn.html#betaRightRight +Pathfinding.AdvancedSmooth.MaxTurn.deltaLeftRight maxturn.html#deltaLeftRight +Pathfinding.AdvancedSmooth.MaxTurn.deltaRightLeft maxturn.html#deltaRightLeft +Pathfinding.AdvancedSmooth.MaxTurn.gammaLeft maxturn.html#gammaLeft +Pathfinding.AdvancedSmooth.MaxTurn.gammaRight maxturn.html#gammaRight +Pathfinding.AdvancedSmooth.MaxTurn.leftCircleCenter maxturn.html#leftCircleCenter +Pathfinding.AdvancedSmooth.MaxTurn.preLeftCircleCenter maxturn.html#preLeftCircleCenter +Pathfinding.AdvancedSmooth.MaxTurn.preRightCircleCenter maxturn.html#preRightCircleCenter +Pathfinding.AdvancedSmooth.MaxTurn.preVaLeft maxturn.html#preVaLeft +Pathfinding.AdvancedSmooth.MaxTurn.preVaRight maxturn.html#preVaRight +Pathfinding.AdvancedSmooth.MaxTurn.rightCircleCenter maxturn.html#rightCircleCenter +Pathfinding.AdvancedSmooth.MaxTurn.vaLeft maxturn.html#vaLeft +Pathfinding.AdvancedSmooth.MaxTurn.vaRight maxturn.html#vaRight +Pathfinding.AdvancedSmooth.Order advancedsmooth.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.AdvancedSmooth.Turn.constructor turn.html#constructor +Pathfinding.AdvancedSmooth.Turn.id turn.html#id +Pathfinding.AdvancedSmooth.Turn.length turn.html#length +Pathfinding.AdvancedSmooth.Turn.score turn.html#score +Pathfinding.AdvancedSmooth.TurnConstructor.ThreeSixtyRadians turnconstructor.html#ThreeSixtyRadians +Pathfinding.AdvancedSmooth.TurnConstructor.changedPreviousTangent turnconstructor.html#changedPreviousTangent +Pathfinding.AdvancedSmooth.TurnConstructor.constantBias turnconstructor.html#constantBias Constant bias to add to the path lengths. \n\nThis can be used to favor certain turn types before others.\n\nBy for example setting this to -5, paths from this path constructor will be chosen if there are no other paths more than 5 world units shorter than this one (as opposed to just any shorter path) +Pathfinding.AdvancedSmooth.TurnConstructor.current turnconstructor.html#current +Pathfinding.AdvancedSmooth.TurnConstructor.factorBias turnconstructor.html#factorBias Bias to multiply the path lengths with. \n\nThis can be used to favor certain turn types before others. \n\n[more in online documentation] +Pathfinding.AdvancedSmooth.TurnConstructor.next turnconstructor.html#next +Pathfinding.AdvancedSmooth.TurnConstructor.normal turnconstructor.html#normal +Pathfinding.AdvancedSmooth.TurnConstructor.prev turnconstructor.html#prev +Pathfinding.AdvancedSmooth.TurnConstructor.prevNormal turnconstructor.html#prevNormal +Pathfinding.AdvancedSmooth.TurnConstructor.t1 turnconstructor.html#t1 +Pathfinding.AdvancedSmooth.TurnConstructor.t2 turnconstructor.html#t2 +Pathfinding.AdvancedSmooth.TurnConstructor.turningRadius turnconstructor.html#turningRadius +Pathfinding.AdvancedSmooth.turnConstruct1 advancedsmooth.html#turnConstruct1 +Pathfinding.AdvancedSmooth.turnConstruct2 advancedsmooth.html#turnConstruct2 +Pathfinding.AdvancedSmooth.turningRadius advancedsmooth.html#turningRadius +Pathfinding.AlternativePath.Order alternativepath.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.AlternativePath.destroyed alternativepath.html#destroyed +Pathfinding.AlternativePath.penalty alternativepath.html#penalty How much penalty (weight) to apply to nodes. +Pathfinding.AlternativePath.prevNodes alternativepath.html#prevNodes The previous path. +Pathfinding.AlternativePath.prevPenalty alternativepath.html#prevPenalty The previous penalty used. \n\nStored just in case it changes during operation +Pathfinding.AlternativePath.randomStep alternativepath.html#randomStep Max number of nodes to skip in a row. +Pathfinding.AlternativePath.rnd alternativepath.html#rnd A random object. +Pathfinding.AnimationLink.LinkClip.clip linkclip.html#clip +Pathfinding.AnimationLink.LinkClip.loopCount linkclip.html#loopCount +Pathfinding.AnimationLink.LinkClip.name linkclip.html#name +Pathfinding.AnimationLink.LinkClip.velocity linkclip.html#velocity +Pathfinding.AnimationLink.animSpeed animationlink.html#animSpeed +Pathfinding.AnimationLink.boneRoot animationlink.html#boneRoot +Pathfinding.AnimationLink.clip animationlink.html#clip +Pathfinding.AnimationLink.referenceMesh animationlink.html#referenceMesh +Pathfinding.AnimationLink.reverseAnim animationlink.html#reverseAnim +Pathfinding.AnimationLink.sequence animationlink.html#sequence +Pathfinding.AstarColor.AreaColors astarcolor.html#AreaColors +Pathfinding.AstarColor.BoundsHandles astarcolor.html#BoundsHandles +Pathfinding.AstarColor.ConnectionHighLerp astarcolor.html#ConnectionHighLerp +Pathfinding.AstarColor.ConnectionLowLerp astarcolor.html#ConnectionLowLerp +Pathfinding.AstarColor.SolidColor astarcolor.html#SolidColor +Pathfinding.AstarColor.UnwalkableNode astarcolor.html#UnwalkableNode +Pathfinding.AstarColor._AreaColors astarcolor.html#_AreaColors Holds user set area colors. \n\nUse GetAreaColor to get an area color +Pathfinding.AstarColor._BoundsHandles astarcolor.html#_BoundsHandles +Pathfinding.AstarColor._ConnectionHighLerp astarcolor.html#_ConnectionHighLerp +Pathfinding.AstarColor._ConnectionLowLerp astarcolor.html#_ConnectionLowLerp +Pathfinding.AstarColor._SolidColor astarcolor.html#_SolidColor +Pathfinding.AstarColor._UnwalkableNode astarcolor.html#_UnwalkableNode +Pathfinding.AstarData.MarkerDeserializeGraphs astardata.html#MarkerDeserializeGraphs +Pathfinding.AstarData.MarkerFindGraphTypes astardata.html#MarkerFindGraphTypes +Pathfinding.AstarData.MarkerLoadFromCache astardata.html#MarkerLoadFromCache +Pathfinding.AstarData.MarkerSerializeGraphs astardata.html#MarkerSerializeGraphs +Pathfinding.AstarData.active astardata.html#active The AstarPath component which owns this AstarData. +Pathfinding.AstarData.cacheStartup astardata.html#cacheStartup Should graph-data be cached. \n\nCaching the startup means saving the whole graphs - not only the settings - to a file (file_cachedStartup) which can be loaded when the game starts. This is usually much faster than scanning the graphs when the game starts. This is configured from the editor under the "Save & Load" tab.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.AstarData.data astardata.html#data Serialized data for all graphs and settings. +Pathfinding.AstarData.dataString astardata.html#dataString Serialized data for all graphs and settings. \n\nStored as a base64 encoded string because otherwise Unity's Undo system would sometimes corrupt the byte data (because it only stores deltas).\n\nThis can be accessed as a byte array from the data property. +Pathfinding.AstarData.file_cachedStartup astardata.html#file_cachedStartup Serialized data for cached startup. \n\nIf set, and cacheStartup is enabled, graphs will be deserialized from this file when the game starts.\n\n [image in online documentation] +Pathfinding.AstarData.graphStructureLocked astardata.html#graphStructureLocked +Pathfinding.AstarData.graphTypes astardata.html#graphTypes All supported graph types. \n\nPopulated through reflection search +Pathfinding.AstarData.graphs astardata.html#graphs All graphs. \n\nThis will be filled only after deserialization has completed. May contain null entries if graph have been removed. +Pathfinding.AstarData.gridGraph astardata.html#gridGraph Shortcut to the first GridGraph. +Pathfinding.AstarData.layerGridGraph astardata.html#layerGridGraph Shortcut to the first LayerGridGraph. \n\n [more in online documentation] +Pathfinding.AstarData.linkGraph astardata.html#linkGraph Shortcut to the first LinkGraph. +Pathfinding.AstarData.navmesh astardata.html#navmesh Shortcut to the first NavMeshGraph. \n\n[more in online documentation] +Pathfinding.AstarData.navmeshGraph astardata.html#navmeshGraph Shortcut to the first NavMeshGraph. +Pathfinding.AstarData.pointGraph astardata.html#pointGraph Shortcut to the first PointGraph. +Pathfinding.AstarData.recastGraph astardata.html#recastGraph Shortcut to the first RecastGraph. \n\n [more in online documentation] +Pathfinding.AstarDebugger.GraphPoint.collectEvent graphpoint.html#collectEvent +Pathfinding.AstarDebugger.GraphPoint.fps graphpoint.html#fps +Pathfinding.AstarDebugger.GraphPoint.memory graphpoint.html#memory +Pathfinding.AstarDebugger.PathTypeDebug.getSize pathtypedebug.html#getSize +Pathfinding.AstarDebugger.PathTypeDebug.getTotalCreated pathtypedebug.html#getTotalCreated +Pathfinding.AstarDebugger.PathTypeDebug.name pathtypedebug.html#name +Pathfinding.AstarDebugger.allocMem astardebugger.html#allocMem +Pathfinding.AstarDebugger.allocRate astardebugger.html#allocRate +Pathfinding.AstarDebugger.boxRect astardebugger.html#boxRect +Pathfinding.AstarDebugger.cachedText astardebugger.html#cachedText +Pathfinding.AstarDebugger.cam astardebugger.html#cam +Pathfinding.AstarDebugger.collectAlloc astardebugger.html#collectAlloc +Pathfinding.AstarDebugger.debugTypes astardebugger.html#debugTypes +Pathfinding.AstarDebugger.delayedDeltaTime astardebugger.html#delayedDeltaTime +Pathfinding.AstarDebugger.delta astardebugger.html#delta +Pathfinding.AstarDebugger.font astardebugger.html#font Font to use. \n\nA monospaced font is the best +Pathfinding.AstarDebugger.fontSize astardebugger.html#fontSize +Pathfinding.AstarDebugger.fpsDropCounterSize astardebugger.html#fpsDropCounterSize +Pathfinding.AstarDebugger.fpsDrops astardebugger.html#fpsDrops +Pathfinding.AstarDebugger.graph astardebugger.html#graph +Pathfinding.AstarDebugger.graphBufferSize astardebugger.html#graphBufferSize +Pathfinding.AstarDebugger.graphHeight astardebugger.html#graphHeight +Pathfinding.AstarDebugger.graphOffset astardebugger.html#graphOffset +Pathfinding.AstarDebugger.graphWidth astardebugger.html#graphWidth +Pathfinding.AstarDebugger.lastAllocMemory astardebugger.html#lastAllocMemory +Pathfinding.AstarDebugger.lastAllocSet astardebugger.html#lastAllocSet +Pathfinding.AstarDebugger.lastCollect astardebugger.html#lastCollect +Pathfinding.AstarDebugger.lastCollectNum astardebugger.html#lastCollectNum +Pathfinding.AstarDebugger.lastDeltaTime astardebugger.html#lastDeltaTime +Pathfinding.AstarDebugger.lastUpdate astardebugger.html#lastUpdate +Pathfinding.AstarDebugger.maxNodePool astardebugger.html#maxNodePool +Pathfinding.AstarDebugger.maxVecPool astardebugger.html#maxVecPool +Pathfinding.AstarDebugger.peakAlloc astardebugger.html#peakAlloc +Pathfinding.AstarDebugger.show astardebugger.html#show +Pathfinding.AstarDebugger.showFPS astardebugger.html#showFPS +Pathfinding.AstarDebugger.showGraph astardebugger.html#showGraph +Pathfinding.AstarDebugger.showInEditor astardebugger.html#showInEditor +Pathfinding.AstarDebugger.showMemProfile astardebugger.html#showMemProfile +Pathfinding.AstarDebugger.showPathProfile astardebugger.html#showPathProfile +Pathfinding.AstarDebugger.style astardebugger.html#style +Pathfinding.AstarDebugger.text astardebugger.html#text +Pathfinding.AstarDebugger.yOffset astardebugger.html#yOffset +Pathfinding.AstarMath.GlobalRandom astarmath.html#GlobalRandom +Pathfinding.AstarMath.GlobalRandomLock astarmath.html#GlobalRandomLock +Pathfinding.AstarPathEditor.aboutArea astarpatheditor.html#aboutArea +Pathfinding.AstarPathEditor.addGraphsArea astarpatheditor.html#addGraphsArea +Pathfinding.AstarPathEditor.alwaysVisibleArea astarpatheditor.html#alwaysVisibleArea +Pathfinding.AstarPathEditor.astarSkin astarpatheditor.html#astarSkin +Pathfinding.AstarPathEditor.colorSettingsArea astarpatheditor.html#colorSettingsArea +Pathfinding.AstarPathEditor.defines astarpatheditor.html#defines Holds defines found in script files, used for optimizations. \n\n [more in online documentation] +Pathfinding.AstarPathEditor.editorSettingsArea astarpatheditor.html#editorSettingsArea +Pathfinding.AstarPathEditor.graphDeleteButtonStyle astarpatheditor.html#graphDeleteButtonStyle +Pathfinding.AstarPathEditor.graphDuplicateButtonStyle astarpatheditor.html#graphDuplicateButtonStyle +Pathfinding.AstarPathEditor.graphEditNameButtonStyle astarpatheditor.html#graphEditNameButtonStyle +Pathfinding.AstarPathEditor.graphEditorTypes astarpatheditor.html#graphEditorTypes List of all graph editors available (e.g GridGraphEditor) +Pathfinding.AstarPathEditor.graphEditors astarpatheditor.html#graphEditors List of all graph editors for the graphs. \n\nMay be larger than script.data.graphs.Length +Pathfinding.AstarPathEditor.graphGizmoButtonStyle astarpatheditor.html#graphGizmoButtonStyle +Pathfinding.AstarPathEditor.graphInfoButtonStyle astarpatheditor.html#graphInfoButtonStyle +Pathfinding.AstarPathEditor.graphNameFocused astarpatheditor.html#graphNameFocused Graph editor which has its 'name' field focused. +Pathfinding.AstarPathEditor.graphNodeCounts astarpatheditor.html#graphNodeCounts Holds node counts for each graph to avoid calculating it every frame. \n\nOnly used for visualization purposes +Pathfinding.AstarPathEditor.graphTypes astarpatheditor.html#graphTypes +Pathfinding.AstarPathEditor.graphsArea astarpatheditor.html#graphsArea +Pathfinding.AstarPathEditor.helpBox astarpatheditor.html#helpBox +Pathfinding.AstarPathEditor.heuristicOptimizationOptions astarpatheditor.html#heuristicOptimizationOptions +Pathfinding.AstarPathEditor.ignoredChecksum astarpatheditor.html#ignoredChecksum Used to make sure correct behaviour when handling undos. +Pathfinding.AstarPathEditor.isPrefab astarpatheditor.html#isPrefab +Pathfinding.AstarPathEditor.lastUndoGroup astarpatheditor.html#lastUndoGroup +Pathfinding.AstarPathEditor.level0AreaStyle astarpatheditor.html#level0AreaStyle +Pathfinding.AstarPathEditor.level0LabelStyle astarpatheditor.html#level0LabelStyle +Pathfinding.AstarPathEditor.level1AreaStyle astarpatheditor.html#level1AreaStyle +Pathfinding.AstarPathEditor.level1LabelStyle astarpatheditor.html#level1LabelStyle +Pathfinding.AstarPathEditor.optimizationSettingsArea astarpatheditor.html#optimizationSettingsArea +Pathfinding.AstarPathEditor.script astarpatheditor.html#script AstarPath instance that is being inspected. +Pathfinding.AstarPathEditor.scriptsFolder astarpatheditor.html#scriptsFolder +Pathfinding.AstarPathEditor.serializationSettingsArea astarpatheditor.html#serializationSettingsArea +Pathfinding.AstarPathEditor.settingsArea astarpatheditor.html#settingsArea +Pathfinding.AstarPathEditor.showCustomAreaColors astarpatheditor.html#showCustomAreaColors +Pathfinding.AstarPathEditor.showSettings astarpatheditor.html#showSettings +Pathfinding.AstarPathEditor.showTagNames astarpatheditor.html#showTagNames +Pathfinding.AstarPathEditor.stylesLoaded astarpatheditor.html#stylesLoaded +Pathfinding.AstarPathEditor.tagsArea astarpatheditor.html#tagsArea +Pathfinding.AstarPathEditor.thinHelpBox astarpatheditor.html#thinHelpBox +Pathfinding.AstarUpdateChecker._lastUpdateCheck astarupdatechecker.html#_lastUpdateCheck +Pathfinding.AstarUpdateChecker._lastUpdateCheckRead astarupdatechecker.html#_lastUpdateCheckRead +Pathfinding.AstarUpdateChecker._latestBetaVersion astarupdatechecker.html#_latestBetaVersion +Pathfinding.AstarUpdateChecker._latestVersion astarupdatechecker.html#_latestVersion +Pathfinding.AstarUpdateChecker._latestVersionDescription astarupdatechecker.html#_latestVersionDescription Description of the latest update of the A* Pathfinding Project. +Pathfinding.AstarUpdateChecker.astarServerData astarupdatechecker.html#astarServerData Holds various URLs and text for the editor. \n\nThis info can be updated when a check for new versions is done to ensure that there are no invalid links. +Pathfinding.AstarUpdateChecker.hasParsedServerMessage astarupdatechecker.html#hasParsedServerMessage +Pathfinding.AstarUpdateChecker.lastUpdateCheck astarupdatechecker.html#lastUpdateCheck Last time an update check was made. +Pathfinding.AstarUpdateChecker.latestBetaVersion astarupdatechecker.html#latestBetaVersion Latest beta version of the A* Pathfinding Project. +Pathfinding.AstarUpdateChecker.latestVersion astarupdatechecker.html#latestVersion Latest version of the A* Pathfinding Project. +Pathfinding.AstarUpdateChecker.latestVersionDescription astarupdatechecker.html#latestVersionDescription Summary of the latest update. +Pathfinding.AstarUpdateChecker.updateCheckDownload astarupdatechecker.html#updateCheckDownload Used for downloading new version information. +Pathfinding.AstarUpdateChecker.updateCheckRate astarupdatechecker.html#updateCheckRate Number of days between update checks. +Pathfinding.AstarUpdateChecker.updateURL astarupdatechecker.html#updateURL URL to the version file containing the latest version number. +Pathfinding.AstarUpdateWindow.largeStyle astarupdatewindow.html#largeStyle +Pathfinding.AstarUpdateWindow.normalStyle astarupdatewindow.html#normalStyle +Pathfinding.AstarUpdateWindow.setReminder astarupdatewindow.html#setReminder +Pathfinding.AstarUpdateWindow.summary astarupdatewindow.html#summary +Pathfinding.AstarUpdateWindow.version astarupdatewindow.html#version +Pathfinding.AstarWorkItem.init astarworkitem.html#init Init function. \n\nMay be null if no initialization is needed. Will be called once, right before the first call to update or updateWithContext. +Pathfinding.AstarWorkItem.initWithContext astarworkitem.html#initWithContext Init function. \n\nMay be null if no initialization is needed. Will be called once, right before the first call to update or updateWithContext.\n\nA context object is sent as a parameter. This can be used to for example queue a flood fill that will be executed either when a work item calls EnsureValidFloodFill or all work items have been completed. If multiple work items are updating nodes so that they need a flood fill afterwards, using the QueueFloodFill method is preferred since then only a single flood fill needs to be performed for all of the work items instead of one per work item. +Pathfinding.AstarWorkItem.update astarworkitem.html#update Update function, called once per frame when the work item executes. \n\nTakes a param force. If that is true, the work item should try to complete the whole item in one go instead of spreading it out over multiple frames.\n\n[more in online documentation] +Pathfinding.AstarWorkItem.updateWithContext astarworkitem.html#updateWithContext Update function, called once per frame when the work item executes. \n\nTakes a param force. If that is true, the work item should try to complete the whole item in one go instead of spreading it out over multiple frames. \n\n[more in online documentation]\n\n\nA context object is sent as a parameter. This can be used to for example queue a flood fill that will be executed either when a work item calls EnsureValidFloodFill or all work items have been completed. If multiple work items are updating nodes so that they need a flood fill afterwards, using the QueueFloodFill method is preferred since then only a single flood fill needs to be performed for all of the work items instead of one per work item. +Pathfinding.AutoRepathPolicy.Mode autorepathpolicy.html#Mode Policy mode for how often to recalculate an agent's path. +Pathfinding.AutoRepathPolicy.lastDestination autorepathpolicy.html#lastDestination +Pathfinding.AutoRepathPolicy.lastRepathTime autorepathpolicy.html#lastRepathTime +Pathfinding.AutoRepathPolicy.maximumPeriod autorepathpolicy.html#maximumPeriod Maximum number of seconds between each automatic path recalculation for Mode.Dynamic. +Pathfinding.AutoRepathPolicy.mode autorepathpolicy.html#mode Policy to use when recalculating paths. \n\n[more in online documentation] +Pathfinding.AutoRepathPolicy.period autorepathpolicy.html#period Number of seconds between each automatic path recalculation for Mode.EveryNSeconds. +Pathfinding.AutoRepathPolicy.sensitivity autorepathpolicy.html#sensitivity How sensitive the agent should be to changes in its destination for Mode.Dynamic. \n\nA higher value means the destination has to move less for the path to be recalculated.\n\n[more in online documentation] +Pathfinding.AutoRepathPolicy.visualizeSensitivity autorepathpolicy.html#visualizeSensitivity If true the sensitivity will be visualized as a circle in the scene view when the game is playing. +Pathfinding.BaseAIEditor.debug baseaieditor.html#debug +Pathfinding.BaseAIEditor.lastSeenCustomGravity baseaieditor.html#lastSeenCustomGravity +Pathfinding.BinaryHeap.D binaryheap.html#D Number of children of each node in the tree. \n\nDifferent values have been tested and 4 has been empirically found to perform the best. \n\n[more in online documentation] +Pathfinding.BinaryHeap.GrowthFactor binaryheap.html#GrowthFactor The tree will grow by at least this factor every time it is expanded. +Pathfinding.BinaryHeap.HeapNode.F heapnode.html#F +Pathfinding.BinaryHeap.HeapNode.TieBreaker heapnode.html#TieBreaker +Pathfinding.BinaryHeap.HeapNode.pathNodeIndex heapnode.html#pathNodeIndex +Pathfinding.BinaryHeap.HeapNode.sortKey heapnode.html#sortKey Bitpacked F and G scores. +Pathfinding.BinaryHeap.NotInHeap binaryheap.html#NotInHeap +Pathfinding.BinaryHeap.TieBreaking binaryheap.html#TieBreaking +Pathfinding.BinaryHeap.heap binaryheap.html#heap Internal backing array for the heap. +Pathfinding.BinaryHeap.insertionOrder binaryheap.html#insertionOrder +Pathfinding.BinaryHeap.isEmpty binaryheap.html#isEmpty True if the heap does not contain any elements. +Pathfinding.BinaryHeap.numberOfItems binaryheap.html#numberOfItems Number of items in the tree. +Pathfinding.BinaryHeap.tieBreaking binaryheap.html#tieBreaking Ties between elements that have the same F score can be broken by the H score or by insertion order. +Pathfinding.BlockManager.BlockMode blockmanager.html#BlockMode +Pathfinding.BlockManager.TraversalProvider.blockManager traversalprovider.html#blockManager Holds information about which nodes are occupied. +Pathfinding.BlockManager.TraversalProvider.mode traversalprovider.html#mode Affects which nodes are considered blocked. +Pathfinding.BlockManager.TraversalProvider.selector traversalprovider.html#selector Blockers for this path. \n\nThe effect depends on mode.\n\nNote that having a large selector has a performance cost.\n\n[more in online documentation] +Pathfinding.BlockManager.blocked blockmanager.html#blocked Contains info on which SingleNodeBlocker objects have blocked a particular node. +Pathfinding.CloseToDestinationMode pathfinding.html#CloseToDestinationMode What to do when the character is close to the destination. +Pathfinding.Collections.AABBTree.AABBComparer.dim aabbcomparer.html#dim +Pathfinding.Collections.AABBTree.AABBComparer.nodes aabbcomparer.html#nodes +Pathfinding.Collections.AABBTree.Key.isValid key.html#isValid +Pathfinding.Collections.AABBTree.Key.node key.html#node +Pathfinding.Collections.AABBTree.Key.value key.html#value +Pathfinding.Collections.AABBTree.NoNode aabbtree.html#NoNode +Pathfinding.Collections.AABBTree.Node.AllocatedBit node.html#AllocatedBit +Pathfinding.Collections.AABBTree.Node.InvalidParent node.html#InvalidParent +Pathfinding.Collections.AABBTree.Node.ParentMask node.html#ParentMask +Pathfinding.Collections.AABBTree.Node.TagInsideBit node.html#TagInsideBit +Pathfinding.Collections.AABBTree.Node.TagPartiallyInsideBit node.html#TagPartiallyInsideBit +Pathfinding.Collections.AABBTree.Node.bounds node.html#bounds +Pathfinding.Collections.AABBTree.Node.flags node.html#flags +Pathfinding.Collections.AABBTree.Node.isAllocated node.html#isAllocated +Pathfinding.Collections.AABBTree.Node.isLeaf node.html#isLeaf +Pathfinding.Collections.AABBTree.Node.left node.html#left +Pathfinding.Collections.AABBTree.Node.parent node.html#parent +Pathfinding.Collections.AABBTree.Node.right node.html#right +Pathfinding.Collections.AABBTree.Node.subtreePartiallyTagged node.html#subtreePartiallyTagged +Pathfinding.Collections.AABBTree.Node.value node.html#value +Pathfinding.Collections.AABBTree.Node.wholeSubtreeTagged node.html#wholeSubtreeTagged +Pathfinding.Collections.AABBTree.freeNodes aabbtree.html#freeNodes +Pathfinding.Collections.AABBTree.nodes aabbtree.html#nodes +Pathfinding.Collections.AABBTree.rebuildCounter aabbtree.html#rebuildCounter +Pathfinding.Collections.AABBTree.root aabbtree.html#root +Pathfinding.Collections.AABBTree.this[Key key] aabbtree.html#thisKeykey User data for a node in the tree. +Pathfinding.Collections.BBTree.BBTreeBox.IsLeaf bbtreebox.html#IsLeaf +Pathfinding.Collections.BBTree.BBTreeBox.left bbtreebox.html#left +Pathfinding.Collections.BBTree.BBTreeBox.nodeOffset bbtreebox.html#nodeOffset +Pathfinding.Collections.BBTree.BBTreeBox.rect bbtreebox.html#rect +Pathfinding.Collections.BBTree.BBTreeBox.right bbtreebox.html#right +Pathfinding.Collections.BBTree.CloseNode.closestPointOnNode closenode.html#closestPointOnNode +Pathfinding.Collections.BBTree.CloseNode.distanceSq closenode.html#distanceSq +Pathfinding.Collections.BBTree.CloseNode.node closenode.html#node +Pathfinding.Collections.BBTree.CloseNode.tieBreakingDistance closenode.html#tieBreakingDistance +Pathfinding.Collections.BBTree.DistanceMetric bbtree.html#DistanceMetric +Pathfinding.Collections.BBTree.MAX_TREE_HEIGHT bbtree.html#MAX_TREE_HEIGHT +Pathfinding.Collections.BBTree.MaximumLeafSize bbtree.html#MaximumLeafSize +Pathfinding.Collections.BBTree.NearbyNodesIterator.BoxWithDist.distSqr boxwithdist.html#distSqr +Pathfinding.Collections.BBTree.NearbyNodesIterator.BoxWithDist.index boxwithdist.html#index +Pathfinding.Collections.BBTree.NearbyNodesIterator.Current nearbynodesiterator.html#Current +Pathfinding.Collections.BBTree.NearbyNodesIterator.current nearbynodesiterator.html#current +Pathfinding.Collections.BBTree.NearbyNodesIterator.distanceThresholdSqr nearbynodesiterator.html#distanceThresholdSqr +Pathfinding.Collections.BBTree.NearbyNodesIterator.indexInLeaf nearbynodesiterator.html#indexInLeaf +Pathfinding.Collections.BBTree.NearbyNodesIterator.nodes nearbynodesiterator.html#nodes +Pathfinding.Collections.BBTree.NearbyNodesIterator.point nearbynodesiterator.html#point +Pathfinding.Collections.BBTree.NearbyNodesIterator.projection nearbynodesiterator.html#projection +Pathfinding.Collections.BBTree.NearbyNodesIterator.stack nearbynodesiterator.html#stack +Pathfinding.Collections.BBTree.NearbyNodesIterator.stackSize nearbynodesiterator.html#stackSize +Pathfinding.Collections.BBTree.NearbyNodesIterator.tieBreakingDistanceThreshold nearbynodesiterator.html#tieBreakingDistanceThreshold +Pathfinding.Collections.BBTree.NearbyNodesIterator.tree nearbynodesiterator.html#tree +Pathfinding.Collections.BBTree.NearbyNodesIterator.triangles nearbynodesiterator.html#triangles +Pathfinding.Collections.BBTree.NearbyNodesIterator.vertices nearbynodesiterator.html#vertices +Pathfinding.Collections.BBTree.ProjectionParams.alignedWithXZPlane projectionparams.html#alignedWithXZPlane +Pathfinding.Collections.BBTree.ProjectionParams.alignedWithXZPlaneBacking projectionparams.html#alignedWithXZPlaneBacking +Pathfinding.Collections.BBTree.ProjectionParams.distanceMetricType projectionparams.html#distanceMetricType +Pathfinding.Collections.BBTree.ProjectionParams.distanceScaleAlongProjectionAxis projectionparams.html#distanceScaleAlongProjectionAxis +Pathfinding.Collections.BBTree.ProjectionParams.planeProjection projectionparams.html#planeProjection +Pathfinding.Collections.BBTree.ProjectionParams.projectedUpNormalized projectionparams.html#projectedUpNormalized +Pathfinding.Collections.BBTree.ProjectionParams.projectionAxis projectionparams.html#projectionAxis +Pathfinding.Collections.BBTree.Size bbtree.html#Size +Pathfinding.Collections.BBTree.nodePermutation bbtree.html#nodePermutation +Pathfinding.Collections.BBTree.tree bbtree.html#tree Holds all tree nodes. +Pathfinding.Collections.CircularBuffer.AbsoluteEndIndex circularbuffer.html#AbsoluteEndIndex Absolute index of the last item in the buffer, may be negative or greater than Length. +Pathfinding.Collections.CircularBuffer.AbsoluteStartIndex circularbuffer.html#AbsoluteStartIndex Absolute index of the first item in the buffer, may be negative or greater than Length. +Pathfinding.Collections.CircularBuffer.Count circularbuffer.html#Count +Pathfinding.Collections.CircularBuffer.First circularbuffer.html#First First item in the buffer, throws if the buffer is empty. +Pathfinding.Collections.CircularBuffer.Last circularbuffer.html#Last Last item in the buffer, throws if the buffer is empty. +Pathfinding.Collections.CircularBuffer.Length circularbuffer.html#Length Number of items in the buffer. +Pathfinding.Collections.CircularBuffer.data circularbuffer.html#data +Pathfinding.Collections.CircularBuffer.head circularbuffer.html#head +Pathfinding.Collections.CircularBuffer.length circularbuffer.html#length +Pathfinding.Collections.CircularBuffer.this[int index] circularbuffer.html#thisintindex Indexes the buffer, with index 0 being the first element. +Pathfinding.Collections.GridLookup.AllItems gridlookup.html#AllItems Linked list of all items. +Pathfinding.Collections.GridLookup.Item.next item.html#next +Pathfinding.Collections.GridLookup.Item.prev item.html#prev +Pathfinding.Collections.GridLookup.Item.root item.html#root +Pathfinding.Collections.GridLookup.Root.flag root.html#flag +Pathfinding.Collections.GridLookup.Root.items root.html#items References to an item in each grid cell that this object is contained inside. +Pathfinding.Collections.GridLookup.Root.next root.html#next Next item in the linked list of all roots. +Pathfinding.Collections.GridLookup.Root.obj root.html#obj Underlying object. +Pathfinding.Collections.GridLookup.Root.prev root.html#prev Previous item in the linked list of all roots. +Pathfinding.Collections.GridLookup.Root.previousBounds root.html#previousBounds +Pathfinding.Collections.GridLookup.Root.previousPosition root.html#previousPosition +Pathfinding.Collections.GridLookup.Root.previousRotation root.html#previousRotation +Pathfinding.Collections.GridLookup.all gridlookup.html#all Linked list of all items. \n\nNote that the first item in the list is a dummy item and does not contain any data. +Pathfinding.Collections.GridLookup.cells gridlookup.html#cells +Pathfinding.Collections.GridLookup.itemPool gridlookup.html#itemPool +Pathfinding.Collections.GridLookup.rootLookup gridlookup.html#rootLookup +Pathfinding.Collections.GridLookup.size gridlookup.html#size +Pathfinding.Collections.HierarchicalBitset.Capacity hierarchicalbitset.html#Capacity +Pathfinding.Collections.HierarchicalBitset.IsCreated hierarchicalbitset.html#IsCreated +Pathfinding.Collections.HierarchicalBitset.IsEmpty hierarchicalbitset.html#IsEmpty True if the bitset is empty. +Pathfinding.Collections.HierarchicalBitset.Iterator.Current iterator.html#Current +Pathfinding.Collections.HierarchicalBitset.Iterator.bitSet iterator.html#bitSet +Pathfinding.Collections.HierarchicalBitset.Iterator.l2bitIndex iterator.html#l2bitIndex +Pathfinding.Collections.HierarchicalBitset.Iterator.l3bitIndex iterator.html#l3bitIndex +Pathfinding.Collections.HierarchicalBitset.Iterator.l3index iterator.html#l3index +Pathfinding.Collections.HierarchicalBitset.Iterator.result iterator.html#result +Pathfinding.Collections.HierarchicalBitset.Iterator.resultCount iterator.html#resultCount +Pathfinding.Collections.HierarchicalBitset.Log64 hierarchicalbitset.html#Log64 +Pathfinding.Collections.HierarchicalBitset.allocator hierarchicalbitset.html#allocator +Pathfinding.Collections.HierarchicalBitset.l1 hierarchicalbitset.html#l1 +Pathfinding.Collections.HierarchicalBitset.l2 hierarchicalbitset.html#l2 +Pathfinding.Collections.HierarchicalBitset.l3 hierarchicalbitset.html#l3 +Pathfinding.Collections.NativeCircularBuffer.AbsoluteEndIndex nativecircularbuffer.html#AbsoluteEndIndex Absolute index of the last item in the buffer, may be negative or greater than Length. +Pathfinding.Collections.NativeCircularBuffer.AbsoluteStartIndex nativecircularbuffer.html#AbsoluteStartIndex Absolute index of the first item in the buffer, may be negative or greater than Length. +Pathfinding.Collections.NativeCircularBuffer.Allocator nativecircularbuffer.html#Allocator The allocator used to create the internal buffer. +Pathfinding.Collections.NativeCircularBuffer.Count nativecircularbuffer.html#Count +Pathfinding.Collections.NativeCircularBuffer.First nativecircularbuffer.html#First First item in the buffer throws if the buffer is empty. +Pathfinding.Collections.NativeCircularBuffer.IsCreated nativecircularbuffer.html#IsCreated +Pathfinding.Collections.NativeCircularBuffer.Last nativecircularbuffer.html#Last Last item in the buffer, throws if the buffer is empty. +Pathfinding.Collections.NativeCircularBuffer.Length nativecircularbuffer.html#Length Number of items in the buffer. +Pathfinding.Collections.NativeCircularBuffer.capacityMask nativecircularbuffer.html#capacityMask Capacity of the allocation minus 1. \n\nInvariant: (a power of two) minus 1 +Pathfinding.Collections.NativeCircularBuffer.data nativecircularbuffer.html#data +Pathfinding.Collections.NativeCircularBuffer.head nativecircularbuffer.html#head +Pathfinding.Collections.NativeCircularBuffer.length nativecircularbuffer.html#length +Pathfinding.Collections.NativeCircularBuffer.this[int index] nativecircularbuffer.html#thisintindex Indexes the buffer, with index 0 being the first element. +Pathfinding.Collections.SlabAllocator.AllocatedBit slaballocator.html#AllocatedBit +Pathfinding.Collections.SlabAllocator.AllocatorData.freeHeads allocatordata.html#freeHeads +Pathfinding.Collections.SlabAllocator.AllocatorData.mem allocatordata.html#mem +Pathfinding.Collections.SlabAllocator.ByteSize slaballocator.html#ByteSize +Pathfinding.Collections.SlabAllocator.Header.length header.html#length +Pathfinding.Collections.SlabAllocator.InvalidAllocation slaballocator.html#InvalidAllocation Allocation which is always invalid. +Pathfinding.Collections.SlabAllocator.IsCreated slaballocator.html#IsCreated +Pathfinding.Collections.SlabAllocator.IsDebugAllocator slaballocator.html#IsDebugAllocator +Pathfinding.Collections.SlabAllocator.LengthMask slaballocator.html#LengthMask +Pathfinding.Collections.SlabAllocator.List.Length list.html#Length +Pathfinding.Collections.SlabAllocator.List.allocationIndex list.html#allocationIndex +Pathfinding.Collections.SlabAllocator.List.allocator list.html#allocator +Pathfinding.Collections.SlabAllocator.List.span list.html#span +Pathfinding.Collections.SlabAllocator.List.this[int index] list.html#thisintindex +Pathfinding.Collections.SlabAllocator.MaxAllocationSize slaballocator.html#MaxAllocationSize +Pathfinding.Collections.SlabAllocator.MaxAllocationSizeIndex slaballocator.html#MaxAllocationSizeIndex +Pathfinding.Collections.SlabAllocator.NextBlock.next nextblock.html#next +Pathfinding.Collections.SlabAllocator.UsedBit slaballocator.html#UsedBit +Pathfinding.Collections.SlabAllocator.ZeroLengthArray slaballocator.html#ZeroLengthArray Allocation representing a zero-length array. +Pathfinding.Collections.SlabAllocator.data slaballocator.html#data +Pathfinding.Collections.SpanExtensions.AllocatorOffset spanextensions.html#AllocatorOffset +Pathfinding.Collections.UnsafeSpan.Allocator unsafespan.html#Allocator +Pathfinding.Collections.UnsafeSpan.Length unsafespan.html#Length Number of elements in this span. +Pathfinding.Collections.UnsafeSpan.length unsafespan.html#length +Pathfinding.Collections.UnsafeSpan.ptr unsafespan.html#ptr +Pathfinding.Collections.UnsafeSpan.this[int index] unsafespan.html#thisintindex +Pathfinding.Collections.UnsafeSpan.this[uint index] unsafespan.html#thisuintindex +Pathfinding.Connection.IdenticalEdge connection.html#IdenticalEdge +Pathfinding.Connection.IncomingConnection connection.html#IncomingConnection +Pathfinding.Connection.NoSharedEdge connection.html#NoSharedEdge +Pathfinding.Connection.OutgoingConnection connection.html#OutgoingConnection +Pathfinding.Connection.adjacentShapeEdge connection.html#adjacentShapeEdge The edge of the shape in the other node, which this connection represents. \n\n[more in online documentation] +Pathfinding.Connection.cost connection.html#cost Cost of moving along this connection. \n\nA cost of 1000 corresponds approximately to the cost of moving one world unit. +Pathfinding.Connection.edgesAreIdentical connection.html#edgesAreIdentical True if the two nodes share an identical edge. \n\nThis is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents.\n\nIn contrast to isEdgeShared, this is true only if the triangle edge is identical (but reversed) in the other node. +Pathfinding.Connection.isEdgeShared connection.html#isEdgeShared True if the two nodes share an edge. \n\nThis is only true if the connection is between two triangle mesh nodes and the nodes share the edge which this connection represents. Note that the edge does not need to be perfectly identical for this to be true, it is enough if the edge is very similar. +Pathfinding.Connection.isIncoming connection.html#isIncoming True if the connection allows movement from the other node to this node. \n\nA connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. +Pathfinding.Connection.isOutgoing connection.html#isOutgoing True if the connection allows movement from this node to the other node. \n\nA connection can be either outgoing, incoming, or both. Most connections are two-way, so both incoming and outgoing. +Pathfinding.Connection.node connection.html#node Node which this connection goes to. +Pathfinding.Connection.shapeEdge connection.html#shapeEdge The edge of the shape which this connection uses. \n\nThis is an index into the shape's vertices.\n\nA value of 0 corresponds to using the side for vertex 0 and vertex 1 on the node. 1 corresponds to vertex 1 and 2, etc. A value of 3 is invalid, and this will be the value if isEdgeShared is false.\n\n[more in online documentation] +Pathfinding.Connection.shapeEdgeInfo connection.html#shapeEdgeInfo Various metadata about the connection, such as the side of the node shape which this connection uses. \n\n- Bits 0..1 represent shapeEdge.\n\n- Bits 2..3 represent adjacentShapeEdge.\n\n- Bit 4 represents isIncoming.\n\n- Bit 5 represents isOutgoing.\n\n- Bit 6 represents edgesAreIdentical.\n\n\n\n[more in online documentation] +Pathfinding.ConstantPath.allNodes constantpath.html#allNodes Contains all nodes the path found. \n\nThis list will be sorted by G score (cost/distance to reach the node). +Pathfinding.ConstantPath.endingCondition constantpath.html#endingCondition Determines when the path calculation should stop. \n\nThis is set up automatically in the constructor to an instance of the Pathfinding.EndingConditionDistance class with a maxGScore is specified in the constructor.\n\n[more in online documentation] +Pathfinding.ConstantPath.originalStartPoint constantpath.html#originalStartPoint +Pathfinding.ConstantPath.startNode constantpath.html#startNode +Pathfinding.ConstantPath.startPoint constantpath.html#startPoint +Pathfinding.CustomGraphEditorAttribute.displayName customgrapheditorattribute.html#displayName Name displayed in the inpector. +Pathfinding.CustomGraphEditorAttribute.editorType customgrapheditorattribute.html#editorType Type of the editor for the graph. +Pathfinding.CustomGraphEditorAttribute.graphType customgrapheditorattribute.html#graphType Graph type which this is an editor for. +Pathfinding.DistanceMetric.Euclidean distancemetric.html#Euclidean Returns a DistanceMetric which will find the closest node in euclidean 3D space. \n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.DistanceMetric.distanceScaleAlongProjectionDirection distancemetric.html#distanceScaleAlongProjectionDirection Distance scaling along the projectionAxis. \n\n[more in online documentation] +Pathfinding.DistanceMetric.isProjectedDistance distancemetric.html#isProjectedDistance True when using the ClosestAsSeenFromAbove or ClosestAsSeenFromAboveSoft modes. +Pathfinding.DistanceMetric.projectionAxis distancemetric.html#projectionAxis Normal of the plane on which nodes will be projected before finding the closest point on them. \n\nWhen zero, this has no effect.\n\nWhen set to the special value (inf, inf, inf) then the graph's natural up direction will be used.\n\nOften you do not want to find the closest point on a node in 3D space, but rather find for example the closest point on the node directly below the agent.\n\nThis allows you to project the nodes onto a plane before finding the closest point on them. For example, if you set this to Vector3.up, then the nodes will be projected onto the XZ plane. Running a GetNearest query will then find the closest node as seen from above.\n\n [image in online documentation]\n\nThis is more flexible, however. You can set the distanceScaleAlongProjectionDirection to any value (though usually somewhere between 0 and 1). With a value of 0, the closest node will be found as seen from above. When the distance is greater than 0, moving along the projectionAxis from the query point will only cost distanceScaleAlongProjectionDirection times the regular distance, but moving sideways will cost the normal amount.\n\n [image in online documentation]\n\nA nice value to use is 0.2 for distanceScaleAlongProjectionDirection. This will make moving upwards or downwards (along the projection direction) only appear like 20% the original distance to the nearest node search. This allows you to find the closest position directly under the agent, if there is a navmesh directly under the agent, but also to search not directly below the agent if that is necessary.\n\n[more in online documentation] +Pathfinding.DynamicGridObstacle.checkTime dynamicgridobstacle.html#checkTime +Pathfinding.DynamicGridObstacle.enabled dynamicgridobstacle.html#enabled +Pathfinding.DynamicGridObstacle.updateError dynamicgridobstacle.html#updateError +Pathfinding.DynamicObstacle.bounds dynamicobstacle.html#bounds +Pathfinding.DynamicObstacle.checkTime dynamicobstacle.html#checkTime Time in seconds between bounding box checks. \n\nIf AstarPath.batchGraphUpdates is enabled, it is not beneficial to have a checkTime much lower than AstarPath.graphUpdateBatchingInterval because that will just add extra unnecessary graph updates.\n\nIn real time seconds (based on Time.realtimeSinceStartup). +Pathfinding.DynamicObstacle.coll dynamicobstacle.html#coll Collider to get bounds information from. +Pathfinding.DynamicObstacle.coll2D dynamicobstacle.html#coll2D 2D Collider to get bounds information from +Pathfinding.DynamicObstacle.colliderEnabled dynamicobstacle.html#colliderEnabled +Pathfinding.DynamicObstacle.lastCheckTime dynamicobstacle.html#lastCheckTime +Pathfinding.DynamicObstacle.pendingGraphUpdates dynamicobstacle.html#pendingGraphUpdates +Pathfinding.DynamicObstacle.prevBounds dynamicobstacle.html#prevBounds Bounds of the collider the last time the graphs were updated. +Pathfinding.DynamicObstacle.prevEnabled dynamicobstacle.html#prevEnabled True if the collider was enabled last time the graphs were updated. +Pathfinding.DynamicObstacle.prevRotation dynamicobstacle.html#prevRotation Rotation of the collider the last time the graphs were updated. +Pathfinding.DynamicObstacle.tr dynamicobstacle.html#tr Cached transform component. +Pathfinding.DynamicObstacle.updateError dynamicobstacle.html#updateError The minimum change in world units along one of the axis of the bounding box of the collider to trigger a graph update. +Pathfinding.ECS.AIGizmosSystem.DrawerCallback.Exists drawercallback.html#Exists +Pathfinding.ECS.AIGizmosSystem.DrawerCallback.world drawercallback.html#world +Pathfinding.ECS.AIGizmosSystem.MovementStateTypeHandleRO aigizmossystem.html#MovementStateTypeHandleRO +Pathfinding.ECS.AIGizmosSystem.ResolvedMovementHandleRO aigizmossystem.html#ResolvedMovementHandleRO +Pathfinding.ECS.AIGizmosSystem.jobRepairPathScheduler aigizmossystem.html#jobRepairPathScheduler +Pathfinding.ECS.AIGizmosSystem.manuallyTriggered aigizmossystem.html#manuallyTriggered +Pathfinding.ECS.AIMoveSystem.MarkerMovementOverride aimovesystem.html#MarkerMovementOverride +Pathfinding.ECS.AIMoveSystem.entityQueryMovementOverride aimovesystem.html#entityQueryMovementOverride +Pathfinding.ECS.AIMoveSystem.entityQueryWithGravity aimovesystem.html#entityQueryWithGravity +Pathfinding.ECS.AIMovementSystemGroup.CustomTimeScale aimovementsystemgroup.html#CustomTimeScale Additional time scale multiplier to apply to the simulation. \n\nThis can be used to speed up or slow down the simulation instead of using Unity's Time.timeScale.\n\nThe final time scale will be Unity's Time.timeScale multiplied by this value.\n\nDefault value is 1.0.\n\n[code in online documentation] +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.CheapSimulationOnly timescaledratemanager.html#CheapSimulationOnly True if it was determined that zero substeps should be simulated. \n\nIn this case all systems will get an opportunity to run a single update, but they should avoid systems that don't have to run every single frame. +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime timescaledratemanager.html#CheapStepDeltaTime +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.CustomTimeScale timescaledratemanager.html#CustomTimeScale Additional time scale multiplier to apply to the simulation. \n\nThis can be used to speed up or slow down the simulation instead of using Unity's Time.timeScale.\n\nThe final time scale will be Unity's Time.timeScale multiplied by this value.\n\nDefault value is 1.0.\n\n[code in online documentation] +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.IsFirstSubstep timescaledratemanager.html#IsFirstSubstep True when this is the first substep of the current simulation. +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep timescaledratemanager.html#IsLastSubstep True when this is the last substep of the current simulation. +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.NumUpdatesThisFrame timescaledratemanager.html#NumUpdatesThisFrame +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.Timestep timescaledratemanager.html#Timestep +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.cheapSimulationOnly timescaledratemanager.html#cheapSimulationOnly +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.cheapTimeData timescaledratemanager.html#cheapTimeData +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.cheapTimeDataQueue timescaledratemanager.html#cheapTimeDataQueue +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.inGroup timescaledratemanager.html#inGroup +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.isFirstSubstep timescaledratemanager.html#isFirstSubstep +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.isLastSubstep timescaledratemanager.html#isLastSubstep +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.lastCheapSimulation timescaledratemanager.html#lastCheapSimulation +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.lastFullSimulation timescaledratemanager.html#lastFullSimulation +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.maximumDt timescaledratemanager.html#maximumDt +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.numUpdatesThisFrame timescaledratemanager.html#numUpdatesThisFrame +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.ownProcessingTimePerIteration timescaledratemanager.html#ownProcessingTimePerIteration +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.stepDt timescaledratemanager.html#stepDt +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.timeDataQueue timescaledratemanager.html#timeDataQueue +Pathfinding.ECS.AIMovementSystemGroup.TimeScaledRateManager.updateIndex timescaledratemanager.html#updateIndex +Pathfinding.ECS.AgentCylinderShape.height agentcylindershape.html#height Height of the agent in world units. +Pathfinding.ECS.AgentCylinderShape.radius agentcylindershape.html#radius Radius of the agent in world units. +Pathfinding.ECS.AgentMovementPlane.value agentmovementplane.html#value The movement plane for the agent. \n\nThe movement plane determines what the "up" direction of the agent is. For most typical 3D games, this will be aligned with the Y axis, but there are games in which the agent needs to navigate on walls, or on spherical worlds. For those games this movement plane will track the plane in which the agent is currently moving.\n\n[more in online documentation] +Pathfinding.ECS.AgentMovementPlaneSource.value agentmovementplanesource.html#value +Pathfinding.ECS.AgentOffMeshLinkTraversal.firstPosition agentoffmeshlinktraversal.html#firstPosition The start point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent starts traversing the off-mesh link, regardless of if the link is traversed from the start to end or from end to start. \n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversal.isReverse agentoffmeshlinktraversal.html#isReverse True if the agent is traversing the off-mesh link from original link's end to its start point. \n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversal.relativeEnd agentoffmeshlinktraversal.html#relativeEnd The end point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent will finish traversing the off-mesh link, regardless of if the link is traversed from start to end or from end to start. +Pathfinding.ECS.AgentOffMeshLinkTraversal.relativeStart agentoffmeshlinktraversal.html#relativeStart The start point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent starts traversing the off-mesh link, regardless of if the link is traversed from the start to end or from end to start. +Pathfinding.ECS.AgentOffMeshLinkTraversal.secondPosition agentoffmeshlinktraversal.html#secondPosition The end point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent will finish traversing the off-mesh link, regardless of if the link is traversed from start to end or from end to start. \n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.backupRotationSmoothing agentoffmeshlinktraversalcontext.html#backupRotationSmoothing +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.concreteLink agentoffmeshlinktraversalcontext.html#concreteLink The off-mesh link that is being traversed. \n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.deltaTime agentoffmeshlinktraversalcontext.html#deltaTime Delta time since the last link simulation. \n\nDuring high time scales, the simulation may run multiple substeps per frame.\n\nThis is not the same as Time.deltaTime. Inside the link coroutine, you should always use this field instead of Time.deltaTime. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.enableBuiltInMovement agentoffmeshlinktraversalcontext.html#enableBuiltInMovement True if the agent's built-in movement logic should be enabled. \n\nWhen traversing an off-mesh link, you typically want the agent's movement to be completely controlled by an animation, or some other code. However, sometimes you may want to use the built-in movement logic to move the agent.\n\nUsing the MoveTowards method will automatically enable the agent's movement logic during that frame.\n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.entity agentoffmeshlinktraversalcontext.html#entity The entity that is traversing the off-mesh link. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.gameObject agentoffmeshlinktraversalcontext.html#gameObject GameObject associated with the agent. \n\nIn most cases, an agent is associated with an agent, but this is not always the case. For example, if you have created an entity without using the FollowerEntity component, this property may return null.\n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.gameObjectCache agentoffmeshlinktraversalcontext.html#gameObjectCache +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.link agentoffmeshlinktraversalcontext.html#link Information about the off-mesh link that the agent is traversing. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.linkInfo agentoffmeshlinktraversalcontext.html#linkInfo Information about the off-mesh link that the agent is traversing. \n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.linkInfoPtr agentoffmeshlinktraversalcontext.html#linkInfoPtr +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.localAvoidanceDisabled agentoffmeshlinktraversalcontext.html#localAvoidanceDisabled +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.managedState agentoffmeshlinktraversalcontext.html#managedState Some internal state of the agent. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementControl agentoffmeshlinktraversalcontext.html#movementControl How the agent should move. \n\nThe agent will move according to this data, every frame, if enableBuiltInMovement is enabled.\n\n[more in online documentation] +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementControlPtr agentoffmeshlinktraversalcontext.html#movementControlPtr +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementDisabled agentoffmeshlinktraversalcontext.html#movementDisabled +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementPlane agentoffmeshlinktraversalcontext.html#movementPlane The plane in which the agent is moving. \n\nIn a 3D game, this will typically be the XZ plane, but in a 2D game it will typically be the XY plane. Games on spherical planets could have planes that are aligned with the surface of the planet. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementPlanePtr agentoffmeshlinktraversalcontext.html#movementPlanePtr +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementSettings agentoffmeshlinktraversalcontext.html#movementSettings The movement settings for the agent. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.movementSettingsPtr agentoffmeshlinktraversalcontext.html#movementSettingsPtr +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.transform agentoffmeshlinktraversalcontext.html#transform ECS LocalTransform component attached to the agent. +Pathfinding.ECS.AgentOffMeshLinkTraversalContext.transformPtr agentoffmeshlinktraversalcontext.html#transformPtr +Pathfinding.ECS.AutoRepathPolicy.Default autorepathpolicy2.html#Default +Pathfinding.ECS.AutoRepathPolicy.Sensitivity autorepathpolicy2.html#Sensitivity How sensitive the agent should be to changes in its destination for Mode.Dynamic. \n\nA higher value means the destination has to move less for the path to be recalculated.\n\n[more in online documentation] +Pathfinding.ECS.AutoRepathPolicy.lastDestination autorepathpolicy2.html#lastDestination +Pathfinding.ECS.AutoRepathPolicy.lastRepathTime autorepathpolicy2.html#lastRepathTime +Pathfinding.ECS.AutoRepathPolicy.mode autorepathpolicy2.html#mode Policy to use when recalculating paths. \n\n[more in online documentation] +Pathfinding.ECS.AutoRepathPolicy.pathFailures autorepathpolicy2.html#pathFailures +Pathfinding.ECS.AutoRepathPolicy.period autorepathpolicy2.html#period Number of seconds between each automatic path recalculation for Mode.EveryNSeconds, and the maximum interval for Mode.Dynamic. +Pathfinding.ECS.ComponentRef.ptr componentref.html#ptr +Pathfinding.ECS.ComponentRef.value componentref.html#value +Pathfinding.ECS.DestinationEntity.destination destinationentity.html#destination The entity whose position the agent is moving towards. \n\nEvery frame, the position of this entity will be copied to the DestinationPoint.destination field of the agent.\n\n[more in online documentation] +Pathfinding.ECS.DestinationEntity.useRotation destinationentity.html#useRotation If true, the agent will try to align itself with the rotation of the destination entity. \n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.ECS.DestinationEntitySystem.UpdateDestinationJob.TransformLookup updatedestinationjob.html#TransformLookup +Pathfinding.ECS.DestinationMoveInCircleSystem.UpdateDestinationJob.TransformLookup updatedestinationjob2.html#TransformLookup +Pathfinding.ECS.DestinationPoint.destination destinationpoint.html#destination The destination point that the agent is moving towards. \n\nThis is the point that the agent is trying to reach, but it may not always be possible to reach it.\n\n[more in online documentation] +Pathfinding.ECS.DestinationPoint.facingDirection destinationpoint.html#facingDirection The direction the agent should face when it reaches the destination. \n\nIf zero, the agent will not try to face any particular direction when reaching the destination. +Pathfinding.ECS.EntityAccess.handle entityaccess.html#handle +Pathfinding.ECS.EntityAccess.lastSystemVersion entityaccess.html#lastSystemVersion +Pathfinding.ECS.EntityAccess.readOnly entityaccess.html#readOnly +Pathfinding.ECS.EntityAccess.this[EntityStorageInfo storage] entityaccess.html#thisEntityStorageInfostorage +Pathfinding.ECS.EntityAccess.worldSequenceNumber entityaccess.html#worldSequenceNumber +Pathfinding.ECS.EntityAccessHelper.GlobalSystemVersionOffset entityaccesshelper.html#GlobalSystemVersionOffset +Pathfinding.ECS.EntityStorageCache.entity entitystoragecache.html#entity +Pathfinding.ECS.EntityStorageCache.lastWorldHash entitystoragecache.html#lastWorldHash +Pathfinding.ECS.EntityStorageCache.storage entitystoragecache.html#storage +Pathfinding.ECS.FollowerControlSystem.MarkerMovementOverrideAfterControl followercontrolsystem.html#MarkerMovementOverrideAfterControl +Pathfinding.ECS.FollowerControlSystem.MarkerMovementOverrideBeforeControl followercontrolsystem.html#MarkerMovementOverrideBeforeControl +Pathfinding.ECS.FollowerControlSystem.entityQueryControlManaged followercontrolsystem.html#entityQueryControlManaged +Pathfinding.ECS.FollowerControlSystem.entityQueryControlManaged2 followercontrolsystem.html#entityQueryControlManaged2 +Pathfinding.ECS.FollowerControlSystem.redrawScope followercontrolsystem.html#redrawScope +Pathfinding.ECS.FollowerEntityProxy.agentCylinderShapeAccessRO followerentityproxy.html#agentCylinderShapeAccessRO +Pathfinding.ECS.FollowerEntityProxy.agentCylinderShapeAccessRW followerentityproxy.html#agentCylinderShapeAccessRW +Pathfinding.ECS.FollowerEntityProxy.agentOffMeshLinkTraversalRO followerentityproxy.html#agentOffMeshLinkTraversalRO +Pathfinding.ECS.FollowerEntityProxy.autoRepath followerentityproxy.html#autoRepath Policy for when the agent recalculates its path. \n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.autoRepathPolicyRW followerentityproxy.html#autoRepathPolicyRW +Pathfinding.ECS.FollowerEntityProxy.canSearch followerentityproxy.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.currentNode followerentityproxy.html#currentNode Node which the agent is currently traversing. \n\nYou can, for example, use this to make the agent use a different animation when traversing nodes with a specific tag.\n\n[more in online documentation]\nWhen traversing an off-mesh link, this will return the final non-link node in the path before the agent started traversing the link. +Pathfinding.ECS.FollowerEntityProxy.debugFlags followerentityproxy.html#debugFlags Enables or disables debug drawing for this agent. \n\nThis is a bitmask with multiple flags so that you can choose exactly what you want to debug.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.desiredVelocity followerentityproxy.html#desiredVelocity Velocity that this agent wants to move with. \n\nIncludes gravity and local avoidance if applicable. In world units per second.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.desiredVelocityWithoutLocalAvoidance followerentityproxy.html#desiredVelocityWithoutLocalAvoidance Velocity that this agent wants to move with before taking local avoidance into account. \n\nIncludes gravity. In world units per second.\n\nSetting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.\n\n[code in online documentation]\n\n[more in online documentation]\n\n\nIf you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.destination followerentityproxy.html#destination Position in the world that this agent should move to. \n\nIf no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.\n\nSetting this property will immediately try to repair the path if the agent already has a path. This will also immediately update properties like reachedDestination, reachedEndOfPath and remainingDistance.\n\nThe agent may do a full path recalculation if the local repair was not sufficient, but this will at earliest happen in the next simulation step.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.destinationFacingDirection followerentityproxy.html#destinationFacingDirection Direction the agent will try to face when it reaches the destination. \n\nIf this is zero, the agent will not try to face any particular direction.\n\nThe following video shows three agents, one with no facing direction set, and then two agents with varying values of the lead in radius. [video in online documentation]\n\nTo set this, use SetDestination.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.destinationPointAccessRO followerentityproxy.html#destinationPointAccessRO +Pathfinding.ECS.FollowerEntityProxy.destinationPointAccessRW followerentityproxy.html#destinationPointAccessRW +Pathfinding.ECS.FollowerEntityProxy.enableGravity followerentityproxy.html#enableGravity Enables or disables gravity. \n\nIf gravity is enabled, the agent will accelerate downwards, and use a raycast to check if it should stop falling.\n\nThis has no effect if the agent's orientation is set to YAxisForward (2D mode). Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.enableLocalAvoidance followerentityproxy.html#enableLocalAvoidance True if local avoidance is enabled for this agent. \n\nEnabling this will automatically add a Pathfinding.ECS.RVO.RVOAgent component to the entity.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.endOfPath followerentityproxy.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or if it's not calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall, so that the agent couldn't get any closer.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.entity followerentityproxy.html#entity +Pathfinding.ECS.FollowerEntityProxy.entityExists followerentityproxy.html#entityExists True if the entity exists. \n\nThis is typically true if the FollowerEntity component is active and enabled and the game is running.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.entityStorageCache followerentityproxy.html#entityStorageCache +Pathfinding.ECS.FollowerEntityProxy.groundMask followerentityproxy.html#groundMask Determines which layers the agent will stand on. \n\nThe agent will use a raycast each frame to check if it should stop falling.\n\nThis layer mask should ideally not contain the agent's own layer, if the agent has a collider, as this may cause it to try to stand on top of itself. +Pathfinding.ECS.FollowerEntityProxy.hasPath followerentityproxy.html#hasPath True if this agent currently has a valid path that it follows. \n\nThis is true if the agent has a path and the path is not stale.\n\nA path may become stale if the graph is updated close to the agent and it hasn't had time to recalculate its path yet. +Pathfinding.ECS.FollowerEntityProxy.height followerentityproxy.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is used for various heuristics, and for visualization purposes. For example, the destination is only considered reached if the destination is not above the agent's head, and it's not more than half the agent's height below its feet.\n\nIf local lavoidance is enabled, this is also used to filter out collisions with agents and obstacles that are too far above or below the agent. +Pathfinding.ECS.FollowerEntityProxy.indicesScratch followerentityproxy.html#indicesScratch +Pathfinding.ECS.FollowerEntityProxy.isStopped followerentityproxy.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.ECS.FollowerEntityProxy.isTraversingOffMeshLink followerentityproxy.html#isTraversingOffMeshLink True if the agent is currently traversing an off-mesh link. \n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.likelyHasReasonableComponents followerentityproxy.html#likelyHasReasonableComponents True if the entity seems to have the required components. \n\nThis is used to detect entities which still exist, but which have had all their FollowerEntity components removed. +Pathfinding.ECS.FollowerEntityProxy.localAvoidanceTemporarilyDisabled followerentityproxy.html#localAvoidanceTemporarilyDisabled True if the agent's local avoidance is temporarily disabled, due to traversing an off-mesh link. \n\nWhen traversing an off-mesh link, the traversal code may temporarily disable local avoidance to allow the agent to traverse the link without being pushed around by other agents.\n\n[more in online documentation]\nWhen the agent is not traversing an off-mesh link, this will always return false.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.localTransformAccessRO followerentityproxy.html#localTransformAccessRO +Pathfinding.ECS.FollowerEntityProxy.localTransformAccessRW followerentityproxy.html#localTransformAccessRW +Pathfinding.ECS.FollowerEntityProxy.managedSettingsAccessRO followerentityproxy.html#managedSettingsAccessRO +Pathfinding.ECS.FollowerEntityProxy.managedSettingsAccessRW followerentityproxy.html#managedSettingsAccessRW +Pathfinding.ECS.FollowerEntityProxy.managedStateAccessRO followerentityproxy.html#managedStateAccessRO +Pathfinding.ECS.FollowerEntityProxy.managedStateAccessRW followerentityproxy.html#managedStateAccessRW +Pathfinding.ECS.FollowerEntityProxy.maxRotationSpeed followerentityproxy.html#maxRotationSpeed Maximum rotation speed in degrees per second. \n\nIf the agent would have to rotate faster than this, it will instead slow down to get more time to rotate.\n\nThe agent may want to rotate faster than rotationSpeed if there's not enough space, so that it has to move in a more narrow arc. It may also want to rotate faster if it is very close to its destination and it wants to make sure it ends up on the right spot without any circling.\n\nIt is recommended to keep this at a value slightly larger than rotationSpeed.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.maxSpeed followerentityproxy.html#maxSpeed Max speed in world units per second. +Pathfinding.ECS.FollowerEntityProxy.movementControlAccessRO followerentityproxy.html#movementControlAccessRO +Pathfinding.ECS.FollowerEntityProxy.movementControlAccessRW followerentityproxy.html#movementControlAccessRW +Pathfinding.ECS.FollowerEntityProxy.movementOutputAccessRW followerentityproxy.html#movementOutputAccessRW +Pathfinding.ECS.FollowerEntityProxy.movementOverrides followerentityproxy.html#movementOverrides Provides callbacks during various parts of the movement calculations. \n\nWith this property you can register callbacks that will be called during various parts of the movement calculations. These can be used to modify movement of the agent.\n\nThe following example demonstrates how one can hook into one of the available phases and modify the agent's movement. In this case, the movement is modified to become wavy.\n\n [video in online documentation]\n\n[code in online documentation]\n\n- BeforeControl: Called before the agent's movement is calculated. At this point, the agent has a valid path, and the next corner that is moving towards has been calculated.\n\n- AfterControl: Called after the agent's desired movement is calculated. The agent has stored its desired movement in the MovementControl component. Local avoidance has not yet run.\n\n- BeforeMovement: Called right before the agent's movement is applied. At this point the agent's final movement (including local avoidance) is stored in the ResolvedMovement component, which you may modify.\n\n\n\n[more in online documentation]\nThe callbacks may be called multiple times per frame, if the fps is low, or if the time scale is high. It may also be called less than once per frame if the fps is very high. Each callback is provided with a dt parameter, which is the time in seconds since the last simulation step. You should prefer using this instead of Time.deltaTime.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.movementPlane followerentityproxy.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.ECS.FollowerEntityProxy.movementPlaneAccessRO followerentityproxy.html#movementPlaneAccessRO +Pathfinding.ECS.FollowerEntityProxy.movementPlaneAccessRW followerentityproxy.html#movementPlaneAccessRW +Pathfinding.ECS.FollowerEntityProxy.movementPlaneSource followerentityproxy.html#movementPlaneSource How to calculate which direction is "up" for the agent. \n\nIn almost all cases, you should use the Graph option. This will make the agent use the graph's natural "up" direction. However, if you are using a spherical world, or a world with some other strange shape, then you may want to use the NavmeshNormal or Raycast options.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.movementSettings followerentityproxy.html#movementSettings Various movement settings. \n\nSome of these settings are exposed on the FollowerEntity directly. For example maxSpeed.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.movementSettingsAccessRO followerentityproxy.html#movementSettingsAccessRO +Pathfinding.ECS.FollowerEntityProxy.movementSettingsAccessRW followerentityproxy.html#movementSettingsAccessRW +Pathfinding.ECS.FollowerEntityProxy.movementStateAccessRO followerentityproxy.html#movementStateAccessRO +Pathfinding.ECS.FollowerEntityProxy.movementStateAccessRW followerentityproxy.html#movementStateAccessRW +Pathfinding.ECS.FollowerEntityProxy.movementStatisticsAccessRO followerentityproxy.html#movementStatisticsAccessRO +Pathfinding.ECS.FollowerEntityProxy.movementStatisticsAccessRW followerentityproxy.html#movementStatisticsAccessRW +Pathfinding.ECS.FollowerEntityProxy.nearestNavmeshBorder followerentityproxy.html#nearestNavmeshBorder Border of the navmesh closest to the agent's current position. \n\nThe border of the navmesh is what separates the walkable and unwalkable part of the world.\n\n [image in online documentation][video in online documentation]\n\nIf no border could be found (for example if the agent has no path), then the hit.point field will be positive infinity.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.nextCornersScratch followerentityproxy.html#nextCornersScratch +Pathfinding.ECS.FollowerEntityProxy.nextOffMeshLink followerentityproxy.html#nextOffMeshLink The off-mesh link that the agent will traverse next, or the one it is traversing (if any). \n\nThe next link in the path will be returned regardless of the distance to it.\n\nThis will be a default OffMeshLinks.OffMeshLinkTracer if the agent is not traversing an off-mesh link, and there are no remaining off-mesh links in its path (the OffMeshLinks.OffMeshLinkTracer.link field will be null).\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.offMeshLink followerentityproxy.html#offMeshLink The off-mesh link that the agent is currently traversing. \n\nThis will be a default OffMeshLinks.OffMeshLinkTracer if the agent is not traversing an off-mesh link (the OffMeshLinks.OffMeshLinkTracer.link field will be null).\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.onSearchPath followerentityproxy.html#onSearchPath Called when the agent recalculates its path. \n\nThis is called both for automatic path recalculations (see canSearch) and manual ones (see SearchPath).\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.onTraverseOffMeshLink followerentityproxy.html#onTraverseOffMeshLink Callback to be called when an agent starts traversing an off-mesh link. \n\nThe handler will be called when the agent starts traversing an off-mesh link. It allows you to to control the agent for the full duration of the link traversal.\n\nUse the passed context struct to get information about the link and to control the agent.\n\n[code in online documentation]\n\n[more in online documentation]\nYou can alternatively set the corresponding property property on the off-mesh link ( NodeLink2.onTraverseOffMeshLink) to specify a callback for a specific off-mesh link.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.orientation followerentityproxy.html#orientation Determines which direction the agent moves in. \n\nFor 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games.\n\nFor 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.\n\nWhen using ZAxisForward, the +Z axis will be the forward direction of the agent, +Y will be upwards, and +X will be the right direction.\n\nWhen using YAxisForward, the +Y axis will be the forward direction of the agent, +Z will be upwards, and +X will be the right direction.\n\n[more in online documentation]\n [image in online documentation] +Pathfinding.ECS.FollowerEntityProxy.pathPending followerentityproxy.html#pathPending True if a path is currently being calculated. +Pathfinding.ECS.FollowerEntityProxy.pathfindingSettings followerentityproxy.html#pathfindingSettings Pathfinding settings. \n\nThe settings in this struct controls how the agent calculates paths to its destination.\n\nThis is analogous to the Seeker component used for other movement scripts.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.position followerentityproxy.html#position Position of the agent. \n\nIn world space. \n\n[more in online documentation]\nIf you want to move the agent you may use Teleport or Move. +Pathfinding.ECS.FollowerEntityProxy.positionSmoothing followerentityproxy.html#positionSmoothing How much to smooth the visual position of the agent. \n\nThis does not affect movement, but smoothes out the position of the agent visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThis will make the agent seem to lag slightly behind the internal position of the agent. It may also cut corners slightly.\n\nThe unit for this field is seconds. +Pathfinding.ECS.FollowerEntityProxy.radius followerentityproxy.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.reachedCrowdedEndOfPath followerentityproxy.html#reachedCrowdedEndOfPath Like reachedEndOfPath, but will also return true if the end of the path is crowded, and this agent has stopped because it cannot get closer. \n\nThis is only relevant if the agent is using local avoidance. Otherwise, this will be identical to reachedEndOfPath.\n\nIf the agent has a stale path (e.g. because the destination changed significantly, or a graph update happened near the agent), false will be returned until the path has been recalculated (typically in the next one or two frames).\n\nYou can see a visualization of this state by enabling "Reached State" in the Local Avoidance Debug Rendering field.\n\n[more in online documentation]\nIn the video below, the agents will get a red ring around them when this property is true.\n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.reachedDestination followerentityproxy.html#reachedDestination True if the ai has reached the destination. \n\nThe agent considers the destination reached when it is within stopDistance world units from the destination. Additionally, the destination must not be above the agent's head, and it must not be more than half the agent's height below its feet.\n\nIf a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation.\n\nThis value will be updated immediately when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.reachedEndOfPath followerentityproxy.html#reachedEndOfPath True if the agent has reached the end of the current path. \n\nThe agent considers the end of the path reached when it is within stopDistance world units from the end of the path. Additionally, the end of the path must not be above the agent's head, and it must not be more than half the agent's height below its feet.\n\nIf a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation.\n\nThis value will be updated immediately when the destination is changed.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.readyToTraverseOffMeshLinkRW followerentityproxy.html#readyToTraverseOffMeshLinkRW +Pathfinding.ECS.FollowerEntityProxy.remainingDistance followerentityproxy.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThis is an approximation for performance reasons, and the agent also does not know the true distance at all times. It tends to be a bit lower than the true distance. It will get more accurate as the agent gets closer to the end of the path. If you want a more accurate distance (at a higher performance cost), you can call GetRemainingPath and sum the length of it.\n\n[more in online documentation]\nThis value will update immediately if the destination property is changed, or if the agent is moved using the position property or the Teleport method.\n\nIf the agent has no path, or if the current path is stale (e.g. if the graph has been updated close to the agent, and it hasn't had time to recalculate its path), this will return positive infinity.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.resolvedMovementAccessRO followerentityproxy.html#resolvedMovementAccessRO +Pathfinding.ECS.FollowerEntityProxy.resolvedMovementAccessRW followerentityproxy.html#resolvedMovementAccessRW +Pathfinding.ECS.FollowerEntityProxy.rotation followerentityproxy.html#rotation Rotation of the agent. \n\nIn world space.\n\nThe entity internally always treats the Z axis as forward, but this property respects the orientation field. So it will return either a rotation with the Y axis as forward, or Z axis as forward, depending on the orientation field.\n\n[more in online documentation]\nThis will return the agent's rotation even if updateRotation is false.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.rotationSmoothing followerentityproxy.html#rotationSmoothing How much to smooth the visual rotation of the agent. \n\nThis does not affect movement, but smoothes out how the agent rotates visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThe smoothing is done primarily using an exponential moving average, but with a small linear term to make the rotation converge faster when the agent is almost facing the desired direction.\n\nAdding smoothing will make the visual rotation of the agent lag a bit behind the actual rotation. Too much smoothing may make the agent seem sluggish, and appear to move sideways.\n\nThe unit for this field is seconds. +Pathfinding.ECS.FollowerEntityProxy.rotationSpeed followerentityproxy.html#rotationSpeed Desired rotation speed in degrees per second. \n\nIf the agent is in an open area and gets a new destination directly behind itself, it will start to rotate around with exactly this rotation speed.\n\nThe agent will slow down its rotation speed as it approaches its desired facing direction. So for example, when it is only 90 degrees away from its desired facing direction, it will only rotate with about half this speed.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.rvoSettings followerentityproxy.html#rvoSettings Local avoidance settings. +Pathfinding.ECS.FollowerEntityProxy.rvoSettingsAccessRO followerentityproxy.html#rvoSettingsAccessRO +Pathfinding.ECS.FollowerEntityProxy.rvoSettingsAccessRW followerentityproxy.html#rvoSettingsAccessRW +Pathfinding.ECS.FollowerEntityProxy.simulateMovement followerentityproxy.html#simulateMovement Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nDisabling this will remove the SimulateMovement component from the entity, which prevents most systems from running for this entity.\n\nWhen disabled, the velocity property, and many other informational properties, will no longer update.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.steeringTarget followerentityproxy.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.ECS.FollowerEntityProxy.stopDistance followerentityproxy.html#stopDistance How far away from the destination should the agent aim to stop, in world units. \n\nIf the agent is within this distance from the destination point it will be considered to have reached the destination.\n\nEven if you want the agent to stop precisely at a given point, it is recommended to keep this slightly above zero. If it is exactly zero, the agent may have a hard time deciding that it has actually reached the end of the path, due to floating point errors and such.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.updatePosition followerentityproxy.html#updatePosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. Instead, only the position property and the internal entity's position will change.\n\nThis is useful if you want to control the movement of the character using some other means, such as root motion, but still want the AI to move freely.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.updateRotation followerentityproxy.html#updateRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. Instead, only the rotation property and the internal entity's rotation will change.\n\nThis is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed sprite to indicate the direction the character is facing.\n\nYou can enable PIDMovement.DebugFlags.Rotation in debugFlags to draw a gizmos arrow in the scene view to indicate the agent's internal rotation.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.velocity followerentityproxy.html#velocity Actual velocity that the agent is moving with, including gravity. \n\nIn world units per second.\n\nThis is useful for, for example, selecting which animations to play, and at what speeds.\n\n[more in online documentation] +Pathfinding.ECS.FollowerEntityProxy.world followerentityproxy.html#world +Pathfinding.ECS.GravityState.verticalVelocity gravitystate.html#verticalVelocity Current vertical velocity of the agent. \n\nThis is the velocity that the agent is moving with due to gravity. It is not necessarily the same as the Y component of the estimated velocity. +Pathfinding.ECS.JobAlignAgentWithMovementDirection.dt jobalignagentwithmovementdirection.html#dt +Pathfinding.ECS.JobApplyGravity.dt jobapplygravity.html#dt +Pathfinding.ECS.JobApplyGravity.raycastCommands jobapplygravity.html#raycastCommands +Pathfinding.ECS.JobApplyGravity.raycastHits jobapplygravity.html#raycastHits +Pathfinding.ECS.JobControl.MarkerConvertObstacles jobcontrol.html#MarkerConvertObstacles +Pathfinding.ECS.JobControl.draw jobcontrol.html#draw +Pathfinding.ECS.JobControl.dt jobcontrol.html#dt +Pathfinding.ECS.JobControl.edgesScratch jobcontrol.html#edgesScratch +Pathfinding.ECS.JobControl.indicesScratch jobcontrol.html#indicesScratch +Pathfinding.ECS.JobControl.navmeshEdgeData jobcontrol.html#navmeshEdgeData +Pathfinding.ECS.JobDrawFollowerGizmos.AgentCylinderShapeHandleRO jobdrawfollowergizmos.html#AgentCylinderShapeHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.AgentMovementPlaneHandleRO jobdrawfollowergizmos.html#AgentMovementPlaneHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.InternalRotation jobdrawfollowergizmos.html#InternalRotation +Pathfinding.ECS.JobDrawFollowerGizmos.LocalTransformTypeHandleRO jobdrawfollowergizmos.html#LocalTransformTypeHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.ManagedStateHandleRW jobdrawfollowergizmos.html#ManagedStateHandleRW +Pathfinding.ECS.JobDrawFollowerGizmos.MovementSettingsHandleRO jobdrawfollowergizmos.html#MovementSettingsHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.MovementStateHandleRO jobdrawfollowergizmos.html#MovementStateHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.Path jobdrawfollowergizmos.html#Path +Pathfinding.ECS.JobDrawFollowerGizmos.ResolvedMovementHandleRO jobdrawfollowergizmos.html#ResolvedMovementHandleRO +Pathfinding.ECS.JobDrawFollowerGizmos.TargetInternalRotation jobdrawfollowergizmos.html#TargetInternalRotation +Pathfinding.ECS.JobDrawFollowerGizmos.TargetInternalRotationHint jobdrawfollowergizmos.html#TargetInternalRotationHint +Pathfinding.ECS.JobDrawFollowerGizmos.UnsmoothedRotation jobdrawfollowergizmos.html#UnsmoothedRotation +Pathfinding.ECS.JobDrawFollowerGizmos.VisualRotationColor jobdrawfollowergizmos.html#VisualRotationColor +Pathfinding.ECS.JobDrawFollowerGizmos.draw jobdrawfollowergizmos.html#draw +Pathfinding.ECS.JobDrawFollowerGizmos.entityManagerHandle jobdrawfollowergizmos.html#entityManagerHandle +Pathfinding.ECS.JobDrawFollowerGizmos.scratchBuffer1 jobdrawfollowergizmos.html#scratchBuffer1 +Pathfinding.ECS.JobDrawFollowerGizmos.scratchBuffer2 jobdrawfollowergizmos.html#scratchBuffer2 +Pathfinding.ECS.JobDrawFollowerGizmosBase.ShapeGizmoColor jobdrawfollowergizmosbase.html#ShapeGizmoColor +Pathfinding.ECS.JobDrawFollowerGizmosBase.draw jobdrawfollowergizmosbase.html#draw +Pathfinding.ECS.JobDrawFollowerGizmosBase.orientation jobdrawfollowergizmosbase.html#orientation +Pathfinding.ECS.JobManagedMovementOverrideAfterControl.dt jobmanagedmovementoverrideaftercontrol.html#dt +Pathfinding.ECS.JobManagedMovementOverrideBeforeControl.dt jobmanagedmovementoverridebeforecontrol.html#dt +Pathfinding.ECS.JobManagedMovementOverrideBeforeMovement.dt jobmanagedmovementoverridebeforemovement.html#dt +Pathfinding.ECS.JobManagedOffMeshLinkTransition.commandBuffer jobmanagedoffmeshlinktransition.html#commandBuffer +Pathfinding.ECS.JobManagedOffMeshLinkTransition.deltaTime jobmanagedoffmeshlinktransition.html#deltaTime +Pathfinding.ECS.JobMoveAgent.dt jobmoveagent.html#dt +Pathfinding.ECS.JobPrepareAgentRaycasts.dt jobprepareagentraycasts.html#dt +Pathfinding.ECS.JobPrepareAgentRaycasts.gravity jobprepareagentraycasts.html#gravity +Pathfinding.ECS.JobPrepareAgentRaycasts.raycastCommands jobprepareagentraycasts.html#raycastCommands +Pathfinding.ECS.JobPrepareAgentRaycasts.raycastQueryParameters jobprepareagentraycasts.html#raycastQueryParameters +Pathfinding.ECS.JobRepairPath.MarkerGetNextCorners jobrepairpath.html#MarkerGetNextCorners +Pathfinding.ECS.JobRepairPath.MarkerRepair jobrepairpath.html#MarkerRepair +Pathfinding.ECS.JobRepairPath.MarkerUpdateReachedEndInfo jobrepairpath.html#MarkerUpdateReachedEndInfo +Pathfinding.ECS.JobRepairPath.Scheduler.AgentCylinderShapeTypeHandleRO scheduler.html#AgentCylinderShapeTypeHandleRO +Pathfinding.ECS.JobRepairPath.Scheduler.AgentMovementPlaneTypeHandleRO scheduler.html#AgentMovementPlaneTypeHandleRO +Pathfinding.ECS.JobRepairPath.Scheduler.AutoRepathPolicyRW scheduler.html#AutoRepathPolicyRW +Pathfinding.ECS.JobRepairPath.Scheduler.DestinationPointTypeHandleRO scheduler.html#DestinationPointTypeHandleRO +Pathfinding.ECS.JobRepairPath.Scheduler.LocalTransformTypeHandleRO scheduler.html#LocalTransformTypeHandleRO +Pathfinding.ECS.JobRepairPath.Scheduler.ManagedStateTypeHandleRW scheduler.html#ManagedStateTypeHandleRW +Pathfinding.ECS.JobRepairPath.Scheduler.MovementSettingsTypeHandleRO scheduler.html#MovementSettingsTypeHandleRO +Pathfinding.ECS.JobRepairPath.Scheduler.MovementStateTypeHandleRW scheduler.html#MovementStateTypeHandleRW +Pathfinding.ECS.JobRepairPath.Scheduler.ReadyToTraverseOffMeshLinkTypeHandleRW scheduler.html#ReadyToTraverseOffMeshLinkTypeHandleRW +Pathfinding.ECS.JobRepairPath.Scheduler.entityManagerHandle scheduler.html#entityManagerHandle +Pathfinding.ECS.JobRepairPath.Scheduler.onlyApplyPendingPaths scheduler.html#onlyApplyPendingPaths +Pathfinding.ECS.JobRepairPath.indicesScratch jobrepairpath.html#indicesScratch +Pathfinding.ECS.JobRepairPath.nextCornersScratch jobrepairpath.html#nextCornersScratch +Pathfinding.ECS.JobRepairPath.onlyApplyPendingPaths jobrepairpath.html#onlyApplyPendingPaths +Pathfinding.ECS.JobRepairPath.scheduler jobrepairpath.html#scheduler +Pathfinding.ECS.JobRepairPathHelpers.PathTracerInfo.endPointOfFirstPart pathtracerinfo.html#endPointOfFirstPart +Pathfinding.ECS.JobRepairPathHelpers.PathTracerInfo.hasValidEndPoint pathtracerinfo.html#hasValidEndPoint +Pathfinding.ECS.JobRepairPathHelpers.PathTracerInfo.isStale pathtracerinfo.html#isStale +Pathfinding.ECS.JobRepairPathHelpers.PathTracerInfo.partCount pathtracerinfo.html#partCount +Pathfinding.ECS.JobStartOffMeshLinkTransition.commandBuffer jobstartoffmeshlinktransition.html#commandBuffer +Pathfinding.ECS.JobSyncEntitiesToTransforms.entities jobsyncentitiestotransforms.html#entities +Pathfinding.ECS.JobSyncEntitiesToTransforms.entityPositions jobsyncentitiestotransforms.html#entityPositions +Pathfinding.ECS.JobSyncEntitiesToTransforms.movementState jobsyncentitiestotransforms.html#movementState +Pathfinding.ECS.JobSyncEntitiesToTransforms.orientationYAxisForward jobsyncentitiestotransforms.html#orientationYAxisForward +Pathfinding.ECS.JobSyncEntitiesToTransforms.syncPositionWithTransform jobsyncentitiestotransforms.html#syncPositionWithTransform +Pathfinding.ECS.JobSyncEntitiesToTransforms.syncRotationWithTransform jobsyncentitiestotransforms.html#syncRotationWithTransform +Pathfinding.ECS.JobUpdateMovementStatistics.dt jobupdatemovementstatistics.html#dt +Pathfinding.ECS.LateSyncSystem.entityQueryPrepareMovement latesyncsystem.html#entityQueryPrepareMovement +Pathfinding.ECS.LateSyncSystem.jobRepairPathScheduler latesyncsystem.html#jobRepairPathScheduler +Pathfinding.ECS.ManagedAgentOffMeshLinkTraversal.context managedagentoffmeshlinktraversal.html#context Internal context used to pass component data to the coroutine. +Pathfinding.ECS.ManagedAgentOffMeshLinkTraversal.coroutine managedagentoffmeshlinktraversal.html#coroutine Coroutine which is used to traverse the link. +Pathfinding.ECS.ManagedAgentOffMeshLinkTraversal.handler managedagentoffmeshlinktraversal.html#handler +Pathfinding.ECS.ManagedAgentOffMeshLinkTraversal.stateMachine managedagentoffmeshlinktraversal.html#stateMachine +Pathfinding.ECS.ManagedEntityAccess.entityManager managedentityaccess.html#entityManager +Pathfinding.ECS.ManagedEntityAccess.handle managedentityaccess.html#handle +Pathfinding.ECS.ManagedEntityAccess.readOnly managedentityaccess.html#readOnly +Pathfinding.ECS.ManagedEntityAccess.this[EntityStorageInfo storage] managedentityaccess.html#thisEntityStorageInfostorage +Pathfinding.ECS.ManagedMovementOverride.callback managedmovementoverride.html#callback +Pathfinding.ECS.ManagedMovementOverrides.entity managedmovementoverrides.html#entity +Pathfinding.ECS.ManagedMovementOverrides.world managedmovementoverrides.html#world +Pathfinding.ECS.ManagedSettings.onTraverseOffMeshLink managedsettings.html#onTraverseOffMeshLink Callback for when the agent starts to traverse an off-mesh link. \n\n[more in online documentation] +Pathfinding.ECS.ManagedSettings.pathfindingSettings managedsettings.html#pathfindingSettings Settings for how an agent searches for paths. \n\nThis struct contains information about which graphs the agent can use, which nodes it can traverse, and if any nodes should be easier or harder to traverse.\n\nA good default value to start from is PathRequestSettings.Default.\n\n[more in online documentation] +Pathfinding.ECS.ManagedState.activePath managedstate.html#activePath Path that is being followed, if any. \n\nThe agent may have moved away from this path since it was calculated. So it may not be up to date. +Pathfinding.ECS.ManagedState.autoRepath managedstate.html#autoRepath Settings for when to recalculate the path. \n\n[more in online documentation] +Pathfinding.ECS.ManagedState.enableGravity managedstate.html#enableGravity True if gravity is enabled for this agent. \n\nThe agent will always fall down according to its own movement plane. The gravity applied is Physics.gravity.y.\n\nEnabling this will enable the GravityState component of the entity.\n\nThis has no effect if the agent's orientation is set to YAxisForward (2D mode). Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode.\n\n[more in online documentation] +Pathfinding.ECS.ManagedState.enableLocalAvoidance managedstate.html#enableLocalAvoidance True if local avoidance is enabled for this agent. \n\nEnabling this will automatically add a Pathfinding.ECS.RVO.RVOAgent component to the entity.\n\n[more in online documentation] +Pathfinding.ECS.ManagedState.onTraverseOffMeshLink managedstate.html#onTraverseOffMeshLink Callback for when the agent starts to traverse an off-mesh link. +Pathfinding.ECS.ManagedState.pathTracer managedstate.html#pathTracer Calculates in which direction to move to follow the path. +Pathfinding.ECS.ManagedState.pathfindingSettings managedstate.html#pathfindingSettings +Pathfinding.ECS.ManagedState.pendingPath managedstate.html#pendingPath Path that is being calculated, if any. +Pathfinding.ECS.ManagedState.rvoSettings managedstate.html#rvoSettings Local avoidance settings. \n\nWhen the agent has local avoidance enabled, these settings will be copied into a Pathfinding.ECS.RVO.RVOAgent component which is attached to the agent.\n\n[more in online documentation] +Pathfinding.ECS.MovementControl.endOfPath movementcontrol.html#endOfPath The end of the current path. \n\nThis informs the local avoidance system about the final desired destination for the agent. This is used to make agents stop if the destination is crowded and it cannot reach its destination.\n\nIf this is not set, agents will often move forever around a crowded destination, always trying to find some way to get closer, but never finding it. +Pathfinding.ECS.MovementControl.hierarchicalNodeIndex movementcontrol.html#hierarchicalNodeIndex The index of the hierarchical node that the agent is currently in. \n\nWill be -1 if the hierarchical node index is not known. \n\n[more in online documentation] +Pathfinding.ECS.MovementControl.maxSpeed movementcontrol.html#maxSpeed The maximum speed at which the agent may move, in meters per second. \n\nIt is recommended to keep this slightly above speed, to allow the local avoidance system to move agents around more efficiently when necessary. +Pathfinding.ECS.MovementControl.overrideLocalAvoidance movementcontrol.html#overrideLocalAvoidance If true, this agent will ignore other agents during local avoidance, but other agents will still avoid this one. \n\nThis is useful for example for a player character which should not avoid other agents, but other agents should avoid the player. +Pathfinding.ECS.MovementControl.rotationSpeed movementcontrol.html#rotationSpeed The speed at which the agent should rotate towards targetRotation + targetRotationOffset, in radians per second. +Pathfinding.ECS.MovementControl.speed movementcontrol.html#speed The speed at which the agent should move towards targetPoint, in meters per second. +Pathfinding.ECS.MovementControl.targetPoint movementcontrol.html#targetPoint The point the agent should move towards. +Pathfinding.ECS.MovementControl.targetRotation movementcontrol.html#targetRotation The desired rotation of the agent, in radians, relative to the current movement plane. \n\nWhen rotating the agent visually, the targetRotationOffset should also be added to this value.\n\n[more in online documentation] +Pathfinding.ECS.MovementControl.targetRotationHint movementcontrol.html#targetRotationHint The desired rotation of the agent, in radians, over a longer time horizon, relative to the current movement plane. \n\nThe targetRotation is usually only over a very short time-horizon, usually a single simulation time step. This variable is used to provide a hint of where the agent wants to rotate to over a slightly longer time scale (on the order of a second or so). It is not used to control movement directly, but it may be used to guide animations, or rotation smoothing.\n\nIf no better hint is available, this should be set to the same value as targetRotation.\n\n[more in online documentation] +Pathfinding.ECS.MovementControl.targetRotationOffset movementcontrol.html#targetRotationOffset Additive modifier to targetRotation, in radians. \n\nThis is used by the local avoidance system to rotate the agent, without this causing a feedback loop. This extra rotation will be ignored by the control system which decides how the agent *wants* to move. It will instead be directly applied to the agent.\n\n[more in online documentation] +Pathfinding.ECS.MovementPlaneFromGraphSystem.JobMovementPlaneFromGraph.movementPlanes jobmovementplanefromgraph.html#movementPlanes +Pathfinding.ECS.MovementPlaneFromGraphSystem.JobMovementPlaneFromNavmeshNormal.dt jobmovementplanefromnavmeshnormal.html#dt +Pathfinding.ECS.MovementPlaneFromGraphSystem.JobMovementPlaneFromNavmeshNormal.que jobmovementplanefromnavmeshnormal.html#que +Pathfinding.ECS.MovementPlaneFromGraphSystem.JobMovementPlaneFromNavmeshNormal.vertices jobmovementplanefromnavmeshnormal.html#vertices +Pathfinding.ECS.MovementPlaneFromGraphSystem.entityQueryGraph movementplanefromgraphsystem.html#entityQueryGraph +Pathfinding.ECS.MovementPlaneFromGraphSystem.entityQueryNormal movementplanefromgraphsystem.html#entityQueryNormal +Pathfinding.ECS.MovementPlaneFromGraphSystem.graphNodeQueue movementplanefromgraphsystem.html#graphNodeQueue +Pathfinding.ECS.MovementPlaneSource ecs2.html#MovementPlaneSource How to calculate which direction is "up" for the agent. +Pathfinding.ECS.MovementSettings.debugFlags movementsettings.html#debugFlags Flags for enabling debug rendering in the scene view. +Pathfinding.ECS.MovementSettings.follower movementsettings.html#follower Additional movement settings. +Pathfinding.ECS.MovementSettings.groundMask movementsettings.html#groundMask Layer mask to use for ground placement. \n\nMake sure this does not include the layer of any colliders attached to this gameobject.\n\n[more in online documentation] +Pathfinding.ECS.MovementSettings.isStopped movementsettings.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.ECS.MovementSettings.movementPlaneSource movementsettings.html#movementPlaneSource How to calculate which direction is "up" for the agent. \n\n[more in online documentation] +Pathfinding.ECS.MovementSettings.positionSmoothing movementsettings.html#positionSmoothing How much to smooth the visual position of the agent. \n\nThis does not affect movement, but smoothes out the position of the agent visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThis will make the agent seem to lag slightly behind the internal position of the agent. It may also cut corners slightly.\n\nThe unit for this field is seconds. +Pathfinding.ECS.MovementSettings.rotationSmoothing movementsettings.html#rotationSmoothing How much to smooth the visual rotation of the agent. \n\nThis does not affect movement, but smoothes out how the agent rotates visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThe smoothing is done primarily using an exponential moving average, but with a small linear term to make the rotation converge faster when the agent is almost facing the desired direction.\n\nAdding smoothing will make the visual rotation of the agent lag a bit behind the actual rotation. Too much smoothing may make the agent seem sluggish, and appear to move sideways.\n\nThe unit for this field is seconds. +Pathfinding.ECS.MovementSettings.stopDistance movementsettings.html#stopDistance How far away from the destination should the agent aim to stop, in world units. \n\nIf the agent is within this distance from the destination point it will be considered to have reached the destination.\n\nEven if you want the agent to stop precisely at a given point, it is recommended to keep this slightly above zero. If it is exactly zero, the agent may have a hard time deciding that it has actually reached the end of the path, due to floating point errors and such.\n\n[more in online documentation] +Pathfinding.ECS.MovementState.GraphIndexMaskInFlags movementstate.html#GraphIndexMaskInFlags +Pathfinding.ECS.MovementState.GraphIndexOffsetInFlags movementstate.html#GraphIndexOffsetInFlags +Pathfinding.ECS.MovementState.HasValidEndPointFlag movementstate.html#HasValidEndPointFlag +Pathfinding.ECS.MovementState.ReachedDestinationFlag movementstate.html#ReachedDestinationFlag +Pathfinding.ECS.MovementState.ReachedEndOfPartFlag movementstate.html#ReachedEndOfPartFlag +Pathfinding.ECS.MovementState.ReachedEndOfPathFlag movementstate.html#ReachedEndOfPathFlag +Pathfinding.ECS.MovementState.TraversingLastPartFlag movementstate.html#TraversingLastPartFlag +Pathfinding.ECS.MovementState.closestOnNavmesh movementstate.html#closestOnNavmesh The closest point on the navmesh to the agent. \n\nThe agent will be snapped to this point. +Pathfinding.ECS.MovementState.endOfPath movementstate.html#endOfPath The end of the current path. \n\nNote that the agent may be heading towards an off-mesh link which is not the same as this point. +Pathfinding.ECS.MovementState.flags movementstate.html#flags Bitmask for various flags. +Pathfinding.ECS.MovementState.followerState movementstate.html#followerState State of the PID controller for the movement. +Pathfinding.ECS.MovementState.graphIndex movementstate.html#graphIndex The index of the graph that the agent is currently traversing. \n\nWill be GraphNode.InvalidGraphIndex if the agent has no path, or the node that the agent is traversing has been destroyed. +Pathfinding.ECS.MovementState.hasValidEndPoint movementstate.html#hasValidEndPoint +Pathfinding.ECS.MovementState.hierarchicalNodeIndex movementstate.html#hierarchicalNodeIndex The index of the hierarchical node that the agent is currently in. \n\nWill be -1 if the hierarchical node index is not known.\n\nThis field is valid during all system updates in the AIMovementSystemGroup. It will be cleared at the end of that group by JobClearTemporaryData, so you'll typically not be able to observe it during other times. It is cleared as a safety measure, as it is not guaranteed to be valid after that group has finished running, as graph updates may have changed the graph.\n\n[more in online documentation] +Pathfinding.ECS.MovementState.isOnValidNode movementstate.html#isOnValidNode True if the agent is currently on a valid node. \n\nThis is true if the agent has a path, and the node that the agent is traversing is walkable and not destroyed.\n\nIf false, the hierarchicalNodeIndex and graphIndex fields are invalid. +Pathfinding.ECS.MovementState.nextCorner movementstate.html#nextCorner The next corner in the path. +Pathfinding.ECS.MovementState.pathTracerVersion movementstate.html#pathTracerVersion Version number of PathTracer.version when the movement state was last updated. \n\nIn particular, closestOnNavmesh, nextCorner, endOfPath, remainingDistanceToEndOfPart, reachedDestination and reachedEndOfPath will only be considered up to date if this is equal to the current version number of the path tracer. +Pathfinding.ECS.MovementState.positionOffset movementstate.html#positionOffset Offset from the agent's internal position to its visual position. \n\nThis is used when position smoothing is enabled. Otherwise it is zero. +Pathfinding.ECS.MovementState.reachedDestination movementstate.html#reachedDestination True if the agent has reached its destination. \n\nThe destination will be considered reached if all of these conditions are met:\n- The agent has a path\n\n- The path is not stale\n\n- The destination is not significantly below the agent's feet.\n\n- The destination is not significantly above the agent's head.\n\n- The agent is on the last part of the path (there are no more remaining off-mesh links).\n\n- The remaining distance to the end of the path + the distance from the end of the path to the destination is less than MovementSettings.stopDistance. +Pathfinding.ECS.MovementState.reachedDestinationAndOrientation movementstate.html#reachedDestinationAndOrientation True if the agent has reached its destination and is facing the desired orientation. \n\nThis will become true if all of these conditions are met:\n- reachedDestination is true\n\n- The agent is facing the desired facing direction as specified in DestinationPoint.facingDirection. +Pathfinding.ECS.MovementState.reachedDestinationAndOrientationFlag movementstate.html#reachedDestinationAndOrientationFlag +Pathfinding.ECS.MovementState.reachedEndOfPart movementstate.html#reachedEndOfPart True if the agent has reached the end of the current part in the path. \n\nThe end of the current part will be considered reached if all of these conditions are met:\n- The agent has a path\n\n- The path is not stale\n\n- The end of the current part is not significantly below the agent's feet.\n\n- The end of the current part is not significantly above the agent's head.\n\n- The remaining distance to the end of the part is not significantly larger than the agent's radius. +Pathfinding.ECS.MovementState.reachedEndOfPath movementstate.html#reachedEndOfPath True if the agent has reached the end of the path. \n\nThe end of the path will be considered reached if all of these conditions are met:\n- The agent has a path\n\n- The path is not stale\n\n- The end of the path is not significantly below the agent's feet.\n\n- The end of the path is not significantly above the agent's head.\n\n- The agent is on the last part of the path (there are no more remaining off-mesh links).\n\n- The remaining distance to the end of the path is less than MovementSettings.stopDistance. +Pathfinding.ECS.MovementState.reachedEndOfPathAndOrientation movementstate.html#reachedEndOfPathAndOrientation True if the agent has reached its destination and is facing the desired orientation. \n\nThis will become true if all of these conditions are met:\n- reachedEndOfPath is true\n\n- The agent is facing the desired facing direction as specified in DestinationPoint.facingDirection. +Pathfinding.ECS.MovementState.reachedEndOfPathAndOrientationFlag movementstate.html#reachedEndOfPathAndOrientationFlag +Pathfinding.ECS.MovementState.remainingDistanceToEndOfPart movementstate.html#remainingDistanceToEndOfPart The remaining distance until the end of the path, or the next off-mesh link. +Pathfinding.ECS.MovementState.rotationOffset movementstate.html#rotationOffset The current additional rotation that is applied to the agent. \n\nThis is used by the local avoidance system to rotate the agent, without this causing a feedback loop.\n\n[more in online documentation] +Pathfinding.ECS.MovementState.rotationOffset2 movementstate.html#rotationOffset2 An additional, purely visual, rotation offset. \n\nThis is used for rotation smoothing, but does not affect the movement of the agent. +Pathfinding.ECS.MovementState.traversingLastPart movementstate.html#traversingLastPart True if the agent is traversing the last part of the path. \n\nIf false, the agent will have to traverse at least one off-mesh link before it gets to its destination. +Pathfinding.ECS.MovementStatistics.estimatedVelocity movementstatistics.html#estimatedVelocity The estimated velocity that the agent is moving with. \n\nThis includes all form of movement, including local avoidance and gravity. +Pathfinding.ECS.MovementStatistics.lastPosition movementstatistics.html#lastPosition The position of the agent at the end of the last movement simulation step. +Pathfinding.ECS.MovementTarget.isReached movementtarget.html#isReached +Pathfinding.ECS.MovementTarget.reached movementtarget.html#reached +Pathfinding.ECS.PhysicsSceneRef.physicsScene physicssceneref.html#physicsScene +Pathfinding.ECS.PollPendingPathsSystem.anyPendingPaths pollpendingpathssystem.html#anyPendingPaths +Pathfinding.ECS.PollPendingPathsSystem.entityQueryPrepare pollpendingpathssystem.html#entityQueryPrepare +Pathfinding.ECS.PollPendingPathsSystem.jobRepairPathScheduler pollpendingpathssystem.html#jobRepairPathScheduler +Pathfinding.ECS.PollPendingPathsSystem.onPathsCalculated pollpendingpathssystem.html#onPathsCalculated +Pathfinding.ECS.RVO.AgentIndex.DeletedBit agentindex.html#DeletedBit +Pathfinding.ECS.RVO.AgentIndex.Index agentindex.html#Index Index of the agent in the simulation's data arrays. \n\n[more in online documentation] +Pathfinding.ECS.RVO.AgentIndex.IndexMask agentindex.html#IndexMask +Pathfinding.ECS.RVO.AgentIndex.Valid agentindex.html#Valid +Pathfinding.ECS.RVO.AgentIndex.Version agentindex.html#Version +Pathfinding.ECS.RVO.AgentIndex.VersionMask agentindex.html#VersionMask +Pathfinding.ECS.RVO.AgentIndex.VersionOffset agentindex.html#VersionOffset +Pathfinding.ECS.RVO.AgentIndex.packedAgentIndex agentindex.html#packedAgentIndex +Pathfinding.ECS.RVO.RVOAgent.Default rvoagent.html#Default Good default settings for an RVO agent. +Pathfinding.ECS.RVO.RVOAgent.agentTimeHorizon rvoagent.html#agentTimeHorizon How far into the future to look for collisions with other agents (in seconds) +Pathfinding.ECS.RVO.RVOAgent.collidesWith rvoagent.html#collidesWith Layer mask specifying which layers this agent will avoid. \n\nYou can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...\n\nThis can be very useful in games which have multiple teams of some sort. For example you usually want the agents in one team to avoid each other, but you do not want them to avoid the enemies.\n\nThis field only affects which other agents that this agent will avoid, it does not affect how other agents react to this agent.\n\n[more in online documentation] +Pathfinding.ECS.RVO.RVOAgent.debug rvoagent.html#debug Enables drawing debug information in the scene view. +Pathfinding.ECS.RVO.RVOAgent.flowFollowingStrength rvoagent.html#flowFollowingStrength +Pathfinding.ECS.RVO.RVOAgent.layer rvoagent.html#layer Specifies the avoidance layer for this agent. \n\nThe collidesWith mask on other agents will determine if they will avoid this agent. +Pathfinding.ECS.RVO.RVOAgent.locked rvoagent.html#locked A locked unit cannot move. \n\nOther units will still avoid it but avoidance quality is not the best. +Pathfinding.ECS.RVO.RVOAgent.maxNeighbours rvoagent.html#maxNeighbours Max number of other agents to take into account. \n\nA smaller value can reduce CPU load, a higher value can lead to better local avoidance quality. +Pathfinding.ECS.RVO.RVOAgent.obstacleTimeHorizon rvoagent.html#obstacleTimeHorizon How far into the future to look for collisions with obstacles (in seconds) +Pathfinding.ECS.RVO.RVOAgent.priority rvoagent.html#priority How strongly other agents will avoid this agent. \n\nUsually a value between 0 and 1. Agents with similar priorities will avoid each other with an equal strength. If an agent sees another agent with a higher priority than itself it will avoid that agent more strongly. In the extreme case (e.g this agent has a priority of 0 and the other agent has a priority of 1) it will treat the other agent as being a moving obstacle. Similarly if an agent sees another agent with a lower priority than itself it will avoid that agent less.\n\nIn general the avoidance strength for this agent is: [code in online documentation] +Pathfinding.ECS.RVO.RVOAgent.priorityMultiplier rvoagent.html#priorityMultiplier Priority multiplier. \n\nThis functions identically to the priority, however it is not exposed in the Unity inspector. It is primarily used by the Pathfinding.RVO.RVODestinationCrowdedBehavior. +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromEntitiesToRVOSimulator.agentData jobcopyfromentitiestorvosimulator.html#agentData +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromEntitiesToRVOSimulator.agentOffMeshLinkTraversalLookup jobcopyfromentitiestorvosimulator.html#agentOffMeshLinkTraversalLookup +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromEntitiesToRVOSimulator.agentOutputData jobcopyfromentitiestorvosimulator.html#agentOutputData +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromEntitiesToRVOSimulator.dt jobcopyfromentitiestorvosimulator.html#dt +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromEntitiesToRVOSimulator.movementPlaneMode jobcopyfromentitiestorvosimulator.html#movementPlaneMode +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromRVOSimulatorToEntities.MaximumCirclePackingDensity jobcopyfromrvosimulatortoentities.html#MaximumCirclePackingDensity See https://en.wikipedia.org/wiki/Circle_packing. +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromRVOSimulatorToEntities.agentDataVersions jobcopyfromrvosimulatortoentities.html#agentDataVersions +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromRVOSimulatorToEntities.agentOutputData jobcopyfromrvosimulatortoentities.html#agentOutputData +Pathfinding.ECS.RVO.RVOSystem.JobCopyFromRVOSimulatorToEntities.quadtree jobcopyfromrvosimulatortoentities.html#quadtree +Pathfinding.ECS.RVO.RVOSystem.agentOffMeshLinkTraversalLookup rvosystem.html#agentOffMeshLinkTraversalLookup +Pathfinding.ECS.RVO.RVOSystem.lastSimulator rvosystem.html#lastSimulator Keeps track of the last simulator that this RVOSystem saw. \n\nThis is a weak GCHandle to allow it to be stored in an ISystem. +Pathfinding.ECS.RepairPathSystem.entityQueryPrepare repairpathsystem.html#entityQueryPrepare +Pathfinding.ECS.RepairPathSystem.jobRepairPathScheduler repairpathsystem.html#jobRepairPathScheduler +Pathfinding.ECS.ResolvedMovement.rotationSpeed resolvedmovement.html#rotationSpeed The speed at which the agent should rotate towards targetRotation + targetRotationOffset, in radians per second. +Pathfinding.ECS.ResolvedMovement.speed resolvedmovement.html#speed The speed at which the agent should move towards targetPoint, in meters per second. +Pathfinding.ECS.ResolvedMovement.targetPoint resolvedmovement.html#targetPoint The point the agent should move towards. +Pathfinding.ECS.ResolvedMovement.targetRotation resolvedmovement.html#targetRotation The desired rotation of the agent, in radians, relative to the current movement plane. \n\nWhen rotating the agent visually, the targetRotationOffset should also be added to this value.\n\n[more in online documentation] +Pathfinding.ECS.ResolvedMovement.targetRotationHint resolvedmovement.html#targetRotationHint The desired rotation of the agent, in radians, over a longer time horizon, relative to the current movement plane. \n\nThe targetRotation is usually only over a very short time-horizon, usually a single simulation time step. This variable is used to provide a hint of where the agent wants to rotate to over a slightly longer time scale (on the order of a second or so). It is not used to control movement directly, but it may be used to guide animations, or rotation smoothing.\n\nIf no better hint is available, this should be set to the same value as targetRotation.\n\n[more in online documentation] +Pathfinding.ECS.ResolvedMovement.targetRotationOffset resolvedmovement.html#targetRotationOffset Additive modifier to targetRotation, in radians. \n\nThis is used by the local avoidance system to rotate the agent, without this causing a feedback loop. This extra rotation will be ignored by the control system which decides how the agent *wants* to move. It will instead be directly applied to the agent.\n\n[more in online documentation] +Pathfinding.ECS.ResolvedMovement.turningRadiusMultiplier resolvedmovement.html#turningRadiusMultiplier +Pathfinding.ECS.SchedulePathSearchSystem.JobCheckStaleness.index jobcheckstaleness.html#index +Pathfinding.ECS.SchedulePathSearchSystem.JobCheckStaleness.isPathStale jobcheckstaleness.html#isPathStale +Pathfinding.ECS.SchedulePathSearchSystem.JobRecalculatePaths.time jobrecalculatepaths.html#time +Pathfinding.ECS.SchedulePathSearchSystem.JobShouldRecalculatePaths.index jobshouldrecalculatepaths.html#index +Pathfinding.ECS.SchedulePathSearchSystem.JobShouldRecalculatePaths.isPathStale jobshouldrecalculatepaths.html#isPathStale +Pathfinding.ECS.SchedulePathSearchSystem.JobShouldRecalculatePaths.time jobshouldrecalculatepaths.html#time +Pathfinding.ECS.SchedulePathSearchSystem.MarkerCheckStaleness schedulepathsearchsystem.html#MarkerCheckStaleness +Pathfinding.ECS.SchedulePathSearchSystem.MarkerRecalculatePaths schedulepathsearchsystem.html#MarkerRecalculatePaths +Pathfinding.ECS.SchedulePathSearchSystem.MarkerSchedulePathSearch schedulepathsearchsystem.html#MarkerSchedulePathSearch +Pathfinding.ECS.SchedulePathSearchSystem.MarkerShouldRecalculatePaths schedulepathsearchsystem.html#MarkerShouldRecalculatePaths +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.entities synctransformstoentitiesjob.html#entities +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.entityPositions synctransformstoentitiesjob.html#entityPositions +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.movementState synctransformstoentitiesjob.html#movementState +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.orientationYAxisForward synctransformstoentitiesjob.html#orientationYAxisForward +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.syncPositionWithTransform synctransformstoentitiesjob.html#syncPositionWithTransform +Pathfinding.ECS.SyncTransformsToEntitiesSystem.SyncTransformsToEntitiesJob.syncRotationWithTransform synctransformstoentitiesjob.html#syncRotationWithTransform +Pathfinding.ECS.SyncTransformsToEntitiesSystem.YAxisForwardToZAxisForward synctransformstoentitiessystem.html#YAxisForwardToZAxisForward +Pathfinding.ECS.SyncTransformsToEntitiesSystem.ZAxisForwardToYAxisForward synctransformstoentitiessystem.html#ZAxisForwardToYAxisForward +Pathfinding.ECS.TraverseOffMeshLinkSystem.entityQueryOffMeshLinkCleanup traverseoffmeshlinksystem.html#entityQueryOffMeshLinkCleanup +Pathfinding.ECS.TraverseOffMeshLinkSystem.jobRepairPathScheduler traverseoffmeshlinksystem.html#jobRepairPathScheduler +Pathfinding.EditorBase.cachedTooltips editorbase.html#cachedTooltips +Pathfinding.EditorBase.cachedURLs editorbase.html#cachedURLs +Pathfinding.EditorBase.content editorbase.html#content +Pathfinding.EditorBase.getDocumentationURL editorbase.html#getDocumentationURL +Pathfinding.EditorBase.noOptions editorbase.html#noOptions +Pathfinding.EditorBase.props editorbase.html#props +Pathfinding.EditorBase.remainingUnhandledProperties editorbase.html#remainingUnhandledProperties +Pathfinding.EditorBase.showInDocContent editorbase.html#showInDocContent +Pathfinding.EditorGUILayoutx.dummyList editorguilayoutx.html#dummyList +Pathfinding.EditorGUILayoutx.lastUpdateTick editorguilayoutx.html#lastUpdateTick +Pathfinding.EditorGUILayoutx.layerNames editorguilayoutx.html#layerNames +Pathfinding.EditorResourceHelper.GizmoLineMaterial editorresourcehelper.html#GizmoLineMaterial +Pathfinding.EditorResourceHelper.GizmoSurfaceMaterial editorresourcehelper.html#GizmoSurfaceMaterial +Pathfinding.EditorResourceHelper.HandlesAALineTexture editorresourcehelper.html#HandlesAALineTexture +Pathfinding.EditorResourceHelper.editorAssets editorresourcehelper.html#editorAssets Path to the editor assets folder for the A* Pathfinding Project. \n\nIf this path turns out to be incorrect, the script will try to find the correct path \n\n[more in online documentation] +Pathfinding.EditorResourceHelper.handlesAALineTex editorresourcehelper.html#handlesAALineTex +Pathfinding.EditorResourceHelper.lineMat editorresourcehelper.html#lineMat +Pathfinding.EditorResourceHelper.surfaceMat editorresourcehelper.html#surfaceMat +Pathfinding.EndingConditionDistance.maxGScore endingconditiondistance.html#maxGScore Max G score a node may have. +Pathfinding.EndingConditionProximity.maxDistance endingconditionproximity.html#maxDistance Maximum world distance to the target node before terminating the path. +Pathfinding.Examples.AgentCounter.agentCountLabel agentcounter.html#agentCountLabel +Pathfinding.Examples.AgentCounter.agentQuery agentcounter.html#agentQuery +Pathfinding.Examples.AnimationLinkTraverser.ai animationlinktraverser.html#ai +Pathfinding.Examples.AnimationLinkTraverser.anim animationlinktraverser.html#anim +Pathfinding.Examples.Astar3DButton.node astar3dbutton.html#node +Pathfinding.Examples.BezierMover.averageCurvature beziermover.html#averageCurvature +Pathfinding.Examples.BezierMover.points beziermover.html#points +Pathfinding.Examples.BezierMover.speed beziermover.html#speed +Pathfinding.Examples.BezierMover.tiltAmount beziermover.html#tiltAmount +Pathfinding.Examples.BezierMover.tiltSmoothing beziermover.html#tiltSmoothing +Pathfinding.Examples.BezierMover.time beziermover.html#time +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.count circlespawnerdata.html#count +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.followingOffset circlespawnerdata.html#followingOffset +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.groups circlespawnerdata.html#groups +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.prefab circlespawnerdata.html#prefab +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.radius circlespawnerdata.html#radius +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.spreadAngle circlespawnerdata.html#spreadAngle +Pathfinding.Examples.CircleSpawner.CircleSpawnerData.spreadRandom circlespawnerdata.html#spreadRandom +Pathfinding.Examples.CircleSpawner.count circlespawner.html#count +Pathfinding.Examples.CircleSpawner.followingOffset circlespawner.html#followingOffset +Pathfinding.Examples.CircleSpawner.groups circlespawner.html#groups +Pathfinding.Examples.CircleSpawner.prefab circlespawner.html#prefab +Pathfinding.Examples.CircleSpawner.radius circlespawner.html#radius +Pathfinding.Examples.CircleSpawner.spreadAngle circlespawner.html#spreadAngle +Pathfinding.Examples.CircleSpawner.spreadRandom circlespawner.html#spreadRandom +Pathfinding.Examples.DocumentationButton.UrlBase documentationbutton.html#UrlBase +Pathfinding.Examples.DocumentationButton.buttonContent documentationbutton.html#buttonContent +Pathfinding.Examples.DocumentationButton.page documentationbutton.html#page +Pathfinding.Examples.DoorController.bounds doorcontroller.html#bounds +Pathfinding.Examples.DoorController.closedtag doorcontroller.html#closedtag +Pathfinding.Examples.DoorController.open doorcontroller.html#open +Pathfinding.Examples.DoorController.opentag doorcontroller.html#opentag +Pathfinding.Examples.DoorController.updateGraphsWithGUO doorcontroller.html#updateGraphsWithGUO +Pathfinding.Examples.DoorController.yOffset doorcontroller.html#yOffset +Pathfinding.Examples.GroupController.adjustCamera groupcontroller.html#adjustCamera +Pathfinding.Examples.GroupController.cam groupcontroller.html#cam +Pathfinding.Examples.GroupController.end groupcontroller.html#end +Pathfinding.Examples.GroupController.rad2Deg groupcontroller.html#rad2Deg Radians to degrees constant. +Pathfinding.Examples.GroupController.selection groupcontroller.html#selection +Pathfinding.Examples.GroupController.selectionBox groupcontroller.html#selectionBox +Pathfinding.Examples.GroupController.sim groupcontroller.html#sim +Pathfinding.Examples.GroupController.start groupcontroller.html#start +Pathfinding.Examples.GroupController.wasDown groupcontroller.html#wasDown +Pathfinding.Examples.HexagonTrigger.anim hexagontrigger.html#anim +Pathfinding.Examples.HexagonTrigger.visible hexagontrigger.html#visible +Pathfinding.Examples.HighlightOnHover.highlight highlightonhover.html#highlight +Pathfinding.Examples.Interactable.ActivateParticleSystem.particleSystem activateparticlesystem.html#particleSystem +Pathfinding.Examples.Interactable.AnimatorPlay.animator animatorplay.html#animator +Pathfinding.Examples.Interactable.AnimatorPlay.normalizedTime animatorplay.html#normalizedTime +Pathfinding.Examples.Interactable.AnimatorPlay.stateName animatorplay.html#stateName +Pathfinding.Examples.Interactable.AnimatorSetBoolAction.animator animatorsetboolaction.html#animator +Pathfinding.Examples.Interactable.AnimatorSetBoolAction.propertyName animatorsetboolaction.html#propertyName +Pathfinding.Examples.Interactable.AnimatorSetBoolAction.value animatorsetboolaction.html#value +Pathfinding.Examples.Interactable.CallFunction.function callfunction.html#function +Pathfinding.Examples.Interactable.CoroutineAction interactable.html#CoroutineAction +Pathfinding.Examples.Interactable.DelayAction.delay delayaction.html#delay +Pathfinding.Examples.Interactable.InstantiatePrefab.position instantiateprefab.html#position +Pathfinding.Examples.Interactable.InstantiatePrefab.prefab instantiateprefab.html#prefab +Pathfinding.Examples.Interactable.InteractAction.interactable interactaction.html#interactable +Pathfinding.Examples.Interactable.MoveToAction.destination movetoaction.html#destination +Pathfinding.Examples.Interactable.MoveToAction.useRotation movetoaction.html#useRotation +Pathfinding.Examples.Interactable.MoveToAction.waitUntilReached movetoaction.html#waitUntilReached +Pathfinding.Examples.Interactable.SetObjectActiveAction.active setobjectactiveaction.html#active +Pathfinding.Examples.Interactable.SetObjectActiveAction.target setobjectactiveaction.html#target +Pathfinding.Examples.Interactable.SetTransformAction.setPosition settransformaction.html#setPosition +Pathfinding.Examples.Interactable.SetTransformAction.setRotation settransformaction.html#setRotation +Pathfinding.Examples.Interactable.SetTransformAction.setScale settransformaction.html#setScale +Pathfinding.Examples.Interactable.SetTransformAction.source settransformaction.html#source +Pathfinding.Examples.Interactable.SetTransformAction.transform settransformaction.html#transform +Pathfinding.Examples.Interactable.TeleportAgentAction.destination teleportagentaction.html#destination +Pathfinding.Examples.Interactable.TeleportAgentOnLinkAction.Destination teleportagentonlinkaction.html#Destination +Pathfinding.Examples.Interactable.TeleportAgentOnLinkAction.destination teleportagentonlinkaction.html#destination +Pathfinding.Examples.Interactable.actions interactable.html#actions +Pathfinding.Examples.InteractableEditor.actions interactableeditor.html#actions +Pathfinding.Examples.LightweightRVO.LightweightAgentData.color lightweightagentdata.html#color +Pathfinding.Examples.LightweightRVO.LightweightAgentData.maxSpeed lightweightagentdata.html#maxSpeed +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.AlignAgentWithMovementDirectionJob.deltaTime alignagentwithmovementdirectionjob.html#deltaTime +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.AlignAgentWithMovementDirectionJob.rotationSpeed alignagentwithmovementdirectionjob.html#rotationSpeed +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.JobControlAgents.debug jobcontrolagents.html#debug +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.JobControlAgents.deltaTime jobcontrolagents.html#deltaTime +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.debug lightweightrvocontrolsystem.html#debug Determines what kind of debug info the RVO system should render as gizmos. +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.entityQueryControl lightweightrvocontrolsystem.html#entityQueryControl +Pathfinding.Examples.LightweightRVO.LightweightRVOControlSystem.entityQueryDirection lightweightrvocontrolsystem.html#entityQueryDirection +Pathfinding.Examples.LightweightRVO.LightweightRVOMoveSystem.JobMoveAgents.deltaTime jobmoveagents.html#deltaTime +Pathfinding.Examples.LightweightRVO.LightweightRVOMoveSystem.entityQuery lightweightrvomovesystem.html#entityQuery +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.JobGenerateMesh.renderingOffset jobgeneratemesh.html#renderingOffset +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.JobGenerateMesh.tris jobgeneratemesh.html#tris +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.JobGenerateMesh.verts jobgeneratemesh.html#verts +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.Vertex.color vertex.html#color +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.Vertex.position vertex.html#position +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.Vertex.uv vertex.html#uv +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.entityQuery lightweightrvorendersystem.html#entityQuery +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.material lightweightrvorendersystem.html#material Material for rendering. +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.mesh lightweightrvorendersystem.html#mesh Mesh for rendering. +Pathfinding.Examples.LightweightRVO.LightweightRVORenderSystem.renderingOffset lightweightrvorendersystem.html#renderingOffset Offset with which to render the mesh from the agent's original positions. +Pathfinding.Examples.LightweightRVO.RVOExampleType lightweightrvo.html#RVOExampleType +Pathfinding.Examples.LightweightRVO.agentCount lightweightrvo.html#agentCount Number of agents created at start. +Pathfinding.Examples.LightweightRVO.agentTimeHorizon lightweightrvo.html#agentTimeHorizon How far in the future too look for agents. +Pathfinding.Examples.LightweightRVO.debug lightweightrvo.html#debug Bitmas of debugging options to enable for the agents. +Pathfinding.Examples.LightweightRVO.exampleScale lightweightrvo.html#exampleScale How large is the area in which the agents are distributed when starting the simulation. +Pathfinding.Examples.LightweightRVO.material lightweightrvo.html#material +Pathfinding.Examples.LightweightRVO.maxNeighbours lightweightrvo.html#maxNeighbours Max number of neighbour agents to take into account. +Pathfinding.Examples.LightweightRVO.maxSpeed lightweightrvo.html#maxSpeed Max speed for an agent. +Pathfinding.Examples.LightweightRVO.obstacleTimeHorizon lightweightrvo.html#obstacleTimeHorizon How far in the future too look for obstacles. +Pathfinding.Examples.LightweightRVO.radius lightweightrvo.html#radius Agent radius. +Pathfinding.Examples.LightweightRVO.renderingOffset lightweightrvo.html#renderingOffset Offset from the agent position the actual drawn postition. \n\nUsed to get rid of z-buffer issues +Pathfinding.Examples.LightweightRVO.type lightweightrvo.html#type How the agents are distributed when starting the simulation. +Pathfinding.Examples.LocalSpaceRichAI.graph localspacerichai.html#graph Root of the object we are moving on. +Pathfinding.Examples.ManualRVOAgent.rvo manualrvoagent.html#rvo +Pathfinding.Examples.ManualRVOAgent.speed manualrvoagent.html#speed +Pathfinding.Examples.MecanimBridge.InputMagnitudeKey mecanimbridge.html#InputMagnitudeKey +Pathfinding.Examples.MecanimBridge.InputMagnitudeKeyHash mecanimbridge.html#InputMagnitudeKeyHash +Pathfinding.Examples.MecanimBridge.NormalizedSpeedKey mecanimbridge.html#NormalizedSpeedKey +Pathfinding.Examples.MecanimBridge.NormalizedSpeedKeyHash mecanimbridge.html#NormalizedSpeedKeyHash +Pathfinding.Examples.MecanimBridge.XAxisKey mecanimbridge.html#XAxisKey +Pathfinding.Examples.MecanimBridge.XAxisKeyHash mecanimbridge.html#XAxisKeyHash +Pathfinding.Examples.MecanimBridge.YAxisKey mecanimbridge.html#YAxisKey +Pathfinding.Examples.MecanimBridge.YAxisKeyHash mecanimbridge.html#YAxisKeyHash +Pathfinding.Examples.MecanimBridge.ai mecanimbridge.html#ai Cached reference to the movement script. +Pathfinding.Examples.MecanimBridge.angularVelocitySmoothing mecanimbridge.html#angularVelocitySmoothing Smoothing factor for the angular velocity, in seconds. \n\n[more in online documentation] +Pathfinding.Examples.MecanimBridge.anim mecanimbridge.html#anim Cached Animator component. +Pathfinding.Examples.MecanimBridge.footTransforms mecanimbridge.html#footTransforms Cached reference to the left and right feet. +Pathfinding.Examples.MecanimBridge.naturalSpeed mecanimbridge.html#naturalSpeed +Pathfinding.Examples.MecanimBridge.prevFootPos mecanimbridge.html#prevFootPos Position of the left and right feet during the previous frame. +Pathfinding.Examples.MecanimBridge.smoothedRotationSpeed mecanimbridge.html#smoothedRotationSpeed +Pathfinding.Examples.MecanimBridge.smoothedVelocity mecanimbridge.html#smoothedVelocity +Pathfinding.Examples.MecanimBridge.tr mecanimbridge.html#tr Cached Transform component. +Pathfinding.Examples.MecanimBridge.velocitySmoothing mecanimbridge.html#velocitySmoothing Smoothing factor for the velocity, in seconds. +Pathfinding.Examples.MecanimBridge2D.RotationMode mecanimbridge2d.html#RotationMode +Pathfinding.Examples.MecanimBridge2D.ai mecanimbridge2d.html#ai Cached reference to the movement script. +Pathfinding.Examples.MecanimBridge2D.anim mecanimbridge2d.html#anim Cached Animator component. +Pathfinding.Examples.MecanimBridge2D.naturalSpeed mecanimbridge2d.html#naturalSpeed The natural movement speed is the speed that the animations are designed for. \n\nOne can for example configure the animator to speed up the animation if the agent moves faster than this, or slow it down if the agent moves slower than this. +Pathfinding.Examples.MecanimBridge2D.rotationMode mecanimbridge2d.html#rotationMode How the agent's rotation is handled. \n\n[more in online documentation] +Pathfinding.Examples.MecanimBridge2D.smoothedVelocity mecanimbridge2d.html#smoothedVelocity +Pathfinding.Examples.MecanimBridge2D.velocitySmoothing mecanimbridge2d.html#velocitySmoothing How much to smooth the velocity of the agent. \n\nThe velocity will be smoothed out over approximately this number of seconds. A value of zero indicates no smoothing. +Pathfinding.Examples.MineBotAnimation.NormalizedSpeedKey minebotanimation.html#NormalizedSpeedKey +Pathfinding.Examples.MineBotAnimation.NormalizedSpeedKeyHash minebotanimation.html#NormalizedSpeedKeyHash +Pathfinding.Examples.MineBotAnimation.ai minebotanimation.html#ai +Pathfinding.Examples.MineBotAnimation.anim minebotanimation.html#anim Animator component. +Pathfinding.Examples.MineBotAnimation.endOfPathEffect minebotanimation.html#endOfPathEffect Effect which will be instantiated when end of path is reached. \n\n[more in online documentation] +Pathfinding.Examples.MineBotAnimation.isAtEndOfPath minebotanimation.html#isAtEndOfPath +Pathfinding.Examples.MineBotAnimation.lastTarget minebotanimation.html#lastTarget Point for the last spawn of endOfPathEffect. +Pathfinding.Examples.MineBotAnimation.naturalSpeed minebotanimation.html#naturalSpeed The natural movement speed is the speed that the animations are designed for. \n\nOne can for example configure the animator to speed up the animation if the agent moves faster than this, or slow it down if the agent moves slower than this. +Pathfinding.Examples.MineBotAnimation.tr minebotanimation.html#tr +Pathfinding.Examples.MinimumUnityVersionWarning.requiresEntities minimumunityversionwarning.html#requiresEntities +Pathfinding.Examples.MinimumUnityVersionWarning.requiresUnity2022_2 minimumunityversionwarning.html#requiresUnity2022_2 +Pathfinding.Examples.MinimumUnityVersionWarning.requiresUnity2022_3 minimumunityversionwarning.html#requiresUnity2022_3 +Pathfinding.Examples.MinimumUnityVersionWarning.requires_entities_graphics minimumunityversionwarning.html#requires_entities_graphics +Pathfinding.Examples.MinimumUnityVersionWarning.requires_render_pipeline minimumunityversionwarning.html#requires_render_pipeline +Pathfinding.Examples.ObjectPlacer.alignToSurface objectplacer.html#alignToSurface Align created objects to the surface normal where it is created. +Pathfinding.Examples.ObjectPlacer.direct objectplacer.html#direct Flush Graph Updates directly after placing. \n\nSlower, but updates are applied immidiately +Pathfinding.Examples.ObjectPlacer.go objectplacer.html#go GameObject to place. \n\nWhen using a Grid Graph you need to make sure the object's layer is included in the collision mask in the GridGraph settings. +Pathfinding.Examples.ObjectPlacer.issueGUOs objectplacer.html#issueGUOs Issue a graph update object after placement. +Pathfinding.Examples.ObjectPlacer.lastPlacedTime objectplacer.html#lastPlacedTime +Pathfinding.Examples.ObjectPlacer.offset objectplacer.html#offset Global offset of the placed object relative to the mouse cursor. +Pathfinding.Examples.ObjectPlacer.randomizeRotation objectplacer.html#randomizeRotation Randomize rotation of the placed object. +Pathfinding.Examples.PathTypesDemo.DemoMode pathtypesdemo.html#DemoMode +Pathfinding.Examples.PathTypesDemo.activeDemo pathtypesdemo.html#activeDemo +Pathfinding.Examples.PathTypesDemo.aimStrength pathtypesdemo.html#aimStrength +Pathfinding.Examples.PathTypesDemo.constantPathMeshGo pathtypesdemo.html#constantPathMeshGo +Pathfinding.Examples.PathTypesDemo.end pathtypesdemo.html#end Target point of paths. +Pathfinding.Examples.PathTypesDemo.lastFloodPath pathtypesdemo.html#lastFloodPath +Pathfinding.Examples.PathTypesDemo.lastPath pathtypesdemo.html#lastPath +Pathfinding.Examples.PathTypesDemo.lastRender pathtypesdemo.html#lastRender +Pathfinding.Examples.PathTypesDemo.lineMat pathtypesdemo.html#lineMat Material used for rendering paths. +Pathfinding.Examples.PathTypesDemo.lineWidth pathtypesdemo.html#lineWidth +Pathfinding.Examples.PathTypesDemo.multipoints pathtypesdemo.html#multipoints +Pathfinding.Examples.PathTypesDemo.onlyShortestPath pathtypesdemo.html#onlyShortestPath +Pathfinding.Examples.PathTypesDemo.pathOffset pathtypesdemo.html#pathOffset Offset from the real path to where it is rendered. \n\nUsed to avoid z-fighting +Pathfinding.Examples.PathTypesDemo.searchLength pathtypesdemo.html#searchLength +Pathfinding.Examples.PathTypesDemo.spread pathtypesdemo.html#spread +Pathfinding.Examples.PathTypesDemo.squareMat pathtypesdemo.html#squareMat Material used for rendering result of the ConstantPath. +Pathfinding.Examples.PathTypesDemo.start pathtypesdemo.html#start Start of paths. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.density proceduralprefab.html#density Number of objects per square world unit. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.maxScale proceduralprefab.html#maxScale Maximum scale of prefab. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.minScale proceduralprefab.html#minScale Minimum scale of prefab. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.perlin proceduralprefab.html#perlin Multiply by [perlin noise]. \n\nValue from 0 to 1 indicating weight. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.perlinOffset proceduralprefab.html#perlinOffset Some offset to avoid identical density maps. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.perlinPower proceduralprefab.html#perlinPower Perlin will be raised to this power. \n\nA higher value gives more distinct edges +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.perlinScale proceduralprefab.html#perlinScale Perlin noise scale. \n\nA higher value spreads out the maximums and minimums of the density. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.prefab proceduralprefab.html#prefab Prefab to use. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.random proceduralprefab.html#random Multiply by [random]. \n\nValue from 0 to 1 indicating weight. +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.randomRotation proceduralprefab.html#randomRotation +Pathfinding.Examples.ProceduralWorld.ProceduralPrefab.singleFixed proceduralprefab.html#singleFixed If checked, a single object will be created in the center of each tile. +Pathfinding.Examples.ProceduralWorld.ProceduralTile.destroyed proceduraltile.html#destroyed +Pathfinding.Examples.ProceduralWorld.ProceduralTile.ie proceduraltile.html#ie +Pathfinding.Examples.ProceduralWorld.ProceduralTile.rnd proceduraltile.html#rnd +Pathfinding.Examples.ProceduralWorld.ProceduralTile.root proceduraltile.html#root +Pathfinding.Examples.ProceduralWorld.ProceduralTile.world proceduraltile.html#world +Pathfinding.Examples.ProceduralWorld.ProceduralTile.x proceduraltile.html#x +Pathfinding.Examples.ProceduralWorld.ProceduralTile.z proceduraltile.html#z +Pathfinding.Examples.ProceduralWorld.RotationRandomness proceduralworld.html#RotationRandomness +Pathfinding.Examples.ProceduralWorld.disableAsyncLoadWithinRange proceduralworld.html#disableAsyncLoadWithinRange +Pathfinding.Examples.ProceduralWorld.prefabs proceduralworld.html#prefabs +Pathfinding.Examples.ProceduralWorld.range proceduralworld.html#range How far away to generate tiles. +Pathfinding.Examples.ProceduralWorld.staticBatching proceduralworld.html#staticBatching Enable static batching on generated tiles. \n\nWill improve overall FPS, but might cause FPS drops on some frames when static batching is done +Pathfinding.Examples.ProceduralWorld.subTiles proceduralworld.html#subTiles +Pathfinding.Examples.ProceduralWorld.target proceduralworld.html#target +Pathfinding.Examples.ProceduralWorld.tileGenerationQueue proceduralworld.html#tileGenerationQueue +Pathfinding.Examples.ProceduralWorld.tileSize proceduralworld.html#tileSize World size of tiles. +Pathfinding.Examples.ProceduralWorld.tiles proceduralworld.html#tiles All tiles. +Pathfinding.Examples.RTS.BTContext.animator btcontext.html#animator +Pathfinding.Examples.RTS.BTContext.transform btcontext.html#transform +Pathfinding.Examples.RTS.BTContext.unit btcontext.html#unit +Pathfinding.Examples.RTS.BTMove.destination btmove.html#destination +Pathfinding.Examples.RTS.BTNode.lastStatus btnode.html#lastStatus +Pathfinding.Examples.RTS.BTSelector.childIndex btselector.html#childIndex +Pathfinding.Examples.RTS.BTSelector.children btselector.html#children +Pathfinding.Examples.RTS.BTSequence.childIndex btsequence.html#childIndex +Pathfinding.Examples.RTS.BTSequence.children btsequence.html#children +Pathfinding.Examples.RTS.BTTransparent.child bttransparent.html#child +Pathfinding.Examples.RTS.Binding.getter binding.html#getter +Pathfinding.Examples.RTS.Binding.setter binding.html#setter +Pathfinding.Examples.RTS.Binding.val binding.html#val +Pathfinding.Examples.RTS.Binding.value binding.html#value +Pathfinding.Examples.RTS.Condition.predicate condition.html#predicate +Pathfinding.Examples.RTS.FindClosestUnit.reserve findclosestunit.html#reserve +Pathfinding.Examples.RTS.FindClosestUnit.target findclosestunit.html#target +Pathfinding.Examples.RTS.FindClosestUnit.type findclosestunit.html#type +Pathfinding.Examples.RTS.Harvest.duration harvest.html#duration +Pathfinding.Examples.RTS.Harvest.resource harvest.html#resource +Pathfinding.Examples.RTS.Harvest.time harvest.html#time +Pathfinding.Examples.RTS.MovementMode rts.html#MovementMode +Pathfinding.Examples.RTS.Once.child once.html#child +Pathfinding.Examples.RTS.RTSAudio.Source.available source.html#available +Pathfinding.Examples.RTS.RTSAudio.Source.source source.html#source +Pathfinding.Examples.RTS.RTSAudio.sources rtsaudio.html#sources +Pathfinding.Examples.RTS.RTSBuildingBarracks.UnitItem.buildingTime unititem.html#buildingTime +Pathfinding.Examples.RTS.RTSBuildingBarracks.UnitItem.cost unititem.html#cost +Pathfinding.Examples.RTS.RTSBuildingBarracks.UnitItem.menuItem unititem.html#menuItem +Pathfinding.Examples.RTS.RTSBuildingBarracks.UnitItem.prefab unititem.html#prefab +Pathfinding.Examples.RTS.RTSBuildingBarracks.items rtsbuildingbarracks.html#items +Pathfinding.Examples.RTS.RTSBuildingBarracks.maxQueueSize rtsbuildingbarracks.html#maxQueueSize +Pathfinding.Examples.RTS.RTSBuildingBarracks.menu rtsbuildingbarracks.html#menu +Pathfinding.Examples.RTS.RTSBuildingBarracks.queue rtsbuildingbarracks.html#queue +Pathfinding.Examples.RTS.RTSBuildingBarracks.queueProgressFraction rtsbuildingbarracks.html#queueProgressFraction +Pathfinding.Examples.RTS.RTSBuildingBarracks.queueStartTime rtsbuildingbarracks.html#queueStartTime +Pathfinding.Examples.RTS.RTSBuildingBarracks.rallyPoint rtsbuildingbarracks.html#rallyPoint +Pathfinding.Examples.RTS.RTSBuildingBarracks.spawnPoint rtsbuildingbarracks.html#spawnPoint +Pathfinding.Examples.RTS.RTSBuildingBarracks.unit rtsbuildingbarracks.html#unit +Pathfinding.Examples.RTS.RTSBuildingButton.cost rtsbuildingbutton.html#cost +Pathfinding.Examples.RTS.RTSBuildingButton.prefab rtsbuildingbutton.html#prefab +Pathfinding.Examples.RTS.RTSBuildingQueueUI.UIItem.QueItem.icon queitem.html#icon +Pathfinding.Examples.RTS.RTSBuildingQueueUI.UIItem.QueItem.progress queitem.html#progress +Pathfinding.Examples.RTS.RTSBuildingQueueUI.UIItem.QueItem.root queitem.html#root +Pathfinding.Examples.RTS.RTSBuildingQueueUI.UIItem.parent uiitem.html#parent +Pathfinding.Examples.RTS.RTSBuildingQueueUI.UIItem.queItems uiitem.html#queItems +Pathfinding.Examples.RTS.RTSBuildingQueueUI.building rtsbuildingqueueui.html#building +Pathfinding.Examples.RTS.RTSBuildingQueueUI.item rtsbuildingqueueui.html#item +Pathfinding.Examples.RTS.RTSBuildingQueueUI.prefab rtsbuildingqueueui.html#prefab +Pathfinding.Examples.RTS.RTSBuildingQueueUI.screenOffset rtsbuildingqueueui.html#screenOffset +Pathfinding.Examples.RTS.RTSBuildingQueueUI.worldOffset rtsbuildingqueueui.html#worldOffset +Pathfinding.Examples.RTS.RTSHarvestableResource.harvestable rtsharvestableresource.html#harvestable +Pathfinding.Examples.RTS.RTSHarvestableResource.resourceType rtsharvestableresource.html#resourceType +Pathfinding.Examples.RTS.RTSHarvestableResource.value rtsharvestableresource.html#value +Pathfinding.Examples.RTS.RTSHarvester.animator rtsharvester.html#animator +Pathfinding.Examples.RTS.RTSHarvester.behave rtsharvester.html#behave +Pathfinding.Examples.RTS.RTSHarvester.ctx rtsharvester.html#ctx +Pathfinding.Examples.RTS.RTSHarvester.unit rtsharvester.html#unit +Pathfinding.Examples.RTS.RTSManager.PlayerCount rtsmanager.html#PlayerCount +Pathfinding.Examples.RTS.RTSManager.audioManager rtsmanager.html#audioManager +Pathfinding.Examples.RTS.RTSManager.instance rtsmanager.html#instance +Pathfinding.Examples.RTS.RTSManager.players rtsmanager.html#players +Pathfinding.Examples.RTS.RTSManager.units rtsmanager.html#units +Pathfinding.Examples.RTS.RTSPlayer.index rtsplayer.html#index +Pathfinding.Examples.RTS.RTSPlayer.resources rtsplayer.html#resources +Pathfinding.Examples.RTS.RTSPlayerResources.resources rtsplayerresources.html#resources +Pathfinding.Examples.RTS.RTSResourceDeterioration.initialResources rtsresourcedeterioration.html#initialResources +Pathfinding.Examples.RTS.RTSResourceDeterioration.maxOffset rtsresourcedeterioration.html#maxOffset +Pathfinding.Examples.RTS.RTSResourceDeterioration.offsetRoot rtsresourcedeterioration.html#offsetRoot +Pathfinding.Examples.RTS.RTSResourceDeterioration.resource rtsresourcedeterioration.html#resource +Pathfinding.Examples.RTS.RTSResourceView.Item.label item2.html#label +Pathfinding.Examples.RTS.RTSResourceView.Item.name item2.html#name +Pathfinding.Examples.RTS.RTSResourceView.Item.resource item2.html#resource +Pathfinding.Examples.RTS.RTSResourceView.Item.smoothedValue item2.html#smoothedValue +Pathfinding.Examples.RTS.RTSResourceView.adjustmentSpeed rtsresourceview.html#adjustmentSpeed +Pathfinding.Examples.RTS.RTSResourceView.items rtsresourceview.html#items +Pathfinding.Examples.RTS.RTSResourceView.team rtsresourceview.html#team +Pathfinding.Examples.RTS.RTSTimedDestruction.time rtstimeddestruction.html#time +Pathfinding.Examples.RTS.RTSUI.Menu.itemPrefab menu.html#itemPrefab +Pathfinding.Examples.RTS.RTSUI.Menu.root menu.html#root +Pathfinding.Examples.RTS.RTSUI.MenuItem.description menuitem.html#description +Pathfinding.Examples.RTS.RTSUI.MenuItem.icon menuitem.html#icon +Pathfinding.Examples.RTS.RTSUI.MenuItem.label menuitem.html#label +Pathfinding.Examples.RTS.RTSUI.State rtsui.html#State +Pathfinding.Examples.RTS.RTSUI.active rtsui.html#active +Pathfinding.Examples.RTS.RTSUI.activeMenu rtsui.html#activeMenu +Pathfinding.Examples.RTS.RTSUI.buildingInfo rtsui.html#buildingInfo +Pathfinding.Examples.RTS.RTSUI.buildingPreview rtsui.html#buildingPreview +Pathfinding.Examples.RTS.RTSUI.click rtsui.html#click +Pathfinding.Examples.RTS.RTSUI.clickFallback rtsui.html#clickFallback +Pathfinding.Examples.RTS.RTSUI.dragStart rtsui.html#dragStart +Pathfinding.Examples.RTS.RTSUI.groundMask rtsui.html#groundMask +Pathfinding.Examples.RTS.RTSUI.hasSelected rtsui.html#hasSelected +Pathfinding.Examples.RTS.RTSUI.ignoreFrame rtsui.html#ignoreFrame +Pathfinding.Examples.RTS.RTSUI.menuItemPrefab rtsui.html#menuItemPrefab +Pathfinding.Examples.RTS.RTSUI.menuRoot rtsui.html#menuRoot +Pathfinding.Examples.RTS.RTSUI.notEnoughResources rtsui.html#notEnoughResources +Pathfinding.Examples.RTS.RTSUI.selectionBox rtsui.html#selectionBox +Pathfinding.Examples.RTS.RTSUI.state rtsui.html#state +Pathfinding.Examples.RTS.RTSUI.worldSpaceUI rtsui.html#worldSpaceUI +Pathfinding.Examples.RTS.RTSUnit.OnUpdateDelegate rtsunit.html#OnUpdateDelegate +Pathfinding.Examples.RTS.RTSUnit.Type rtsunit.html#Type +Pathfinding.Examples.RTS.RTSUnit.ai rtsunit.html#ai +Pathfinding.Examples.RTS.RTSUnit.attackTarget rtsunit.html#attackTarget +Pathfinding.Examples.RTS.RTSUnit.deathEffect rtsunit.html#deathEffect +Pathfinding.Examples.RTS.RTSUnit.detectionRange rtsunit.html#detectionRange +Pathfinding.Examples.RTS.RTSUnit.health rtsunit.html#health +Pathfinding.Examples.RTS.RTSUnit.lastDestination rtsunit.html#lastDestination +Pathfinding.Examples.RTS.RTSUnit.lastSeenAttackTarget rtsunit.html#lastSeenAttackTarget +Pathfinding.Examples.RTS.RTSUnit.locked rtsunit.html#locked +Pathfinding.Examples.RTS.RTSUnit.mSelected rtsunit.html#mSelected +Pathfinding.Examples.RTS.RTSUnit.maxHealth rtsunit.html#maxHealth +Pathfinding.Examples.RTS.RTSUnit.movementMode rtsunit.html#movementMode +Pathfinding.Examples.RTS.RTSUnit.onMakeActiveUnit rtsunit.html#onMakeActiveUnit +Pathfinding.Examples.RTS.RTSUnit.owner rtsunit.html#owner +Pathfinding.Examples.RTS.RTSUnit.position rtsunit.html#position Position at the start of the current frame. +Pathfinding.Examples.RTS.RTSUnit.radius rtsunit.html#radius +Pathfinding.Examples.RTS.RTSUnit.reachedDestination rtsunit.html#reachedDestination +Pathfinding.Examples.RTS.RTSUnit.reservedBy rtsunit.html#reservedBy +Pathfinding.Examples.RTS.RTSUnit.resource rtsunit.html#resource +Pathfinding.Examples.RTS.RTSUnit.rvo rtsunit.html#rvo +Pathfinding.Examples.RTS.RTSUnit.selected rtsunit.html#selected +Pathfinding.Examples.RTS.RTSUnit.selectionIndicator rtsunit.html#selectionIndicator +Pathfinding.Examples.RTS.RTSUnit.selectionIndicatorEnabled rtsunit.html#selectionIndicatorEnabled +Pathfinding.Examples.RTS.RTSUnit.storedCrystals rtsunit.html#storedCrystals +Pathfinding.Examples.RTS.RTSUnit.team rtsunit.html#team +Pathfinding.Examples.RTS.RTSUnit.transform rtsunit.html#transform +Pathfinding.Examples.RTS.RTSUnit.type rtsunit.html#type +Pathfinding.Examples.RTS.RTSUnit.weapon rtsunit.html#weapon +Pathfinding.Examples.RTS.RTSUnitBuilder.BuildingItem.cost buildingitem.html#cost +Pathfinding.Examples.RTS.RTSUnitBuilder.BuildingItem.menuItem buildingitem.html#menuItem +Pathfinding.Examples.RTS.RTSUnitBuilder.BuildingItem.prefab buildingitem.html#prefab +Pathfinding.Examples.RTS.RTSUnitBuilder.items rtsunitbuilder.html#items +Pathfinding.Examples.RTS.RTSUnitBuilder.menu rtsunitbuilder.html#menu +Pathfinding.Examples.RTS.RTSUnitBuilder.unit rtsunitbuilder.html#unit +Pathfinding.Examples.RTS.RTSUnitManager.activeUnit rtsunitmanager.html#activeUnit +Pathfinding.Examples.RTS.RTSUnitManager.batchSelection rtsunitmanager.html#batchSelection +Pathfinding.Examples.RTS.RTSUnitManager.cam rtsunitmanager.html#cam +Pathfinding.Examples.RTS.RTSUnitManager.mActiveUnit rtsunitmanager.html#mActiveUnit +Pathfinding.Examples.RTS.RTSUnitManager.selectedUnits rtsunitmanager.html#selectedUnits +Pathfinding.Examples.RTS.RTSUnitManager.units rtsunitmanager.html#units +Pathfinding.Examples.RTS.RTSWaveSpawner.Wave.count wave.html#count +Pathfinding.Examples.RTS.RTSWaveSpawner.Wave.delay wave.html#delay +Pathfinding.Examples.RTS.RTSWaveSpawner.Wave.health wave.html#health +Pathfinding.Examples.RTS.RTSWaveSpawner.Wave.prefab wave.html#prefab +Pathfinding.Examples.RTS.RTSWaveSpawner.Wave.spawnPoint wave.html#spawnPoint +Pathfinding.Examples.RTS.RTSWaveSpawner.target rtswavespawner.html#target +Pathfinding.Examples.RTS.RTSWaveSpawner.team rtswavespawner.html#team +Pathfinding.Examples.RTS.RTSWaveSpawner.waveCounter rtswavespawner.html#waveCounter +Pathfinding.Examples.RTS.RTSWaveSpawner.waves rtswavespawner.html#waves +Pathfinding.Examples.RTS.RTSWeapon.attackDuration rtsweapon.html#attackDuration +Pathfinding.Examples.RTS.RTSWeapon.canMoveWhileAttacking rtsweapon.html#canMoveWhileAttacking +Pathfinding.Examples.RTS.RTSWeapon.cooldown rtsweapon.html#cooldown +Pathfinding.Examples.RTS.RTSWeapon.isAttacking rtsweapon.html#isAttacking +Pathfinding.Examples.RTS.RTSWeapon.lastAttackTime rtsweapon.html#lastAttackTime +Pathfinding.Examples.RTS.RTSWeapon.range rtsweapon.html#range +Pathfinding.Examples.RTS.RTSWeapon.ranged rtsweapon.html#ranged +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.damage rtsweaponsimpleranged.html#damage +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.rotationRootY rtsweaponsimpleranged.html#rotationRootY +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.rotationSpeed rtsweaponsimpleranged.html#rotationSpeed +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.sfx rtsweaponsimpleranged.html#sfx +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.sourceEffect rtsweaponsimpleranged.html#sourceEffect +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.sourceEffectRoot rtsweaponsimpleranged.html#sourceEffectRoot +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.targetEffect rtsweaponsimpleranged.html#targetEffect +Pathfinding.Examples.RTS.RTSWeaponSimpleRanged.volume rtsweaponsimpleranged.html#volume +Pathfinding.Examples.RTS.RTSWorldSpaceUI.Item.screenOffset item3.html#screenOffset +Pathfinding.Examples.RTS.RTSWorldSpaceUI.Item.tracking item3.html#tracking +Pathfinding.Examples.RTS.RTSWorldSpaceUI.Item.transform item3.html#transform +Pathfinding.Examples.RTS.RTSWorldSpaceUI.Item.valid item3.html#valid +Pathfinding.Examples.RTS.RTSWorldSpaceUI.Item.worldOffset item3.html#worldOffset +Pathfinding.Examples.RTS.RTSWorldSpaceUI.items rtsworldspaceui.html#items +Pathfinding.Examples.RTS.RTSWorldSpaceUI.worldCamera rtsworldspaceui.html#worldCamera +Pathfinding.Examples.RTS.ResourceType rts.html#ResourceType +Pathfinding.Examples.RTS.SimpleAction.action simpleaction.html#action +Pathfinding.Examples.RTS.Status rts.html#Status +Pathfinding.Examples.RTS.Value.Bound value.html#Bound +Pathfinding.Examples.RTS.Value.binding value.html#binding +Pathfinding.Examples.RTS.Value.val value.html#val +Pathfinding.Examples.RTS.Value.value value.html#value +Pathfinding.Examples.RTSTiltInMovementDirection.accelerationFraction rtstiltinmovementdirection.html#accelerationFraction +Pathfinding.Examples.RTSTiltInMovementDirection.ai rtstiltinmovementdirection.html#ai +Pathfinding.Examples.RTSTiltInMovementDirection.amount rtstiltinmovementdirection.html#amount +Pathfinding.Examples.RTSTiltInMovementDirection.lastVelocity rtstiltinmovementdirection.html#lastVelocity +Pathfinding.Examples.RTSTiltInMovementDirection.motorSound rtstiltinmovementdirection.html#motorSound +Pathfinding.Examples.RTSTiltInMovementDirection.smoothAcceleration rtstiltinmovementdirection.html#smoothAcceleration +Pathfinding.Examples.RTSTiltInMovementDirection.soundAdjustmentSpeed rtstiltinmovementdirection.html#soundAdjustmentSpeed +Pathfinding.Examples.RTSTiltInMovementDirection.soundGain rtstiltinmovementdirection.html#soundGain +Pathfinding.Examples.RTSTiltInMovementDirection.soundIdleVolume rtstiltinmovementdirection.html#soundIdleVolume +Pathfinding.Examples.RTSTiltInMovementDirection.soundPitchGain rtstiltinmovementdirection.html#soundPitchGain +Pathfinding.Examples.RTSTiltInMovementDirection.speed rtstiltinmovementdirection.html#speed +Pathfinding.Examples.RTSTiltInMovementDirection.target rtstiltinmovementdirection.html#target +Pathfinding.Examples.RVOAgentPlacer.agents rvoagentplacer.html#agents +Pathfinding.Examples.RVOAgentPlacer.goalOffset rvoagentplacer.html#goalOffset +Pathfinding.Examples.RVOAgentPlacer.mask rvoagentplacer.html#mask +Pathfinding.Examples.RVOAgentPlacer.prefab rvoagentplacer.html#prefab +Pathfinding.Examples.RVOAgentPlacer.rad2Deg rvoagentplacer.html#rad2Deg +Pathfinding.Examples.RVOAgentPlacer.repathRate rvoagentplacer.html#repathRate +Pathfinding.Examples.RVOAgentPlacer.ringSize rvoagentplacer.html#ringSize +Pathfinding.Examples.RVOExampleAgent.canSearchAgain rvoexampleagent.html#canSearchAgain +Pathfinding.Examples.RVOExampleAgent.controller rvoexampleagent.html#controller +Pathfinding.Examples.RVOExampleAgent.groundMask rvoexampleagent.html#groundMask +Pathfinding.Examples.RVOExampleAgent.maxSpeed rvoexampleagent.html#maxSpeed +Pathfinding.Examples.RVOExampleAgent.moveNextDist rvoexampleagent.html#moveNextDist +Pathfinding.Examples.RVOExampleAgent.nextRepath rvoexampleagent.html#nextRepath +Pathfinding.Examples.RVOExampleAgent.path rvoexampleagent.html#path +Pathfinding.Examples.RVOExampleAgent.rends rvoexampleagent.html#rends +Pathfinding.Examples.RVOExampleAgent.repathRate rvoexampleagent.html#repathRate +Pathfinding.Examples.RVOExampleAgent.seeker rvoexampleagent.html#seeker +Pathfinding.Examples.RVOExampleAgent.slowdownDistance rvoexampleagent.html#slowdownDistance +Pathfinding.Examples.RVOExampleAgent.target rvoexampleagent.html#target +Pathfinding.Examples.RVOExampleAgent.vectorPath rvoexampleagent.html#vectorPath +Pathfinding.Examples.RVOExampleAgent.wp rvoexampleagent.html#wp +Pathfinding.Examples.SmoothCameraFollow.damping smoothcamerafollow.html#damping +Pathfinding.Examples.SmoothCameraFollow.distance smoothcamerafollow.html#distance +Pathfinding.Examples.SmoothCameraFollow.enableRotation smoothcamerafollow.html#enableRotation +Pathfinding.Examples.SmoothCameraFollow.height smoothcamerafollow.html#height +Pathfinding.Examples.SmoothCameraFollow.rotationDamping smoothcamerafollow.html#rotationDamping +Pathfinding.Examples.SmoothCameraFollow.smoothRotation smoothcamerafollow.html#smoothRotation +Pathfinding.Examples.SmoothCameraFollow.staticOffset smoothcamerafollow.html#staticOffset +Pathfinding.Examples.SmoothCameraFollow.target smoothcamerafollow.html#target +Pathfinding.Examples.TurnBasedAI.blockManager turnbasedai.html#blockManager +Pathfinding.Examples.TurnBasedAI.blocker turnbasedai.html#blocker +Pathfinding.Examples.TurnBasedAI.movementPoints turnbasedai.html#movementPoints +Pathfinding.Examples.TurnBasedAI.targetNode turnbasedai.html#targetNode +Pathfinding.Examples.TurnBasedAI.traversalProvider turnbasedai.html#traversalProvider +Pathfinding.Examples.TurnBasedDoor.animator turnbaseddoor.html#animator +Pathfinding.Examples.TurnBasedDoor.blocker turnbaseddoor.html#blocker +Pathfinding.Examples.TurnBasedDoor.open turnbaseddoor.html#open +Pathfinding.Examples.TurnBasedManager.State turnbasedmanager.html#State +Pathfinding.Examples.TurnBasedManager.eventSystem turnbasedmanager.html#eventSystem +Pathfinding.Examples.TurnBasedManager.layerMask turnbasedmanager.html#layerMask +Pathfinding.Examples.TurnBasedManager.movementSpeed turnbasedmanager.html#movementSpeed +Pathfinding.Examples.TurnBasedManager.nodePrefab turnbasedmanager.html#nodePrefab +Pathfinding.Examples.TurnBasedManager.possibleMoves turnbasedmanager.html#possibleMoves +Pathfinding.Examples.TurnBasedManager.selected turnbasedmanager.html#selected +Pathfinding.Examples.TurnBasedManager.state turnbasedmanager.html#state +Pathfinding.FadeArea.animationSpeed fadearea.html#animationSpeed +Pathfinding.FadeArea.areaStyle fadearea.html#areaStyle +Pathfinding.FadeArea.editor fadearea.html#editor +Pathfinding.FadeArea.fancyEffects fadearea.html#fancyEffects Animate dropdowns when they open and close. +Pathfinding.FadeArea.labelStyle fadearea.html#labelStyle +Pathfinding.FadeArea.lastRect fadearea.html#lastRect +Pathfinding.FadeArea.lastUpdate fadearea.html#lastUpdate +Pathfinding.FadeArea.open fadearea.html#open Is this area open. \n\nThis is not the same as if any contents are visible, use BeginFade for that. +Pathfinding.FadeArea.value fadearea.html#value +Pathfinding.FadeArea.visible fadearea.html#visible +Pathfinding.FakeTransform.position faketransform.html#position +Pathfinding.FakeTransform.rotation faketransform.html#rotation +Pathfinding.FloodPath.TemporaryNodeBit floodpath.html#TemporaryNodeBit +Pathfinding.FloodPath.originalStartPoint floodpath.html#originalStartPoint +Pathfinding.FloodPath.parents floodpath.html#parents +Pathfinding.FloodPath.saveParents floodpath.html#saveParents If false, will not save any information. \n\nUsed by some internal parts of the system which doesn't need it. +Pathfinding.FloodPath.startNode floodpath.html#startNode +Pathfinding.FloodPath.startPoint floodpath.html#startPoint +Pathfinding.FloodPath.validationHash floodpath.html#validationHash +Pathfinding.FloodPathTracer.flood floodpathtracer.html#flood Reference to the FloodPath which searched the path originally. +Pathfinding.FloodPathTracer.hasEndPoint floodpathtracer.html#hasEndPoint Determines if a search for an end node should be done. \n\nSet by different path types. \n\n[more in online documentation] +Pathfinding.FollowerEntity.FollowerEntityMigrations followerentity.html#FollowerEntityMigrations +Pathfinding.FollowerEntity.archetype followerentity.html#archetype +Pathfinding.FollowerEntity.archetypeWorld followerentity.html#archetypeWorld +Pathfinding.FollowerEntity.autoRepath followerentity.html#autoRepath Policy for when the agent recalculates its path. \n\n[more in online documentation] +Pathfinding.FollowerEntity.autoRepathBacking followerentity.html#autoRepathBacking +Pathfinding.FollowerEntity.canMove followerentity.html#canMove Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nThis is also useful if you want to have full control over when the movement calculations run. Take a look at MovementUpdate\n\n[more in online documentation] +Pathfinding.FollowerEntity.canSearch followerentity.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.FollowerEntity.currentNode followerentity.html#currentNode Node which the agent is currently traversing. \n\nYou can, for example, use this to make the agent use a different animation when traversing nodes with a specific tag.\n\n[more in online documentation]\nWhen traversing an off-mesh link, this will return the final non-link node in the path before the agent started traversing the link. +Pathfinding.FollowerEntity.debugFlags followerentity.html#debugFlags Enables or disables debug drawing for this agent. \n\nThis is a bitmask with multiple flags so that you can choose exactly what you want to debug.\n\n[more in online documentation] +Pathfinding.FollowerEntity.desiredVelocity followerentity.html#desiredVelocity Velocity that this agent wants to move with. \n\nIncludes gravity and local avoidance if applicable. In world units per second.\n\n[more in online documentation] +Pathfinding.FollowerEntity.desiredVelocityWithoutLocalAvoidance followerentity.html#desiredVelocityWithoutLocalAvoidance Velocity that this agent wants to move with before taking local avoidance into account. \n\nIncludes gravity. In world units per second.\n\nSetting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.\n\n[code in online documentation]\n\n[more in online documentation]\n\n\nIf you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.\n\n[more in online documentation] +Pathfinding.FollowerEntity.destination followerentity.html#destination Position in the world that this agent should move to. \n\nIf no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.\n\nSetting this property will immediately try to repair the path if the agent already has a path. This will also immediately update properties like reachedDestination, reachedEndOfPath and remainingDistance.\n\nThe agent may do a full path recalculation if the local repair was not sufficient, but this will at earliest happen in the next simulation step.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.FollowerEntity.destinationFacingDirection followerentity.html#destinationFacingDirection Direction the agent will try to face when it reaches the destination. \n\nIf this is zero, the agent will not try to face any particular direction.\n\nThe following video shows three agents, one with no facing direction set, and then two agents with varying values of the lead in radius. [video in online documentation]\n\nTo set this, use SetDestination.\n\n[more in online documentation] +Pathfinding.FollowerEntity.enableGravity followerentity.html#enableGravity Enables or disables gravity. \n\nIf gravity is enabled, the agent will accelerate downwards, and use a raycast to check if it should stop falling.\n\nThis has no effect if the agent's orientation is set to YAxisForward (2D mode). Gravity does not really make sense for top-down 2D games. The gravity setting is also hidden from the inspector in this mode.\n\n[more in online documentation] +Pathfinding.FollowerEntity.enableGravityBacking followerentity.html#enableGravityBacking +Pathfinding.FollowerEntity.enableLocalAvoidance followerentity.html#enableLocalAvoidance True if local avoidance is enabled for this agent. \n\nEnabling this will automatically add a Pathfinding.ECS.RVO.RVOAgent component to the entity.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.FollowerEntity.enableLocalAvoidanceBacking followerentity.html#enableLocalAvoidanceBacking +Pathfinding.FollowerEntity.endOfPath followerentity.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or if it's not calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall, so that the agent couldn't get any closer.\n\n[more in online documentation] +Pathfinding.FollowerEntity.entity followerentity.html#entity Entity which this movement script represents. \n\nThis is the same as proxy.entity, but is provided for convenience. It is only valid when this script is enabled.\n\n[more in online documentation] +Pathfinding.FollowerEntity.entityExists followerentity.html#entityExists True if this component's entity exists. \n\nThis is typically true if the component is active and enabled and the game is running.\n\n[more in online documentation] +Pathfinding.FollowerEntity.groundMask followerentity.html#groundMask Determines which layers the agent will stand on. \n\nThe agent will use a raycast each frame to check if it should stop falling.\n\nThis layer mask should ideally not contain the agent's own layer, if the agent has a collider, as this may cause it to try to stand on top of itself. +Pathfinding.FollowerEntity.hasPath followerentity.html#hasPath True if this agent currently has a valid path that it follows. \n\nThis is true if the agent has a path and the path is not stale.\n\nA path may become stale if the graph is updated close to the agent and it hasn't had time to recalculate its path yet. +Pathfinding.FollowerEntity.height followerentity.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is used for various heuristics, and for visualization purposes. For example, the destination is only considered reached if the destination is not above the agent's head, and it's not more than half the agent's height below its feet.\n\nIf local lavoidance is enabled, this is also used to filter out collisions with agents and obstacles that are too far above or below the agent. +Pathfinding.FollowerEntity.isStopped followerentity.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.FollowerEntity.isTraversingOffMeshLink followerentity.html#isTraversingOffMeshLink True if the agent is currently traversing an off-mesh link. \n\n[more in online documentation] +Pathfinding.FollowerEntity.localAvoidanceTemporarilyDisabled followerentity.html#localAvoidanceTemporarilyDisabled True if the agent's local avoidance is temporarily disabled, due to traversing an off-mesh link. \n\nWhen traversing an off-mesh link, the traversal code may temporarily disable local avoidance to allow the agent to traverse the link without being pushed around by other agents.\n\n[more in online documentation]\nWhen the agent is not traversing an off-mesh link, this will always return false.\n\n[more in online documentation] +Pathfinding.FollowerEntity.managedSettings followerentity.html#managedSettings +Pathfinding.FollowerEntity.managedState followerentity.html#managedState +Pathfinding.FollowerEntity.maxRotationSpeed followerentity.html#maxRotationSpeed Maximum rotation speed in degrees per second. \n\nIf the agent would have to rotate faster than this, it will instead slow down to get more time to rotate.\n\nThe agent may want to rotate faster than rotationSpeed if there's not enough space, so that it has to move in a more narrow arc. It may also want to rotate faster if it is very close to its destination and it wants to make sure it ends up on the right spot without any circling.\n\nIt is recommended to keep this at a value slightly larger than rotationSpeed.\n\n[more in online documentation] +Pathfinding.FollowerEntity.maxSpeed followerentity.html#maxSpeed Max speed in world units per second. +Pathfinding.FollowerEntity.movement followerentity.html#movement +Pathfinding.FollowerEntity.movementOverrides followerentity.html#movementOverrides Provides callbacks during various parts of the movement calculations. \n\nWith this property you can register callbacks that will be called during various parts of the movement calculations. These can be used to modify movement of the agent.\n\nThe following example demonstrates how one can hook into one of the available phases and modify the agent's movement. In this case, the movement is modified to become wavy.\n\n [video in online documentation]\n\n[code in online documentation]\n\n- BeforeControl: Called before the agent's movement is calculated. At this point, the agent has a valid path, and the next corner that is moving towards has been calculated.\n\n- AfterControl: Called after the agent's desired movement is calculated. The agent has stored its desired movement in the MovementControl component. Local avoidance has not yet run.\n\n- BeforeMovement: Called right before the agent's movement is applied. At this point the agent's final movement (including local avoidance) is stored in the ResolvedMovement component, which you may modify.\n\n\n\n[more in online documentation]\nThe callbacks may be called multiple times per frame, if the fps is low, or if the time scale is high. It may also be called less than once per frame if the fps is very high. Each callback is provided with a dt parameter, which is the time in seconds since the last simulation step. You should prefer using this instead of Time.deltaTime.\n\n[more in online documentation] +Pathfinding.FollowerEntity.movementPlane followerentity.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.FollowerEntity.movementPlaneSource followerentity.html#movementPlaneSource How to calculate which direction is "up" for the agent. \n\nIn almost all cases, you should use the Graph option. This will make the agent use the graph's natural "up" direction. However, if you are using a spherical world, or a world with some other strange shape, then you may want to use the NavmeshNormal or Raycast options.\n\n[more in online documentation] +Pathfinding.FollowerEntity.movementPlaneSourceBacking followerentity.html#movementPlaneSourceBacking +Pathfinding.FollowerEntity.movementSettings followerentity.html#movementSettings Various movement settings. \n\nSome of these settings are exposed on the FollowerEntity directly. For example maxSpeed.\n\n[more in online documentation] +Pathfinding.FollowerEntity.nearestNavmeshBorder followerentity.html#nearestNavmeshBorder Border of the navmesh closest to the agent's current position. \n\nThe border of the navmesh is what separates the walkable and unwalkable part of the world.\n\n [image in online documentation][video in online documentation]\n\nIf no border could be found (for example if the agent has no path), then the hit.point field will be positive infinity.\n\n[more in online documentation] +Pathfinding.FollowerEntity.nextOffMeshLink followerentity.html#nextOffMeshLink The off-mesh link that the agent will traverse next, or the one it is traversing (if any). \n\nThe next link in the path will be returned regardless of the distance to it.\n\nThis will be a default OffMeshLinks.OffMeshLinkTracer if the agent is not traversing an off-mesh link, and there are no remaining off-mesh links in its path (the OffMeshLinks.OffMeshLinkTracer.link field will be null).\n\n[more in online documentation] +Pathfinding.FollowerEntity.offMeshLink followerentity.html#offMeshLink The off-mesh link that the agent is currently traversing. \n\nThis will be a default OffMeshLinks.OffMeshLinkTracer if the agent is not traversing an off-mesh link (the OffMeshLinks.OffMeshLinkTracer.link field will be null).\n\n[more in online documentation] +Pathfinding.FollowerEntity.onSearchPath followerentity.html#onSearchPath Called when the agent recalculates its path. \n\nThis is called both for automatic path recalculations (see canSearch) and manual ones (see SearchPath).\n\n[more in online documentation] +Pathfinding.FollowerEntity.onTraverseOffMeshLink followerentity.html#onTraverseOffMeshLink Callback to be called when an agent starts traversing an off-mesh link. \n\nThe handler will be called when the agent starts traversing an off-mesh link. It allows you to to control the agent for the full duration of the link traversal.\n\nUse the passed context struct to get information about the link and to control the agent.\n\n[code in online documentation]\n\n[more in online documentation]\nYou can alternatively set the corresponding property property on the off-mesh link ( NodeLink2.onTraverseOffMeshLink) to specify a callback for a specific off-mesh link.\n\n[more in online documentation] +Pathfinding.FollowerEntity.orientation followerentity.html#orientation Determines which direction the agent moves in. \n\nFor 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games.\n\nFor 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games.\n\nWhen using ZAxisForward, the +Z axis will be the forward direction of the agent, +Y will be upwards, and +X will be the right direction.\n\nWhen using YAxisForward, the +Y axis will be the forward direction of the agent, +Z will be upwards, and +X will be the right direction.\n\n[more in online documentation]\n [image in online documentation] +Pathfinding.FollowerEntity.orientationBacking followerentity.html#orientationBacking Determines which direction the agent moves in. \n\n[more in online documentation] +Pathfinding.FollowerEntity.pathPending followerentity.html#pathPending True if a path is currently being calculated. +Pathfinding.FollowerEntity.pathfindingSettings followerentity.html#pathfindingSettings Pathfinding settings. \n\nThe settings in this struct controls how the agent calculates paths to its destination.\n\nThis is analogous to the Seeker component used for other movement scripts.\n\n[more in online documentation] +Pathfinding.FollowerEntity.position followerentity.html#position Position of the agent. \n\nIn world space. \n\n[more in online documentation]\nIf you want to move the agent you may use Teleport or Move. +Pathfinding.FollowerEntity.positionSmoothing followerentity.html#positionSmoothing How much to smooth the visual position of the agent. \n\nThis does not affect movement, but smoothes out the position of the agent visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThis will make the agent seem to lag slightly behind the internal position of the agent. It may also cut corners slightly.\n\nThe unit for this field is seconds. +Pathfinding.FollowerEntity.proxy followerentity.html#proxy Proxy for the entity which this movement script represents. \n\nAn entity will be created when this script is enabled, and destroyed when this script is disabled.\n\nCheck the class documentation to see which components it usually has, and what systems typically affect it. +Pathfinding.FollowerEntity.radius followerentity.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.FollowerEntity.reachedCrowdedEndOfPath followerentity.html#reachedCrowdedEndOfPath Like reachedEndOfPath, but will also return true if the end of the path is crowded, and this agent has stopped because it cannot get closer. \n\nThis is only relevant if the agent is using local avoidance. Otherwise, this will be identical to reachedEndOfPath.\n\nIf the agent has a stale path (e.g. because the destination changed significantly, or a graph update happened near the agent), false will be returned until the path has been recalculated (typically in the next one or two frames).\n\nYou can see a visualization of this state by enabling "Reached State" in the Local Avoidance Debug Rendering field.\n\n[more in online documentation]\nIn the video below, the agents will get a red ring around them when this property is true.\n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.FollowerEntity.reachedDestination followerentity.html#reachedDestination True if the ai has reached the destination. \n\nThe agent considers the destination reached when it is within stopDistance world units from the destination. Additionally, the destination must not be above the agent's head, and it must not be more than half the agent's height below its feet.\n\nIf a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation.\n\nThis value will be updated immediately when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.FollowerEntity.reachedEndOfPath followerentity.html#reachedEndOfPath True if the agent has reached the end of the current path. \n\nThe agent considers the end of the path reached when it is within stopDistance world units from the end of the path. Additionally, the end of the path must not be above the agent's head, and it must not be more than half the agent's height below its feet.\n\nIf a facing direction was specified when setting the destination, this will only return true once the agent is approximately facing the correct orientation.\n\nThis value will be updated immediately when the destination is changed.\n\n[more in online documentation] +Pathfinding.FollowerEntity.remainingDistance followerentity.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThis is an approximation for performance reasons, and the agent also does not know the true distance at all times. It tends to be a bit lower than the true distance. It will get more accurate as the agent gets closer to the end of the path. If you want a more accurate distance (at a higher performance cost), you can call GetRemainingPath and sum the length of it.\n\n[more in online documentation]\nThis value will update immediately if the destination property is changed, or if the agent is moved using the position property or the Teleport method.\n\nIf the agent has no path, or if the current path is stale (e.g. if the graph has been updated close to the agent, and it hasn't had time to recalculate its path), this will return positive infinity.\n\n[more in online documentation] +Pathfinding.FollowerEntity.rotation followerentity.html#rotation Rotation of the agent. \n\nIn world space.\n\nThe entity internally always treats the Z axis as forward, but this property respects the orientation field. So it will return either a rotation with the Y axis as forward, or Z axis as forward, depending on the orientation field.\n\n[more in online documentation]\nThis will return the agent's rotation even if updateRotation is false.\n\n[more in online documentation] +Pathfinding.FollowerEntity.rotationSmoothing followerentity.html#rotationSmoothing How much to smooth the visual rotation of the agent. \n\nThis does not affect movement, but smoothes out how the agent rotates visually.\n\nRecommended values are between 0.0 and 0.5. A value of zero will disable smoothing completely.\n\nThe smoothing is done primarily using an exponential moving average, but with a small linear term to make the rotation converge faster when the agent is almost facing the desired direction.\n\nAdding smoothing will make the visual rotation of the agent lag a bit behind the actual rotation. Too much smoothing may make the agent seem sluggish, and appear to move sideways.\n\nThe unit for this field is seconds. +Pathfinding.FollowerEntity.rotationSpeed followerentity.html#rotationSpeed Desired rotation speed in degrees per second. \n\nIf the agent is in an open area and gets a new destination directly behind itself, it will start to rotate around with exactly this rotation speed.\n\nThe agent will slow down its rotation speed as it approaches its desired facing direction. So for example, when it is only 90 degrees away from its desired facing direction, it will only rotate with about half this speed.\n\n[more in online documentation] +Pathfinding.FollowerEntity.rvoSettings followerentity.html#rvoSettings Local avoidance settings. +Pathfinding.FollowerEntity.rvoSettingsBacking followerentity.html#rvoSettingsBacking +Pathfinding.FollowerEntity.shape followerentity.html#shape +Pathfinding.FollowerEntity.simulateMovement followerentity.html#simulateMovement Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nDisabling this will remove the SimulateMovement component from the entity, which prevents most systems from running for this entity.\n\nWhen disabled, the velocity property, and many other informational properties, will no longer update.\n\n[more in online documentation] +Pathfinding.FollowerEntity.steeringTarget followerentity.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.FollowerEntity.stopDistance followerentity.html#stopDistance How far away from the destination should the agent aim to stop, in world units. \n\nIf the agent is within this distance from the destination point it will be considered to have reached the destination.\n\nEven if you want the agent to stop precisely at a given point, it is recommended to keep this slightly above zero. If it is exactly zero, the agent may have a hard time deciding that it has actually reached the end of the path, due to floating point errors and such.\n\n[more in online documentation] +Pathfinding.FollowerEntity.syncPosition followerentity.html#syncPosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. Instead, only the position property and the internal entity's position will change.\n\nThis is useful if you want to control the movement of the character using some other means, such as root motion, but still want the AI to move freely.\n\n[more in online documentation] +Pathfinding.FollowerEntity.syncRotation followerentity.html#syncRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. Instead, only the rotation property and the internal entity's rotation will change.\n\nThis is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed sprite to indicate the direction the character is facing.\n\nYou can enable PIDMovement.DebugFlags.Rotation in debugFlags to draw a gizmos arrow in the scene view to indicate the agent's internal rotation.\n\n[more in online documentation] +Pathfinding.FollowerEntity.tr followerentity.html#tr Cached transform component. +Pathfinding.FollowerEntity.updatePosition followerentity.html#updatePosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. Instead, only the position property and the internal entity's position will change.\n\nThis is useful if you want to control the movement of the character using some other means, such as root motion, but still want the AI to move freely.\n\n[more in online documentation] +Pathfinding.FollowerEntity.updateRotation followerentity.html#updateRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. Instead, only the rotation property and the internal entity's rotation will change.\n\nThis is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed sprite to indicate the direction the character is facing.\n\nYou can enable PIDMovement.DebugFlags.Rotation in debugFlags to draw a gizmos arrow in the scene view to indicate the agent's internal rotation.\n\n[more in online documentation] +Pathfinding.FollowerEntity.velocity followerentity.html#velocity Actual velocity that the agent is moving with, including gravity. \n\nIn world units per second.\n\nThis is useful for, for example, selecting which animations to play, and at what speeds.\n\n[more in online documentation] +Pathfinding.FollowerEntity.world followerentity.html#world The world in which the entity resides. \n\nThis is the same as proxy.world, but is provided for convenience. It is only valid when this script is enabled.\n\n[more in online documentation] +Pathfinding.FollowerEntityEditor.PositionSyncOptions followerentityeditor.html#PositionSyncOptions +Pathfinding.FollowerEntityEditor.RotationSyncOptions followerentityeditor.html#RotationSyncOptions +Pathfinding.FollowerEntityEditor.debug followerentityeditor.html#debug +Pathfinding.FollowerEntityEditor.legend followerentityeditor.html#legend +Pathfinding.FollowerEntityEditor.tagPenaltiesOpen followerentityeditor.html#tagPenaltiesOpen +Pathfinding.Funnel.FunnelPortalIndexMask funnel.html#FunnelPortalIndexMask +Pathfinding.Funnel.FunnelPortals.left funnelportals.html#left +Pathfinding.Funnel.FunnelPortals.right funnelportals.html#right +Pathfinding.Funnel.FunnelState.leftFunnel funnelstate.html#leftFunnel Left side of the funnel. +Pathfinding.Funnel.FunnelState.projectionAxis funnelstate.html#projectionAxis If set to anything other than (0,0,0), then all portals will be projected on a plane with this normal. \n\nThis is used to make the funnel fit a rotated graph better. It is ideally used for grid graphs, but navmesh/recast graphs are probably better off with it set to zero.\n\nThe vector should be normalized (unless zero), in world space, and should never be changed after the first portal has been added (unless the funnel is cleared first). +Pathfinding.Funnel.FunnelState.rightFunnel funnelstate.html#rightFunnel Right side of the funnel. +Pathfinding.Funnel.FunnelState.unwrappedPortals funnelstate.html#unwrappedPortals Unwrapped version of the funnel portals in 2D space. \n\nThe input is a funnel like in the image below. It may be rotated and twisted. [image in online documentation][image in online documentation]\n\nThis array is used as a cache and the unwrapped portals are calculated on demand. Thus it may not contain all portals. +Pathfinding.Funnel.PartType funnel.html#PartType The type of a PathPart. +Pathfinding.Funnel.PathPart.endIndex pathpart.html#endIndex Index of the last node in this part. +Pathfinding.Funnel.PathPart.endPoint pathpart.html#endPoint Exact end-point of this part or off-mesh link. +Pathfinding.Funnel.PathPart.startIndex pathpart.html#startIndex Index of the first node in this part. +Pathfinding.Funnel.PathPart.startPoint pathpart.html#startPoint Exact start-point of this part or off-mesh link. +Pathfinding.Funnel.PathPart.type pathpart.html#type If this is an off-mesh link or a sequence of nodes in a single graph. +Pathfinding.Funnel.RightSideBit funnel.html#RightSideBit +Pathfinding.FunnelModifier.FunnelQuality funnelmodifier.html#FunnelQuality +Pathfinding.FunnelModifier.Order funnelmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.FunnelModifier.accountForGridPenalties funnelmodifier.html#accountForGridPenalties When using a grid graph, take penalties, tag penalties and ITraversalProvider penalties into account. \n\nEnabling this is quite slow. It can easily make the modifier take twice the amount of time to run. So unless you are using penalties/tags/ITraversalProvider penalties that you need to take into account when simplifying the path, you should leave this disabled. +Pathfinding.FunnelModifier.quality funnelmodifier.html#quality Determines if funnel simplification is used. \n\nWhen using the low quality setting only the funnel algorithm is used but when the high quality setting an additional step is done to simplify the path even more.\n\nOn tiled recast/navmesh graphs, but sometimes on normal ones as well, it can be good to simplify the funnel as a post-processing step to make the paths straighter.\n\nThis has a moderate performance impact during frames when a path calculation is completed. This is why it is disabled by default. For any units that you want high quality movement for you should enable it.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.FunnelModifier.splitAtEveryPortal funnelmodifier.html#splitAtEveryPortal Insert a vertex every time the path crosses a portal instead of only at the corners of the path. \n\nThe resulting path will have exactly one vertex per portal if this is enabled. This may introduce vertices with the same position in the output (esp. in corners where many portals meet). [image in online documentation]\n\n[more in online documentation] +Pathfinding.GUIUtilityx.colors guiutilityx.html#colors +Pathfinding.GlobalNodeStorage.DebugPathNode.fractionAlongEdge debugpathnode.html#fractionAlongEdge +Pathfinding.GlobalNodeStorage.DebugPathNode.g debugpathnode.html#g +Pathfinding.GlobalNodeStorage.DebugPathNode.h debugpathnode.html#h +Pathfinding.GlobalNodeStorage.DebugPathNode.parentIndex debugpathnode.html#parentIndex +Pathfinding.GlobalNodeStorage.DebugPathNode.pathID debugpathnode.html#pathID +Pathfinding.GlobalNodeStorage.IndexedStack.Count indexedstack.html#Count +Pathfinding.GlobalNodeStorage.IndexedStack.buffer indexedstack.html#buffer +Pathfinding.GlobalNodeStorage.InitialTemporaryNodes globalnodestorage.html#InitialTemporaryNodes +Pathfinding.GlobalNodeStorage.JobAllocateNodes.allowBoundsChecks joballocatenodes.html#allowBoundsChecks +Pathfinding.GlobalNodeStorage.JobAllocateNodes.count joballocatenodes.html#count +Pathfinding.GlobalNodeStorage.JobAllocateNodes.createNode joballocatenodes.html#createNode +Pathfinding.GlobalNodeStorage.JobAllocateNodes.nodeStorage joballocatenodes.html#nodeStorage +Pathfinding.GlobalNodeStorage.JobAllocateNodes.result joballocatenodes.html#result +Pathfinding.GlobalNodeStorage.JobAllocateNodes.variantsPerNode joballocatenodes.html#variantsPerNode +Pathfinding.GlobalNodeStorage.PathfindingThreadData.debugPathNodes pathfindingthreaddata.html#debugPathNodes +Pathfinding.GlobalNodeStorage.PathfindingThreadData.pathNodes pathfindingthreaddata.html#pathNodes +Pathfinding.GlobalNodeStorage.astar globalnodestorage.html#astar +Pathfinding.GlobalNodeStorage.destroyedNodesVersion globalnodestorage.html#destroyedNodesVersion Number of nodes that have been destroyed in total. +Pathfinding.GlobalNodeStorage.lastAllocationJob globalnodestorage.html#lastAllocationJob +Pathfinding.GlobalNodeStorage.nextNodeIndex globalnodestorage.html#nextNodeIndex Holds the next node index which has not been used by any previous node. \n\n[more in online documentation] +Pathfinding.GlobalNodeStorage.nodeIndexPools globalnodestorage.html#nodeIndexPools Holds indices for nodes that have been destroyed. \n\nTo avoid trashing a lot of memory structures when nodes are frequently deleted and created, node indices are reused.\n\nThere's one pool for each possible number of node variants (1, 2 and 3). +Pathfinding.GlobalNodeStorage.nodes globalnodestorage.html#nodes Maps from NodeIndex to node. +Pathfinding.GlobalNodeStorage.pathfindingThreadData globalnodestorage.html#pathfindingThreadData +Pathfinding.GlobalNodeStorage.reservedPathNodeData globalnodestorage.html#reservedPathNodeData The number of nodes for which path node data has been reserved. \n\nWill be at least as high as nextNodeIndex +Pathfinding.GlobalNodeStorage.temporaryNodeCount globalnodestorage.html#temporaryNodeCount +Pathfinding.GraphDebugMode pathfinding.html#GraphDebugMode How to visualize the graphs in the editor. +Pathfinding.GraphEditor.editor grapheditor.html#editor +Pathfinding.GraphEditor.fadeArea grapheditor.html#fadeArea Stores if the graph is visible or not in the inspector. +Pathfinding.GraphEditor.infoFadeArea grapheditor.html#infoFadeArea Stores if the graph info box is visible or not in the inspector. +Pathfinding.GraphEditorBase.target grapheditorbase.html#target NavGraph this editor is exposing. +Pathfinding.GraphHitInfo.distance graphhitinfo.html#distance Distance from origin to point. +Pathfinding.GraphHitInfo.node graphhitinfo.html#node Node which contained the edge which was hit. \n\nIf the linecast did not hit anything then this will be set to the last node along the line's path (the one which contains the endpoint).\n\nFor layered grid graphs the linecast will return true (i.e: no free line of sight) if, when walking the graph, we ended up at the right X,Z coordinate for the end node, but the end node was on a different level (e.g the floor below or above in a building). In this case no node edge was really hit so this field will still be null.\n\nIf the origin was inside an unwalkable node, then this field will be set to that node.\n\nIf no node could be found which contained the origin, then this field will be set to null. +Pathfinding.GraphHitInfo.origin graphhitinfo.html#origin Start of the segment/ray. \n\nNote that the point passed to the Linecast method will be clamped to the surface on the navmesh, but it will be identical when seen from above. +Pathfinding.GraphHitInfo.point graphhitinfo.html#point Hit point. \n\nThis is typically a point on the border of the navmesh.\n\nIn case no obstacle was hit then this will be set to the endpoint of the segment.\n\nIf the origin was inside an unwalkable node, then this will be set to the origin point. +Pathfinding.GraphHitInfo.tangent graphhitinfo.html#tangent Tangent of the edge which was hit. \n\n [image in online documentation]\n\nIf nothing was hit, this will be Vector3.zero.\n\nIn some cases, an obstruction could be reported, but no edge was hit per se. For example if the origin was inside an unwalkable node. In those cases this field will be set to Vector3.zero. +Pathfinding.GraphHitInfo.tangentOrigin graphhitinfo.html#tangentOrigin Where the tangent starts. \n\ntangentOrigin and tangent together actually describes the edge which was hit. [image in online documentation]\n\nIf nothing was hit, this will be Vector3.zero. +Pathfinding.GraphMask.Everything graphmask.html#Everything +Pathfinding.GraphMask.InvertedBit graphmask.html#InvertedBit +Pathfinding.GraphMask.LargeGraphIndicesBit graphmask.html#LargeGraphIndicesBit +Pathfinding.GraphMask.RemainingGraphIndicesBit graphmask.html#RemainingGraphIndicesBit +Pathfinding.GraphMask.SmallestLargeGraphIndex graphmask.html#SmallestLargeGraphIndex +Pathfinding.GraphMask.containsAllGraphs graphmask.html#containsAllGraphs True if the mask contains all graphs, i.e. \n\nif it is equal to GraphMask.everything +Pathfinding.GraphMask.everything graphmask.html#everything A mask containing every graph. +Pathfinding.GraphMask.isPureBitmask graphmask.html#isPureBitmask +Pathfinding.GraphMask.value graphmask.html#value Internal representation of the mask. \n\nDepending on LargeGraphIndicesBit (bit 31) the mask is in one of two modes:\n- If LargeGraphIndicesBit is set, then the lower 3x8 bits are used to store indices of 3 graphs (0-254, 255 represents no graph). The InvertedBit (bit 30) is used to indicate if the mask is inverted.\n\n- If LargeGraphIndicesBit is not set, then the lower 30 bits are a normal bitmask for the first 30 graphs, and bit 30 (RemainingGraphIndicesBit) represents all graph indices larger or equal to 30.\n\n\nCombining two masks is not trivial and can actually throw an exception if you try to combine too many large graph indices. Fortunately, hitting this limit is very rare, and will never happen in normal use.\n\nThere are technically two representations that contain all graphs. All bits set, and all bits except the LargeGraphIndicesBit. However, we prevent the latter from being created, to avoid confusion.\n\n[more in online documentation] +Pathfinding.GraphMaskDrawer.graphLabels graphmaskdrawer.html#graphLabels +Pathfinding.GraphModifier.EventType graphmodifier.html#EventType GraphModifier event type. +Pathfinding.GraphModifier.next graphmodifier.html#next +Pathfinding.GraphModifier.prev graphmodifier.html#prev +Pathfinding.GraphModifier.root graphmodifier.html#root All active graph modifiers. +Pathfinding.GraphModifier.uniqueID graphmodifier.html#uniqueID Unique persistent ID for this component, used for serialization. +Pathfinding.GraphModifier.usedIDs graphmodifier.html#usedIDs Maps persistent IDs to the component that uses it. +Pathfinding.GraphNode.Area graphnode.html#Area Connected component that contains the node. \n\nThis is visualized in the scene view as differently colored nodes (if the graph coloring mode is set to 'Areas'). Each area represents a set of nodes such that there is no valid path between nodes of different colors.\n\n[more in online documentation] +Pathfinding.GraphNode.Destroyed graphnode.html#Destroyed +Pathfinding.GraphNode.DestroyedNodeIndex graphnode.html#DestroyedNodeIndex +Pathfinding.GraphNode.Flags graphnode.html#Flags Holds various bitpacked variables. \n\nBit 0: Walkable Bits 1 through 17: HierarchicalNodeIndex Bit 18: IsHierarchicalNodeDirty Bits 19 through 23: Tag Bits 24 through 31: GraphIndex\n\n[more in online documentation] +Pathfinding.GraphNode.FlagsGraphMask graphnode.html#FlagsGraphMask Mask of graph index bits. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsGraphOffset graphnode.html#FlagsGraphOffset Start of graph index bits. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsHierarchicalIndexOffset graphnode.html#FlagsHierarchicalIndexOffset Start of hierarchical node index bits. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsTagMask graphnode.html#FlagsTagMask Mask of tag bits. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsTagOffset graphnode.html#FlagsTagOffset Start of tag bits. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsWalkableMask graphnode.html#FlagsWalkableMask Mask of the walkable bit. \n\n[more in online documentation] +Pathfinding.GraphNode.FlagsWalkableOffset graphnode.html#FlagsWalkableOffset Position of the walkable bit. \n\n[more in online documentation] +Pathfinding.GraphNode.Graph graphnode.html#Graph Graph which this node belongs to. \n\nIf you know the node belongs to a particular graph type, you can cast it to that type: [code in online documentation]\n\nWill return null if the node has been destroyed. +Pathfinding.GraphNode.GraphIndex graphnode.html#GraphIndex Graph which contains this node. \n\n[more in online documentation] +Pathfinding.GraphNode.HierarchicalDirtyMask graphnode.html#HierarchicalDirtyMask Mask of the IsHierarchicalNodeDirty bit. \n\n[more in online documentation] +Pathfinding.GraphNode.HierarchicalDirtyOffset graphnode.html#HierarchicalDirtyOffset Start of IsHierarchicalNodeDirty bits. \n\n[more in online documentation] +Pathfinding.GraphNode.HierarchicalIndexMask graphnode.html#HierarchicalIndexMask Mask of hierarchical node index bits. \n\n[more in online documentation] +Pathfinding.GraphNode.HierarchicalNodeIndex graphnode.html#HierarchicalNodeIndex Hierarchical Node that contains this node. \n\nThe graph is divided into clusters of small hierarchical nodes in which there is a path from every node to every other node. This structure is used to speed up connected component calculations which is used to quickly determine if a node is reachable from another node.\n\n[more in online documentation] +Pathfinding.GraphNode.InvalidGraphIndex graphnode.html#InvalidGraphIndex +Pathfinding.GraphNode.InvalidNodeIndex graphnode.html#InvalidNodeIndex +Pathfinding.GraphNode.IsHierarchicalNodeDirty graphnode.html#IsHierarchicalNodeDirty Some internal bookkeeping. +Pathfinding.GraphNode.MaxGraphIndex graphnode.html#MaxGraphIndex Max number of graphs-1. +Pathfinding.GraphNode.MaxHierarchicalNodeIndex graphnode.html#MaxHierarchicalNodeIndex +Pathfinding.GraphNode.MaxTagIndex graphnode.html#MaxTagIndex Max number of tags - 1. \n\nAlways a power of 2 minus one +Pathfinding.GraphNode.NodeIndex graphnode.html#NodeIndex Internal unique index. \n\nEvery node will get a unique index. This index is not necessarily correlated with e.g the position of the node in the graph. +Pathfinding.GraphNode.NodeIndexMask graphnode.html#NodeIndexMask +Pathfinding.GraphNode.PathNodeVariants graphnode.html#PathNodeVariants How many path node variants should be created for each node. \n\nThis should be a constant for each node type.\n\nTypically this is 1, but for example the triangle mesh node type has 3 variants, one for each edge.\n\n[more in online documentation] +Pathfinding.GraphNode.Penalty graphnode.html#Penalty Additional cost for entering this node. \n\nThis can be used to make it harder/slower to walk over specific nodes. A cost of 1000 ( Int3.Precision) corresponds to the cost of moving 1 world unit.\n\nThis cost does not scale with the distance traversed across the node. For recast/navmesh graphs, it is recommended to use costs that scale with the distance instead. For example TraversalCosts.tagCostMultipliers.\n\n[more in online documentation] +Pathfinding.GraphNode.Tag graphnode.html#Tag Node tag. \n\n[more in online documentation] +Pathfinding.GraphNode.TemporaryFlag1 graphnode.html#TemporaryFlag1 Temporary flag for internal purposes. \n\nMay only be used in the Unity thread. Must be reset to false after every use. +Pathfinding.GraphNode.TemporaryFlag1Mask graphnode.html#TemporaryFlag1Mask +Pathfinding.GraphNode.TemporaryFlag2 graphnode.html#TemporaryFlag2 Temporary flag for internal purposes. \n\nMay only be used in the Unity thread. Must be reset to false after every use. +Pathfinding.GraphNode.TemporaryFlag2Mask graphnode.html#TemporaryFlag2Mask +Pathfinding.GraphNode.Walkable graphnode.html#Walkable True if the node is traversable. \n\n[more in online documentation] +Pathfinding.GraphNode.flags graphnode.html#flags Bitpacked field holding several pieces of data. \n\n[more in online documentation] +Pathfinding.GraphNode.nodeIndex graphnode.html#nodeIndex Internal unique index. \n\nAlso stores some bitpacked values such as TemporaryFlag1 and TemporaryFlag2. +Pathfinding.GraphNode.penalty graphnode.html#penalty Penalty cost for walking on this node. \n\nThis can be used to make it harder/slower to walk over certain nodes.\n\nA penalty of 1000 (Int3.Precision) corresponds to the cost of walking one world unit.\n\n[more in online documentation] +Pathfinding.GraphNode.position graphnode.html#position Position of the node in world space. \n\n[more in online documentation] +Pathfinding.GraphUpdateDebugMode pathfinding.html#GraphUpdateDebugMode How to visualize graph updates in the editor. \n\nThis is used to visualize the bounds of graph updates, and how they affect the graph.\n\n[more in online documentation] +Pathfinding.GraphUpdateObject.GraphUpdateData.nodeIndices graphupdatedata.html#nodeIndices Node indices to update. \n\nRemaining nodes should be left alone. Additionally, if math.any(nodeNormals[i]) is false, then the node should not be updated, since it is not a valid node. +Pathfinding.GraphUpdateObject.GraphUpdateData.nodeNormals graphupdatedata.html#nodeNormals +Pathfinding.GraphUpdateObject.GraphUpdateData.nodePenalties graphupdatedata.html#nodePenalties +Pathfinding.GraphUpdateObject.GraphUpdateData.nodePositions graphupdatedata.html#nodePositions +Pathfinding.GraphUpdateObject.GraphUpdateData.nodeTags graphupdatedata.html#nodeTags +Pathfinding.GraphUpdateObject.GraphUpdateData.nodeWalkable graphupdatedata.html#nodeWalkable +Pathfinding.GraphUpdateObject.JobGraphUpdate.bounds jobgraphupdate.html#bounds +Pathfinding.GraphUpdateObject.JobGraphUpdate.data jobgraphupdate.html#data +Pathfinding.GraphUpdateObject.JobGraphUpdate.modifyTag jobgraphupdate.html#modifyTag +Pathfinding.GraphUpdateObject.JobGraphUpdate.modifyWalkability jobgraphupdate.html#modifyWalkability +Pathfinding.GraphUpdateObject.JobGraphUpdate.penaltyDelta jobgraphupdate.html#penaltyDelta +Pathfinding.GraphUpdateObject.JobGraphUpdate.shape jobgraphupdate.html#shape +Pathfinding.GraphUpdateObject.JobGraphUpdate.tagValue jobgraphupdate.html#tagValue +Pathfinding.GraphUpdateObject.JobGraphUpdate.walkabilityValue jobgraphupdate.html#walkabilityValue +Pathfinding.GraphUpdateObject.STAGE_ABORTED graphupdateobject.html#STAGE_ABORTED +Pathfinding.GraphUpdateObject.STAGE_APPLIED graphupdateobject.html#STAGE_APPLIED +Pathfinding.GraphUpdateObject.STAGE_CREATED graphupdateobject.html#STAGE_CREATED +Pathfinding.GraphUpdateObject.STAGE_PENDING graphupdateobject.html#STAGE_PENDING +Pathfinding.GraphUpdateObject.addPenalty graphupdateobject.html#addPenalty Penalty to add to the nodes. \n\nA penalty of 1000 is equivalent to the cost of moving 1 world unit. +Pathfinding.GraphUpdateObject.bounds graphupdateobject.html#bounds The bounds to update nodes within. \n\nDefined in world space. +Pathfinding.GraphUpdateObject.graphMask graphupdateobject.html#graphMask Which graphs to update. \n\nThis ia bitmask containing all graphs which should be updated.\n\n[more in online documentation] +Pathfinding.GraphUpdateObject.internalStage graphupdateobject.html#internalStage Info about if a graph update has been applied or not. \n\nEither an enum (see STAGE_CREATED and associated constants) or a non-negative count of the number of graphs that are waiting to apply this graph update. +Pathfinding.GraphUpdateObject.modifyTag graphupdateobject.html#modifyTag If true, all nodes' tag will be set to setTag. +Pathfinding.GraphUpdateObject.modifyWalkability graphupdateobject.html#modifyWalkability If true, all nodes' walkable variable will be set to setWalkability. \n\nIt is not recommended to combine this with updatePhysics since then you will just overwrite what updatePhysics calculated. +Pathfinding.GraphUpdateObject.nnConstraint graphupdateobject.html#nnConstraint NNConstraint to use. \n\nThe Pathfinding.NNConstraint.SuitableGraph function will be called on the NNConstraint to enable filtering of which graphs to update.\n\n[more in online documentation] +Pathfinding.GraphUpdateObject.resetPenaltyOnPhysics graphupdateobject.html#resetPenaltyOnPhysics Reset penalties to their initial values when updating grid graphs and updatePhysics is true. \n\nIf you want to keep old penalties even when you update the graph you may want to disable this option.\n\nThe images below shows two overlapping graph update objects, the right one happened to be applied before the left one. They both have updatePhysics = true and are set to increase the penalty of the nodes by some amount.\n\nThe first image shows the result when resetPenaltyOnPhysics is false. Both penalties are added correctly. [image in online documentation]\n\nThis second image shows when resetPenaltyOnPhysics is set to true. The first GUO is applied correctly, but then the second one (the left one) is applied and during its updating, it resets the penalties first and then adds penalty to the nodes. The result is that the penalties from both GUOs are not added together. The green patch in at the border is there because physics recalculation (recalculation of the position of the node, checking for obstacles etc.) affects a slightly larger area than the original GUO bounds because of the Grid Graph -> Collision Testing -> Diameter setting (it is enlarged by that value). So some extra nodes have their penalties reset.\n\n [image in online documentation] +Pathfinding.GraphUpdateObject.setTag graphupdateobject.html#setTag If modifyTag is true, all nodes' tag will be set to this value. +Pathfinding.GraphUpdateObject.setWalkability graphupdateobject.html#setWalkability If modifyWalkability is true, the nodes' walkable variable will be set to this value. +Pathfinding.GraphUpdateObject.shape graphupdateobject.html#shape A shape can be specified if a bounds object does not give enough precision. \n\nNote that if you set this, you should set the bounds so that it encloses the shape because the bounds will be used as an initial fast check for which nodes that should be updated. +Pathfinding.GraphUpdateObject.stage graphupdateobject.html#stage Info about if a graph update has been applied or not. +Pathfinding.GraphUpdateObject.trackChangedNodes graphupdateobject.html#trackChangedNodes Track which nodes are changed and save backup data. \n\nUsed internally to revert changes if needed.\n\n[more in online documentation] +Pathfinding.GraphUpdateObject.updateErosion graphupdateobject.html#updateErosion Update Erosion for GridGraphs. \n\nWhen enabled, erosion will be recalculated for grid graphs after the GUO has been applied.\n\nIn the below image you can see the different effects you can get with the different values.\n\nThe first image shows the graph when no GUO has been applied. The blue box is not identified as an obstacle by the graph, the reason there are unwalkable nodes around it is because there is a height difference (nodes are placed on top of the box) so erosion will be applied (an erosion value of 2 is used in this graph). The orange box is identified as an obstacle, so the area of unwalkable nodes around it is a bit larger since both erosion and collision has made nodes unwalkable.\n\nThe GUO used simply sets walkability to true, i.e making all nodes walkable.\n\n [image in online documentation]\n\nWhen updateErosion=True, the reason the blue box still has unwalkable nodes around it is because there is still a height difference so erosion will still be applied. The orange box on the other hand has no height difference and all nodes are set to walkable.\n\nWhen updateErosion=False, all nodes walkability are simply set to be walkable in this example.\n\n[more in online documentation] +Pathfinding.GraphUpdateObject.updatePhysics graphupdateobject.html#updatePhysics Use physics checks to update nodes. \n\nWhen updating a grid graph and this is true, the nodes' position and walkability will be updated using physics checks with settings from "Collision Testing" and "Height Testing".\n\nWhen updating a PointGraph, setting this to true will make it re-evaluate all connections in the graph which passes through the bounds.\n\nThis has no effect when updating GridGraphs if modifyWalkability is turned on. You should not combine updatePhysics and modifyWalkability.\n\nOn RecastGraphs, having this enabled will trigger a complete recalculation of all tiles intersecting the bounds. This is quite slow (but powerful). If you only want to update e.g penalty on existing nodes, leave it disabled. +Pathfinding.GraphUpdateProcessor.IsAnyGraphUpdateInProgress graphupdateprocessor.html#IsAnyGraphUpdateInProgress Returns if any graph updates are in progress. +Pathfinding.GraphUpdateProcessor.IsAnyGraphUpdateQueued graphupdateprocessor.html#IsAnyGraphUpdateQueued Returns if any graph updates are waiting to be applied. +Pathfinding.GraphUpdateProcessor.MarkerApply graphupdateprocessor.html#MarkerApply +Pathfinding.GraphUpdateProcessor.MarkerCalculate graphupdateprocessor.html#MarkerCalculate +Pathfinding.GraphUpdateProcessor.MarkerSleep graphupdateprocessor.html#MarkerSleep +Pathfinding.GraphUpdateProcessor.anyGraphUpdateInProgress graphupdateprocessor.html#anyGraphUpdateInProgress Used for IsAnyGraphUpdateInProgress. +Pathfinding.GraphUpdateProcessor.astar graphupdateprocessor.html#astar Holds graphs that can be updated. +Pathfinding.GraphUpdateProcessor.graphUpdateQueue graphupdateprocessor.html#graphUpdateQueue Queue containing all waiting graph update queries. \n\nAdd to this queue by using AddToQueue. \n\n[more in online documentation] +Pathfinding.GraphUpdateProcessor.pendingGraphUpdates graphupdateprocessor.html#pendingGraphUpdates +Pathfinding.GraphUpdateProcessor.pendingPromises graphupdateprocessor.html#pendingPromises +Pathfinding.GraphUpdateScene.GizmoColorSelected graphupdatescene.html#GizmoColorSelected +Pathfinding.GraphUpdateScene.GizmoColorUnselected graphupdatescene.html#GizmoColorUnselected +Pathfinding.GraphUpdateScene.applyOnScan graphupdatescene.html#applyOnScan Apply this graph update object whenever a graph is rescanned. +Pathfinding.GraphUpdateScene.applyOnStart graphupdatescene.html#applyOnStart Apply this graph update object on start. +Pathfinding.GraphUpdateScene.convex graphupdatescene.html#convex Use the convex hull of the points instead of the original polygon. \n\n[more in online documentation] +Pathfinding.GraphUpdateScene.convexPoints graphupdatescene.html#convexPoints Private cached convex hull of the points. +Pathfinding.GraphUpdateScene.firstApplied graphupdatescene.html#firstApplied Has apply been called yet. \n\nUsed to prevent applying twice when both applyOnScan and applyOnStart are enabled +Pathfinding.GraphUpdateScene.legacyMode graphupdatescene.html#legacyMode Emulates behavior from before version 4.0. +Pathfinding.GraphUpdateScene.legacyUseWorldSpace graphupdatescene.html#legacyUseWorldSpace Use world space for coordinates. \n\nIf true, the shape will not follow when moving around the transform. +Pathfinding.GraphUpdateScene.minBoundsHeight graphupdatescene.html#minBoundsHeight Minumum height of the bounds of the resulting Graph Update Object. \n\nUseful when all points are laid out on a plane but you still need a bounds with a height greater than zero since a zero height graph update object would usually result in no nodes being updated. +Pathfinding.GraphUpdateScene.modifyTag graphupdatescene.html#modifyTag Should the tags of the nodes be modified. \n\nIf enabled, set all nodes' tags to setTag +Pathfinding.GraphUpdateScene.modifyWalkability graphupdatescene.html#modifyWalkability If true, then all affected nodes will be made walkable or unwalkable according to setWalkability. +Pathfinding.GraphUpdateScene.penaltyDelta graphupdatescene.html#penaltyDelta Penalty to add to nodes. \n\nUsually you need quite large values, at least 1000-10000. A higher penalty means that agents will try to avoid those nodes more.\n\nBe careful when setting negative values since if a node gets a negative penalty it will underflow and instead get really large. In most cases a warning will be logged if that happens.\n\n[more in online documentation] +Pathfinding.GraphUpdateScene.points graphupdatescene.html#points Points which define the region to update. +Pathfinding.GraphUpdateScene.resetPenaltyOnPhysics graphupdatescene.html#resetPenaltyOnPhysics Reset penalties to their initial values when updating grid graphs and updatePhysics is true. \n\nIf you want to keep old penalties even when you update the graph you may want to disable this option.\n\nThe images below shows two overlapping graph update objects, the right one happened to be applied before the left one. They both have updatePhysics = true and are set to increase the penalty of the nodes by some amount.\n\nThe first image shows the result when resetPenaltyOnPhysics is false. Both penalties are added correctly. [image in online documentation]\n\nThis second image shows when resetPenaltyOnPhysics is set to true. The first GUO is applied correctly, but then the second one (the left one) is applied and during its updating, it resets the penalties first and then adds penalty to the nodes. The result is that the penalties from both GUOs are not added together. The green patch in at the border is there because physics recalculation (recalculation of the position of the node, checking for obstacles etc.) affects a slightly larger area than the original GUO bounds because of the Grid Graph -> Collision Testing -> Diameter setting (it is enlarged by that value). So some extra nodes have their penalties reset.\n\n [image in online documentation] +Pathfinding.GraphUpdateScene.setTag graphupdatescene.html#setTag If modifyTag is enabled, set all nodes' tags to this value. +Pathfinding.GraphUpdateScene.setTagCompatibility graphupdatescene.html#setTagCompatibility +Pathfinding.GraphUpdateScene.setTagInvert graphupdatescene.html#setTagInvert Private cached inversion of setTag. \n\nUsed for InvertSettings() +Pathfinding.GraphUpdateScene.setWalkability graphupdatescene.html#setWalkability Nodes will be made walkable or unwalkable according to this value if modifyWalkability is true. +Pathfinding.GraphUpdateScene.updateErosion graphupdatescene.html#updateErosion Update Erosion for GridGraphs. \n\nWhen enabled, erosion will be recalculated for grid graphs after the GUO has been applied.\n\nIn the below image you can see the different effects you can get with the different values.\n\nThe first image shows the graph when no GUO has been applied. The blue box is not identified as an obstacle by the graph, the reason there are unwalkable nodes around it is because there is a height difference (nodes are placed on top of the box) so erosion will be applied (an erosion value of 2 is used in this graph). The orange box is identified as an obstacle, so the area of unwalkable nodes around it is a bit larger since both erosion and collision has made nodes unwalkable.\n\nThe GUO used simply sets walkability to true, i.e making all nodes walkable.\n\n [image in online documentation]\n\nWhen updateErosion=True, the reason the blue box still has unwalkable nodes around it is because there is still a height difference so erosion will still be applied. The orange box on the other hand has no height difference and all nodes are set to walkable.\n\nWhen updateErosion=False, all nodes walkability are simply set to be walkable in this example.\n\n[more in online documentation] +Pathfinding.GraphUpdateScene.updatePhysics graphupdatescene.html#updatePhysics Update node's walkability and connectivity using physics functions. \n\nFor grid graphs, this will update the node's position and walkability exactly like when doing a scan of the graph. If enabled for grid graphs, modifyWalkability will be ignored.\n\nFor Point Graphs, this will recalculate all connections which passes through the bounds of the resulting Graph Update Object using raycasts (if enabled). +Pathfinding.GraphUpdateSceneEditor.PointColor graphupdatesceneeditor.html#PointColor +Pathfinding.GraphUpdateSceneEditor.PointSelectedColor graphupdatesceneeditor.html#PointSelectedColor +Pathfinding.GraphUpdateSceneEditor.pointGizmosRadius graphupdatesceneeditor.html#pointGizmosRadius +Pathfinding.GraphUpdateSceneEditor.scripts graphupdatesceneeditor.html#scripts +Pathfinding.GraphUpdateSceneEditor.selectedPoint graphupdatesceneeditor.html#selectedPoint +Pathfinding.GraphUpdateShape.BurstShape.Everything burstshape.html#Everything Shape that contains everything. +Pathfinding.GraphUpdateShape.BurstShape.containsEverything burstshape.html#containsEverything +Pathfinding.GraphUpdateShape.BurstShape.forward burstshape.html#forward +Pathfinding.GraphUpdateShape.BurstShape.origin burstshape.html#origin +Pathfinding.GraphUpdateShape.BurstShape.points burstshape.html#points +Pathfinding.GraphUpdateShape.BurstShape.right burstshape.html#right +Pathfinding.GraphUpdateShape._convex graphupdateshape.html#_convex +Pathfinding.GraphUpdateShape._convexPoints graphupdateshape.html#_convexPoints +Pathfinding.GraphUpdateShape._points graphupdateshape.html#_points +Pathfinding.GraphUpdateShape.convex graphupdateshape.html#convex Sets if the convex hull of the points should be calculated. \n\nConvex hulls are faster but non-convex hulls can be used to specify more complicated shapes. +Pathfinding.GraphUpdateShape.forward graphupdateshape.html#forward +Pathfinding.GraphUpdateShape.minimumHeight graphupdateshape.html#minimumHeight +Pathfinding.GraphUpdateShape.origin graphupdateshape.html#origin +Pathfinding.GraphUpdateShape.points graphupdateshape.html#points Gets or sets the points of the polygon in the shape. \n\nThese points should be specified in clockwise order. Will automatically calculate the convex hull if convex is set to true +Pathfinding.GraphUpdateShape.right graphupdateshape.html#right +Pathfinding.GraphUpdateShape.up graphupdateshape.html#up +Pathfinding.GraphUpdateStage pathfinding.html#GraphUpdateStage Info about if a graph update has been applied or not. +Pathfinding.GraphUpdateThreading pathfinding.html#GraphUpdateThreading +Pathfinding.Graphs.Grid.ColliderType grid.html#ColliderType Determines collision check shape. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.RaycastErrorMargin graphcollision.html#RaycastErrorMargin Offset to apply after each raycast to make sure we don't hit the same point again in CheckHeightAll. +Pathfinding.Graphs.Grid.GraphCollision.collisionCheck graphcollision.html#collisionCheck Toggle collision check. +Pathfinding.Graphs.Grid.GraphCollision.collisionOffset graphcollision.html#collisionOffset Height above the ground that collision checks should be done. \n\nFor example, if the ground was found at y=0, collisionOffset = 2 type = Capsule and height = 3 then the physics system will be queried to see if there are any colliders in a capsule for which the bottom sphere that is made up of is centered at y=2 and the top sphere has its center at y=2+3=5.\n\nIf type = Sphere then the sphere's center would be at y=2 in this case. +Pathfinding.Graphs.Grid.GraphCollision.contactFilter graphcollision.html#contactFilter Used for 2D collision queries. +Pathfinding.Graphs.Grid.GraphCollision.diameter graphcollision.html#diameter Diameter of capsule or sphere when checking for collision. \n\nWhen checking for collisions the system will check if any colliders overlap a specific shape at the node's position. The shape is determined by the type field.\n\nA diameter of 1 means that the shape has a diameter equal to the node's width, or in other words it is equal to nodeSize .\n\nIf type is set to Ray, this does not affect anything.\n\n [image in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.dummyArray graphcollision.html#dummyArray Just so that the Physics2D.OverlapPoint method has some buffer to store things in. \n\nWe never actually read from this array, so we don't even care if this is thread safe. +Pathfinding.Graphs.Grid.GraphCollision.finalRadius graphcollision.html#finalRadius diameter * scale * 0.5. \n\nWhere scale usually is nodeSize \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.finalRaycastRadius graphcollision.html#finalRaycastRadius thickRaycastDiameter * scale * 0.5. \n\nWhere scale usually is nodeSize \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.fromHeight graphcollision.html#fromHeight The height to check from when checking height ('ray length' in the inspector). \n\nAs the image below visualizes, different ray lengths can make the ray hit different things. The distance is measured up from the graph plane.\n\n [image in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.height graphcollision.html#height Height of capsule or length of ray when checking for collision. \n\nIf type is set to Sphere, this does not affect anything.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.heightCheck graphcollision.html#heightCheck Toggle height check. \n\nIf false, the grid will be flat.\n\nThis setting will be ignored when 2D physics is used. +Pathfinding.Graphs.Grid.GraphCollision.heightMask graphcollision.html#heightMask Layers to be included in the height check. +Pathfinding.Graphs.Grid.GraphCollision.hitBuffer graphcollision.html#hitBuffer Internal buffer used by CheckHeightAll. +Pathfinding.Graphs.Grid.GraphCollision.mask graphcollision.html#mask Layers to be treated as obstacles. +Pathfinding.Graphs.Grid.GraphCollision.rayDirection graphcollision.html#rayDirection Direction of the ray when checking for collision. \n\nIf type is not Ray, this does not affect anything\n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.thickRaycast graphcollision.html#thickRaycast Toggles thick raycast. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.thickRaycastDiameter graphcollision.html#thickRaycastDiameter Diameter of the thick raycast in nodes. \n\n1 equals nodeSize +Pathfinding.Graphs.Grid.GraphCollision.type graphcollision.html#type Collision shape to use. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.unwalkableWhenNoGround graphcollision.html#unwalkableWhenNoGround Make nodes unwalkable when no ground was found with the height raycast. \n\nIf height raycast is turned off, this doesn't affect anything. +Pathfinding.Graphs.Grid.GraphCollision.up graphcollision.html#up Direction to use as UP. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.upheight graphcollision.html#upheight up * height. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GraphCollision.use2D graphcollision.html#use2D Use Unity 2D Physics API. \n\nIf enabled, the 2D Physics API will be used, and if disabled, the 3D Physics API will be used.\n\nThis changes the collider types (see type) from 3D versions to their corresponding 2D versions. For example the sphere shape becomes a circle.\n\nThe heightCheck setting will be ignored when 2D physics is used.\n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphNodeData.LightReader.nodePositions lightreader.html#nodePositions +Pathfinding.Graphs.Grid.GridGraphNodeData.LightReader.nodeWalkable lightreader.html#nodeWalkable +Pathfinding.Graphs.Grid.GridGraphNodeData.LightReader.nodes lightreader.html#nodes +Pathfinding.Graphs.Grid.GridGraphNodeData.allocationMethod gridgraphnodedata.html#allocationMethod +Pathfinding.Graphs.Grid.GridGraphNodeData.bounds gridgraphnodedata.html#bounds Bounds for the part of the graph that this data represents. \n\nFor example if the first layer of a layered grid graph is being updated between x=10 and x=20, z=5 and z=15 then this will be IntBounds(xmin=10, ymin=0, zmin=5, xmax=20, ymax=0, zmax=15) +Pathfinding.Graphs.Grid.GridGraphNodeData.connections gridgraphnodedata.html#connections Bitpacked connections of all nodes. \n\nConnections are stored in different formats depending on layeredDataLayout. You can use LayeredGridAdjacencyMapper and FlatGridAdjacencyMapper to access connections for the different data layouts.\n\nData is valid in these passes:\n- BeforeCollision: Invalid\n\n- BeforeConnections: Invalid\n\n- AfterConnections: Valid\n\n- AfterErosion: Valid (but will be overwritten)\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.layeredDataLayout gridgraphnodedata.html#layeredDataLayout True if the data may have multiple layers. \n\nFor layered data the nodes are laid out as `data[y*width*depth + z*width + x]`. For non-layered data the nodes are laid out as `data[z*width + x]` (which is equivalent to the above layout assuming y=0).\n\nThis also affects how node connections are stored. You can use LayeredGridAdjacencyMapper and FlatGridAdjacencyMapper to access connections for the different data layouts. +Pathfinding.Graphs.Grid.GridGraphNodeData.layers gridgraphnodedata.html#layers Number of layers that the data contains. \n\nFor a non-layered grid graph this will always be 1. +Pathfinding.Graphs.Grid.GridGraphNodeData.normals gridgraphnodedata.html#normals Normals of all nodes. \n\nIf height testing is disabled the normal will be (0,1,0) for all nodes. If a node doesn't exist (only happens in layered grid graphs) or if the height raycast didn't hit anything then the normal will be (0,0,0).\n\nData is valid in these passes:\n- BeforeCollision: Valid\n\n- BeforeConnections: Valid\n\n- AfterConnections: Valid\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.numNodes gridgraphnodedata.html#numNodes +Pathfinding.Graphs.Grid.GridGraphNodeData.penalties gridgraphnodedata.html#penalties Bitpacked connections of all nodes. \n\nData is valid in these passes:\n- BeforeCollision: Valid\n\n- BeforeConnections: Valid\n\n- AfterConnections: Valid\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.positions gridgraphnodedata.html#positions Positions of all nodes. \n\nData is valid in these passes:\n- BeforeCollision: Valid\n\n- BeforeConnections: Valid\n\n- AfterConnections: Valid\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.tags gridgraphnodedata.html#tags Tags of all nodes. \n\nData is valid in these passes:\n- BeforeCollision: Valid (but if erosion uses tags then it will be overwritten later)\n\n- BeforeConnections: Valid (but if erosion uses tags then it will be overwritten later)\n\n- AfterConnections: Valid (but if erosion uses tags then it will be overwritten later)\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.walkable gridgraphnodedata.html#walkable Walkability of all nodes before erosion happens. \n\nData is valid in these passes:\n- BeforeCollision: Valid (it will be combined with collision testing later)\n\n- BeforeConnections: Valid\n\n- AfterConnections: Valid\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphNodeData.walkableWithErosion gridgraphnodedata.html#walkableWithErosion Walkability of all nodes after erosion happens. \n\nThis is the final walkability of the nodes. If no erosion is used then the data will just be copied from the walkable array.\n\nData is valid in these passes:\n- BeforeCollision: Invalid\n\n- BeforeConnections: Invalid\n\n- AfterConnections: Invalid\n\n- AfterErosion: Valid\n\n- PostProcess: Valid +Pathfinding.Graphs.Grid.GridGraphScanData.bounds gridgraphscandata.html#bounds Bounds of the data arrays. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.dependencyTracker gridgraphscandata.html#dependencyTracker Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually. \n\nAlways use when scheduling jobs. +Pathfinding.Graphs.Grid.GridGraphScanData.heightHits gridgraphscandata.html#heightHits Raycasts hits used for height testing. \n\nThis data is only valid if height testing is enabled, otherwise the array is uninitialized (heightHits.IsCreated will be false).\n\nData is valid in these passes:\n- BeforeCollision: Valid (if height testing is enabled)\n\n- BeforeConnections: Valid (if height testing is enabled)\n\n- AfterConnections: Valid (if height testing is enabled)\n\n- AfterErosion: Valid (if height testing is enabled)\n\n- PostProcess: Valid (if height testing is enabled)\n\n\n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.heightHitsBounds gridgraphscandata.html#heightHitsBounds Bounds for the heightHits array. \n\nDuring an update, the scan data may contain more nodes than we are doing height testing for. For a few nodes around the update, the data will be read from the existing graph, instead. This is done for performance. This means that there may not be any height testing information these nodes. However, all nodes that will be written to will always have height testing information. +Pathfinding.Graphs.Grid.GridGraphScanData.layeredDataLayout gridgraphscandata.html#layeredDataLayout True if the data may have multiple layers. \n\nFor layered data the nodes are laid out as `data[y*width*depth + z*width + x]`. For non-layered data the nodes are laid out as `data[z*width + x]` (which is equivalent to the above layout assuming y=0).\n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodeConnections gridgraphscandata.html#nodeConnections Node connections. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodeNormals gridgraphscandata.html#nodeNormals Node normals. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodePenalties gridgraphscandata.html#nodePenalties Node penalties. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodePositions gridgraphscandata.html#nodePositions Node positions. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodeTags gridgraphscandata.html#nodeTags Node tags. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodeWalkable gridgraphscandata.html#nodeWalkable Node walkability. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodeWalkableWithErosion gridgraphscandata.html#nodeWalkableWithErosion Node walkability with erosion. \n\n[more in online documentation] +Pathfinding.Graphs.Grid.GridGraphScanData.nodes gridgraphscandata.html#nodes Data for all nodes in the graph update that is being calculated. +Pathfinding.Graphs.Grid.GridGraphScanData.transform gridgraphscandata.html#transform Transforms graph-space to world space. +Pathfinding.Graphs.Grid.GridGraphScanData.up gridgraphscandata.html#up The up direction of the graph, in world space. +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.active joballocatenodes2.html#active +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.dataBounds joballocatenodes2.html#dataBounds +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.newGridNodeDelegate joballocatenodes2.html#newGridNodeDelegate +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.nodeArrayBounds joballocatenodes2.html#nodeArrayBounds +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.nodeNormals joballocatenodes2.html#nodeNormals +Pathfinding.Graphs.Grid.Jobs.JobAllocateNodes.nodes joballocatenodes2.html#nodes +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.allowBoundsChecks jobcalculategridconnections.html#allowBoundsChecks +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.arrayBounds jobcalculategridconnections.html#arrayBounds +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.bounds jobcalculategridconnections.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.characterHeight jobcalculategridconnections.html#characterHeight +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.cutCorners jobcalculategridconnections.html#cutCorners +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.graphToWorld jobcalculategridconnections.html#graphToWorld +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.layeredDataLayout jobcalculategridconnections.html#layeredDataLayout +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.maxStepHeight jobcalculategridconnections.html#maxStepHeight +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.maxStepUsesSlope jobcalculategridconnections.html#maxStepUsesSlope +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.neighbours jobcalculategridconnections.html#neighbours +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.nodeConnections jobcalculategridconnections.html#nodeConnections All bitpacked node connections. +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.nodeNormals jobcalculategridconnections.html#nodeNormals +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.nodePositions jobcalculategridconnections.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.nodeWalkable jobcalculategridconnections.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobCalculateGridConnections.use2D jobcalculategridconnections.html#use2D +Pathfinding.Graphs.Grid.Jobs.JobCheckCollisions.collision jobcheckcollisions.html#collision +Pathfinding.Graphs.Grid.Jobs.JobCheckCollisions.collisionResult jobcheckcollisions.html#collisionResult +Pathfinding.Graphs.Grid.Jobs.JobCheckCollisions.nodePositions jobcheckcollisions.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobCheckCollisions.startIndex jobcheckcollisions.html#startIndex +Pathfinding.Graphs.Grid.Jobs.JobColliderHitsToBooleans.hits jobcolliderhitstobooleans.html#hits +Pathfinding.Graphs.Grid.Jobs.JobColliderHitsToBooleans.result jobcolliderhitstobooleans.html#result +Pathfinding.Graphs.Grid.Jobs.JobCopyBuffers.bounds jobcopybuffers.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobCopyBuffers.copyPenaltyAndTags jobcopybuffers.html#copyPenaltyAndTags +Pathfinding.Graphs.Grid.Jobs.JobCopyBuffers.input jobcopybuffers.html#input +Pathfinding.Graphs.Grid.Jobs.JobCopyBuffers.output jobcopybuffers.html#output +Pathfinding.Graphs.Grid.Jobs.JobErosion.bounds joberosion.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobErosion.erosion joberosion.html#erosion +Pathfinding.Graphs.Grid.Jobs.JobErosion.erosionStartTag joberosion.html#erosionStartTag +Pathfinding.Graphs.Grid.Jobs.JobErosion.erosionTagsPrecedenceMask joberosion.html#erosionTagsPrecedenceMask +Pathfinding.Graphs.Grid.Jobs.JobErosion.erosionUsesTags joberosion.html#erosionUsesTags +Pathfinding.Graphs.Grid.Jobs.JobErosion.hexagonNeighbourIndices joberosion.html#hexagonNeighbourIndices +Pathfinding.Graphs.Grid.Jobs.JobErosion.neighbours joberosion.html#neighbours +Pathfinding.Graphs.Grid.Jobs.JobErosion.nodeConnections joberosion.html#nodeConnections +Pathfinding.Graphs.Grid.Jobs.JobErosion.nodeTags joberosion.html#nodeTags +Pathfinding.Graphs.Grid.Jobs.JobErosion.nodeWalkable joberosion.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobErosion.outNodeWalkable joberosion.html#outNodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobErosion.writeMask joberosion.html#writeMask +Pathfinding.Graphs.Grid.Jobs.JobFilterDiagonalConnections.allowBoundsChecks jobfilterdiagonalconnections.html#allowBoundsChecks +Pathfinding.Graphs.Grid.Jobs.JobFilterDiagonalConnections.cutCorners jobfilterdiagonalconnections.html#cutCorners +Pathfinding.Graphs.Grid.Jobs.JobFilterDiagonalConnections.neighbours jobfilterdiagonalconnections.html#neighbours +Pathfinding.Graphs.Grid.Jobs.JobFilterDiagonalConnections.nodeConnections jobfilterdiagonalconnections.html#nodeConnections All bitpacked node connections. +Pathfinding.Graphs.Grid.Jobs.JobFilterDiagonalConnections.slice jobfilterdiagonalconnections.html#slice +Pathfinding.Graphs.Grid.Jobs.JobMergeRaycastCollisionHits.hit1 jobmergeraycastcollisionhits.html#hit1 +Pathfinding.Graphs.Grid.Jobs.JobMergeRaycastCollisionHits.hit2 jobmergeraycastcollisionhits.html#hit2 +Pathfinding.Graphs.Grid.Jobs.JobMergeRaycastCollisionHits.result jobmergeraycastcollisionhits.html#result +Pathfinding.Graphs.Grid.Jobs.JobNodeGridLayout.bounds jobnodegridlayout.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobNodeGridLayout.graphToWorld jobnodegridlayout.html#graphToWorld +Pathfinding.Graphs.Grid.Jobs.JobNodeGridLayout.nodePositions jobnodegridlayout.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.characterHeight jobnodewalkability.html#characterHeight For layered grid graphs, if there's a node above another node closer than this distance, the lower node will be made unwalkable. +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.layerStride jobnodewalkability.html#layerStride Number of nodes in each layer. +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.maxSlope jobnodewalkability.html#maxSlope Max slope in degrees. +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.nodeNormals jobnodewalkability.html#nodeNormals +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.nodePositions jobnodewalkability.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.nodeWalkable jobnodewalkability.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.unwalkableWhenNoGround jobnodewalkability.html#unwalkableWhenNoGround If true, nodes will be made unwalkable if no ground was found under them. +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.up jobnodewalkability.html#up Normalized up direction of the graph. +Pathfinding.Graphs.Grid.Jobs.JobNodeWalkability.useRaycastNormal jobnodewalkability.html#useRaycastNormal If true, use the normal of the raycast hit to check if the ground is flat enough to stand on. \n\nAny nodes with a steeper slope than maxSlope will be made unwalkable. +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.commands jobpreparecapsulecommands.html#commands +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.direction jobpreparecapsulecommands.html#direction +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.mask jobpreparecapsulecommands.html#mask +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.originOffset jobpreparecapsulecommands.html#originOffset +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.origins jobpreparecapsulecommands.html#origins +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.physicsScene jobpreparecapsulecommands.html#physicsScene +Pathfinding.Graphs.Grid.Jobs.JobPrepareCapsuleCommands.radius jobpreparecapsulecommands.html#radius +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.bounds jobpreparegridraycast.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.graphToWorld jobpreparegridraycast.html#graphToWorld +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.physicsScene jobpreparegridraycast.html#physicsScene +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.raycastCommands jobpreparegridraycast.html#raycastCommands +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.raycastDirection jobpreparegridraycast.html#raycastDirection +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.raycastMask jobpreparegridraycast.html#raycastMask +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycast.raycastOffset jobpreparegridraycast.html#raycastOffset +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.bounds jobpreparegridraycastthick.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.graphToWorld jobpreparegridraycastthick.html#graphToWorld +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.physicsScene jobpreparegridraycastthick.html#physicsScene +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.radius jobpreparegridraycastthick.html#radius +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.raycastCommands jobpreparegridraycastthick.html#raycastCommands +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.raycastDirection jobpreparegridraycastthick.html#raycastDirection +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.raycastMask jobpreparegridraycastthick.html#raycastMask +Pathfinding.Graphs.Grid.Jobs.JobPrepareGridRaycastThick.raycastOffset jobpreparegridraycastthick.html#raycastOffset +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.direction jobprepareraycasts.html#direction +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.distance jobprepareraycasts.html#distance +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.mask jobprepareraycasts.html#mask +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.originOffset jobprepareraycasts.html#originOffset +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.origins jobprepareraycasts.html#origins +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.physicsScene jobprepareraycasts.html#physicsScene +Pathfinding.Graphs.Grid.Jobs.JobPrepareRaycasts.raycastCommands jobprepareraycasts.html#raycastCommands +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.commands jobpreparespherecommands.html#commands +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.mask jobpreparespherecommands.html#mask +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.originOffset jobpreparespherecommands.html#originOffset +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.origins jobpreparespherecommands.html#origins +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.physicsScene jobpreparespherecommands.html#physicsScene +Pathfinding.Graphs.Grid.Jobs.JobPrepareSphereCommands.radius jobpreparespherecommands.html#radius +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodeConnections reader.html#nodeConnections +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodePenalties reader.html#nodePenalties +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodePositions reader.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodeTags reader.html#nodeTags +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodeWalkable reader.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodeWalkableWithErosion reader.html#nodeWalkableWithErosion +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.Reader.nodes reader.html#nodes +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.allowBoundsChecks jobreadnodedata.html#allowBoundsChecks +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.graphIndex jobreadnodedata.html#graphIndex +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodeConnections jobreadnodedata.html#nodeConnections +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodePenalties jobreadnodedata.html#nodePenalties +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodePositions jobreadnodedata.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodeTags jobreadnodedata.html#nodeTags +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodeWalkable jobreadnodedata.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodeWalkableWithErosion jobreadnodedata.html#nodeWalkableWithErosion +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.nodesHandle jobreadnodedata.html#nodesHandle +Pathfinding.Graphs.Grid.Jobs.JobReadNodeData.slice jobreadnodedata.html#slice +Pathfinding.Graphs.Grid.Jobs.JobRelocateNodes.bounds jobrelocatenodes.html#bounds +Pathfinding.Graphs.Grid.Jobs.JobRelocateNodes.graphToWorld jobrelocatenodes.html#graphToWorld +Pathfinding.Graphs.Grid.Jobs.JobRelocateNodes.positions jobrelocatenodes.html#positions +Pathfinding.Graphs.Grid.Jobs.JobRelocateNodes.previousWorldToGraph jobrelocatenodes.html#previousWorldToGraph +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.allowBoundsChecks jobwritenodedata.html#allowBoundsChecks +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.dataBounds jobwritenodedata.html#dataBounds +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.graphIndex jobwritenodedata.html#graphIndex +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodeArrayBounds jobwritenodedata.html#nodeArrayBounds (width, depth) of the array that the nodesHandle refers to +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodeConnections jobwritenodedata.html#nodeConnections +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodePenalties jobwritenodedata.html#nodePenalties +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodePositions jobwritenodedata.html#nodePositions +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodeTags jobwritenodedata.html#nodeTags +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodeWalkable jobwritenodedata.html#nodeWalkable +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodeWalkableWithErosion jobwritenodedata.html#nodeWalkableWithErosion +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.nodesHandle jobwritenodedata.html#nodesHandle +Pathfinding.Graphs.Grid.Jobs.JobWriteNodeData.writeMask jobwritenodedata.html#writeMask +Pathfinding.Graphs.Grid.RayDirection grid.html#RayDirection Determines collision check ray direction. +Pathfinding.Graphs.Grid.Rules.CustomGridGraphRuleEditorAttribute.name customgridgraphruleeditorattribute.html#name +Pathfinding.Graphs.Grid.Rules.CustomGridGraphRuleEditorAttribute.type customgridgraphruleeditorattribute.html#type +Pathfinding.Graphs.Grid.Rules.GridGraphRule.Hash gridgraphrule.html#Hash Hash of the settings for this rule. \n\nThe Register method will be called again whenever the hash changes. If the hash does not change it is assumed that the Register method does not need to be called again. +Pathfinding.Graphs.Grid.Rules.GridGraphRule.Pass gridgraphrule.html#Pass Where in the scanning process a rule will be executed. \n\nCheck the documentation for GridGraphScanData to see which data fields are valid in which passes. +Pathfinding.Graphs.Grid.Rules.GridGraphRule.dirty gridgraphrule.html#dirty +Pathfinding.Graphs.Grid.Rules.GridGraphRule.enabled gridgraphrule.html#enabled Only enabled rules are executed. +Pathfinding.Graphs.Grid.Rules.GridGraphRules.Context.data context.html#data Data for all the nodes as NativeArrays. +Pathfinding.Graphs.Grid.Rules.GridGraphRules.Context.graph context.html#graph Graph which is being scanned or updated. +Pathfinding.Graphs.Grid.Rules.GridGraphRules.Context.tracker context.html#tracker Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually. \n\nAlways use when scheduling jobs. +Pathfinding.Graphs.Grid.Rules.GridGraphRules.jobSystemCallbacks gridgraphrules.html#jobSystemCallbacks +Pathfinding.Graphs.Grid.Rules.GridGraphRules.lastHash gridgraphrules.html#lastHash +Pathfinding.Graphs.Grid.Rules.GridGraphRules.mainThreadCallbacks gridgraphrules.html#mainThreadCallbacks +Pathfinding.Graphs.Grid.Rules.GridGraphRules.rules gridgraphrules.html#rules List of all rules. +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.JobPenaltyAngle.angleToPenalty jobpenaltyangle.html#angleToPenalty +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.JobPenaltyAngle.nodeNormals jobpenaltyangle.html#nodeNormals +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.JobPenaltyAngle.penalty jobpenaltyangle.html#penalty +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.JobPenaltyAngle.up jobpenaltyangle.html#up +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.angleToPenalty ruleanglepenalty.html#angleToPenalty +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.curve ruleanglepenalty.html#curve +Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty.penaltyScale ruleanglepenalty.html#penaltyScale +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.JobElevationPenalty.elevationToPenalty jobelevationpenalty.html#elevationToPenalty +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.JobElevationPenalty.nodePositions jobelevationpenalty.html#nodePositions +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.JobElevationPenalty.penalty jobelevationpenalty.html#penalty +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.JobElevationPenalty.worldToGraph jobelevationpenalty.html#worldToGraph +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.curve ruleelevationpenalty.html#curve +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.elevationRange ruleelevationpenalty.html#elevationRange +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.elevationToPenalty ruleelevationpenalty.html#elevationToPenalty +Pathfinding.Graphs.Grid.Rules.RuleElevationPenalty.penaltyScale ruleelevationpenalty.html#penaltyScale +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.PerLayerRule.action perlayerrule.html#action The action to apply to matching nodes. +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.PerLayerRule.layer perlayerrule.html#layer Layer this rule applies to. +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.PerLayerRule.tag perlayerrule.html#tag Tag for the RuleAction.SetTag action. \n\nMust be between 0 and Pathfinding.GraphNode.MaxTagIndex +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.RuleAction ruleperlayermodifications.html#RuleAction +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.SetTagBit ruleperlayermodifications.html#SetTagBit +Pathfinding.Graphs.Grid.Rules.RulePerLayerModifications.layerRules ruleperlayermodifications.html#layerRules +Pathfinding.Graphs.Grid.Rules.RuleTexture.ChannelUse ruletexture.html#ChannelUse +Pathfinding.Graphs.Grid.Rules.RuleTexture.Hash ruletexture.html#Hash Hash of the settings for this rule. \n\nThe Register method will be called again whenever the hash changes. If the hash does not change it is assumed that the Register method does not need to be called again. +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.bounds jobtexturepenalty.html#bounds +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.channelDeterminesWalkability jobtexturepenalty.html#channelDeterminesWalkability +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.channelPenalties jobtexturepenalty.html#channelPenalties +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.colorData jobtexturepenalty.html#colorData +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.colorDataSize jobtexturepenalty.html#colorDataSize +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.nodeNormals jobtexturepenalty.html#nodeNormals +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.penalty jobtexturepenalty.html#penalty +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.scale jobtexturepenalty.html#scale +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePenalty.walkable jobtexturepenalty.html#walkable +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.bounds jobtextureposition.html#bounds +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.channelPositionScale jobtextureposition.html#channelPositionScale +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.colorData jobtextureposition.html#colorData +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.colorDataSize jobtextureposition.html#colorDataSize +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.graphToWorld jobtextureposition.html#graphToWorld +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.nodeNormals jobtextureposition.html#nodeNormals +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.nodePositions jobtextureposition.html#nodePositions +Pathfinding.Graphs.Grid.Rules.RuleTexture.JobTexturePosition.scale jobtextureposition.html#scale +Pathfinding.Graphs.Grid.Rules.RuleTexture.ScalingMode ruletexture.html#ScalingMode +Pathfinding.Graphs.Grid.Rules.RuleTexture.channelScales ruletexture.html#channelScales +Pathfinding.Graphs.Grid.Rules.RuleTexture.channels ruletexture.html#channels +Pathfinding.Graphs.Grid.Rules.RuleTexture.colors ruletexture.html#colors +Pathfinding.Graphs.Grid.Rules.RuleTexture.nodesPerPixel ruletexture.html#nodesPerPixel +Pathfinding.Graphs.Grid.Rules.RuleTexture.scalingMode ruletexture.html#scalingMode +Pathfinding.Graphs.Grid.Rules.RuleTexture.texture ruletexture.html#texture +Pathfinding.Graphs.Navmesh.CircleGeometryUtilities.circleRadiusAdjustmentFactors circlegeometryutilities.html#circleRadiusAdjustmentFactors Cached values for CircleRadiusAdjustmentFactor. \n\nWe can calculate the area of a polygonized circle, and equate that with the area of a unit circle [code in online documentation]\n\nGenerated using the python code: [code in online documentation]\n\nIt would be nice to generate this using a static constructor, but that is not supported by Unity's burst compiler. +Pathfinding.Graphs.Navmesh.ColliderMeshBuilder2D.ShapeMesh.bounds shapemesh.html#bounds +Pathfinding.Graphs.Navmesh.ColliderMeshBuilder2D.ShapeMesh.endIndex shapemesh.html#endIndex +Pathfinding.Graphs.Navmesh.ColliderMeshBuilder2D.ShapeMesh.matrix shapemesh.html#matrix +Pathfinding.Graphs.Navmesh.ColliderMeshBuilder2D.ShapeMesh.startIndex shapemesh.html#startIndex +Pathfinding.Graphs.Navmesh.ColliderMeshBuilder2D.ShapeMesh.tag shapemesh.html#tag +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.BuildNodeTilesOutput.Progress buildnodetilesoutput.html#Progress +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.BuildNodeTilesOutput.progressSource buildnodetilesoutput.html#progressSource +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.BuildNodeTilesOutput.tiles buildnodetilesoutput.html#tiles +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.graphIndex jobbuildnodes.html#graphIndex +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.graphToWorldSpace jobbuildnodes.html#graphToWorldSpace +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.initialPenalty jobbuildnodes.html#initialPenalty +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.maxTileConnectionEdgeDistance jobbuildnodes.html#maxTileConnectionEdgeDistance +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.recalculateNormals jobbuildnodes.html#recalculateNormals +Pathfinding.Graphs.Navmesh.Jobs.JobBuildNodes.tileLayout jobbuildnodes.html#tileLayout +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.JobTransformTileCoordinates.matrix jobtransformtilecoordinates.html#matrix +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.JobTransformTileCoordinates.outputVertices jobtransformtilecoordinates.html#outputVertices +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.JobTransformTileCoordinates.vertices jobtransformtilecoordinates.html#vertices +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.indices jobbuildtilemeshfromvertices.html#indices +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.meshToGraph jobbuildtilemeshfromvertices.html#meshToGraph +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.outputBuffers jobbuildtilemeshfromvertices.html#outputBuffers +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.recalculateNormals jobbuildtilemeshfromvertices.html#recalculateNormals +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVertices.vertices jobbuildtilemeshfromvertices.html#vertices +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildCompactField jobbuildtilemeshfromvoxels.html#MarkerBuildCompactField +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildConnections jobbuildtilemeshfromvoxels.html#MarkerBuildConnections +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildContours jobbuildtilemeshfromvoxels.html#MarkerBuildContours +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildDistanceField jobbuildtilemeshfromvoxels.html#MarkerBuildDistanceField +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildMesh jobbuildtilemeshfromvoxels.html#MarkerBuildMesh +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerBuildRegions jobbuildtilemeshfromvoxels.html#MarkerBuildRegions +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerConvertAreasToTags jobbuildtilemeshfromvoxels.html#MarkerConvertAreasToTags +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerErodeWalkableArea jobbuildtilemeshfromvoxels.html#MarkerErodeWalkableArea +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerFilterLedges jobbuildtilemeshfromvoxels.html#MarkerFilterLedges +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerFilterLowHeightSpans jobbuildtilemeshfromvoxels.html#MarkerFilterLowHeightSpans +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerRemoveDuplicateVertices jobbuildtilemeshfromvoxels.html#MarkerRemoveDuplicateVertices +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerTransformTileCoordinates jobbuildtilemeshfromvoxels.html#MarkerTransformTileCoordinates +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.MarkerVoxelize jobbuildtilemeshfromvoxels.html#MarkerVoxelize +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.backgroundTraversability jobbuildtilemeshfromvoxels.html#backgroundTraversability +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.cellHeight jobbuildtilemeshfromvoxels.html#cellHeight +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.cellSize jobbuildtilemeshfromvoxels.html#cellSize +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.characterRadiusInVoxels jobbuildtilemeshfromvoxels.html#characterRadiusInVoxels +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.contourMaxError jobbuildtilemeshfromvoxels.html#contourMaxError +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.currentTileCounter jobbuildtilemeshfromvoxels.html#currentTileCounter +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.dimensionMode jobbuildtilemeshfromvoxels.html#dimensionMode +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.graphSpaceLimits jobbuildtilemeshfromvoxels.html#graphSpaceLimits Limits of the graph space bounds for the whole graph on the XZ plane. \n\nUsed to crop the border tiles to exactly the limits of the graph's bounding box. +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.graphToWorldSpace jobbuildtilemeshfromvoxels.html#graphToWorldSpace +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.inputMeshes jobbuildtilemeshfromvoxels.html#inputMeshes +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.maxEdgeLength jobbuildtilemeshfromvoxels.html#maxEdgeLength +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.maxSlope jobbuildtilemeshfromvoxels.html#maxSlope +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.maxTiles jobbuildtilemeshfromvoxels.html#maxTiles Max number of tiles to process in this job. +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.minRegionSize jobbuildtilemeshfromvoxels.html#minRegionSize +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.outputMeshes jobbuildtilemeshfromvoxels.html#outputMeshes +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.relevantGraphSurfaceMode jobbuildtilemeshfromvoxels.html#relevantGraphSurfaceMode +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.relevantGraphSurfaces jobbuildtilemeshfromvoxels.html#relevantGraphSurfaces +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.tileBorderSizeInVoxels jobbuildtilemeshfromvoxels.html#tileBorderSizeInVoxels +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.tileBuilder jobbuildtilemeshfromvoxels.html#tileBuilder +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.tileGraphSpaceBounds jobbuildtilemeshfromvoxels.html#tileGraphSpaceBounds +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.voxelToTileSpace jobbuildtilemeshfromvoxels.html#voxelToTileSpace +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.voxelWalkableClimb jobbuildtilemeshfromvoxels.html#voxelWalkableClimb +Pathfinding.Graphs.Navmesh.Jobs.JobBuildTileMeshFromVoxels.voxelWalkableHeight jobbuildtilemeshfromvoxels.html#voxelWalkableHeight +Pathfinding.Graphs.Navmesh.Jobs.JobCalculateTriangleConnections.TileNodeConnectionsUnsafe.neighbourCounts tilenodeconnectionsunsafe.html#neighbourCounts Number of neighbours for each triangle. +Pathfinding.Graphs.Navmesh.Jobs.JobCalculateTriangleConnections.TileNodeConnectionsUnsafe.neighbours tilenodeconnectionsunsafe.html#neighbours Stream of packed connection edge infos (from Connection.PackShapeEdgeInfo) +Pathfinding.Graphs.Navmesh.Jobs.JobCalculateTriangleConnections.nodeConnections jobcalculatetriangleconnections.html#nodeConnections +Pathfinding.Graphs.Navmesh.Jobs.JobCalculateTriangleConnections.tileMeshes jobcalculatetriangleconnections.html#tileMeshes +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.ConnectTilesMarker jobconnecttiles.html#ConnectTilesMarker +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.coordinateSum jobconnecttiles.html#coordinateSum +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.direction jobconnecttiles.html#direction +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.maxTileConnectionEdgeDistance jobconnecttiles.html#maxTileConnectionEdgeDistance Maximum vertical distance between two tiles to create a connection between them. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.tileRect jobconnecttiles.html#tileRect +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.tileWorldSize jobconnecttiles.html#tileWorldSize +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.tiles jobconnecttiles.html#tiles GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.zOffset jobconnecttiles.html#zOffset +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTiles.zStride jobconnecttiles.html#zStride +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTilesSingle.maxTileConnectionEdgeDistance jobconnecttilessingle.html#maxTileConnectionEdgeDistance Maximum vertical distance between two tiles to create a connection between them. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTilesSingle.tileIndex1 jobconnecttilessingle.html#tileIndex1 Index of the first tile in the tiles array. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTilesSingle.tileIndex2 jobconnecttilessingle.html#tileIndex2 Index of the second tile in the tiles array. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTilesSingle.tileWorldSize jobconnecttilessingle.html#tileWorldSize Size of a tile in world units. +Pathfinding.Graphs.Navmesh.Jobs.JobConnectTilesSingle.tiles jobconnecttilessingle.html#tiles GCHandle referring to a NavmeshTile[] array of size tileRect.Width*tileRect.Height. +Pathfinding.Graphs.Navmesh.Jobs.JobConvertAreasToTags.areas jobconvertareastotags.html#areas +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.graphIndex jobcreatetiles.html#graphIndex Graph index of the graph that these nodes will be added to. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.graphTileCount jobcreatetiles.html#graphTileCount Number of tiles in the graph. \n\nThis may be much bigger than the tileRect that we are actually processing. For example if a graph update is performed, the tileRect will just cover the tiles that are recalculated, while graphTileCount will contain all tiles in the graph. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.graphToWorldSpace jobcreatetiles.html#graphToWorldSpace Matrix to convert from graph space to world space. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.initialPenalty jobcreatetiles.html#initialPenalty Initial penalty for all nodes in the tile. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.preCutTileMeshes jobcreatetiles.html#preCutTileMeshes An array of TileMesh.TileMeshUnsafe of length tileRect.Width*tileRect.Height, or an uninitialized array. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.recalculateNormals jobcreatetiles.html#recalculateNormals If true, all triangles will be guaranteed to be laid out in clockwise order. \n\nIf false, their original order will be preserved. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.tileMeshes jobcreatetiles.html#tileMeshes An array of TileMesh.TileMeshUnsafe of length tileRect.Width*tileRect.Height. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.tileRect jobcreatetiles.html#tileRect Rectangle of tiles that we are processing. \n\n(xmax, ymax) must be smaller than graphTileCount. If for examples graphTileCount is (10, 10) and tileRect is {2, 3, 5, 6} then we are processing tiles (2, 3) to (5, 6) inclusive. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.tileWorldSize jobcreatetiles.html#tileWorldSize Size of a tile in world units along the graph's X and Z axes. +Pathfinding.Graphs.Navmesh.Jobs.JobCreateTiles.tiles jobcreatetiles.html#tiles An array of NavmeshTile of length tileRect.Width*tileRect.Height. \n\nThis array will be filled with the created tiles. +Pathfinding.Graphs.Navmesh.Jobs.JobTransformTileCoordinates.matrix jobtransformtilecoordinates2.html#matrix +Pathfinding.Graphs.Navmesh.Jobs.JobTransformTileCoordinates.vertices jobtransformtilecoordinates2.html#vertices +Pathfinding.Graphs.Navmesh.Jobs.JobWriteNodeConnections.nodeConnections jobwritenodeconnections.html#nodeConnections Connections for each tile. +Pathfinding.Graphs.Navmesh.Jobs.JobWriteNodeConnections.tiles jobwritenodeconnections.html#tiles Array of NavmeshTile. +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.compactVoxelField tilebuilderburst.html#compactVoxelField +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.contourVertices tilebuilderburst.html#contourVertices +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.contours tilebuilderburst.html#contours +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.distanceField tilebuilderburst.html#distanceField +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.linkedVoxelField tilebuilderburst.html#linkedVoxelField +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.tmpQueue1 tilebuilderburst.html#tmpQueue1 +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.tmpQueue2 tilebuilderburst.html#tmpQueue2 +Pathfinding.Graphs.Navmesh.Jobs.TileBuilderBurst.voxelMesh tilebuilderburst.html#voxelMesh +Pathfinding.Graphs.Navmesh.NavmeshTile.bbTree navmeshtile.html#bbTree Bounding Box Tree for node lookups. +Pathfinding.Graphs.Navmesh.NavmeshTile.d navmeshtile.html#d Depth, in tile coordinates. \n\n[more in online documentation] +Pathfinding.Graphs.Navmesh.NavmeshTile.flag navmeshtile.html#flag Temporary flag used for batching. +Pathfinding.Graphs.Navmesh.NavmeshTile.graph navmeshtile.html#graph The graph which contains this tile. +Pathfinding.Graphs.Navmesh.NavmeshTile.isCut navmeshtile.html#isCut True if this tile may have been cut by NavmeshCuts, or had pieces added by NavmeshAdd components. \n\nIf true, the preCutVertsInTileSpace, preCutTris and preCutTags fields will be valid. +Pathfinding.Graphs.Navmesh.NavmeshTile.nodes navmeshtile.html#nodes All nodes in the tile. +Pathfinding.Graphs.Navmesh.NavmeshTile.preCutTags navmeshtile.html#preCutTags +Pathfinding.Graphs.Navmesh.NavmeshTile.preCutTris navmeshtile.html#preCutTris +Pathfinding.Graphs.Navmesh.NavmeshTile.preCutVertsInTileSpace navmeshtile.html#preCutVertsInTileSpace +Pathfinding.Graphs.Navmesh.NavmeshTile.transform navmeshtile.html#transform Transforms coordinates from graph space to world space. +Pathfinding.Graphs.Navmesh.NavmeshTile.tris navmeshtile.html#tris All triangle indices in the tile. \n\nOne triangle is 3 indices. The triangles are in the same order as the nodes.\n\nThis represents an allocation using the Persistent allocator. +Pathfinding.Graphs.Navmesh.NavmeshTile.verts navmeshtile.html#verts All vertices in the tile. \n\nThe vertices are in world space.\n\nThis represents an allocation using the Persistent allocator. +Pathfinding.Graphs.Navmesh.NavmeshTile.vertsInGraphSpace navmeshtile.html#vertsInGraphSpace All vertices in the tile. \n\nThe vertices are in graph space.\n\nThis represents an allocation using the Persistent allocator. +Pathfinding.Graphs.Navmesh.NavmeshTile.w navmeshtile.html#w Width, in tile coordinates. \n\n[more in online documentation] +Pathfinding.Graphs.Navmesh.NavmeshTile.x navmeshtile.html#x Tile X Coordinate. +Pathfinding.Graphs.Navmesh.NavmeshTile.z navmeshtile.html#z Tile Z Coordinate. +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.anyTilesDirty navmeshupdatesettings.html#anyTilesDirty +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.attachedToGraph navmeshupdatesettings.html#attachedToGraph +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.clipperLookup navmeshupdatesettings.html#clipperLookup +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.dirtyTileCoordinates navmeshupdatesettings.html#dirtyTileCoordinates +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.dirtyTiles navmeshupdatesettings.html#dirtyTiles +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.enabled navmeshupdatesettings.html#enabled +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.graph navmeshupdatesettings.html#graph +Pathfinding.Graphs.Navmesh.NavmeshUpdates.NavmeshUpdateSettings.tileLayout navmeshupdatesettings.html#tileLayout +Pathfinding.Graphs.Navmesh.NavmeshUpdates.astar navmeshupdates.html#astar +Pathfinding.Graphs.Navmesh.NavmeshUpdates.lastUpdateTime navmeshupdates.html#lastUpdateTime Last time navmesh cuts were applied. +Pathfinding.Graphs.Navmesh.NavmeshUpdates.listeners navmeshupdates.html#listeners +Pathfinding.Graphs.Navmesh.NavmeshUpdates.updateInterval navmeshupdates.html#updateInterval How often to check if an update needs to be done (real seconds between checks). \n\nFor worlds with a very large number of NavmeshCut objects, it might be bad for performance to do this check every frame. If you think this is a performance penalty, increase this number to check less often.\n\nIf negative, no updates will be done. They must be manually triggered using ForceUpdate.\n\nFor almost all games, this can be kept at 0. If you are smoothly moving navmesh cuts around, however, you may want to increase this a bit, to avoid triggering a navmesh update every frame.\n\nAn update not only has the cost associated with the update directly, but also the cost of recalculating the path for any agents that are using the affected part of the navmesh.\n\n[code in online documentation][image in online documentation] +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.BoxColliderTris recastmeshgatherer.html#BoxColliderTris Box Collider triangle indices can be reused for multiple instances. \n\n[more in online documentation] +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.BoxColliderVerts recastmeshgatherer.html#BoxColliderVerts Box Collider vertices can be reused for multiple instances. \n\n[more in online documentation] +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.area gatheredmesh.html#area Area ID of the mesh. \n\n0 means walkable, and -1 indicates that the mesh should be treated as unwalkable. Other positive values indicate a custom area ID which will create a seam in the navmesh. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.areaIsTag gatheredmesh.html#areaIsTag See RasterizationMesh.area. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.bounds gatheredmesh.html#bounds World bounds of the mesh. \n\nAssumed to already be multiplied with the matrix. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.doubleSided gatheredmesh.html#doubleSided See RasterizationMesh.doubleSided. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.flatten gatheredmesh.html#flatten See RasterizationMesh.flatten. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.indexEnd gatheredmesh.html#indexEnd End index in the triangle array. \n\n-1 indicates the end of the array. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.indexStart gatheredmesh.html#indexStart Start index in the triangle array. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.matrix gatheredmesh.html#matrix Matrix to transform the vertices by. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.meshDataIndex gatheredmesh.html#meshDataIndex Index in the meshData array. \n\nCan be retrieved from the RecastMeshGatherer.AddMeshBuffers method. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.solid gatheredmesh.html#solid If true then the mesh will be treated as solid and its interior will be unwalkable. \n\nThe unwalkable region will be the minimum to maximum y coordinate in each cell. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.GatheredMesh.tagDataIndex gatheredmesh.html#tagDataIndex If not -1, this is the index in the tags array, containing one tag per triangle. \n\nOtherwise, the tag is set by area.\n\nareaIsTag will be ignored if set. +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.TerrainChunk.tags terrainchunk.html#tags +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.TerrainChunk.tris terrainchunk.html#tris +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.TerrainChunk.verts terrainchunk.html#verts +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.alphaMaps jobgenerateheightmapchunk.html#alphaMaps +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.alphamapScale jobgenerateheightmapchunk.html#alphamapScale +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.areaMapping jobgenerateheightmapchunk.html#areaMapping +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.areaMappingThresholds jobgenerateheightmapchunk.html#areaMappingThresholds +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.chunkSize jobgenerateheightmapchunk.html#chunkSize +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.chunks jobgenerateheightmapchunk.html#chunks +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.heights jobgenerateheightmapchunk.html#heights +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.holes jobgenerateheightmapchunk.html#holes +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.output jobgenerateheightmapchunk.html#output +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.sampleRect jobgenerateheightmapchunk.html#sampleRect +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.JobGenerateHeightmapChunk.stride jobgenerateheightmapchunk.html#stride +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.Markers.MarkerCalculateBounds markers.html#MarkerCalculateBounds +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.Markers.MarkerCreateRasterizationMeshes markers.html#MarkerCreateRasterizationMeshes +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.Markers.MarkerGetMissingMeshDataAndBounds markers.html#MarkerGetMissingMeshDataAndBounds +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.Markers.MarkerPatchMissingMeshDataAndBounds markers.html#MarkerPatchMissingMeshDataAndBounds +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCacheItem.Box meshcacheitem.html#Box +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCacheItem.mesh meshcacheitem.html#mesh +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCacheItem.quantizedHeight meshcacheitem.html#quantizedHeight +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCacheItem.rows meshcacheitem.html#rows +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCacheItem.type meshcacheitem.html#type +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCollection.meshes meshcollection.html#meshes +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCollection.meshesUnreadableAtRuntime meshcollection.html#meshesUnreadableAtRuntime +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCollection.tagsBuffers meshcollection.html#tagsBuffers +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCollection.triangleBuffers meshcollection.html#triangleBuffers +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshCollection.vertexBuffers meshcollection.html#vertexBuffers +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.MeshType recastmeshgatherer.html#MeshType +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.TreeInfo.localScale treeinfo.html#localScale +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.TreeInfo.submeshIndices treeinfo.html#submeshIndices +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.TreeInfo.supportsRotation treeinfo.html#supportsRotation +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.bounds recastmeshgatherer.html#bounds +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.cachedMeshes recastmeshgatherer.html#cachedMeshes +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.dummyMaterials recastmeshgatherer.html#dummyMaterials +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.mask recastmeshgatherer.html#mask +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.maxColliderApproximationError recastmeshgatherer.html#maxColliderApproximationError +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.meshData recastmeshgatherer.html#meshData +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.meshes recastmeshgatherer.html#meshes +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.meshesUnreadableAtRuntime recastmeshgatherer.html#meshesUnreadableAtRuntime +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.modificationsByLayer recastmeshgatherer.html#modificationsByLayer +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.modificationsByLayer2D recastmeshgatherer.html#modificationsByLayer2D +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.perTerrainLayerModifications recastmeshgatherer.html#perTerrainLayerModifications +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.physicsScene recastmeshgatherer.html#physicsScene +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.physicsScene2D recastmeshgatherer.html#physicsScene2D +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.tagMask recastmeshgatherer.html#tagMask +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.tagsBuffers recastmeshgatherer.html#tagsBuffers +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.terrainDownsamplingFactor recastmeshgatherer.html#terrainDownsamplingFactor +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.triangleBuffers recastmeshgatherer.html#triangleBuffers +Pathfinding.Graphs.Navmesh.RecastMeshGatherer.vertexBuffers recastmeshgatherer.html#vertexBuffers +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.Next relevantgraphsurface.html#Next +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.Position relevantgraphsurface.html#Position +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.Prev relevantgraphsurface.html#Prev +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.Root relevantgraphsurface.html#Root +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.maxRange relevantgraphsurface.html#maxRange +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.next relevantgraphsurface.html#next +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.position relevantgraphsurface.html#position +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.prev relevantgraphsurface.html#prev +Pathfinding.Graphs.Navmesh.RelevantGraphSurface.root relevantgraphsurface.html#root +Pathfinding.Graphs.Navmesh.TileBuilder.BucketMapping.bucketRanges bucketmapping.html#bucketRanges For each tile, the range of pointers in pointers that correspond to that tile. \n\nThis is a cumulative sum of the number of pointers in each bucket.\n\nBucket i will contain pointers in the range [i > 0 ? bucketRanges[i-1] : 0, bucketRanges[i]).\n\nThe length is the same as the number of tiles. +Pathfinding.Graphs.Navmesh.TileBuilder.BucketMapping.meshes bucketmapping.html#meshes All meshes that should be voxelized. +Pathfinding.Graphs.Navmesh.TileBuilder.BucketMapping.pointers bucketmapping.html#pointers Indices into the meshes array. +Pathfinding.Graphs.Navmesh.TileBuilder.TileBorderSizeInVoxels tilebuilder.html#TileBorderSizeInVoxels Number of extra voxels on each side of a tile to ensure accurate navmeshes near the tile border. \n\nThe width of a tile is expanded by 2 times this value (1x to the left and 1x to the right) +Pathfinding.Graphs.Navmesh.TileBuilder.TileBorderSizeInWorldUnits tilebuilder.html#TileBorderSizeInWorldUnits +Pathfinding.Graphs.Navmesh.TileBuilder.TileBuilderOutput.Progress tilebuilderoutput.html#Progress +Pathfinding.Graphs.Navmesh.TileBuilder.TileBuilderOutput.currentTileCounter tilebuilderoutput.html#currentTileCounter +Pathfinding.Graphs.Navmesh.TileBuilder.TileBuilderOutput.meshesUnreadableAtRuntime tilebuilderoutput.html#meshesUnreadableAtRuntime +Pathfinding.Graphs.Navmesh.TileBuilder.TileBuilderOutput.tileMeshes tilebuilderoutput.html#tileMeshes +Pathfinding.Graphs.Navmesh.TileBuilder.backgroundTraversability tilebuilder.html#backgroundTraversability +Pathfinding.Graphs.Navmesh.TileBuilder.characterRadiusInVoxels tilebuilder.html#characterRadiusInVoxels +Pathfinding.Graphs.Navmesh.TileBuilder.collectionSettings tilebuilder.html#collectionSettings +Pathfinding.Graphs.Navmesh.TileBuilder.contourMaxError tilebuilder.html#contourMaxError +Pathfinding.Graphs.Navmesh.TileBuilder.dimensionMode tilebuilder.html#dimensionMode +Pathfinding.Graphs.Navmesh.TileBuilder.maxEdgeLength tilebuilder.html#maxEdgeLength +Pathfinding.Graphs.Navmesh.TileBuilder.maxSlope tilebuilder.html#maxSlope +Pathfinding.Graphs.Navmesh.TileBuilder.minRegionSize tilebuilder.html#minRegionSize +Pathfinding.Graphs.Navmesh.TileBuilder.perLayerModifications tilebuilder.html#perLayerModifications +Pathfinding.Graphs.Navmesh.TileBuilder.perTerrainLayerModifications tilebuilder.html#perTerrainLayerModifications +Pathfinding.Graphs.Navmesh.TileBuilder.relevantGraphSurfaceMode tilebuilder.html#relevantGraphSurfaceMode +Pathfinding.Graphs.Navmesh.TileBuilder.tileBorderSizeInVoxels tilebuilder.html#tileBorderSizeInVoxels +Pathfinding.Graphs.Navmesh.TileBuilder.tileLayout tilebuilder.html#tileLayout +Pathfinding.Graphs.Navmesh.TileBuilder.tileRect tilebuilder.html#tileRect +Pathfinding.Graphs.Navmesh.TileBuilder.walkableClimb tilebuilder.html#walkableClimb +Pathfinding.Graphs.Navmesh.TileBuilder.walkableHeight tilebuilder.html#walkableHeight +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.cutCollection jobcuttiles.html#cutCollection +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.inputTileMeshes jobcuttiles.html#inputTileMeshes +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.outputTileMeshes jobcuttiles.html#outputTileMeshes +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.tileTags jobcuttiles.html#tileTags +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.tileTriangles jobcuttiles.html#tileTriangles +Pathfinding.Graphs.Navmesh.TileCutter.JobCutTiles.tileVertices jobcuttiles.html#tileVertices +Pathfinding.Graphs.Navmesh.TileCutter.TileCutterOutput.Progress tilecutteroutput.html#Progress +Pathfinding.Graphs.Navmesh.TileCutter.TileCutterOutput.tileMeshes tilecutteroutput.html#tileMeshes +Pathfinding.Graphs.Navmesh.TileCutter.cuts tilecutter.html#cuts +Pathfinding.Graphs.Navmesh.TileCutter.graph tilecutter.html#graph +Pathfinding.Graphs.Navmesh.TileCutter.tileLayout tilecutter.html#tileLayout +Pathfinding.Graphs.Navmesh.TileHandler.ContourMeta.cutsAddedGeom contourmeta.html#cutsAddedGeom +Pathfinding.Graphs.Navmesh.TileHandler.ContourMeta.isDual contourmeta.html#isDual +Pathfinding.Graphs.Navmesh.TileHandler.CopyTriangulationToOutputMarker tilehandler.html#CopyTriangulationToOutputMarker +Pathfinding.Graphs.Navmesh.TileHandler.CutCollection.contourVertices cutcollection.html#contourVertices Vertices of all cut contours in all tiles Stored in tile space for the tile they belong to. +Pathfinding.Graphs.Navmesh.TileHandler.CutCollection.contours cutcollection.html#contours +Pathfinding.Graphs.Navmesh.TileHandler.CutCollection.contoursExtra cutcollection.html#contoursExtra +Pathfinding.Graphs.Navmesh.TileHandler.CutCollection.cuttingRequired cutcollection.html#cuttingRequired +Pathfinding.Graphs.Navmesh.TileHandler.CutCollection.tileCuts cutcollection.html#tileCuts +Pathfinding.Graphs.Navmesh.TileHandler.CutFunctionPtr tilehandler.html#CutFunctionPtr +Pathfinding.Graphs.Navmesh.TileHandler.DelegateGCRoot tilehandler.html#DelegateGCRoot +Pathfinding.Graphs.Navmesh.TileHandler.EdgeSnappingMaxDistance tilehandler.html#EdgeSnappingMaxDistance See SnapEdges. +Pathfinding.Graphs.Navmesh.TileHandler.MarkerAllocate tilehandler.html#MarkerAllocate +Pathfinding.Graphs.Navmesh.TileHandler.MarkerClipHorizontal tilehandler.html#MarkerClipHorizontal +Pathfinding.Graphs.Navmesh.TileHandler.MarkerClipping tilehandler.html#MarkerClipping +Pathfinding.Graphs.Navmesh.TileHandler.MarkerCompress tilehandler.html#MarkerCompress +Pathfinding.Graphs.Navmesh.TileHandler.MarkerCopyClippingResult tilehandler.html#MarkerCopyClippingResult +Pathfinding.Graphs.Navmesh.TileHandler.MarkerCore tilehandler.html#MarkerCore +Pathfinding.Graphs.Navmesh.TileHandler.MarkerEdgeSnapping tilehandler.html#MarkerEdgeSnapping +Pathfinding.Graphs.Navmesh.TileHandler.MarkerPrepare tilehandler.html#MarkerPrepare +Pathfinding.Graphs.Navmesh.TileHandler.MarkerRefine tilehandler.html#MarkerRefine +Pathfinding.Graphs.Navmesh.TileHandler.MarkerRemoveDegenerateLines tilehandler.html#MarkerRemoveDegenerateLines +Pathfinding.Graphs.Navmesh.TileHandler.MarkerRemoveDegenerateTriangles tilehandler.html#MarkerRemoveDegenerateTriangles +Pathfinding.Graphs.Navmesh.TileHandler.MarkerTriangulate tilehandler.html#MarkerTriangulate +Pathfinding.Graphs.Navmesh.TileHandler.Point64Wrapper.x point64wrapper.html#x +Pathfinding.Graphs.Navmesh.TileHandler.Point64Wrapper.y point64wrapper.html#y +Pathfinding.Graphs.Navmesh.TileHandler.Scale tilehandler.html#Scale Scale all coordinates by this value to make intersection calculations more accurate, while clipping. \n\nWhen an intersection is calculated during clipping, it has to be rounded to the nearest integer coordinate. If this intersection is then used to calculate another intersection, the error will accumulate. By scaling up the coordinates, and dividing all coordinates after all clipping is finished, the error is reduced.\n\nThis should be a power of two, for best performance. It should not be too large, as then large coordinates can cause overflows in various calculations.\n\nTo disable, set to 1. But this is not recommended. +Pathfinding.Graphs.Navmesh.TileHandler.TileCuts.contourEndIndex tilecuts.html#contourEndIndex +Pathfinding.Graphs.Navmesh.TileHandler.TileCuts.contourStartIndex tilecuts.html#contourStartIndex +Pathfinding.Graphs.Navmesh.TileHandler.TileSnappingMaxDistance tilehandler.html#TileSnappingMaxDistance See ConvertVerticesAndSnapToTileBoundaries. \n\nThe navmesh cut vertices are snapped to tile borders, if they are within this distance to the edge. This is used to avoid tiiiny slivers of triangles resulting from cuts that are just infringing on a tile. The normal snapping (using EdgeSnappingMaxDistance) cannot be used for tile borders, because that would make the tile borders not be straight anymore.\n\nI don't think there's any technical upper limit to this value. It's a tradeoff between the size of the slivers, and how accurately it matches the original geometry. +Pathfinding.Graphs.Navmesh.TileHandlerCache.cachedClippers tilehandlercache.html#cachedClippers +Pathfinding.Graphs.Navmesh.TileLayout.CellHeight tilelayout.html#CellHeight Voxel y coordinates will be stored as ushorts which have 65536 values. \n\nLeave a margin to make sure things do not overflow +Pathfinding.Graphs.Navmesh.TileLayout.TileWorldSize tilelayout.html#TileWorldSize +Pathfinding.Graphs.Navmesh.TileLayout.TileWorldSizeX tilelayout.html#TileWorldSizeX Size of a tile in world units, along the graph's X axis. +Pathfinding.Graphs.Navmesh.TileLayout.TileWorldSizeZ tilelayout.html#TileWorldSizeZ Size of a tile in world units, along the graph's Z axis. +Pathfinding.Graphs.Navmesh.TileLayout.cellSize tilelayout.html#cellSize Voxel sample size (x,z). \n\nWhen generating a recast graph what happens is that the world is voxelized. You can think of this as constructing an approximation of the world out of lots of boxes. If you have played Minecraft it looks very similar (but with smaller boxes). [image in online documentation]\n\nLower values will yield higher quality navmeshes, however the graph will be slower to scan.\n\n [image in online documentation] +Pathfinding.Graphs.Navmesh.TileLayout.graphSpaceSize tilelayout.html#graphSpaceSize Size in graph space of the whole grid. \n\nIf the original bounding box was not an exact multiple of the tile size, this will be less than the total width of all tiles. +Pathfinding.Graphs.Navmesh.TileLayout.tileCount tilelayout.html#tileCount How many tiles there are in the grid. +Pathfinding.Graphs.Navmesh.TileLayout.tileSizeInVoxels tilelayout.html#tileSizeInVoxels Size of a tile in voxels along the X and Z axes. +Pathfinding.Graphs.Navmesh.TileLayout.transform tilelayout.html#transform Transforms coordinates from graph space to world space. +Pathfinding.Graphs.Navmesh.TileMesh.TileMeshUnsafe.tags tilemeshunsafe.html#tags One tag per triangle. +Pathfinding.Graphs.Navmesh.TileMesh.TileMeshUnsafe.triangles tilemeshunsafe.html#triangles Three indices per triangle. +Pathfinding.Graphs.Navmesh.TileMesh.TileMeshUnsafe.verticesInTileSpace tilemeshunsafe.html#verticesInTileSpace One vertex per triangle. +Pathfinding.Graphs.Navmesh.TileMesh.tags tilemesh.html#tags One tag per triangle. +Pathfinding.Graphs.Navmesh.TileMesh.triangles tilemesh.html#triangles +Pathfinding.Graphs.Navmesh.TileMesh.verticesInTileSpace tilemesh.html#verticesInTileSpace +Pathfinding.Graphs.Navmesh.TileMeshes.tileMeshes tilemeshes.html#tileMeshes Tiles laid out row by row. +Pathfinding.Graphs.Navmesh.TileMeshes.tileRect tilemeshes.html#tileRect Which tiles in the graph this group of tiles represents. +Pathfinding.Graphs.Navmesh.TileMeshes.tileWorldSize tilemeshes.html#tileWorldSize World-space size of each tile. +Pathfinding.Graphs.Navmesh.TileMeshesUnsafe.tileMeshes tilemeshesunsafe.html#tileMeshes +Pathfinding.Graphs.Navmesh.TileMeshesUnsafe.tileRect tilemeshesunsafe.html#tileRect +Pathfinding.Graphs.Navmesh.TileMeshesUnsafe.tileWorldSize tilemeshesunsafe.html#tileWorldSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CellMinMax.max cellminmax.html#max +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CellMinMax.min cellminmax.html#min +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CellMinMax.objectID cellminmax.html#objectID +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelCell.count compactvoxelcell.html#count +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelCell.index compactvoxelcell.html#index +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.MaxLayers compactvoxelfield.html#MaxLayers Unmotivated variable, but let's clamp the layers at 65535. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.NotConnected compactvoxelfield.html#NotConnected +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.UnwalkableArea compactvoxelfield.html#UnwalkableArea +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.areaTypes compactvoxelfield.html#areaTypes +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.cells compactvoxelfield.html#cells +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.depth compactvoxelfield.html#depth +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.spans compactvoxelfield.html#spans +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.voxelWalkableHeight compactvoxelfield.html#voxelWalkableHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelField.width compactvoxelfield.html#width +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelSpan.con compactvoxelspan.html#con +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelSpan.h compactvoxelspan.html#h +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelSpan.reg compactvoxelspan.html#reg +Pathfinding.Graphs.Navmesh.Voxelization.Burst.CompactVoxelSpan.y compactvoxelspan.html#y +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildCompactField.input jobbuildcompactfield.html#input +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildCompactField.output jobbuildcompactfield.html#output +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildConnections.field jobbuildconnections.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildConnections.voxelWalkableClimb jobbuildconnections.html#voxelWalkableClimb +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildConnections.voxelWalkableHeight jobbuildconnections.html#voxelWalkableHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.buildFlags jobbuildcontours.html#buildFlags +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.cellSize jobbuildcontours.html#cellSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.field jobbuildcontours.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.maxEdgeLength jobbuildcontours.html#maxEdgeLength +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.maxError jobbuildcontours.html#maxError +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.outputContours jobbuildcontours.html#outputContours +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildContours.outputVerts jobbuildcontours.html#outputVerts +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildDistanceField.field jobbuilddistancefield.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildDistanceField.output jobbuilddistancefield.html#output +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildMesh.contourVertices jobbuildmesh.html#contourVertices +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildMesh.contours jobbuildmesh.html#contours contour set to build a mesh from. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildMesh.field jobbuildmesh.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildMesh.mesh jobbuildmesh.html#mesh Results will be written to this mesh. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.RelevantGraphSurfaceInfo.position relevantgraphsurfaceinfo.html#position +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.RelevantGraphSurfaceInfo.range relevantgraphsurfaceinfo.html#range +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.borderSize jobbuildregions.html#borderSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.cellHeight jobbuildregions.html#cellHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.cellSize jobbuildregions.html#cellSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.distanceField jobbuildregions.html#distanceField +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.dstQue jobbuildregions.html#dstQue +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.field jobbuildregions.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.graphSpaceBounds jobbuildregions.html#graphSpaceBounds +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.graphTransform jobbuildregions.html#graphTransform +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.minRegionSize jobbuildregions.html#minRegionSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.relevantGraphSurfaceMode jobbuildregions.html#relevantGraphSurfaceMode +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.relevantGraphSurfaces jobbuildregions.html#relevantGraphSurfaces +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobBuildRegions.srcQue jobbuildregions.html#srcQue +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobErodeWalkableArea.field joberodewalkablearea.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobErodeWalkableArea.radius joberodewalkablearea.html#radius +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLedges.cellHeight jobfilterledges.html#cellHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLedges.cellSize jobfilterledges.html#cellSize +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLedges.field jobfilterledges.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLedges.voxelWalkableClimb jobfilterledges.html#voxelWalkableClimb +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLedges.voxelWalkableHeight jobfilterledges.html#voxelWalkableHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLowHeightSpans.field jobfilterlowheightspans.html#field +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobFilterLowHeightSpans.voxelWalkableHeight jobfilterlowheightspans.html#voxelWalkableHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.bucket jobvoxelize.html#bucket +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.cellHeight jobvoxelize.html#cellHeight The y-axis cell size to use for fields. \n\n[Limit: > 0] [Units: wu] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.cellSize jobvoxelize.html#cellSize The xz-plane cell size to use for fields. \n\n[Limit: > 0] [Units: wu] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.graphSpaceBounds jobvoxelize.html#graphSpaceBounds +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.graphSpaceLimits jobvoxelize.html#graphSpaceLimits +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.graphTransform jobvoxelize.html#graphTransform +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.inputMeshes jobvoxelize.html#inputMeshes +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.maxSlope jobvoxelize.html#maxSlope The maximum slope that is considered walkable. \n\n[Limits: 0 <= value < 90] [Units: Degrees] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.voxelArea jobvoxelize.html#voxelArea +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.voxelWalkableClimb jobvoxelize.html#voxelWalkableClimb Maximum ledge height that is considered to still be traversable. \n\n[Limit: >=0] [Units: vx] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.JobVoxelize.voxelWalkableHeight jobvoxelize.html#voxelWalkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to be considered walkable. \n\n[Limit: >= 3] [Units: vx] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.AvgSpanLayerCountEstimate linkedvoxelfield.html#AvgSpanLayerCountEstimate Initial estimate on the average number of spans (layers) in the voxel representation. \n\nShould be greater or equal to 1 +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.InvalidSpanValue linkedvoxelfield.html#InvalidSpanValue Constant for default LinkedVoxelSpan top and bottom values. \n\nIt is important with the U since ~0 != ~0U This can be used to check if a LinkedVoxelSpan is valid and not just the default span +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.MaxHeight linkedvoxelfield.html#MaxHeight +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.MaxHeightInt linkedvoxelfield.html#MaxHeightInt +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.depth linkedvoxelfield.html#depth The depth of the field along the z-axis. \n\n[Limit: >= 0] [Units: voxels] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.flatten linkedvoxelfield.html#flatten +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.height linkedvoxelfield.html#height The maximum height coordinate. \n\n[Limit: >= 0, <= MaxHeight] [Units: voxels] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.linkedCellMinMax linkedvoxelfield.html#linkedCellMinMax +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.linkedSpans linkedvoxelfield.html#linkedSpans +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.removedStack linkedvoxelfield.html#removedStack +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelField.width linkedvoxelfield.html#width The width of the field along the x-axis. \n\n[Limit: >= 0] [Units: voxels] +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelSpan.area linkedvoxelspan.html#area +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelSpan.bottom linkedvoxelspan.html#bottom +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelSpan.next linkedvoxelspan.html#next +Pathfinding.Graphs.Navmesh.Voxelization.Burst.LinkedVoxelSpan.top linkedvoxelspan.html#top +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.area rasterizationmesh.html#area Area to set for the mesh. \n\nThis will split the navmesh on area boundaries. If you OR this value with VoxelUtilityBurst.TagReg, then the area will be propagated to the final node tags. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.areas rasterizationmesh.html#areas If set, overrides area on a per-triangle basis. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.bounds rasterizationmesh.html#bounds World bounds of the mesh. \n\nAssumed to already be multiplied with the matrix +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.doubleSided rasterizationmesh.html#doubleSided If true, both sides of the mesh will be walkable. \n\nIf false, only the side that the normal points towards will be walkable +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.flatten rasterizationmesh.html#flatten If true, the mesh will be flattened to the base of the graph during rasterization. \n\nThis is intended for rasterizing 2D meshes which always lie in a single plane.\n\nThis will also cause unwalkable spans have precedence over walkable ones at all times, instead of only when the unwalkable span is sufficiently high up over a walkable span. Since when flattening, "sufficiently high up" makes no sense. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.matrix rasterizationmesh.html#matrix +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.solid rasterizationmesh.html#solid If true then the mesh will be treated as solid and its interior will be unwalkable. \n\nThe unwalkable region will be the minimum to maximum y coordinate in each cell. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.triangles rasterizationmesh.html#triangles +Pathfinding.Graphs.Navmesh.Voxelization.Burst.RasterizationMesh.vertices rasterizationmesh.html#vertices +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelContour.area voxelcontour.html#area Area ID of the contour. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelContour.nverts voxelcontour.html#nverts +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelContour.reg voxelcontour.html#reg Region ID of the contour. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelContour.vertexStartIndex voxelcontour.html#vertexStartIndex Vertex coordinates, each vertex contains 4 components. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelMesh.areas voxelmesh.html#areas Area index for each triangle. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelMesh.tris voxelmesh.html#tris Triangles of the mesh. \n\nEach element points to a vertex in the verts array +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelMesh.verts voxelmesh.html#verts Vertices of the mesh. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.BorderReg voxelutilityburst.html#BorderReg If heightfield region ID has the following bit set, the region is on border area and excluded from many calculations. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.ContourRegMask voxelutilityburst.html#ContourRegMask Mask used with contours to extract region id. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.DX voxelutilityburst.html#DX +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.DZ voxelutilityburst.html#DZ +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.RC_AREA_BORDER voxelutilityburst.html#RC_AREA_BORDER +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.RC_BORDER_VERTEX voxelutilityburst.html#RC_BORDER_VERTEX If contour region ID has the following bit set, the vertex will be later removed in order to match the segments and vertices at tile boundaries. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.RC_CONTOUR_TESS_AREA_EDGES voxelutilityburst.html#RC_CONTOUR_TESS_AREA_EDGES Tessellate edges between areas. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.RC_CONTOUR_TESS_TILE_EDGES voxelutilityburst.html#RC_CONTOUR_TESS_TILE_EDGES Tessellate edges at the border of the tile. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.RC_CONTOUR_TESS_WALL_EDGES voxelutilityburst.html#RC_CONTOUR_TESS_WALL_EDGES Tessellate wall edges. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.TagReg voxelutilityburst.html#TagReg If a cell region has this bit set then The remaining region bits (see TagRegMask) will be used for the node's tag. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.TagRegMask voxelutilityburst.html#TagRegMask All bits in the region which will be interpreted as a tag. +Pathfinding.Graphs.Navmesh.Voxelization.Burst.VoxelUtilityBurst.VERTEX_BUCKET_COUNT voxelutilityburst.html#VERTEX_BUCKET_COUNT +Pathfinding.Graphs.Navmesh.Voxelization.Int3PolygonClipper.clipPolygonCache int3polygonclipper.html#clipPolygonCache +Pathfinding.Graphs.Navmesh.Voxelization.VoxelPolygonClipper.n voxelpolygonclipper.html#n +Pathfinding.Graphs.Navmesh.Voxelization.VoxelPolygonClipper.this[int i] voxelpolygonclipper.html#thisinti +Pathfinding.Graphs.Navmesh.Voxelization.VoxelPolygonClipper.x voxelpolygonclipper.html#x +Pathfinding.Graphs.Navmesh.Voxelization.VoxelPolygonClipper.y voxelpolygonclipper.html#y +Pathfinding.Graphs.Navmesh.Voxelization.VoxelPolygonClipper.z voxelpolygonclipper.html#z +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.costIndexStride euclideanembeddingsearchpath.html#costIndexStride +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.costs euclideanembeddingsearchpath.html#costs +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.furthestNode euclideanembeddingsearchpath.html#furthestNode +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.furthestNodeScore euclideanembeddingsearchpath.html#furthestNodeScore +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.pivotIndex euclideanembeddingsearchpath.html#pivotIndex +Pathfinding.Graphs.Util.EuclideanEmbedding.EuclideanEmbeddingSearchPath.startNode euclideanembeddingsearchpath.html#startNode +Pathfinding.Graphs.Util.EuclideanEmbedding.costs euclideanembedding.html#costs Costs laid out as n*[int],n*[int],n*[int] where n is the number of pivot points. \n\nEach node has n integers which is the cost from that node to the pivot node. They are at around the same place in the array for simplicity and for cache locality.\n\ncost(nodeIndex, pivotIndex) = costs[nodeIndex*pivotCount+pivotIndex] +Pathfinding.Graphs.Util.EuclideanEmbedding.dirty euclideanembedding.html#dirty +Pathfinding.Graphs.Util.EuclideanEmbedding.mode euclideanembedding.html#mode If heuristic optimization should be used and how to place the pivot points. \n\n[more in online documentation] +Pathfinding.Graphs.Util.EuclideanEmbedding.pivotCount euclideanembedding.html#pivotCount +Pathfinding.Graphs.Util.EuclideanEmbedding.pivotPointRoot euclideanembedding.html#pivotPointRoot All children of this transform will be used as pivot points. +Pathfinding.Graphs.Util.EuclideanEmbedding.pivots euclideanembedding.html#pivots +Pathfinding.Graphs.Util.EuclideanEmbedding.ra euclideanembedding.html#ra +Pathfinding.Graphs.Util.EuclideanEmbedding.rc euclideanembedding.html#rc +Pathfinding.Graphs.Util.EuclideanEmbedding.rval euclideanembedding.html#rval +Pathfinding.Graphs.Util.EuclideanEmbedding.seed euclideanembedding.html#seed +Pathfinding.Graphs.Util.EuclideanEmbedding.spreadOutCount euclideanembedding.html#spreadOutCount +Pathfinding.Graphs.Util.HeuristicOptimizationMode util.html#HeuristicOptimizationMode +Pathfinding.GridGraph.CombinedGridGraphUpdatePromise.promises combinedgridgraphupdatepromise.html#promises +Pathfinding.GridGraph.Depth gridgraph.html#Depth +Pathfinding.GridGraph.FixedPrecisionScale gridgraph.html#FixedPrecisionScale Scaling used for the coordinates in the Linecast methods that take normalized points using integer coordinates. \n\nTo convert from world space, each coordinate is multiplied by this factor and then rounded to the nearest integer.\n\nTypically you do not need to use this constant yourself, instead use the Linecast overloads that do not take integer coordinates. +Pathfinding.GridGraph.GridGraphMovePromise.dx gridgraphmovepromise.html#dx +Pathfinding.GridGraph.GridGraphMovePromise.dz gridgraphmovepromise.html#dz +Pathfinding.GridGraph.GridGraphMovePromise.graph gridgraphmovepromise.html#graph +Pathfinding.GridGraph.GridGraphMovePromise.promises gridgraphmovepromise.html#promises +Pathfinding.GridGraph.GridGraphMovePromise.rects gridgraphmovepromise.html#rects +Pathfinding.GridGraph.GridGraphMovePromise.startingSize gridgraphmovepromise.html#startingSize +Pathfinding.GridGraph.GridGraphSnapshot.graph gridgraphsnapshot.html#graph +Pathfinding.GridGraph.GridGraphSnapshot.nodes gridgraphsnapshot.html#nodes +Pathfinding.GridGraph.GridGraphUpdatePromise.CostEstimate gridgraphupdatepromise.html#CostEstimate +Pathfinding.GridGraph.GridGraphUpdatePromise.NodesHolder.nodes nodesholder.html#nodes +Pathfinding.GridGraph.GridGraphUpdatePromise.allocationMethod gridgraphupdatepromise.html#allocationMethod +Pathfinding.GridGraph.GridGraphUpdatePromise.context gridgraphupdatepromise.html#context +Pathfinding.GridGraph.GridGraphUpdatePromise.dependencyTracker gridgraphupdatepromise.html#dependencyTracker +Pathfinding.GridGraph.GridGraphUpdatePromise.emptyUpdate gridgraphupdatepromise.html#emptyUpdate +Pathfinding.GridGraph.GridGraphUpdatePromise.fullRecalculationBounds gridgraphupdatepromise.html#fullRecalculationBounds +Pathfinding.GridGraph.GridGraphUpdatePromise.graph gridgraphupdatepromise.html#graph +Pathfinding.GridGraph.GridGraphUpdatePromise.graphUpdateObject gridgraphupdatepromise.html#graphUpdateObject +Pathfinding.GridGraph.GridGraphUpdatePromise.isFinalUpdate gridgraphupdatepromise.html#isFinalUpdate +Pathfinding.GridGraph.GridGraphUpdatePromise.nodeArrayBounds gridgraphupdatepromise.html#nodeArrayBounds +Pathfinding.GridGraph.GridGraphUpdatePromise.nodes gridgraphupdatepromise.html#nodes +Pathfinding.GridGraph.GridGraphUpdatePromise.nodesDependsOn gridgraphupdatepromise.html#nodesDependsOn +Pathfinding.GridGraph.GridGraphUpdatePromise.ownsJobDependencyTracker gridgraphupdatepromise.html#ownsJobDependencyTracker +Pathfinding.GridGraph.GridGraphUpdatePromise.readBounds gridgraphupdatepromise.html#readBounds +Pathfinding.GridGraph.GridGraphUpdatePromise.recalculationMode gridgraphupdatepromise.html#recalculationMode +Pathfinding.GridGraph.GridGraphUpdatePromise.rect gridgraphupdatepromise.html#rect +Pathfinding.GridGraph.GridGraphUpdatePromise.transform gridgraphupdatepromise.html#transform +Pathfinding.GridGraph.GridGraphUpdatePromise.writeMaskBounds gridgraphupdatepromise.html#writeMaskBounds +Pathfinding.GridGraph.HexagonConnectionMask gridgraph.html#HexagonConnectionMask Mask based on hexagonNeighbourIndices. \n\nThis indicates which connections (out of the 8 standard ones) should be enabled for hexagonal graphs.\n\n[code in online documentation] +Pathfinding.GridGraph.LayerCount gridgraph.html#LayerCount Number of layers in the graph. \n\nFor grid graphs this is always 1, for layered grid graphs it can be higher. The nodes array has the size width*depth*layerCount. +Pathfinding.GridGraph.MaxLayers gridgraph.html#MaxLayers +Pathfinding.GridGraph.RecalculationMode gridgraph.html#RecalculationMode +Pathfinding.GridGraph.StandardDimetricAngle gridgraph.html#StandardDimetricAngle Commonly used value for isometricAngle. +Pathfinding.GridGraph.StandardIsometricAngle gridgraph.html#StandardIsometricAngle Commonly used value for isometricAngle. +Pathfinding.GridGraph.TextureData.ChannelUse texturedata.html#ChannelUse +Pathfinding.GridGraph.TextureData.channels texturedata.html#channels +Pathfinding.GridGraph.TextureData.data texturedata.html#data +Pathfinding.GridGraph.TextureData.enabled texturedata.html#enabled +Pathfinding.GridGraph.TextureData.factors texturedata.html#factors +Pathfinding.GridGraph.TextureData.source texturedata.html#source +Pathfinding.GridGraph.Width gridgraph.html#Width +Pathfinding.GridGraph.allNeighbourIndices gridgraph.html#allNeighbourIndices Which neighbours are going to be used when neighbours=8. +Pathfinding.GridGraph.aspectRatio gridgraph.html#aspectRatio Scaling of the graph along the X axis. \n\nThis should be used if you want different scales on the X and Y axis of the grid\n\nThis option is only visible in the inspector if the graph shape is set to isometric or advanced. +Pathfinding.GridGraph.axisAlignedNeighbourIndices gridgraph.html#axisAlignedNeighbourIndices Which neighbours are going to be used when neighbours=4. +Pathfinding.GridGraph.bounds gridgraph.html#bounds World bounding box for the graph. \n\nThis always contains the whole graph.\n\n[more in online documentation] +Pathfinding.GridGraph.center gridgraph.html#center Center point of the grid in world space. \n\nThe graph can be positioned anywhere in the world.\n\n[more in online documentation] +Pathfinding.GridGraph.collision gridgraph.html#collision Settings on how to check for walkability and height. +Pathfinding.GridGraph.cutCorners gridgraph.html#cutCorners If disabled, will not cut corners on obstacles. \n\nIf this is true, and neighbours is set to Eight, obstacle corners are allowed to be cut by a connection.\n\n [image in online documentation] +Pathfinding.GridGraph.depth gridgraph.html#depth Depth (height) of the grid in nodes. \n\nGrid graphs are typically anywhere from 10-500 nodes wide. But it can go up to 1024 nodes wide by default. Consider using a recast graph instead, if you find yourself needing a very high resolution grid.\n\nThis value will be clamped to at most 1024 unless ASTAR_LARGER_GRIDS has been enabled in the A* Inspector -> Optimizations tab.\n\n[more in online documentation] +Pathfinding.GridGraph.erodeIterations gridgraph.html#erodeIterations Number of times to erode the graph. \n\nThe graph can be eroded to add extra margin to obstacles. It is very convenient if your graph contains ledges, and where the walkable nodes without erosion are too close to the edge.\n\nBelow is an image showing a graph with 0, 1 and 2 erosion iterations: [image in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.erosionFirstTag gridgraph.html#erosionFirstTag Tag to start from when using tags for erosion. \n\n[more in online documentation] +Pathfinding.GridGraph.erosionTagsPrecedenceMask gridgraph.html#erosionTagsPrecedenceMask Bitmask for which tags can be overwritten by erosion tags. \n\nWhen erosionUseTags is enabled, nodes near unwalkable nodes will be marked with tags. However, if these nodes already have tags, you may want the custom tag to take precedence. This mask controls which tags are allowed to be replaced by the new erosion tags.\n\nIn the image below, erosion has applied tags which have overwritten both the base tag (tag 0) and the custom tag set on the nodes (shown in red). [image in online documentation]\n\nIn the image below, erosion has applied tags, but it was not allowed to overwrite the custom tag set on the nodes (shown in red). [image in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.erosionUseTags gridgraph.html#erosionUseTags Use tags instead of walkability for erosion. \n\nTags will be used for erosion instead of marking nodes as unwalkable. The nodes will be marked with tags in an increasing order starting with the tag erosionFirstTag. Debug with the Tags mode to see the effect. With this enabled you can in effect set how close different AIs are allowed to get to walls using the Valid Tags field on the Seeker component. [image in online documentation][image in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.hexagonNeighbourIndices gridgraph.html#hexagonNeighbourIndices Which neighbours are going to be used when neighbours=6. +Pathfinding.GridGraph.inspectorGridMode gridgraph.html#inspectorGridMode Determines the layout of the grid graph inspector in the Unity Editor. \n\nA grid graph can be set up as a normal grid, isometric grid or hexagonal grid. Each of these modes use a slightly different inspector layout. When changing the shape in the inspector, it will automatically set other relevant fields to appropriate values. For example, when setting the shape to hexagonal it will automatically set the neighbours field to Six.\n\nThis field is only used in the editor, it has no effect on the rest of the game whatsoever.\n\nIf you want to change the grid shape like in the inspector you can use the SetGridShape method. +Pathfinding.GridGraph.inspectorHexagonSizeMode gridgraph.html#inspectorHexagonSizeMode Determines how the size of each hexagon is set in the inspector. \n\nFor hexagons the normal nodeSize field doesn't really correspond to anything specific on the hexagon's geometry, so this enum is used to give the user the opportunity to adjust more concrete dimensions of the hexagons without having to pull out a calculator to calculate all the square roots and complicated conversion factors.\n\nThis field is only used in the graph inspector, the nodeSize field will always use the same internal units. If you want to set the node size through code then you can use ConvertHexagonSizeToNodeSize.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.is2D gridgraph.html#is2D Get or set if the graph should be in 2D mode. \n\n[more in online documentation] +Pathfinding.GridGraph.isScanned gridgraph.html#isScanned True if the graph has been scanned and contains nodes. \n\nGraphs are typically scanned when the game starts, but they can also be scanned manually.\n\nIf a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.\n\n[more in online documentation] +Pathfinding.GridGraph.isometricAngle gridgraph.html#isometricAngle Angle in degrees to use for the isometric projection. \n\nIf you are making a 2D isometric game, you may want to use this parameter to adjust the layout of the graph to match your game. This will essentially scale the graph along one of its diagonals to produce something like this:\n\nA perspective view of an isometric graph. [image in online documentation]\n\nA top down view of an isometric graph. Note that the graph is entirely 2D, there is no perspective in this image. [image in online documentation]\n\nFor commonly used values see StandardIsometricAngle and StandardDimetricAngle.\n\nUsually the angle that you want to use is either 30 degrees (alternatively 90-30 = 60 degrees) or atan(1/sqrt(2)) which is approximately 35.264 degrees (alternatively 90 - 35.264 = 54.736 degrees). You might also want to rotate the graph plus or minus 45 degrees around the Y axis to get the oritientation required for your game.\n\nYou can read more about it on the wikipedia page linked below.\n\n[more in online documentation]\nThis option is only visible in the inspector if the graph shape is set to isometric or advanced. +Pathfinding.GridGraph.maxClimb gridgraph.html#maxClimb The max y coordinate difference between two nodes to enable a connection. \n\n[more in online documentation] +Pathfinding.GridGraph.maxSlope gridgraph.html#maxSlope The max slope in degrees for a node to be walkable. +Pathfinding.GridGraph.maxStepHeight gridgraph.html#maxStepHeight The max y coordinate difference between two nodes to enable a connection. \n\nSet to 0 to ignore the value.\n\nThis affects for example how the graph is generated around ledges and stairs.\n\n[more in online documentation] +Pathfinding.GridGraph.maxStepUsesSlope gridgraph.html#maxStepUsesSlope Take the slope into account for maxStepHeight. \n\nWhen this is enabled the normals of the terrain will be used to make more accurate estimates of how large the steps are between adjacent nodes.\n\nWhen this is disabled then calculated step between two nodes is their y coordinate difference. This may be inaccurate, especially at the start of steep slopes.\n\n [image in online documentation]\n\nIn the image below you can see an example of what happens near a ramp. In the topmost image the ramp is not connected with the rest of the graph which is obviously not what we want. In the middle image an attempt has been made to raise the max step height while keeping maxStepUsesSlope disabled. However this causes too many connections to be added. The agent should not be able to go up the ramp from the side. Finally in the bottommost image the maxStepHeight has been restored to the original value but maxStepUsesSlope has been enabled. This configuration handles the ramp in a much smarter way. Note that all the values in the image are just example values, they may be different for your scene. [image in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.neighbourCosts gridgraph.html#neighbourCosts Costs to neighbour nodes. \n\nSee neighbourOffsets. +Pathfinding.GridGraph.neighbourOffsets gridgraph.html#neighbourOffsets Index offset to get neighbour nodes. \n\nAdded to a node's index to get a neighbour node index.\n\n[code in online documentation] +Pathfinding.GridGraph.neighbourXOffsets gridgraph.html#neighbourXOffsets Offsets in the X direction for neighbour nodes. \n\nOnly 1, 0 or -1 +Pathfinding.GridGraph.neighbourZOffsets gridgraph.html#neighbourZOffsets Offsets in the Z direction for neighbour nodes. \n\nOnly 1, 0 or -1 +Pathfinding.GridGraph.neighbours gridgraph.html#neighbours Number of neighbours for each node. \n\nEither four, six, eight connections per node.\n\nSix connections is primarily for hexagonal graphs. +Pathfinding.GridGraph.newGridNodeDelegate gridgraph.html#newGridNodeDelegate Delegate which creates and returns a single instance of the node type for this graph. \n\nThis may be set in the constructor for graphs inheriting from the GridGraph to change the node type of the graph. +Pathfinding.GridGraph.nodeData gridgraph.html#nodeData Internal data for each node. \n\nIt also contains some data not stored in the node objects, such as normals for the surface of the graph. These normals need to be saved when the maxStepUsesSlope option is enabled for graph updates to work. +Pathfinding.GridGraph.nodeDataRef gridgraph.html#nodeDataRef +Pathfinding.GridGraph.nodeSize gridgraph.html#nodeSize Size of one node in world units. \n\nFor a grid layout, this is the length of the sides of the grid squares.\n\nFor a hexagonal layout, this value does not correspond to any specific dimension of the hexagon. Instead you can convert it to a dimension on a hexagon using ConvertNodeSizeToHexagonSize.\n\n[more in online documentation] +Pathfinding.GridGraph.nodes gridgraph.html#nodes All nodes in this graph. \n\nNodes are laid out row by row.\n\nThe first node has grid coordinates X=0, Z=0, the second one X=1, Z=0\n\nthe last one has grid coordinates X=width-1, Z=depth-1.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.penaltyAngle gridgraph.html#penaltyAngle [more in online documentation] +Pathfinding.GridGraph.penaltyAngleFactor gridgraph.html#penaltyAngleFactor How much penalty is applied depending on the slope of the terrain. \n\nAt a 90 degree slope (not that exactly 90 degree slopes can occur, but almost 90 degree), this penalty is applied. At a 45 degree slope, half of this is applied and so on. Note that you may require very large values, a value of 1000 is equivalent to the cost of moving 1 world unit.\n\n[more in online documentation] +Pathfinding.GridGraph.penaltyAnglePower gridgraph.html#penaltyAnglePower How much extra to penalize very steep angles. \n\n[more in online documentation] +Pathfinding.GridGraph.penaltyPosition gridgraph.html#penaltyPosition Use position (y-coordinate) to calculate penalty. \n\n[more in online documentation] +Pathfinding.GridGraph.penaltyPositionFactor gridgraph.html#penaltyPositionFactor Scale factor for penalty when calculating from position. \n\n[more in online documentation] +Pathfinding.GridGraph.penaltyPositionOffset gridgraph.html#penaltyPositionOffset Offset for the position when calculating penalty. \n\n[more in online documentation] +Pathfinding.GridGraph.rotation gridgraph.html#rotation Rotation of the grid in degrees. \n\nThe nodes are laid out along the X and Z axes of the rotation.\n\nFor a 2D game, the rotation will typically be set to (-90, 270, 90). If the graph is aligned with the XY plane, the inspector will automatically switch to 2D mode.\n\n[more in online documentation] +Pathfinding.GridGraph.rules gridgraph.html#rules Additional rules to use when scanning the grid graph. \n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.GridGraph.showMeshOutline gridgraph.html#showMeshOutline Show an outline of the grid nodes in the Unity Editor. +Pathfinding.GridGraph.showMeshSurface gridgraph.html#showMeshSurface Show the surface of the graph. \n\nEach node will be drawn as a square (unless e.g hexagon graph mode has been enabled). +Pathfinding.GridGraph.showNodeConnections gridgraph.html#showNodeConnections Show the connections between the grid nodes in the Unity Editor. +Pathfinding.GridGraph.size gridgraph.html#size Size of the grid. \n\nWill always be positive and larger than nodeSize. \n\n[more in online documentation] +Pathfinding.GridGraph.textureData gridgraph.html#textureData Holds settings for using a texture as source for a grid graph. \n\nTexure data can be used for fine grained control over how the graph will look. It can be used for positioning, penalty and walkability control.\n\nBelow is a screenshot of a grid graph with a penalty map applied. It has the effect of the AI taking the longer path along the green (low penalty) areas.\n\n [image in online documentation][more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.GridGraph.transform gridgraph.html#transform Determines how the graph transforms graph space to world space. \n\n[more in online documentation] +Pathfinding.GridGraph.unclampedSize gridgraph.html#unclampedSize Size of the grid. \n\nCan be negative or smaller than nodeSize +Pathfinding.GridGraph.uniformEdgeCosts gridgraph.html#uniformEdgeCosts If true, all edge costs will be set to the same value. \n\nIf false, diagonals will cost more. This is useful for a hexagon graph where the diagonals are actually the same length as the normal edges (since the graph has been skewed)\n\nIf the graph is set to hexagonal in the inspector, this will be automatically set to true. +Pathfinding.GridGraph.useRaycastNormal gridgraph.html#useRaycastNormal Use heigh raycasting normal for max slope calculation. \n\nTrue if maxSlope is less than 90 degrees. +Pathfinding.GridGraph.width gridgraph.html#width Width of the grid in nodes. \n\nGrid graphs are typically anywhere from 10-500 nodes wide. But it can go up to 1024 nodes wide by default. Consider using a recast graph instead, if you find yourself needing a very high resolution grid.\n\nThis value will be clamped to at most 1024 unless ASTAR_LARGER_GRIDS has been enabled in the A* Inspector -> Optimizations tab.\n\n[more in online documentation] +Pathfinding.GridGraphEditor.GridPivot gridgrapheditor.html#GridPivot +Pathfinding.GridGraphEditor.arcBuffer gridgrapheditor.html#arcBuffer +Pathfinding.GridGraphEditor.cachedSceneGridLayouts gridgrapheditor.html#cachedSceneGridLayouts +Pathfinding.GridGraphEditor.cachedSceneGridLayoutsTimestamp gridgrapheditor.html#cachedSceneGridLayoutsTimestamp +Pathfinding.GridGraphEditor.collisionPreviewOpen gridgrapheditor.html#collisionPreviewOpen Shows the preview for the collision testing options. \n\n [image in online documentation]\n\nOn the left you can see a top-down view of the graph with a grid of nodes. On the right you can see a side view of the graph. The white line at the bottom is the base of the graph, with node positions indicated using small dots. When using 2D physics, only the top-down view is visible.\n\nThe green shape indicates the shape that will be used for collision checking. +Pathfinding.GridGraphEditor.gridPivotSelectBackground gridgrapheditor.html#gridPivotSelectBackground Cached gui style. +Pathfinding.GridGraphEditor.gridPivotSelectButton gridgrapheditor.html#gridPivotSelectButton Cached gui style. +Pathfinding.GridGraphEditor.handlePoints gridgrapheditor.html#handlePoints +Pathfinding.GridGraphEditor.hexagonSizeContents gridgrapheditor.html#hexagonSizeContents +Pathfinding.GridGraphEditor.interpolatedGridWidthInNodes gridgrapheditor.html#interpolatedGridWidthInNodes +Pathfinding.GridGraphEditor.isMouseDown gridgrapheditor.html#isMouseDown +Pathfinding.GridGraphEditor.lastTime gridgrapheditor.html#lastTime +Pathfinding.GridGraphEditor.lineBuffer gridgrapheditor.html#lineBuffer +Pathfinding.GridGraphEditor.lockStyle gridgrapheditor.html#lockStyle Cached gui style. +Pathfinding.GridGraphEditor.locked gridgrapheditor.html#locked +Pathfinding.GridGraphEditor.pivot gridgrapheditor.html#pivot +Pathfinding.GridGraphEditor.ruleEditorInstances gridgrapheditor.html#ruleEditorInstances +Pathfinding.GridGraphEditor.ruleEditors gridgrapheditor.html#ruleEditors +Pathfinding.GridGraphEditor.ruleHeaders gridgrapheditor.html#ruleHeaders +Pathfinding.GridGraphEditor.ruleTypes gridgrapheditor.html#ruleTypes +Pathfinding.GridGraphEditor.savedDimensions gridgrapheditor.html#savedDimensions +Pathfinding.GridGraphEditor.savedNodeSize gridgrapheditor.html#savedNodeSize +Pathfinding.GridGraphEditor.savedTransform gridgrapheditor.html#savedTransform +Pathfinding.GridGraphEditor.selectedTilemap gridgrapheditor.html#selectedTilemap +Pathfinding.GridGraphEditor.showExtra gridgrapheditor.html#showExtra +Pathfinding.GridHitInfo.direction gridhitinfo.html#direction Direction from the node to the edge that was hit. \n\nThis will be in the range of 0 to 4 (exclusive) or -1 if no particular edge was hit.\n\n[more in online documentation] +Pathfinding.GridHitInfo.node gridhitinfo.html#node The node which contained the edge that was hit. \n\nThis may be null in case no particular edge was hit. +Pathfinding.GridNode.EdgeNode gridnode.html#EdgeNode Work in progress for a feature that required info about which nodes were at the border of the graph. \n\n[more in online documentation] +Pathfinding.GridNode.GridFlagsAxisAlignedConnectionMask gridnode.html#GridFlagsAxisAlignedConnectionMask +Pathfinding.GridNode.GridFlagsConnectionBit0 gridnode.html#GridFlagsConnectionBit0 +Pathfinding.GridNode.GridFlagsConnectionMask gridnode.html#GridFlagsConnectionMask +Pathfinding.GridNode.GridFlagsConnectionOffset gridnode.html#GridFlagsConnectionOffset +Pathfinding.GridNode.GridFlagsEdgeNodeMask gridnode.html#GridFlagsEdgeNodeMask +Pathfinding.GridNode.GridFlagsEdgeNodeOffset gridnode.html#GridFlagsEdgeNodeOffset +Pathfinding.GridNode.HasAnyGridConnections gridnode.html#HasAnyGridConnections True if this node has any grid connections. +Pathfinding.GridNode.HasConnectionsToAllAxisAlignedNeighbours gridnode.html#HasConnectionsToAllAxisAlignedNeighbours True if the node has grid connections to all its 4 axis-aligned neighbours. \n\n[more in online documentation] +Pathfinding.GridNode.HasConnectionsToAllEightNeighbours gridnode.html#HasConnectionsToAllEightNeighbours True if the node has grid connections to all its 8 neighbours. \n\n[more in online documentation] +Pathfinding.GridNode.InternalGridFlags gridnode.html#InternalGridFlags Internal use only. +Pathfinding.GridNode._gridGraphs gridnode.html#_gridGraphs +Pathfinding.GridNodeBase.CoordinatesInGrid gridnodebase.html#CoordinatesInGrid The X and Z coordinates of the node in the grid. \n\nThe node in the bottom left corner has (x,z) = (0,0) and the one in the opposite corner has (x,z) = (width-1, depth-1)\n\n[more in online documentation] +Pathfinding.GridNodeBase.GridFlagsWalkableErosionMask gridnodebase.html#GridFlagsWalkableErosionMask +Pathfinding.GridNodeBase.GridFlagsWalkableErosionOffset gridnodebase.html#GridFlagsWalkableErosionOffset +Pathfinding.GridNodeBase.GridFlagsWalkableTmpMask gridnodebase.html#GridFlagsWalkableTmpMask +Pathfinding.GridNodeBase.GridFlagsWalkableTmpOffset gridnodebase.html#GridFlagsWalkableTmpOffset +Pathfinding.GridNodeBase.HasAnyGridConnections gridnodebase.html#HasAnyGridConnections True if this node has any grid connections. +Pathfinding.GridNodeBase.HasConnectionsToAllAxisAlignedNeighbours gridnodebase.html#HasConnectionsToAllAxisAlignedNeighbours True if the node has grid connections to all its 4 axis-aligned neighbours. \n\n[more in online documentation] +Pathfinding.GridNodeBase.HasConnectionsToAllEightNeighbours gridnodebase.html#HasConnectionsToAllEightNeighbours True if the node has grid connections to all its 8 neighbours. \n\n[more in online documentation] +Pathfinding.GridNodeBase.NodeInGridIndex gridnodebase.html#NodeInGridIndex The index of the node in the grid. \n\nThis is x + z*graph.width So you can get the X and Z indices using [code in online documentation]\n\n[more in online documentation] +Pathfinding.GridNodeBase.NodeInGridIndexLayerOffset gridnodebase.html#NodeInGridIndexLayerOffset +Pathfinding.GridNodeBase.NodeInGridIndexMask gridnodebase.html#NodeInGridIndexMask +Pathfinding.GridNodeBase.TmpWalkable gridnodebase.html#TmpWalkable Temporary variable used internally when updating the graph. +Pathfinding.GridNodeBase.WalkableErosion gridnodebase.html#WalkableErosion Stores walkability before erosion is applied. \n\nUsed internally when updating the graph. +Pathfinding.GridNodeBase.XCoordinateInGrid gridnodebase.html#XCoordinateInGrid X coordinate of the node in the grid. \n\nThe node in the bottom left corner has (x,z) = (0,0) and the one in the opposite corner has (x,z) = (width-1, depth-1)\n\n[more in online documentation] +Pathfinding.GridNodeBase.ZCoordinateInGrid gridnodebase.html#ZCoordinateInGrid Z coordinate of the node in the grid. \n\nThe node in the bottom left corner has (x,z) = (0,0) and the one in the opposite corner has (x,z) = (width-1, depth-1)\n\n[more in online documentation] +Pathfinding.GridNodeBase.connections gridnodebase.html#connections Custon non-grid connections from this node. \n\n[more in online documentation]\nThis field is removed if the ASTAR_GRID_NO_CUSTOM_CONNECTIONS compiler directive is used. Removing it can save a tiny bit of memory. You can enable the define in the Optimizations tab in the A* inspector. \n\n[more in online documentation] +Pathfinding.GridNodeBase.gridFlags gridnodebase.html#gridFlags +Pathfinding.GridNodeBase.nodeInGridIndex gridnodebase.html#nodeInGridIndex Bitfield containing the x and z coordinates of the node as well as the layer (for layered grid graphs). \n\n[more in online documentation] +Pathfinding.GridNodeBase.offsetToDirection gridnodebase.html#offsetToDirection Converts from dx + 3*dz to a neighbour direction. \n\nUsed by OffsetToConnectionDirection.\n\nAssumes that dx and dz are both in the range [0,2]. \n\n[more in online documentation] +Pathfinding.GridStringPulling.FixedPrecisionScale gridstringpulling.html#FixedPrecisionScale +Pathfinding.GridStringPulling.PredicateFailMode gridstringpulling.html#PredicateFailMode +Pathfinding.GridStringPulling.TriangleBounds.d1 trianglebounds.html#d1 +Pathfinding.GridStringPulling.TriangleBounds.d2 trianglebounds.html#d2 +Pathfinding.GridStringPulling.TriangleBounds.d3 trianglebounds.html#d3 +Pathfinding.GridStringPulling.TriangleBounds.t1 trianglebounds.html#t1 +Pathfinding.GridStringPulling.TriangleBounds.t2 trianglebounds.html#t2 +Pathfinding.GridStringPulling.TriangleBounds.t3 trianglebounds.html#t3 +Pathfinding.GridStringPulling.directionToCorners gridstringpulling.html#directionToCorners Z | |. \n\n3 2 \ | / – - X - —– X / | \ 0 1\n\n| | +Pathfinding.GridStringPulling.marker1 gridstringpulling.html#marker1 +Pathfinding.GridStringPulling.marker2 gridstringpulling.html#marker2 +Pathfinding.GridStringPulling.marker3 gridstringpulling.html#marker3 +Pathfinding.GridStringPulling.marker4 gridstringpulling.html#marker4 +Pathfinding.GridStringPulling.marker5 gridstringpulling.html#marker5 +Pathfinding.GridStringPulling.marker6 gridstringpulling.html#marker6 +Pathfinding.GridStringPulling.marker7 gridstringpulling.html#marker7 +Pathfinding.Heuristic pathfinding.html#Heuristic How to estimate the cost of moving to the destination during pathfinding. \n\nThe heuristic is the estimated cost from the current node to the target. The different heuristics have roughly the same performance except not using any heuristic at all ( Heuristic.None) which is usually significantly slower.\n\nIn the image below you can see a comparison of the different heuristic options for an 8-connected grid and for a 4-connected grid. Note that all paths within the green area will all have the same length. The only difference between the heuristics is which of those paths of the same length that will be chosen. Note that while the Diagonal Manhattan and Manhattan options seem to behave very differently on an 8-connected grid they only do it in this case because of very small rounding errors. Usually they behave almost identically on 8-connected grids.\n\n [image in online documentation]\n\nGenerally for a 4-connected grid graph the Manhattan option should be used as it is the true distance on a 4-connected grid. For an 8-connected grid graph the Diagonal Manhattan option is the mathematically most correct option, however the Euclidean option is often preferred, especially if you are simplifying the path afterwards using modifiers.\n\nFor any graph that is not grid based the Euclidean option is the best one to use.\n\n[more in online documentation] +Pathfinding.HeuristicObjective.euclideanEmbeddingCosts heuristicobjective.html#euclideanEmbeddingCosts +Pathfinding.HeuristicObjective.euclideanEmbeddingPivots heuristicobjective.html#euclideanEmbeddingPivots +Pathfinding.HeuristicObjective.hasHeuristic heuristicobjective.html#hasHeuristic +Pathfinding.HeuristicObjective.heuristic heuristicobjective.html#heuristic +Pathfinding.HeuristicObjective.heuristicScale heuristicobjective.html#heuristicScale +Pathfinding.HeuristicObjective.mn heuristicobjective.html#mn +Pathfinding.HeuristicObjective.mx heuristicobjective.html#mx +Pathfinding.HeuristicObjective.targetNodeIndex heuristicobjective.html#targetNodeIndex +Pathfinding.HierarchicalGraph.HierarhicalNodeData.bounds hierarhicalnodedata.html#bounds +Pathfinding.HierarchicalGraph.HierarhicalNodeData.connectionAllocations hierarhicalnodedata.html#connectionAllocations +Pathfinding.HierarchicalGraph.HierarhicalNodeData.connectionAllocator hierarhicalnodedata.html#connectionAllocator +Pathfinding.HierarchicalGraph.JobRecalculateComponents.Context.children context2.html#children +Pathfinding.HierarchicalGraph.JobRecalculateComponents.Context.connections context2.html#connections +Pathfinding.HierarchicalGraph.JobRecalculateComponents.Context.graphindex context2.html#graphindex +Pathfinding.HierarchicalGraph.JobRecalculateComponents.Context.hierarchicalNodeIndex context2.html#hierarchicalNodeIndex +Pathfinding.HierarchicalGraph.JobRecalculateComponents.Context.queue context2.html#queue +Pathfinding.HierarchicalGraph.JobRecalculateComponents.bounds jobrecalculatecomponents.html#bounds +Pathfinding.HierarchicalGraph.JobRecalculateComponents.connectionAllocations jobrecalculatecomponents.html#connectionAllocations +Pathfinding.HierarchicalGraph.JobRecalculateComponents.dirtiedHierarchicalNodes jobrecalculatecomponents.html#dirtiedHierarchicalNodes +Pathfinding.HierarchicalGraph.JobRecalculateComponents.hGraphGC jobrecalculatecomponents.html#hGraphGC +Pathfinding.HierarchicalGraph.JobRecalculateComponents.numHierarchicalNodes jobrecalculatecomponents.html#numHierarchicalNodes +Pathfinding.HierarchicalGraph.JobRecalculateComponents.warnedAboutTooManyHierarchicalNodes jobrecalculatecomponents.html#warnedAboutTooManyHierarchicalNodes +Pathfinding.HierarchicalGraph.MaxChildrenPerNode hierarchicalgraph.html#MaxChildrenPerNode +Pathfinding.HierarchicalGraph.MinChildrenPerNode hierarchicalgraph.html#MinChildrenPerNode +Pathfinding.HierarchicalGraph.NumConnectedComponents hierarchicalgraph.html#NumConnectedComponents +Pathfinding.HierarchicalGraph.Tiling hierarchicalgraph.html#Tiling +Pathfinding.HierarchicalGraph.anyNodesDestroyed hierarchicalgraph.html#anyNodesDestroyed +Pathfinding.HierarchicalGraph.areas hierarchicalgraph.html#areas +Pathfinding.HierarchicalGraph.bounds hierarchicalgraph.html#bounds +Pathfinding.HierarchicalGraph.children hierarchicalgraph.html#children +Pathfinding.HierarchicalGraph.connectionAllocations hierarchicalgraph.html#connectionAllocations +Pathfinding.HierarchicalGraph.connectionAllocator hierarchicalgraph.html#connectionAllocator +Pathfinding.HierarchicalGraph.currentConnections hierarchicalgraph.html#currentConnections +Pathfinding.HierarchicalGraph.dirtiedHierarchicalNodes hierarchicalgraph.html#dirtiedHierarchicalNodes +Pathfinding.HierarchicalGraph.dirty hierarchicalgraph.html#dirty +Pathfinding.HierarchicalGraph.dirtyNodes hierarchicalgraph.html#dirtyNodes +Pathfinding.HierarchicalGraph.freeNodeIndices hierarchicalgraph.html#freeNodeIndices +Pathfinding.HierarchicalGraph.gcHandle hierarchicalgraph.html#gcHandle +Pathfinding.HierarchicalGraph.gizmoVersion hierarchicalgraph.html#gizmoVersion +Pathfinding.HierarchicalGraph.navmeshEdges hierarchicalgraph.html#navmeshEdges +Pathfinding.HierarchicalGraph.nodeStorage hierarchicalgraph.html#nodeStorage +Pathfinding.HierarchicalGraph.numHierarchicalNodes hierarchicalgraph.html#numHierarchicalNodes Holds areas.Length as a burst-accessible reference. +Pathfinding.HierarchicalGraph.rwLock hierarchicalgraph.html#rwLock +Pathfinding.HierarchicalGraph.temporaryQueue hierarchicalgraph.html#temporaryQueue +Pathfinding.HierarchicalGraph.temporaryStack hierarchicalgraph.html#temporaryStack +Pathfinding.HierarchicalGraph.version hierarchicalgraph.html#version +Pathfinding.HierarchicalGraph.versions hierarchicalgraph.html#versions +Pathfinding.IAstarAI.canMove iastarai.html#canMove Enables or disables movement completely. \n\nIf you want the agent to stand still, but still react to local avoidance and use gravity: use isStopped instead.\n\nThis is also useful if you want to have full control over when the movement calculations run. Take a look at MovementUpdate\n\n[more in online documentation] +Pathfinding.IAstarAI.canSearch iastarai.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.IAstarAI.desiredVelocity iastarai.html#desiredVelocity Velocity that this agent wants to move with. \n\nIncludes gravity and local avoidance if applicable. In world units per second.\n\n[more in online documentation] +Pathfinding.IAstarAI.desiredVelocityWithoutLocalAvoidance iastarai.html#desiredVelocityWithoutLocalAvoidance Velocity that this agent wants to move with before taking local avoidance into account. \n\nIncludes gravity. In world units per second.\n\nSetting this property will set the current velocity that the agent is trying to move with, including gravity. This can be useful if you want to make the agent come to a complete stop in a single frame or if you want to modify the velocity in some way.\n\n[code in online documentation]\n\n[more in online documentation]\n\n\nIf you are not using local avoidance then this property will in almost all cases be identical to desiredVelocity plus some noise due to floating point math.\n\n[more in online documentation] +Pathfinding.IAstarAI.destination iastarai.html#destination Position in the world that this agent should move to. \n\nIf no destination has been set yet, then (+infinity, +infinity, +infinity) will be returned.\n\nNote that setting this property does not immediately cause the agent to recalculate its path. So it may take some time before the agent starts to move towards this point. Most movement scripts have a repathRate field which indicates how often the agent looks for a new path. You can also call the SearchPath method to immediately start to search for a new path. Paths are calculated asynchronously so when an agent starts to search for path it may take a few frames (usually 1 or 2) until the result is available. During this time the pathPending property will return true.\n\nIf you are setting a destination and then want to know when the agent has reached that destination then you could either use reachedDestination (recommended) or check both pathPending and reachedEndOfPath. Check the documentation for the respective fields to learn about their differences.\n\n[code in online documentation][code in online documentation] +Pathfinding.IAstarAI.endOfPath iastarai.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.\n\nThis is only updated when the path is recalculated. +Pathfinding.IAstarAI.hasPath iastarai.html#hasPath True if this agent currently has a path that it follows. +Pathfinding.IAstarAI.height iastarai.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.\n\n[more in online documentation] +Pathfinding.IAstarAI.isStopped iastarai.html#isStopped Gets or sets if the agent should stop moving. \n\nIf this is set to true the agent will immediately start to slow down as quickly as it can to come to a full stop. The agent will still react to local avoidance and gravity (if applicable), but it will not try to move in any particular direction.\n\nThe current path of the agent will not be cleared, so when this is set to false again the agent will continue moving along the previous path.\n\nThis is a purely user-controlled parameter, so for example it is not set automatically when the agent stops moving because it has reached the target. Use reachedEndOfPath for that.\n\nIf this property is set to true while the agent is traversing an off-mesh link (RichAI script only), then the agent will continue traversing the link and stop once it has completed it.\n\n[more in online documentation]\nThe steeringTarget property will continue to indicate the point which the agent would move towards if it would not be stopped.\n\nFor most movement scripts, this will make the agent slow down quickly, but not instantly stop. If you want to instantly stop the agent, you could either disable the whole component instead (but then it will not react to local avoidance, of course, and it will clear its current path), or you could set desiredVelocityWithoutLocalAvoidance to Vector3.zero, which will stop it immediately. +Pathfinding.IAstarAI.maxSpeed iastarai.html#maxSpeed Max speed in world units per second. +Pathfinding.IAstarAI.movementPlane iastarai.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.IAstarAI.onSearchPath iastarai.html#onSearchPath Called when the agent recalculates its path. \n\nThis is called both for automatic path recalculations (see canSearch) and manual ones (see SearchPath).\n\n[more in online documentation] +Pathfinding.IAstarAI.pathPending iastarai.html#pathPending True if a path is currently being calculated. +Pathfinding.IAstarAI.position iastarai.html#position Position of the agent. \n\nIn world space. \n\n[more in online documentation]\nIf you want to move the agent you may use Teleport or Move. +Pathfinding.IAstarAI.radius iastarai.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.IAstarAI.reachedDestination iastarai.html#reachedDestination True if the ai has reached the destination. \n\nThis is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within AIPath.endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).\n\nThis value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than AIPath.endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.\n\nFurthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).\n\nThe cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.\n\nIn contrast to reachedEndOfPath, this property is immediately updated when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.IAstarAI.reachedEndOfPath iastarai.html#reachedEndOfPath True if the agent has reached the end of the current path. \n\nNote that setting the destination does not immediately update the path, nor is there any guarantee that the AI will actually be able to reach the destination that you set. The AI will try to get as close as possible. Often you want to use reachedDestination instead which is easier to work with.\n\nIt is very hard to provide a method for detecting if the AI has reached the destination that works across all different games because the destination may not even lie on the navmesh and how that is handled differs from game to game (see also the code snippet in the docs for destination).\n\n[more in online documentation] +Pathfinding.IAstarAI.remainingDistance iastarai.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThe RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. However it will be accurate when the agent is within 1 corner of the destination. You can use GetRemainingPath to calculate the actual remaining path more precisely.\n\nThe AIPath and AILerp scripts use a more accurate distance calculation at all times.\n\nIf the agent does not currently have a path, then positive infinity will be returned.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.IAstarAI.rotation iastarai.html#rotation Rotation of the agent. \n\nIn world space. \n\n[more in online documentation] +Pathfinding.IAstarAI.simulateMovement iastarai.html#simulateMovement Enables or disables movement simulations completely. \n\nThis is primarily useful if you want to override the default movement behavior. If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. reachedDestination). Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement.\n\nIf you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use isStopped instead. If you want to make the agent stop immediately, disable the whole component instead.\n\n[more in online documentation] +Pathfinding.IAstarAI.steeringTarget iastarai.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.IAstarAI.updatePosition iastarai.html#updatePosition Determines if the character's position should be coupled to the Transform's position. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not move. Instead, only the position property will change.\n\nThis is useful if you want to control the movement of the character using some other means, such as root motion, but still want the AI to move freely.\n\n[more in online documentation] +Pathfinding.IAstarAI.updateRotation iastarai.html#updateRotation Determines if the character's rotation should be coupled to the Transform's rotation. \n\nIf false then all movement calculations will happen as usual, but the GameObject that this component is attached to will not rotate. Instead, only the rotation property will change.\n\nThis is particularly useful for 2D games where you want the Transform to stay in the same orientation, and instead swap out the displayed sprite to indicate the direction the character is facing.\n\n[more in online documentation] +Pathfinding.IAstarAI.velocity iastarai.html#velocity Actual velocity that the agent is moving with. \n\nIn world units per second.\n\n[more in online documentation] +Pathfinding.IGraphInternals.SerializedEditorSettings igraphinternals.html#SerializedEditorSettings +Pathfinding.IGraphUpdatePromise.Progress igraphupdatepromise.html#Progress Returns the progress of the update. \n\nThis should be a value between 0 and 1. +Pathfinding.IOffMeshLinkHandler.name ioffmeshlinkhandler.html#name Name of the handler. \n\nThis is used to identify the handler in the inspector. +Pathfinding.IPathInternals.Pooled ipathinternals.html#Pooled +Pathfinding.IPathModifier.Order ipathmodifier.html#Order +Pathfinding.ITransformedGraph.transform itransformedgraph.html#transform +Pathfinding.ITraversalProvider.filterDiagonalGridConnections itraversalprovider.html#filterDiagonalGridConnections Filter diagonal connections using GridGraph.cutCorners for effects applied by this ITraversalProvider. \n\nThis includes tags and other effects that this ITraversalProvider controls.\n\nThis only has an effect if GridGraph.cutCorners is set to false and your grid has GridGraph.neighbours set to Eight.\n\nTake this example, the grid is completely walkable, but an ITraversalProvider is used to make the nodes marked with '#' as unwalkable. The agent 'S' is in the middle.\n\n[code in online documentation]\n\nIf filterDiagonalGridConnections is false the agent will be free to use the diagonal connections to move away from that spot. However, if filterDiagonalGridConnections is true (the default) then the diagonal connections will be disabled and the agent will be stuck.\n\nTypically, there are a few common use cases:\n- If your ITraversalProvider makes walls and obstacles and you want it to behave identically to obstacles included in the original grid graph scan, then this should be true.\n\n- If your ITraversalProvider is used for agent to agent avoidance and you want them to be able to move around each other more freely, then this should be false.\n\n\n\n[more in online documentation] +Pathfinding.InspectorGridHexagonNodeSize pathfinding.html#InspectorGridHexagonNodeSize +Pathfinding.InspectorGridMode pathfinding.html#InspectorGridMode +Pathfinding.Int3.FloatPrecision int3.html#FloatPrecision Precision as a float +Pathfinding.Int3.Precision int3.html#Precision Precision for the integer coordinates. \n\nOne world unit is divided into [value] pieces. A value of 1000 would mean millimeter precision, a value of 1 would mean meter precision (assuming 1 world unit = 1 meter). This value affects the maximum coordinates for nodes as well as how large the cost values are for moving between two nodes. A higher value means that you also have to set all penalty values to a higher value to compensate since the normal cost of moving will be higher. +Pathfinding.Int3.PrecisionFactor int3.html#PrecisionFactor 1 divided by Precision +Pathfinding.Int3.costMagnitude int3.html#costMagnitude Magnitude used for the cost between two nodes. \n\nThe default cost between two nodes can be calculated like this: [code in online documentation]\n\nThis is simply the magnitude, rounded to the nearest integer +Pathfinding.Int3.magnitude int3.html#magnitude Returns the magnitude of the vector. \n\nThe magnitude is the 'length' of the vector from 0,0,0 to this point. Can be used for distance calculations: [code in online documentation] +Pathfinding.Int3.sqrMagnitude int3.html#sqrMagnitude The squared magnitude of the vector. +Pathfinding.Int3.sqrMagnitudeLong int3.html#sqrMagnitudeLong The squared magnitude of the vector. +Pathfinding.Int3.this[int i] int3.html#thisinti +Pathfinding.Int3.x int3.html#x +Pathfinding.Int3.y int3.html#y +Pathfinding.Int3.z int3.html#z +Pathfinding.Int3.zero int3.html#zero +Pathfinding.IntBounds.max intbounds.html#max +Pathfinding.IntBounds.min intbounds.html#min +Pathfinding.IntBounds.size intbounds.html#size +Pathfinding.IntBounds.volume intbounds.html#volume +Pathfinding.IntRect.Area intrect.html#Area +Pathfinding.IntRect.Height intrect.html#Height +Pathfinding.IntRect.Max intrect.html#Max +Pathfinding.IntRect.Min intrect.html#Min +Pathfinding.IntRect.Width intrect.html#Width +Pathfinding.IntRect.xmax intrect.html#xmax +Pathfinding.IntRect.xmin intrect.html#xmin +Pathfinding.IntRect.ymax intrect.html#ymax +Pathfinding.IntRect.ymin intrect.html#ymin +Pathfinding.Jobs.DisposeArena.buffer disposearena.html#buffer +Pathfinding.Jobs.DisposeArena.buffer2 disposearena.html#buffer2 +Pathfinding.Jobs.DisposeArena.buffer3 disposearena.html#buffer3 +Pathfinding.Jobs.DisposeArena.gcHandles disposearena.html#gcHandles +Pathfinding.Jobs.DisposeArena.ptrBuffer disposearena.html#ptrBuffer +Pathfinding.Jobs.DisposeArena.ptrBufferTracked disposearena.html#ptrBufferTracked +Pathfinding.Jobs.IJobExtensions.ManagedActionJob.handle managedactionjob.html#handle +Pathfinding.Jobs.IJobExtensions.ManagedJob.handle managedjob.html#handle +Pathfinding.Jobs.IJobParallelForBatched.allowBoundsChecks ijobparallelforbatched.html#allowBoundsChecks +Pathfinding.Jobs.IndexActionJob.action indexactionjob.html#action +Pathfinding.Jobs.IndexActionJob.length indexactionjob.html#length +Pathfinding.Jobs.JobAND.data joband.html#data +Pathfinding.Jobs.JobAND.result joband.html#result +Pathfinding.Jobs.JobClampHitToRay.commands jobclamphittoray.html#commands +Pathfinding.Jobs.JobClampHitToRay.hits jobclamphittoray.html#hits +Pathfinding.Jobs.JobCopy.from jobcopy.html#from +Pathfinding.Jobs.JobCopy.to jobcopy.html#to +Pathfinding.Jobs.JobCopyHits.hits jobcopyhits.html#hits +Pathfinding.Jobs.JobCopyHits.normals jobcopyhits.html#normals +Pathfinding.Jobs.JobCopyHits.points jobcopyhits.html#points +Pathfinding.Jobs.JobCopyHits.slice jobcopyhits.html#slice +Pathfinding.Jobs.JobCopyRectangle.input jobcopyrectangle.html#input +Pathfinding.Jobs.JobCopyRectangle.inputSlice jobcopyrectangle.html#inputSlice +Pathfinding.Jobs.JobCopyRectangle.output jobcopyrectangle.html#output +Pathfinding.Jobs.JobCopyRectangle.outputSlice jobcopyrectangle.html#outputSlice +Pathfinding.Jobs.JobDependencyAnalyzer.BufferOffset jobdependencyanalyzer.html#BufferOffset Offset to the m_Buffer field inside each NativeArray<T> +Pathfinding.Jobs.JobDependencyAnalyzer.ReflectionData.checkUninitializedRead reflectiondata.html#checkUninitializedRead +Pathfinding.Jobs.JobDependencyAnalyzer.ReflectionData.fieldNames reflectiondata.html#fieldNames +Pathfinding.Jobs.JobDependencyAnalyzer.ReflectionData.fieldOffsets reflectiondata.html#fieldOffsets +Pathfinding.Jobs.JobDependencyAnalyzer.ReflectionData.writes reflectiondata.html#writes +Pathfinding.Jobs.JobDependencyAnalyzer.SpanPtrOffset jobdependencyanalyzer.html#SpanPtrOffset +Pathfinding.Jobs.JobDependencyAnalyzer.reflectionData jobdependencyanalyzer.html#reflectionData +Pathfinding.Jobs.JobDependencyAnalyzerAssociated.jobCounter jobdependencyanalyzerassociated.html#jobCounter +Pathfinding.Jobs.JobDependencyAnalyzerAssociated.tempJobDependencyHashes jobdependencyanalyzerassociated.html#tempJobDependencyHashes +Pathfinding.Jobs.JobDependencyTracker.AllWritesDependency jobdependencytracker.html#AllWritesDependency JobHandle that represents a dependency for all jobs. \n\nAll native arrays that are written (and have been tracked by this tracker) to will have their final results in them when the returned job handle is complete.\n\n[more in online documentation] +Pathfinding.Jobs.JobDependencyTracker.JobInstance.handle jobinstance.html#handle +Pathfinding.Jobs.JobDependencyTracker.JobInstance.hash jobinstance.html#hash +Pathfinding.Jobs.JobDependencyTracker.JobOverlapCapsuleCommandDummy.commands joboverlapcapsulecommanddummy.html#commands +Pathfinding.Jobs.JobDependencyTracker.JobOverlapCapsuleCommandDummy.results joboverlapcapsulecommanddummy.html#results +Pathfinding.Jobs.JobDependencyTracker.JobOverlapSphereCommandDummy.commands joboverlapspherecommanddummy.html#commands +Pathfinding.Jobs.JobDependencyTracker.JobOverlapSphereCommandDummy.results joboverlapspherecommanddummy.html#results +Pathfinding.Jobs.JobDependencyTracker.JobRaycastCommandDummy.commands jobraycastcommanddummy.html#commands +Pathfinding.Jobs.JobDependencyTracker.JobRaycastCommandDummy.results jobraycastcommanddummy.html#results +Pathfinding.Jobs.JobDependencyTracker.JobSpherecastCommandDummy.commands jobspherecastcommanddummy.html#commands +Pathfinding.Jobs.JobDependencyTracker.JobSpherecastCommandDummy.results jobspherecastcommanddummy.html#results +Pathfinding.Jobs.JobDependencyTracker.NativeArraySlot.hasWrite nativearrayslot.html#hasWrite +Pathfinding.Jobs.JobDependencyTracker.NativeArraySlot.hash nativearrayslot.html#hash +Pathfinding.Jobs.JobDependencyTracker.NativeArraySlot.initialized nativearrayslot.html#initialized +Pathfinding.Jobs.JobDependencyTracker.NativeArraySlot.lastReads nativearrayslot.html#lastReads +Pathfinding.Jobs.JobDependencyTracker.NativeArraySlot.lastWrite nativearrayslot.html#lastWrite +Pathfinding.Jobs.JobDependencyTracker.arena jobdependencytracker.html#arena +Pathfinding.Jobs.JobDependencyTracker.dependenciesScratchBuffer jobdependencytracker.html#dependenciesScratchBuffer +Pathfinding.Jobs.JobDependencyTracker.forceLinearDependencies jobdependencytracker.html#forceLinearDependencies +Pathfinding.Jobs.JobDependencyTracker.linearDependencies jobdependencytracker.html#linearDependencies +Pathfinding.Jobs.JobDependencyTracker.slots jobdependencytracker.html#slots +Pathfinding.Jobs.JobDependencyTracker.supportsMultithreading jobdependencytracker.html#supportsMultithreading +Pathfinding.Jobs.JobDependencyTracker.timeSlice jobdependencytracker.html#timeSlice +Pathfinding.Jobs.JobHandleWithMainThreadWork.coroutine jobhandlewithmainthreadwork.html#coroutine +Pathfinding.Jobs.JobHandleWithMainThreadWork.tracker jobhandlewithmainthreadwork.html#tracker +Pathfinding.Jobs.JobMaxHitCount.hits jobmaxhitcount.html#hits +Pathfinding.Jobs.JobMaxHitCount.layerStride jobmaxhitcount.html#layerStride +Pathfinding.Jobs.JobMaxHitCount.maxHitCount jobmaxhitcount.html#maxHitCount +Pathfinding.Jobs.JobMaxHitCount.maxHits jobmaxhitcount.html#maxHits +Pathfinding.Jobs.JobMemSet.data jobmemset.html#data +Pathfinding.Jobs.JobMemSet.value jobmemset.html#value +Pathfinding.Jobs.JobParallelForBatchedExtensions.ParallelForBatchJobStruct.jobReflectionData parallelforbatchjobstruct.html#jobReflectionData +Pathfinding.Jobs.JobRaycastAll.JobCombineResults.maxHits jobcombineresults.html#maxHits +Pathfinding.Jobs.JobRaycastAll.JobCombineResults.results jobcombineresults.html#results +Pathfinding.Jobs.JobRaycastAll.JobCombineResults.semiResults jobcombineresults.html#semiResults +Pathfinding.Jobs.JobRaycastAll.JobCreateCommands.commands jobcreatecommands.html#commands +Pathfinding.Jobs.JobRaycastAll.JobCreateCommands.minStep jobcreatecommands.html#minStep +Pathfinding.Jobs.JobRaycastAll.JobCreateCommands.physicsScene jobcreatecommands.html#physicsScene +Pathfinding.Jobs.JobRaycastAll.JobCreateCommands.raycastHits jobcreatecommands.html#raycastHits +Pathfinding.Jobs.JobRaycastAll.commands jobraycastall.html#commands +Pathfinding.Jobs.JobRaycastAll.maxHits jobraycastall.html#maxHits +Pathfinding.Jobs.JobRaycastAll.minStep jobraycastall.html#minStep +Pathfinding.Jobs.JobRaycastAll.physicsScene jobraycastall.html#physicsScene +Pathfinding.Jobs.JobRaycastAll.results jobraycastall.html#results +Pathfinding.Jobs.JobRaycastAll.semiResults jobraycastall.html#semiResults +Pathfinding.Jobs.JobRotate3DArray.arr jobrotate3darray.html#arr +Pathfinding.Jobs.JobRotate3DArray.dx jobrotate3darray.html#dx +Pathfinding.Jobs.JobRotate3DArray.dz jobrotate3darray.html#dz +Pathfinding.Jobs.JobRotate3DArray.size jobrotate3darray.html#size +Pathfinding.Jobs.LinearDependencies jobs3.html#LinearDependencies +Pathfinding.Jobs.Slice3D.coversEverything slice3d.html#coversEverything True if the slice covers the whole outer array. +Pathfinding.Jobs.Slice3D.innerStrides slice3d.html#innerStrides +Pathfinding.Jobs.Slice3D.int slice3d.html#int +Pathfinding.Jobs.Slice3D.length slice3d.html#length +Pathfinding.Jobs.Slice3D.outerSize slice3d.html#outerSize +Pathfinding.Jobs.Slice3D.outerStartIndex slice3d.html#outerStartIndex +Pathfinding.Jobs.Slice3D.outerStrides slice3d.html#outerStrides +Pathfinding.Jobs.Slice3D.slice slice3d.html#slice +Pathfinding.Jobs.SliceActionJob.action sliceactionjob.html#action +Pathfinding.Jobs.SliceActionJob.slice sliceactionjob.html#slice +Pathfinding.Jobs.TimeSlice.Infinite timeslice.html#Infinite +Pathfinding.Jobs.TimeSlice.endTick timeslice.html#endTick +Pathfinding.Jobs.TimeSlice.expired timeslice.html#expired +Pathfinding.Jobs.TimeSlice.isInfinite timeslice.html#isInfinite +Pathfinding.LayerGridGraph.LayerCount layergridgraph.html#LayerCount Number of layers in the graph. \n\nFor grid graphs this is always 1, for layered grid graphs it can be higher. The nodes array has the size width*depth*layerCount. +Pathfinding.LayerGridGraph.MaxLayers layergridgraph.html#MaxLayers +Pathfinding.LayerGridGraph.characterHeight layergridgraph.html#characterHeight Nodes with a short distance to the node above it will be set unwalkable. +Pathfinding.LayerGridGraph.lastScannedDepth layergridgraph.html#lastScannedDepth +Pathfinding.LayerGridGraph.lastScannedWidth layergridgraph.html#lastScannedWidth +Pathfinding.LayerGridGraph.layerCount layergridgraph.html#layerCount Number of layers. \n\n[more in online documentation] +Pathfinding.LevelGridNode.AllConnectionsMask levelgridnode.html#AllConnectionsMask +Pathfinding.LevelGridNode.AxisAlignedConnectionsMask levelgridnode.html#AxisAlignedConnectionsMask +Pathfinding.LevelGridNode.ConnectionMask levelgridnode.html#ConnectionMask +Pathfinding.LevelGridNode.ConnectionStride levelgridnode.html#ConnectionStride +Pathfinding.LevelGridNode.DiagonalConnectionsMask levelgridnode.html#DiagonalConnectionsMask +Pathfinding.LevelGridNode.HasAnyGridConnections levelgridnode.html#HasAnyGridConnections True if this node has any grid connections. +Pathfinding.LevelGridNode.HasConnectionsToAllAxisAlignedNeighbours levelgridnode.html#HasConnectionsToAllAxisAlignedNeighbours True if the node has grid connections to all its 4 axis-aligned neighbours. \n\n[more in online documentation] +Pathfinding.LevelGridNode.HasConnectionsToAllEightNeighbours levelgridnode.html#HasConnectionsToAllEightNeighbours True if the node has grid connections to all its 8 neighbours. \n\n[more in online documentation] +Pathfinding.LevelGridNode.LayerCoordinateInGrid levelgridnode.html#LayerCoordinateInGrid Layer coordinate of the node in the grid. \n\nIf there are multiple nodes in the same (x,z) cell, then they will be stored in different layers. Together with NodeInGridIndex, you can look up the node in the nodes array [code in online documentation]\n\n[more in online documentation] +Pathfinding.LevelGridNode.MaxLayerCount levelgridnode.html#MaxLayerCount Maximum number of layers the layered grid graph supports. \n\nThis can be changed in the A* Inspector -> Optimizations tab by enabling or disabling the ASTAR_LEVELGRIDNODE_MORE_LAYERS option. +Pathfinding.LevelGridNode.MaxNeighbours levelgridnode.html#MaxNeighbours +Pathfinding.LevelGridNode.NoConnection levelgridnode.html#NoConnection +Pathfinding.LevelGridNode._gridGraphs levelgridnode.html#_gridGraphs +Pathfinding.LevelGridNode.gridConnections levelgridnode.html#gridConnections +Pathfinding.LevelGridNode.gridGraphs levelgridnode.html#gridGraphs +Pathfinding.LinkGraph.LinkGraphUpdatePromise.graph linkgraphupdatepromise.html#graph +Pathfinding.LinkGraph.isScanned linkgraph.html#isScanned True if the graph has been scanned and contains nodes. \n\nGraphs are typically scanned when the game starts, but they can also be scanned manually.\n\nIf a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.\n\n[more in online documentation] +Pathfinding.LinkGraph.nodeCount linkgraph.html#nodeCount +Pathfinding.LinkGraph.nodes linkgraph.html#nodes +Pathfinding.LinkGraph.persistent linkgraph.html#persistent True if the graph will be included when serializing graph data. \n\nIf false, the graph will be ignored when saving graph data.\n\nMost graphs are persistent, but the LinkGraph is not persistent because links are always re-created from components at runtime. +Pathfinding.LinkGraph.showInInspector linkgraph.html#showInInspector True if the graph should be visible in the editor. \n\nFalse is used for some internal graph types that users don't have to worry about. +Pathfinding.LinkNode.linkConcrete linknode.html#linkConcrete +Pathfinding.LinkNode.linkSource linknode.html#linkSource +Pathfinding.LinkNode.nodeInGraphIndex linknode.html#nodeInGraphIndex +Pathfinding.LocalSpaceGraph.graphTransform localspacegraph.html#graphTransform +Pathfinding.LocalSpaceGraph.originalMatrix localspacegraph.html#originalMatrix +Pathfinding.LocalSpaceGraph.transformation localspacegraph.html#transformation +Pathfinding.MeshNode.connections meshnode.html#connections All connections from this node. \n\n[more in online documentation]\n\n\nMay be null if the node has no connections. +Pathfinding.MonoModifier.Order monomodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.MonoModifier.seeker monomodifier.html#seeker +Pathfinding.MoveInCircle.DestinationMoveInCircle.offset destinationmoveincircle.html#offset +Pathfinding.MoveInCircle.DestinationMoveInCircle.radius destinationmoveincircle.html#radius +Pathfinding.MoveInCircle.DestinationMoveInCircle.target destinationmoveincircle.html#target +Pathfinding.MoveInCircle.ai moveincircle.html#ai +Pathfinding.MoveInCircle.offset moveincircle.html#offset Distance between the agent's current position, and the destination it will get. \n\nUse a negative value to make the agent move in the opposite direction around the circle. +Pathfinding.MoveInCircle.radius moveincircle.html#radius Radius of the circle. +Pathfinding.MoveInCircle.target moveincircle.html#target [MoveInCircle] \n\nTarget point to rotate around +Pathfinding.MultiTargetPath.callbacks multitargetpath.html#callbacks Callbacks to call for each individual path. +Pathfinding.MultiTargetPath.chosenTarget multitargetpath.html#chosenTarget The closest target index (if any target was found) +Pathfinding.MultiTargetPath.endPointKnownBeforeCalculation multitargetpath.html#endPointKnownBeforeCalculation True if this path type has a well defined end point, even before calculation starts. \n\nThis is for example true for the ABPath type, but false for the RandomPath type. +Pathfinding.MultiTargetPath.inverted multitargetpath.html#inverted False if the path goes from one point to multiple targets. \n\nTrue if it goes from multiple start points to one target point +Pathfinding.MultiTargetPath.nodePaths multitargetpath.html#nodePaths Stores all paths to the targets. \n\nElements are null if no path was found +Pathfinding.MultiTargetPath.originalTargetPoints multitargetpath.html#originalTargetPoints Target points specified when creating the path. \n\nThese are not snapped to the nearest nodes +Pathfinding.MultiTargetPath.pathsForAll multitargetpath.html#pathsForAll If true, a path to all targets will be returned, otherwise just the one to the closest one. +Pathfinding.MultiTargetPath.targetNodeCount multitargetpath.html#targetNodeCount Number of target nodes left to find. +Pathfinding.MultiTargetPath.targetNodes multitargetpath.html#targetNodes Nearest nodes to the targetPoints. +Pathfinding.MultiTargetPath.targetPathCosts multitargetpath.html#targetPathCosts The cost of the calculated path for each target. \n\nWill be 0 if a path was not found. +Pathfinding.MultiTargetPath.targetPoints multitargetpath.html#targetPoints Target points specified when creating the path. \n\nThese are snapped to the nearest nodes +Pathfinding.MultiTargetPath.targetsFound multitargetpath.html#targetsFound Indicates if the target has been found. \n\nAlso true if the target cannot be reached (is in another area) +Pathfinding.MultiTargetPath.vectorPaths multitargetpath.html#vectorPaths Stores all vector paths to the targets. \n\nElements are null if no path was found +Pathfinding.NNConstraint.Default nnconstraint.html#Default The default NNConstraint. \n\nEquivalent to new NNConstraint (). This NNConstraint has settings which works for most, it only finds walkable nodes and it constrains distance set by A* Inspector -> Settings -> Max Nearest Node Distance\n\n[more in online documentation] +Pathfinding.NNConstraint.None nnconstraint.html#None Returns a constraint which does not filter the results. +Pathfinding.NNConstraint.Walkable nnconstraint.html#Walkable An NNConstraint which filters out unwalkable nodes. \n\nThis is the most commonly used NNConstraint.\n\nIt also constrains the nearest node to be within the distance set by A* Inspector -> Settings -> Max Nearest Node Distance +Pathfinding.NNConstraint.area nnconstraint.html#area Area ID to constrain to. \n\nWill not affect anything if less than 0 (zero) or if constrainArea is false +Pathfinding.NNConstraint.constrainArea nnconstraint.html#constrainArea Only treat nodes in the area area as suitable. \n\nDoes not affect anything if area is less than 0 (zero) +Pathfinding.NNConstraint.constrainDistance nnconstraint.html#constrainDistance Constrain distance to node. \n\nUses distance from AstarPath.maxNearestNodeDistance. If this is false, it will completely ignore the distance limit.\n\nIf there are no suitable nodes within the distance limit then the search will terminate with a null node as a result. \n\n[more in online documentation] +Pathfinding.NNConstraint.constrainTags nnconstraint.html#constrainTags Sets if tags should be constrained. \n\n[more in online documentation] +Pathfinding.NNConstraint.constrainWalkability nnconstraint.html#constrainWalkability Constrain the search to only walkable or unwalkable nodes depending on walkable. +Pathfinding.NNConstraint.distanceMetric nnconstraint.html#distanceMetric Determines how to measure distances to the navmesh. \n\nThe default is a euclidean distance, which works well for most things.\n\n[more in online documentation] +Pathfinding.NNConstraint.distanceXZ nnconstraint.html#distanceXZ if available, do an XZ check instead of checking on all axes. \n\nThe navmesh/recast graph as well as the grid/layered grid graph supports this.\n\nThis can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for: [image in online documentation]\n\n[more in online documentation] +Pathfinding.NNConstraint.graphMask nnconstraint.html#graphMask Graphs treated as valid to search on. \n\nThis is a bitmask meaning that bit 0 specifies whether or not the first graph in the graphs list should be able to be included in the search, bit 1 specifies whether or not the second graph should be included and so on. [code in online documentation][code in online documentation]\n\n[more in online documentation] +Pathfinding.NNConstraint.tags nnconstraint.html#tags Nodes which have any of these tags set are suitable. \n\nThis is a bitmask, i.e bit 0 indicates that tag 0 is good, bit 3 indicates tag 3 is good etc. \n\n[more in online documentation] +Pathfinding.NNConstraint.walkable nnconstraint.html#walkable Only search for walkable or unwalkable nodes if constrainWalkability is enabled. \n\nIf true, only walkable nodes will be searched for, otherwise only unwalkable nodes will be searched for. Does not affect anything if constrainWalkability if false. +Pathfinding.NNConstraintGraphUpdateObjectProxy.graphMask nnconstraintgraphupdateobjectproxy.html#graphMask +Pathfinding.NNConstraintGraphUpdateObjectProxy.graphUpdateObject nnconstraintgraphupdateobjectproxy.html#graphUpdateObject +Pathfinding.NNConstraintPathProxy.distanceMetric nnconstraintpathproxy.html#distanceMetric +Pathfinding.NNConstraintPathProxy.graphMask nnconstraintpathproxy.html#graphMask +Pathfinding.NNConstraintPathProxy.path nnconstraintpathproxy.html#path +Pathfinding.NNConstraintPathProxy.pathID nnconstraintpathproxy.html#pathID +Pathfinding.NNConstraintPathProxy.tags nnconstraintpathproxy.html#tags +Pathfinding.NNInfo.Empty nninfo.html#Empty +Pathfinding.NNInfo.clampedPosition nninfo.html#clampedPosition Closest point on the navmesh. \n\n[more in online documentation] +Pathfinding.NNInfo.distanceCostSqr nninfo.html#distanceCostSqr Cost for picking this node as the closest node. \n\nThis is typically the squared distance from the query point to position.\n\nHowever, it may be different if the NearestNodeConstraint used a different cost function. For example, if NearestNodeConstraint.distanceMetric is DistanceMetric.ClosestAsSeenFromAbove(), then this value will be the squared distance in the XZ plane.\n\nThis value is not guaranteed to be smaller or equal to the squared euclidean distance from the query point to position. In particular for a navmesh/recast graph with a DistanceMetric.ClosestAsSeenFromAboveSoft NearestNodeConstraint it may be slightly greater for some configurations. This is fine because we are only using this value for the rough distance limit performanced by AstarPath.maxNearestNodeDistance, and it's not a problem if it is slightly inaccurate.\n\n[more in online documentation]\nIf node is null, then this value is positive infinity. +Pathfinding.NNInfo.node nninfo.html#node Closest node. \n\nMay be null if there was no node which satisfied all constraints of the search. +Pathfinding.NNInfo.position nninfo.html#position Closest point on the navmesh. \n\nThis is the query position clamped to the closest point on the node.\n\nIf node is null, then this value is (+inf, +inf, +inf). +Pathfinding.NavGraph.SerializedEditorSettings navgraph.html#SerializedEditorSettings +Pathfinding.NavGraph.active navgraph.html#active Reference to the AstarPath object in the scene. +Pathfinding.NavGraph.bounds navgraph.html#bounds World bounding box for the graph. \n\nThis always contains the whole graph.\n\n[more in online documentation]\nIt is ok for a graph type to return an infinitely large bounding box, but this may make some operations less efficient. The point graph will always return an infinitely large bounding box. +Pathfinding.NavGraph.drawGizmos navgraph.html#drawGizmos Enable to draw gizmos in the Unity scene view. \n\nIn the inspector this value corresponds to the state of the 'eye' icon in the top left corner of every graph inspector. +Pathfinding.NavGraph.exists navgraph.html#exists True if the graph exists, false if it has been destroyed. +Pathfinding.NavGraph.graphIndex navgraph.html#graphIndex Index of the graph, used for identification purposes. +Pathfinding.NavGraph.guid navgraph.html#guid Used as an ID of the graph, considered to be unique. \n\n[more in online documentation] +Pathfinding.NavGraph.infoScreenOpen navgraph.html#infoScreenOpen Used in the editor to check if the info screen is open. \n\nShould be inside UNITY_EDITOR only #ifs but just in case anyone tries to serialize a NavGraph instance using Unity, I have left it like this as it would otherwise cause a crash when building. Version 3.0.8.1 was released because of this bug only +Pathfinding.NavGraph.initialPenalty navgraph.html#initialPenalty Default penalty to apply to all nodes. \n\n[more in online documentation] +Pathfinding.NavGraph.isScanned navgraph.html#isScanned True if the graph has been scanned and contains nodes. \n\nGraphs are typically scanned when the game starts, but they can also be scanned manually.\n\nIf a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.\n\n[more in online documentation] +Pathfinding.NavGraph.name navgraph.html#name Name of the graph. \n\nCan be set in the unity editor +Pathfinding.NavGraph.open navgraph.html#open Is the graph open in the editor. +Pathfinding.NavGraph.persistent navgraph.html#persistent True if the graph will be included when serializing graph data. \n\nIf false, the graph will be ignored when saving graph data.\n\nMost graphs are persistent, but the LinkGraph is not persistent because links are always re-created from components at runtime. +Pathfinding.NavGraph.serializedEditorSettings navgraph.html#serializedEditorSettings Used in the Unity editor to store serialized settings for graph inspectors. +Pathfinding.NavGraph.showInInspector navgraph.html#showInInspector True if the graph should be visible in the editor. \n\nFalse is used for some internal graph types that users don't have to worry about. +Pathfinding.NavMeshGraph.MaxTileConnectionEdgeDistance navmeshgraph.html#MaxTileConnectionEdgeDistance Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge. \n\nWhen tiles are connected to each other, the nodes sometimes do not line up perfectly so some allowance must be made to allow tiles that do not match exactly to be connected with each other. +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.cutSettings navmeshgraphscanpromise.html#cutSettings +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.emptyGraph navmeshgraphscanpromise.html#emptyGraph +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.forcedBoundsSize navmeshgraphscanpromise.html#forcedBoundsSize +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.graph navmeshgraphscanpromise.html#graph +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.tileRect navmeshgraphscanpromise.html#tileRect +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.tiles navmeshgraphscanpromise.html#tiles +Pathfinding.NavMeshGraph.NavMeshGraphScanPromise.transform navmeshgraphscanpromise.html#transform +Pathfinding.NavMeshGraph.NavMeshGraphUpdatePromise.graph navmeshgraphupdatepromise.html#graph +Pathfinding.NavMeshGraph.NavMeshGraphUpdatePromise.graphUpdates navmeshgraphupdatepromise.html#graphUpdates +Pathfinding.NavMeshGraph.NavmeshCuttingCharacterRadius navmeshgraph.html#NavmeshCuttingCharacterRadius +Pathfinding.NavMeshGraph.RecalculateNormals navmeshgraph.html#RecalculateNormals Determines how normals are calculated. \n\nDisable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.\n\nBy default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction. The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way. This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh. If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.\n\nIf you for example take a look at the image below. In the upper case then the nodes on the bottom half of the mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option is set to false instead the nodes are connected properly as in the original mesh all normals point outwards. [image in online documentation]\n\nThe default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that has all normals pointing downwards and this option is false, then some methods like PointOnNavmesh will not work correctly as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction. +Pathfinding.NavMeshGraph.TileWorldSizeX navmeshgraph.html#TileWorldSizeX Size of a tile in world units along the X axis. +Pathfinding.NavMeshGraph.TileWorldSizeZ navmeshgraph.html#TileWorldSizeZ Size of a tile in world units along the Z axis. +Pathfinding.NavMeshGraph.bounds navmeshgraph.html#bounds World bounding box for the graph. \n\nThis always contains the whole graph.\n\n[more in online documentation]\nIf no mesh has been assigned, this will return a zero sized bounding box at the origin.\n\n [image in online documentation] +Pathfinding.NavMeshGraph.cachedSourceMeshBoundsMin navmeshgraph.html#cachedSourceMeshBoundsMin Cached bounding box minimum of sourceMesh. \n\nThis is important when the graph has been saved to a file and is later loaded again, but the original mesh does not exist anymore (or has been moved). In that case we still need to be able to find the bounding box since the CalculateTransform method uses it. +Pathfinding.NavMeshGraph.navmeshCuttingCharacterRadius navmeshgraph.html#navmeshCuttingCharacterRadius Radius to use when expanding navmesh cuts. \n\n[more in online documentation] +Pathfinding.NavMeshGraph.offset navmeshgraph.html#offset Offset in world space. +Pathfinding.NavMeshGraph.recalculateNormals navmeshgraph.html#recalculateNormals Determines how normals are calculated. \n\nDisable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.\n\nBy default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction. The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way. This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh. If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.\n\nIf you for example take a look at the image below. In the upper case then the nodes on the bottom half of the mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option is set to false instead the nodes are connected properly as in the original mesh all normals point outwards. [image in online documentation]\n\nThe default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that has all normals pointing downwards and this option is false, then some methods like PointOnNavmesh will not work correctly as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction. +Pathfinding.NavMeshGraph.rotation navmeshgraph.html#rotation Rotation in degrees. +Pathfinding.NavMeshGraph.scale navmeshgraph.html#scale Scale of the graph. +Pathfinding.NavMeshGraph.sourceMesh navmeshgraph.html#sourceMesh Mesh to construct navmesh from. +Pathfinding.NavmeshAdd.Center navmeshadd.html#Center +Pathfinding.NavmeshAdd.GizmoColor navmeshadd.html#GizmoColor +Pathfinding.NavmeshAdd.MeshType navmeshadd.html#MeshType +Pathfinding.NavmeshAdd.center navmeshadd.html#center +Pathfinding.NavmeshAdd.gizmoBuffer navmeshadd.html#gizmoBuffer +Pathfinding.NavmeshAdd.mesh navmeshadd.html#mesh Custom mesh to use. \n\nThe contour(s) of the mesh will be extracted. If you get the "max perturbations" error when cutting with this, check the normals on the mesh. They should all point in the same direction. Try flipping them if that does not help. +Pathfinding.NavmeshAdd.meshScale navmeshadd.html#meshScale +Pathfinding.NavmeshAdd.rectangleSize navmeshadd.html#rectangleSize Size of the rectangle. +Pathfinding.NavmeshAdd.tr navmeshadd.html#tr cached transform component +Pathfinding.NavmeshAdd.tris navmeshadd.html#tris Cached triangles. +Pathfinding.NavmeshAdd.type navmeshadd.html#type +Pathfinding.NavmeshAdd.updateDistance navmeshadd.html#updateDistance Distance between positions to require an update of the navmesh. \n\nA smaller distance gives better accuracy, but requires more updates when moving the object over time, so it is often slower. +Pathfinding.NavmeshAdd.updateRotationDistance navmeshadd.html#updateRotationDistance How many degrees rotation that is required for an update to the navmesh. \n\nShould be between 0 and 180. +Pathfinding.NavmeshAdd.useRotationAndScale navmeshadd.html#useRotationAndScale Includes rotation and scale in calculations. \n\nThis is slower since a lot more matrix multiplications are needed but gives more flexibility. +Pathfinding.NavmeshAdd.verts navmeshadd.html#verts Cached vertices. +Pathfinding.NavmeshBase.LinecastShapeEdgeLookup navmeshbase.html#LinecastShapeEdgeLookup Used to optimize linecasts by precomputing some values. +Pathfinding.NavmeshBase.MaxTileConnectionEdgeDistance navmeshbase.html#MaxTileConnectionEdgeDistance Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge. \n\nWhen tiles are connected to each other, the nodes sometimes do not line up perfectly so some allowance must be made to allow tiles that do not match exactly to be connected with each other. +Pathfinding.NavmeshBase.NavmeshCuttingCharacterRadius navmeshbase.html#NavmeshCuttingCharacterRadius +Pathfinding.NavmeshBase.OnRecalculatedTiles navmeshbase.html#OnRecalculatedTiles Called when tiles have been completely recalculated. \n\nThis is called after scanning the graph and after performing graph updates that completely recalculate tiles (not ones that simply modify e.g penalties). It is not called after NavmeshCut updates. +Pathfinding.NavmeshBase.RecalculateNormals navmeshbase.html#RecalculateNormals Determines how normals are calculated. \n\nDisable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.\n\nBy default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction. The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way. This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh. If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.\n\nIf you for example take a look at the image below. In the upper case then the nodes on the bottom half of the mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option is set to false instead the nodes are connected properly as in the original mesh all normals point outwards. [image in online documentation]\n\nThe default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that has all normals pointing downwards and this option is false, then some methods like PointOnNavmesh will not work correctly as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction. +Pathfinding.NavmeshBase.TileIndexMask navmeshbase.html#TileIndexMask +Pathfinding.NavmeshBase.TileIndexOffset navmeshbase.html#TileIndexOffset +Pathfinding.NavmeshBase.TileWorldSizeX navmeshbase.html#TileWorldSizeX Size of a tile in world units along the X axis. +Pathfinding.NavmeshBase.TileWorldSizeZ navmeshbase.html#TileWorldSizeZ Size of a tile in world units along the Z axis. +Pathfinding.NavmeshBase.VertexIndexMask navmeshbase.html#VertexIndexMask +Pathfinding.NavmeshBase.batchNodesToDestroy navmeshbase.html#batchNodesToDestroy List of nodes that are going to be destroyed as part of a batch update. +Pathfinding.NavmeshBase.batchPendingNavmeshCutting navmeshbase.html#batchPendingNavmeshCutting True if the current batch of tile updates requires navmesh cutting to be done. +Pathfinding.NavmeshBase.batchTileUpdate navmeshbase.html#batchTileUpdate Positive if currently updating tiles in a batch. +Pathfinding.NavmeshBase.batchUpdatedTiles navmeshbase.html#batchUpdatedTiles List of tiles updating during batch. +Pathfinding.NavmeshBase.enableNavmeshCutting navmeshbase.html#enableNavmeshCutting Should navmesh cuts affect this graph. \n\n[more in online documentation] +Pathfinding.NavmeshBase.forcedBoundsSize navmeshbase.html#forcedBoundsSize Size of the bounding box. +Pathfinding.NavmeshBase.isScanned navmeshbase.html#isScanned True if the graph has been scanned and contains nodes. \n\nGraphs are typically scanned when the game starts, but they can also be scanned manually.\n\nIf a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.\n\n[more in online documentation] +Pathfinding.NavmeshBase.navmeshUpdateData navmeshbase.html#navmeshUpdateData Handles navmesh cutting. \n\n[more in online documentation] +Pathfinding.NavmeshBase.nearestSearchOnlyXZ navmeshbase.html#nearestSearchOnlyXZ Perform nearest node searches in XZ space only. \n\nRecomended for single-layered environments. Faster but can be inaccurate esp. in multilayered contexts. You should not use this if the graph is rotated since then the XZ plane no longer corresponds to the ground plane.\n\nThis can be important on sloped surfaces. See the image below in which the closest point for each blue point is queried for: [image in online documentation]\n\nYou can also control this using a NearestNodeConstraint.distanceMetric.\n\n[more in online documentation] +Pathfinding.NavmeshBase.nodeRecyclingHashBuffer navmeshbase.html#nodeRecyclingHashBuffer Temporary buffer used in PrepareNodeRecycling. +Pathfinding.NavmeshBase.showMeshOutline navmeshbase.html#showMeshOutline Show an outline of the polygons in the Unity Editor. \n\n [image in online documentation] +Pathfinding.NavmeshBase.showMeshSurface navmeshbase.html#showMeshSurface Show the surface of the navmesh. \n\n [image in online documentation] +Pathfinding.NavmeshBase.showNodeConnections navmeshbase.html#showNodeConnections Show the connections between the polygons in the Unity Editor. \n\n [image in online documentation] +Pathfinding.NavmeshBase.tileXCount navmeshbase.html#tileXCount Number of tiles along the X-axis. +Pathfinding.NavmeshBase.tileZCount navmeshbase.html#tileZCount Number of tiles along the Z-axis. +Pathfinding.NavmeshBase.tiles navmeshbase.html#tiles All tiles. \n\n[more in online documentation] +Pathfinding.NavmeshBase.transform navmeshbase.html#transform Determines how the graph transforms graph space to world space. \n\n[more in online documentation] +Pathfinding.NavmeshClamp.prevNode navmeshclamp.html#prevNode +Pathfinding.NavmeshClamp.prevPos navmeshclamp.html#prevPos +Pathfinding.NavmeshClipper.OnDisableCallback navmeshclipper.html#OnDisableCallback Called every time a NavmeshCut/NavmeshAdd component is disabled. +Pathfinding.NavmeshClipper.OnEnableCallback navmeshclipper.html#OnEnableCallback Called every time a NavmeshCut/NavmeshAdd component is enabled. +Pathfinding.NavmeshClipper.all navmeshclipper.html#all +Pathfinding.NavmeshClipper.allEnabled navmeshclipper.html#allEnabled All navmesh clipper components in the scene. \n\nNot ordered in any particular way. \n\n[more in online documentation] +Pathfinding.NavmeshClipper.graphMask navmeshclipper.html#graphMask Which graphs that are affected by this component. \n\nYou can use this to make a graph ignore a particular navmesh cut altogether.\n\nNote that navmesh cuts can only affect navmesh/recast graphs.\n\nIf you change this field during runtime you must disable the component and enable it again for the changes to be detected.\n\n[more in online documentation] +Pathfinding.NavmeshClipper.listIndex navmeshclipper.html#listIndex +Pathfinding.NavmeshCut.Contour.contour contour.html#contour +Pathfinding.NavmeshCut.Contour.ymax contour.html#ymax +Pathfinding.NavmeshCut.Contour.ymin contour.html#ymin +Pathfinding.NavmeshCut.ContourBurst.endIndex contourburst.html#endIndex +Pathfinding.NavmeshCut.ContourBurst.startIndex contourburst.html#startIndex +Pathfinding.NavmeshCut.ContourBurst.ymax contourburst.html#ymax +Pathfinding.NavmeshCut.ContourBurst.ymin contourburst.html#ymin +Pathfinding.NavmeshCut.GizmoColor navmeshcut.html#GizmoColor +Pathfinding.NavmeshCut.GizmoColor2 navmeshcut.html#GizmoColor2 +Pathfinding.NavmeshCut.MeshType navmeshcut.html#MeshType +Pathfinding.NavmeshCut.RadiusExpansionMode navmeshcut.html#RadiusExpansionMode +Pathfinding.NavmeshCut.center navmeshcut.html#center +Pathfinding.NavmeshCut.circleRadius navmeshcut.html#circleRadius Radius of the circle. +Pathfinding.NavmeshCut.circleResolution navmeshcut.html#circleResolution Number of vertices on the circle. +Pathfinding.NavmeshCut.contourTransformationMatrix navmeshcut.html#contourTransformationMatrix +Pathfinding.NavmeshCut.cutsAddedGeom navmeshcut.html#cutsAddedGeom Cuts geometry added by a NavmeshAdd component. \n\nYou rarely need to change this +Pathfinding.NavmeshCut.edges navmeshcut.html#edges Cached variable, to avoid allocations. +Pathfinding.NavmeshCut.height navmeshcut.html#height The cut will be extruded to this height. +Pathfinding.NavmeshCut.isDual navmeshcut.html#isDual Only makes a split in the navmesh, but does not remove the geometry to make a hole. \n\nThis is slower than a normal cut +Pathfinding.NavmeshCut.lastMesh navmeshcut.html#lastMesh +Pathfinding.NavmeshCut.mesh navmeshcut.html#mesh Custom mesh to use. \n\nThe contour(s) of the mesh will be extracted. If you get the "max perturbations" error when cutting with this, check the normals on the mesh. They should all point in the same direction. Try flipping them if that does not help.\n\nThis mesh should only be a 2D surface, not a volume. +Pathfinding.NavmeshCut.meshContourVertices navmeshcut.html#meshContourVertices +Pathfinding.NavmeshCut.meshContours navmeshcut.html#meshContours +Pathfinding.NavmeshCut.meshScale navmeshcut.html#meshScale Scale of the custom mesh, if used. +Pathfinding.NavmeshCut.pointers navmeshcut.html#pointers Cached variable, to avoid allocations. +Pathfinding.NavmeshCut.radiusExpansionMode navmeshcut.html#radiusExpansionMode If the cut should be expanded by the agent radius or not. \n\nSee RadiusExpansionMode for more details. +Pathfinding.NavmeshCut.rectangleSize navmeshcut.html#rectangleSize Size of the rectangle. +Pathfinding.NavmeshCut.tr navmeshcut.html#tr cached transform component +Pathfinding.NavmeshCut.type navmeshcut.html#type Shape of the cut. +Pathfinding.NavmeshCut.updateDistance navmeshcut.html#updateDistance How much the cut must move before the navmesh is updated. \n\nA smaller distance gives better accuracy, but requires more updates when moving the object over time, so it is often slower.\n\nEven if the graph update itself is fast, having a low value can make agents have to recalculate their paths a lot more often, leading to lower performance. +Pathfinding.NavmeshCut.updateRotationDistance navmeshcut.html#updateRotationDistance How many degrees the object must rotate before the navmesh is updated. \n\nShould be between 0 and 180. +Pathfinding.NavmeshCut.useRotationAndScale navmeshcut.html#useRotationAndScale Includes rotation and scale in calculations. \n\nIf this is disabled, the object's rotation and scale is not taken into account when determining the shape of the cut.\n\nEnabling this is a bit slower, since a lot more matrix multiplications are needed. +Pathfinding.NavmeshCutEditor.MeshTypeOptions navmeshcuteditor.html#MeshTypeOptions +Pathfinding.NavmeshCutJobs.AngleComparator.origin anglecomparator.html#origin +Pathfinding.NavmeshCutJobs.BoxCorners navmeshcutjobs.html#BoxCorners +Pathfinding.NavmeshCutJobs.JobCalculateContour.circleRadius jobcalculatecontour.html#circleRadius +Pathfinding.NavmeshCutJobs.JobCalculateContour.circleResolution jobcalculatecontour.html#circleResolution +Pathfinding.NavmeshCutJobs.JobCalculateContour.height jobcalculatecontour.html#height +Pathfinding.NavmeshCutJobs.JobCalculateContour.localToWorldMatrix jobcalculatecontour.html#localToWorldMatrix +Pathfinding.NavmeshCutJobs.JobCalculateContour.matrix jobcalculatecontour.html#matrix +Pathfinding.NavmeshCutJobs.JobCalculateContour.meshContourVertices jobcalculatecontour.html#meshContourVertices +Pathfinding.NavmeshCutJobs.JobCalculateContour.meshContours jobcalculatecontour.html#meshContours +Pathfinding.NavmeshCutJobs.JobCalculateContour.meshScale jobcalculatecontour.html#meshScale +Pathfinding.NavmeshCutJobs.JobCalculateContour.meshType jobcalculatecontour.html#meshType +Pathfinding.NavmeshCutJobs.JobCalculateContour.outputContours jobcalculatecontour.html#outputContours +Pathfinding.NavmeshCutJobs.JobCalculateContour.outputVertices jobcalculatecontour.html#outputVertices +Pathfinding.NavmeshCutJobs.JobCalculateContour.radiusMargin jobcalculatecontour.html#radiusMargin +Pathfinding.NavmeshCutJobs.JobCalculateContour.rectangleSize jobcalculatecontour.html#rectangleSize +Pathfinding.NavmeshEdges.JobCalculateObstacles.MarkerBBox jobcalculateobstacles.html#MarkerBBox +Pathfinding.NavmeshEdges.JobCalculateObstacles.MarkerCollect jobcalculateobstacles.html#MarkerCollect +Pathfinding.NavmeshEdges.JobCalculateObstacles.MarkerObstacles jobcalculateobstacles.html#MarkerObstacles +Pathfinding.NavmeshEdges.JobCalculateObstacles.MarkerTrace jobcalculateobstacles.html#MarkerTrace +Pathfinding.NavmeshEdges.JobCalculateObstacles.allocationLock jobcalculateobstacles.html#allocationLock +Pathfinding.NavmeshEdges.JobCalculateObstacles.bounds jobcalculateobstacles.html#bounds +Pathfinding.NavmeshEdges.JobCalculateObstacles.dirtyHierarchicalNodes jobcalculateobstacles.html#dirtyHierarchicalNodes +Pathfinding.NavmeshEdges.JobCalculateObstacles.hGraphGC jobcalculateobstacles.html#hGraphGC +Pathfinding.NavmeshEdges.JobCalculateObstacles.obstacleVertexGroups jobcalculateobstacles.html#obstacleVertexGroups +Pathfinding.NavmeshEdges.JobCalculateObstacles.obstacleVertices jobcalculateobstacles.html#obstacleVertices +Pathfinding.NavmeshEdges.JobCalculateObstacles.obstacles jobcalculateobstacles.html#obstacles +Pathfinding.NavmeshEdges.JobRecalculateObstaclesBatchCount navmeshedges.html#JobRecalculateObstaclesBatchCount +Pathfinding.NavmeshEdges.JobResizeObstacles.numHierarchicalNodes jobresizeobstacles.html#numHierarchicalNodes +Pathfinding.NavmeshEdges.JobResizeObstacles.obstacles jobresizeobstacles.html#obstacles +Pathfinding.NavmeshEdges.NavmeshBorderData.hierarhicalNodeData navmeshborderdata.html#hierarhicalNodeData +Pathfinding.NavmeshEdges.NavmeshBorderData.obstacleData navmeshborderdata.html#obstacleData +Pathfinding.NavmeshEdges.allocationLock navmeshedges.html#allocationLock +Pathfinding.NavmeshEdges.gizmoVersion navmeshedges.html#gizmoVersion +Pathfinding.NavmeshEdges.hierarchicalGraph navmeshedges.html#hierarchicalGraph +Pathfinding.NavmeshEdges.obstacleData navmeshedges.html#obstacleData +Pathfinding.NavmeshEdges.rwLock navmeshedges.html#rwLock +Pathfinding.NavmeshPrefab.SerializeJob.output serializejob.html#output +Pathfinding.NavmeshPrefab.SerializeJob.tileMeshesPromise serializejob.html#tileMeshesPromise +Pathfinding.NavmeshPrefab.SerializedOutput.Progress serializedoutput.html#Progress +Pathfinding.NavmeshPrefab.SerializedOutput.arena serializedoutput.html#arena +Pathfinding.NavmeshPrefab.SerializedOutput.data serializedoutput.html#data +Pathfinding.NavmeshPrefab.SerializedOutput.promise serializedoutput.html#promise +Pathfinding.NavmeshPrefab.applyOnStart navmeshprefab.html#applyOnStart If true, the tiles stored in this prefab will be loaded and applied to the first recast graph in the scene when this component is enabled. \n\nIf false, you will have to call the Apply(RecastGraph) method manually.\n\nIf this component is disabled and then enabled again, the tiles will be reloaded. +Pathfinding.NavmeshPrefab.bounds navmeshprefab.html#bounds Bounding box for the navmesh to be stored in this prefab. \n\nShould be a multiple of the tile size of the associated recast graph.\n\n[more in online documentation] +Pathfinding.NavmeshPrefab.removeTilesWhenDisabled navmeshprefab.html#removeTilesWhenDisabled If true, the tiles that this prefab loaded into the graph will be removed when this component is disabled or destroyed. \n\nIf false, the tiles will remain in the graph. +Pathfinding.NavmeshPrefab.serializedNavmesh navmeshprefab.html#serializedNavmesh Reference to the serialized tile data. +Pathfinding.NavmeshPrefab.startHasRun navmeshprefab.html#startHasRun +Pathfinding.NavmeshPrefabEditor.pendingScanProgressId navmeshprefabeditor.html#pendingScanProgressId +Pathfinding.NearestNodeConstraint.None nearestnodeconstraint.html#None Constraint which allows all nodes. +Pathfinding.NearestNodeConstraint.WalkabilityConstraint nearestnodeconstraint.html#WalkabilityConstraint +Pathfinding.NearestNodeConstraint.Walkable nearestnodeconstraint.html#Walkable Constraint which filters out all unwalkable nodes. +Pathfinding.NearestNodeConstraint.allNodesAreSuitable nearestnodeconstraint.html#allNodesAreSuitable True if all nodes are suitable with respect to the constraints set in this struct. \n\nThe Suitable method will return true for all nodes if this is true. +Pathfinding.NearestNodeConstraint.area nearestnodeconstraint.html#area Area ID to constrain to. \n\nWill not affect anything if negative.\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.distanceMetric nearestnodeconstraint.html#distanceMetric Determines how to measure distances to the navmesh. \n\nThe default is a euclidean distance, which works well for most things.\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.filter nearestnodeconstraint.html#filter Allow only nodes which the given filter function returns true for. \n\nThe other constraints on this struct will still apply, in addition to the filter function.\n\n[more in online documentation]\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.graphMask nearestnodeconstraint.html#graphMask Graphs which are traversable. \n\nThis is a bitmask, meaning that bit 0 specifies whether or not the first graph in the graphs list should be included in the search, bit 1 specifies whether or not the second graph should be included, and so on.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.maxDistance nearestnodeconstraint.html#maxDistance The maximum distance to the navmesh to search before the search fails. \n\nThis is the maximum distance from the query point to the closest point on the navmesh. If null (the default), the default limit set on the AstarPath component will be used ( AstarPath.maxNearestNodeDistance).\n\nPositive infinity is a valid value, meaning it will search for the closest node regardless of distance. 0 is also a valid value, but it usually only makes sense with a different distance metric than the default.\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.maxDistanceSqr nearestnodeconstraint.html#maxDistanceSqr Squared maxDistance. \n\nNegative value means to use the default A* limit ( AstarPath.maxNearestNodeDistance). Positive value means to use a custom limit.\n\nTypically you'll modify maxDistance instead of this.\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.tags nearestnodeconstraint.html#tags Set of tags which are traversable. \n\nThis is a bitmask, i.e bit 0 indicates that tag 0 is traversable, bit 3 indicates tag 3 is traversable etc.\n\nBy default all tags are traversable, which corresponds to the value -1.\n\n[code in online documentation][image in online documentation]\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.traversal nearestnodeconstraint.html#traversal Sub-filter with more constraints. +Pathfinding.NearestNodeConstraint.traversalProvider nearestnodeconstraint.html#traversalProvider Allow traversing only nodes that are also traversable by the given ITraversalProvider. \n\nThe other constraints on this struct will still apply, in addition to the ITraversalProvider.\n\n[more in online documentation] +Pathfinding.NearestNodeConstraint.walkable nearestnodeconstraint.html#walkable Constraint to only search only walkable nodes, only unwalkable nodes, or both. \n\n[more in online documentation] +Pathfinding.NodeLink.End nodelink.html#End +Pathfinding.NodeLink.Start nodelink.html#Start +Pathfinding.NodeLink.costFactor nodelink.html#costFactor The connection will be this times harder/slower to traverse. \n\nNote that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether. +Pathfinding.NodeLink.deleteConnection nodelink.html#deleteConnection Delete existing connection instead of adding one. +Pathfinding.NodeLink.end nodelink.html#end End position of the link. +Pathfinding.NodeLink.oneWay nodelink.html#oneWay Make a one-way connection. +Pathfinding.NodeLink2.EndTransform nodelink2.html#EndTransform +Pathfinding.NodeLink2.GizmosColor nodelink2.html#GizmosColor +Pathfinding.NodeLink2.GizmosColorSelected nodelink2.html#GizmosColorSelected +Pathfinding.NodeLink2.StartTransform nodelink2.html#StartTransform +Pathfinding.NodeLink2.costFactor nodelink2.html#costFactor The connection will be this times harder/slower to traverse. \n\nA cost factor of 1 means that the link is equally expensive as moving the same distance on the normal navmesh. But a cost factor greater than 1 means that it is proportionally more expensive.\n\nYou should not use a cost factor less than 1 unless you also change the AstarPath.heuristicScale field (A* Inspector -> Settings -> Pathfinding) to at most the minimum cost factor that you use anywhere in the scene (or disable the heuristic altogether). This is because the pathfinding algorithm assumes that paths are at least as costly as walking just the straight line distance to the target, and if you use a cost factor less than 1, that assumption is no longer true. What then happens is that the pathfinding search may ignore some links because it doesn't even think to search in that direction, even if they would have lead to a lower path cost.\n\n[more in online documentation]\nRead more about this at https://en.wikipedia.org/wiki/Admissible_heuristic. +Pathfinding.NodeLink2.end nodelink2.html#end End position of the link. +Pathfinding.NodeLink2.graphMask nodelink2.html#graphMask Which graphs this link is allowed to connect. \n\nThe link will always connect the nodes closest to the start and end points on the graphs that it is allowed to connect. +Pathfinding.NodeLink2.isActive nodelink2.html#isActive True if the link is connected to the graph. \n\nThis will be true if the link has been successfully connected to the graph, and false if it either has failed, or if the component/gameobject is disabled.\n\nWhen the component is enabled, the link will be scheduled to be added to the graph, it will not take effect immediately. This means that this property will return false until the next time graph updates are processed (usually later this frame, or next frame). To ensure the link is refreshed immediately, you can call AstarPath.active.FlushWorkItems. +Pathfinding.NodeLink2.linkSource nodelink2.html#linkSource +Pathfinding.NodeLink2.onTraverseOffMeshLink nodelink2.html#onTraverseOffMeshLink Callback to be called when an agent starts traversing an off-mesh link. \n\nThe handler will be called when the agent starts traversing an off-mesh link. It allows you to to control the agent for the full duration of the link traversal.\n\nUse the passed context struct to get information about the link and to control the agent.\n\n[code in online documentation]\n\n[more in online documentation]\nYou can alternatively set the corresponding property property on the agent ( FollowerEntity.onTraverseOffMeshLink) to specify a callback for a all off-mesh links.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.NodeLink2.onTraverseOffMeshLinkHandler nodelink2.html#onTraverseOffMeshLinkHandler +Pathfinding.NodeLink2.oneWay nodelink2.html#oneWay Make a one-way connection. +Pathfinding.NodeLink2.pathfindingTag nodelink2.html#pathfindingTag The tag to apply to the link. \n\nThis can be used to exclude certain agents from using the link, or make it more expensive to use.\n\n[more in online documentation] +Pathfinding.NodeLink2Editor.HandlerContent nodelink2editor.html#HandlerContent +Pathfinding.NodeLink3.EndNode nodelink3.html#EndNode +Pathfinding.NodeLink3.EndTransform nodelink3.html#EndTransform +Pathfinding.NodeLink3.GizmosColor nodelink3.html#GizmosColor +Pathfinding.NodeLink3.GizmosColorSelected nodelink3.html#GizmosColorSelected +Pathfinding.NodeLink3.StartNode nodelink3.html#StartNode +Pathfinding.NodeLink3.StartTransform nodelink3.html#StartTransform +Pathfinding.NodeLink3.clamped1 nodelink3.html#clamped1 +Pathfinding.NodeLink3.clamped2 nodelink3.html#clamped2 +Pathfinding.NodeLink3.connectedNode1 nodelink3.html#connectedNode1 +Pathfinding.NodeLink3.connectedNode2 nodelink3.html#connectedNode2 +Pathfinding.NodeLink3.costFactor nodelink3.html#costFactor The connection will be this times harder/slower to traverse. \n\nNote that values lower than one will not always make the pathfinder choose this path instead of another path even though this one should lead to a lower total cost unless you also adjust the Heuristic Scale in A* Inspector -> Settings -> Pathfinding or disable the heuristic altogether. +Pathfinding.NodeLink3.end nodelink3.html#end End position of the link. +Pathfinding.NodeLink3.endNode nodelink3.html#endNode +Pathfinding.NodeLink3.postScanCalled nodelink3.html#postScanCalled +Pathfinding.NodeLink3.reference nodelink3.html#reference +Pathfinding.NodeLink3.startNode nodelink3.html#startNode +Pathfinding.NodeLink3Node.link nodelink3node.html#link +Pathfinding.NodeLink3Node.portalA nodelink3node.html#portalA +Pathfinding.NodeLink3Node.portalB nodelink3node.html#portalB +Pathfinding.NumNeighbours pathfinding.html#NumNeighbours Number of neighbours for a single grid node. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.Anchor.center anchor.html#center Where the link connects to the navmesh. +Pathfinding.OffMeshLinks.Anchor.point1 anchor.html#point1 First point on the segment that makes up this anchor. +Pathfinding.OffMeshLinks.Anchor.point2 anchor.html#point2 Second point on the segment that makes up this anchor. +Pathfinding.OffMeshLinks.Anchor.rotation anchor.html#rotation Rotation that the character should align itself with when traversing the link. +Pathfinding.OffMeshLinks.Anchor.width anchor.html#width Width of the link. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.Directionality offmeshlinks.html#Directionality Determines how a link is connected in the graph. +Pathfinding.OffMeshLinks.OffMeshLinkCombined.concrete offmeshlinkcombined.html#concrete +Pathfinding.OffMeshLinks.OffMeshLinkCombined.source offmeshlinkcombined.html#source +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.component offmeshlinkconcrete.html#component The Component associated with this link. \n\nTypically this will be a NodeLink2 component. But users can also create their own components and fill out this field as appropriate.\n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the component associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.costFactor offmeshlinkconcrete.html#costFactor +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.directionality offmeshlinkconcrete.html#directionality +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.end offmeshlinkconcrete.html#end The end of the link. +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.endLinkNode offmeshlinkconcrete.html#endLinkNode +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.endNodes offmeshlinkconcrete.html#endNodes +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.gameObject offmeshlinkconcrete.html#gameObject The GameObject associated with this link. \n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the GameObject associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.handler offmeshlinkconcrete.html#handler +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.source offmeshlinkconcrete.html#source +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.staleConnections offmeshlinkconcrete.html#staleConnections +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.start offmeshlinkconcrete.html#start The start of the link. +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.startLinkNode offmeshlinkconcrete.html#startLinkNode +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.startNodes offmeshlinkconcrete.html#startNodes +Pathfinding.OffMeshLinks.OffMeshLinkConcrete.tag offmeshlinkconcrete.html#tag Tag to apply to this link. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkSource.bounds offmeshlinksource.html#bounds Bounding box which encapsulates the link and any position on the navmesh it could possibly be connected to. \n\nThis is used to determine which links need to be recalculated when a graph update happens. +Pathfinding.OffMeshLinks.OffMeshLinkSource.component offmeshlinksource.html#component The Component associated with this link. \n\nTypically this will be a NodeLink2 component. But users can also create their own components and fill out this field as appropriate.\n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the component associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkSource.costFactor offmeshlinksource.html#costFactor Multiplies the cost of traversing this link by this amount. +Pathfinding.OffMeshLinks.OffMeshLinkSource.directionality offmeshlinksource.html#directionality +Pathfinding.OffMeshLinks.OffMeshLinkSource.end offmeshlinksource.html#end The end of the link. +Pathfinding.OffMeshLinks.OffMeshLinkSource.gameObject offmeshlinksource.html#gameObject The GameObject associated with this link. \n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the GameObject associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkSource.graphMask offmeshlinksource.html#graphMask Graph mask for which graphs the link is allowed to connect to. \n\nThe link's endpoints will be connected to the closest valid node on any graph that matches the mask. +Pathfinding.OffMeshLinks.OffMeshLinkSource.handler offmeshlinksource.html#handler +Pathfinding.OffMeshLinks.OffMeshLinkSource.maxSnappingDistance offmeshlinksource.html#maxSnappingDistance Maximum distance from the start/end points to the navmesh. \n\nIf the distance is greater than this, the link will not be connected to the navmesh. +Pathfinding.OffMeshLinks.OffMeshLinkSource.start offmeshlinksource.html#start The start of the link. +Pathfinding.OffMeshLinks.OffMeshLinkSource.status offmeshlinksource.html#status +Pathfinding.OffMeshLinks.OffMeshLinkSource.tag offmeshlinksource.html#tag Tag to apply to this link. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkSource.treeKey offmeshlinksource.html#treeKey +Pathfinding.OffMeshLinks.OffMeshLinkStatus offmeshlinks.html#OffMeshLinkStatus +Pathfinding.OffMeshLinks.OffMeshLinkTracer.component offmeshlinktracer.html#component The Component associated with this link. \n\nTypically this will be a NodeLink2 component. But users can also create their own components and fill out this field as appropriate.\n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the component associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkTracer.gameObject offmeshlinktracer.html#gameObject The GameObject associated with this link. \n\nThis field is not used for anything by the pathfinding system itself, it is only used to make it easier for users to find the GameObject associated with a link.\n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkTracer.isReverse offmeshlinktracer.html#isReverse True if the agent is traversing the off-mesh link from original link's end to its start point. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkTracer.link offmeshlinktracer.html#link The off-mesh link that the agent is traversing. \n\n[more in online documentation] +Pathfinding.OffMeshLinks.OffMeshLinkTracer.relativeEnd offmeshlinktracer.html#relativeEnd The end point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent will finish traversing the off-mesh link, regardless of if the link is traversed from start to end or from end to start. +Pathfinding.OffMeshLinks.OffMeshLinkTracer.relativeStart offmeshlinktracer.html#relativeStart The start point of the off-mesh link from the agent's perspective. \n\nThis is the point where the agent starts traversing the off-mesh link, regardless of if the link is traversed from the start to end or from end to start. +Pathfinding.OffMeshLinks.astar offmeshlinks.html#astar +Pathfinding.OffMeshLinks.pendingAdd offmeshlinks.html#pendingAdd +Pathfinding.OffMeshLinks.tree offmeshlinks.html#tree +Pathfinding.OffMeshLinks.updateScheduled offmeshlinks.html#updateScheduled +Pathfinding.OptimizationHandler.DefineDefinition.consistent definedefinition.html#consistent +Pathfinding.OptimizationHandler.DefineDefinition.description definedefinition.html#description +Pathfinding.OptimizationHandler.DefineDefinition.enabled definedefinition.html#enabled +Pathfinding.OptimizationHandler.DefineDefinition.name definedefinition.html#name +Pathfinding.OptimizationHandler.deprecatedBuildTargets optimizationhandler.html#deprecatedBuildTargets Various build targets that Unity have deprecated. \n\nThere is apparently no way to figure out which these are without hard coding them. +Pathfinding.OrientationMode pathfinding.html#OrientationMode Determines which direction the agent moves in. \n\nFor 3D games you most likely want the ZAxisIsForward option as that is the convention for 3D games. For 2D games you most likely want the YAxisIsForward option as that is the convention for 2D games. +Pathfinding.PID.AnglePIDControlOutput2D.positionDelta anglepidcontroloutput2d.html#positionDelta How much to move in a single time-step. \n\nIn world units. +Pathfinding.PID.AnglePIDControlOutput2D.rotationDelta anglepidcontroloutput2d.html#rotationDelta How much to rotate in a single time-step. \n\nIn radians. +Pathfinding.PID.AnglePIDControlOutput2D.targetRotation anglepidcontroloutput2d.html#targetRotation +Pathfinding.PID.AnglePIDController.DampingRatio anglepidcontroller.html#DampingRatio +Pathfinding.PID.PIDMovement.ALLOWED_OVERLAP_FACTOR pidmovement.html#ALLOWED_OVERLAP_FACTOR +Pathfinding.PID.PIDMovement.ControlParams.agentRadius controlparams.html#agentRadius +Pathfinding.PID.PIDMovement.ControlParams.closestOnNavmesh controlparams.html#closestOnNavmesh +Pathfinding.PID.PIDMovement.ControlParams.debugFlags controlparams.html#debugFlags +Pathfinding.PID.PIDMovement.ControlParams.edges controlparams.html#edges +Pathfinding.PID.PIDMovement.ControlParams.endOfPath controlparams.html#endOfPath +Pathfinding.PID.PIDMovement.ControlParams.facingDirectionAtEndOfPath controlparams.html#facingDirectionAtEndOfPath +Pathfinding.PID.PIDMovement.ControlParams.maxDesiredWallDistance controlparams.html#maxDesiredWallDistance +Pathfinding.PID.PIDMovement.ControlParams.movementPlane controlparams.html#movementPlane +Pathfinding.PID.PIDMovement.ControlParams.nextCorner controlparams.html#nextCorner +Pathfinding.PID.PIDMovement.ControlParams.p controlparams.html#p +Pathfinding.PID.PIDMovement.ControlParams.remainingDistance controlparams.html#remainingDistance +Pathfinding.PID.PIDMovement.ControlParams.rotation controlparams.html#rotation +Pathfinding.PID.PIDMovement.ControlParams.speed controlparams.html#speed +Pathfinding.PID.PIDMovement.DESTINATION_CLEARANCE_FACTOR pidmovement.html#DESTINATION_CLEARANCE_FACTOR +Pathfinding.PID.PIDMovement.DebugFlags pidmovement.html#DebugFlags +Pathfinding.PID.PIDMovement.EdgeBuffers.straightRegionEdgesL edgebuffers.html#straightRegionEdgesL +Pathfinding.PID.PIDMovement.EdgeBuffers.straightRegionEdgesR edgebuffers.html#straightRegionEdgesR +Pathfinding.PID.PIDMovement.EdgeBuffers.triangleRegionEdgesL edgebuffers.html#triangleRegionEdgesL +Pathfinding.PID.PIDMovement.EdgeBuffers.triangleRegionEdgesR edgebuffers.html#triangleRegionEdgesR +Pathfinding.PID.PIDMovement.MAX_FRACTION_OF_REMAINING_DISTANCE pidmovement.html#MAX_FRACTION_OF_REMAINING_DISTANCE +Pathfinding.PID.PIDMovement.MarkerConvertObstacles pidmovement.html#MarkerConvertObstacles +Pathfinding.PID.PIDMovement.MarkerOptimizeDirection pidmovement.html#MarkerOptimizeDirection +Pathfinding.PID.PIDMovement.MarkerPID pidmovement.html#MarkerPID +Pathfinding.PID.PIDMovement.MarkerSidewaysAvoidance pidmovement.html#MarkerSidewaysAvoidance +Pathfinding.PID.PIDMovement.MarkerSmallestDistance pidmovement.html#MarkerSmallestDistance +Pathfinding.PID.PIDMovement.OPTIMIZATION_ITERATIONS pidmovement.html#OPTIMIZATION_ITERATIONS +Pathfinding.PID.PIDMovement.PersistentState.maxDesiredWallDistance persistentstate.html#maxDesiredWallDistance +Pathfinding.PID.PIDMovement.STEP_MULTIPLIER pidmovement.html#STEP_MULTIPLIER +Pathfinding.PID.PIDMovement.allowRotatingOnSpot pidmovement.html#allowRotatingOnSpot If rotation on the spot is allowed or not. \n\nWhen the agent wants to turn significantly, enabling this will make it turn on the spot instead of moving in an arc. This can make for more responsive and natural movement for humanoid characters. +Pathfinding.PID.PIDMovement.allowRotatingOnSpotBacking pidmovement.html#allowRotatingOnSpotBacking If rotation on the spot is allowed or not. \n\n1 for allowed, 0 for not allowed.\n\nThat we have to use a byte instead of a boolean is due to a Burst limitation. +Pathfinding.PID.PIDMovement.desiredWallDistance pidmovement.html#desiredWallDistance How big of a distance to try to keep from obstacles. \n\nTypically around 1 or 2 times the agent radius is a good value for this.\n\nTry to avoid making it so large that there might not be enough space for the agent to keep this amount of distance from obstacles. It may start to move less optimally if it is not possible to keep this distance.\n\nThis works well in open spaces, but if your game consists of a lot of tight corridors, a low, or zero value may be better.\n\nThis will be multiplied by the agent's scale to get the actual distance. +Pathfinding.PID.PIDMovement.leadInRadiusWhenApproachingDestination pidmovement.html#leadInRadiusWhenApproachingDestination How wide of a turn to make when approaching a destination for which a desired facing direction has been set. \n\nThe following video shows three agents, one with no facing direction set, and then two agents with varying values of the lead in radius. [video in online documentation]\n\nSetting this to zero will make the agent move directly to the end of the path and rotate on the spot to face the desired facing direction, once it is there.\n\nWhen approaching a destination for which no desired facing direction has been set, this field has no effect.\n\n[more in online documentation]\nThis will be multiplied by the agent's scale to get the actual radius. +Pathfinding.PID.PIDMovement.maxOnSpotRotationSpeed pidmovement.html#maxOnSpotRotationSpeed Maximum rotation speed in degrees per second while rotating on the spot. \n\nOnly used if allowRotatingOnSpot is enabled. +Pathfinding.PID.PIDMovement.maxRotationSpeed pidmovement.html#maxRotationSpeed Maximum rotation speed in degrees per second. \n\nIf the agent would have to rotate faster than this, it will instead slow down to get more time to rotate.\n\nThe agent may want to rotate faster than rotationSpeed if there's not enough space, so that it has to move in a more narrow arc. It may also want to rotate faster if it is very close to its destination and it wants to make sure it ends up on the right spot without any circling.\n\nIt is recommended to keep this at a value slightly larger than rotationSpeed.\n\n[more in online documentation] +Pathfinding.PID.PIDMovement.rotationSpeed pidmovement.html#rotationSpeed Desired rotation speed in degrees per second. \n\nIf the agent is in an open area and gets a new destination directly behind itself, it will start to rotate around with exactly this rotation speed.\n\nThe agent will slow down its rotation speed as it approaches its desired facing direction. So for example, when it is only 90 degrees away from its desired facing direction, it will only rotate with about half this speed.\n\n[more in online documentation] +Pathfinding.PID.PIDMovement.slowdownTime pidmovement.html#slowdownTime Time for the agent to slow down to a complete stop when it approaches the destination point, in seconds. \n\nOne can calculate the deceleration like: speed/slowdownTime (with units m/s^2). +Pathfinding.PID.PIDMovement.slowdownTimeWhenTurningOnSpot pidmovement.html#slowdownTimeWhenTurningOnSpot Time for the agent to slow down to a complete stop when rotating on the spot. \n\nIf set to zero, the agent will instantly stop and start to turn around.\n\nOnly used if allowRotatingOnSpot is enabled. +Pathfinding.PID.PIDMovement.speed pidmovement.html#speed Desired speed of the agent in meters per second. \n\nThis will be multiplied by the agent's scale to get the actual speed. +Pathfinding.Path.CompleteState path.html#CompleteState Current state of the path. +Pathfinding.Path.MarkerOpenCandidateConnectionsToEnd path.html#MarkerOpenCandidateConnectionsToEnd +Pathfinding.Path.MarkerTrace path.html#MarkerTrace +Pathfinding.Path.PipelineState path.html#PipelineState Returns the state of the path in the pathfinding pipeline. +Pathfinding.Path.Pooled path.html#Pooled True if the path is currently pooled. \n\nDo not set this value. Only read. It is used internally.\n\n[more in online documentation] +Pathfinding.Path.SearchContext.OpenCandidateParams.fractionAlongEdge opencandidateparams.html#fractionAlongEdge +Pathfinding.Path.SearchContext.OpenCandidateParams.parentPathNode opencandidateparams.html#parentPathNode +Pathfinding.Path.SearchContext.OpenCandidateParams.pathID opencandidateparams.html#pathID +Pathfinding.Path.SearchContext.OpenCandidateParams.pathNodes opencandidateparams.html#pathNodes +Pathfinding.Path.SearchContext.OpenCandidateParams.targetG opencandidateparams.html#targetG +Pathfinding.Path.SearchContext.OpenCandidateParams.targetNodeIndex opencandidateparams.html#targetNodeIndex +Pathfinding.Path.SearchContext.OpenCandidateParams.targetNodePosition opencandidateparams.html#targetNodePosition +Pathfinding.Path.SearchContext.OpenCandidateParams.targetPathNode opencandidateparams.html#targetPathNode +Pathfinding.Path.SearchContext.heuristicObjective searchcontext.html#heuristicObjective Target to use for H score calculations. \n\n[more in online documentation] +Pathfinding.Path.SearchContext.path searchcontext.html#path +Pathfinding.Path.SearchContext.pathHandler searchcontext.html#pathHandler +Pathfinding.Path.SearchContext.pathID searchcontext.html#pathID +Pathfinding.Path.SearchContext.traversalConstraint searchcontext.html#traversalConstraint +Pathfinding.Path.SearchContext.traversalCosts searchcontext.html#traversalCosts +Pathfinding.Path.callback path.html#callback Callback to call when the path is complete. \n\nThis is usually sent to the Seeker component which post processes the path and then calls a callback to the script which requested the path +Pathfinding.Path.checkedTraversalProviders path.html#checkedTraversalProviders +Pathfinding.Path.claimCount path.html#claimCount +Pathfinding.Path.claimed path.html#claimed List of claims on this path with reference objects. \n\nWe use an array instead of List<T> to avoid an indirection and reduce memory usage a bit (actually has a measureable impact). +Pathfinding.Path.completeState path.html#completeState Backing field for CompleteState. +Pathfinding.Path.duration path.html#duration How long it took to calculate this path in milliseconds. +Pathfinding.Path.enabledTags path.html#enabledTags Which graph tags are traversable. \n\nThis is a bitmask so -1 = all bits set = all tags traversable. For example, to set bit 5 to true, you would do [code in online documentation][code in online documentation]\n\nThe Seeker has a popup field where you can set which tags to use. \n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.Path.error path.html#error If the path failed, this is true. \n\nThis typically happens if there's no valid node close enough to the start point of the path, or if there's no node close enough to the target point that is reachable from the start point. The errorLog will have more information about what happened.\n\n[more in online documentation] +Pathfinding.Path.errorLog path.html#errorLog Additional info on why a path failed. \n\n[more in online documentation] +Pathfinding.Path.hasBeenReset path.html#hasBeenReset True if the Reset function has been called. \n\nUsed to alert users when they are doing something wrong. +Pathfinding.Path.heuristic path.html#heuristic Determines which heuristic to use. +Pathfinding.Path.heuristicScale path.html#heuristicScale Scale of the heuristic values. \n\n[more in online documentation] +Pathfinding.Path.immediateCallback path.html#immediateCallback Immediate callback to call when the path is complete. \n\n[more in online documentation] +Pathfinding.Path.nearestNodeConstraint path.html#nearestNodeConstraint Constraint for how to search for nodes that are traversable by this path. \n\nThis constraint is what the path uses to search for closest nodes to the start and end points.\n\nThis is derived from the traversalConstraint and the nearestNodeDistanceMetric.\n\n[more in online documentation] +Pathfinding.Path.nearestNodeDistanceMetric path.html#nearestNodeDistanceMetric Distance metric to use when searching for the start and end nodes in the graph. \n\nBy default, the euclidean distance metric is used. This allows the path to, for example, find the closest start node to the start point, as measured using "normal" distance. However, you can change this to instead measure distance by ignoring the Y coordinate. This is quite useful for characters, as it will find the node right below the agents feet, even if the agent is slightly above the ground on a slope.\n\n[more in online documentation] +Pathfinding.Path.nnConstraint path.html#nnConstraint Constraint for how to search for nodes. \n\n[more in online documentation] +Pathfinding.Path.path path.html#path Holds the path as a GraphNode list. \n\nThese are all nodes that the path traversed, as calculated by the pathfinding algorithm. This may not be the same nodes as the post processed path traverses.\n\n[more in online documentation] +Pathfinding.Path.pathID path.html#pathID ID of this path. \n\nUsed to distinguish between different paths +Pathfinding.Path.releasedNotSilent path.html#releasedNotSilent True if the path has been released with a non-silent call yet. \n\n[more in online documentation] +Pathfinding.Path.searchedNodes path.html#searchedNodes Number of nodes this path has searched. +Pathfinding.Path.tagPenalties path.html#tagPenalties Penalties for each tag. \n\nTag 0 which is the default tag, will get a penalty of tagPenalties[0]. These should only be non-negative values since the A* algorithm cannot handle negative penalties.\n\nWhen assigning an array to this property it must have a length of 32.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.Path.traversalConstraint path.html#traversalConstraint Constrains which nodes the path can traverse. \n\nBy default, all walkable nodes are traversable. But you can modify this to, for example, only allow nodes with a certain tag to be traversed, or supply completely custom rules by using an ITraversalProvider.\n\n[more in online documentation] +Pathfinding.Path.traversalCosts path.html#traversalCosts Specifies the cost of traversing different nodes. \n\nBy default, nodes have no special costs associated with them. But you can modify this to, for example, make nodes with a certain tag more expensive to traverse, or supply completely custom rules by using an ITraversalProvider.\n\n[more in online documentation] +Pathfinding.Path.traversalProvider path.html#traversalProvider Provides additional traversal information to a path request. \n\n[more in online documentation] +Pathfinding.Path.vectorPath path.html#vectorPath Holds the (possibly post-processed) path as a Vector3 list. \n\nThis list may be modified by path modifiers to be smoother or simpler compared to the raw path generated by the pathfinding algorithm.\n\n[more in online documentation] +Pathfinding.PathCompleteState pathfinding.html#PathCompleteState State of a path request. +Pathfinding.PathEndingCondition.path pathendingcondition.html#path Path which this ending condition is used on. +Pathfinding.PathHandler.DebugStringBuilder pathhandler.html#DebugStringBuilder StringBuilder that paths can use to build debug strings. \n\nBetter for performance and memory usage to use a single StringBuilder instead of each path creating its own +Pathfinding.PathHandler.PathID pathhandler.html#PathID ID for the path currently being calculated or last path that was calculated. +Pathfinding.PathHandler.debugPathNodes pathhandler.html#debugPathNodes +Pathfinding.PathHandler.heap pathhandler.html#heap Binary heap to keep track of nodes on the "Open list". \n\n[more in online documentation] +Pathfinding.PathHandler.nodeStorage pathhandler.html#nodeStorage +Pathfinding.PathHandler.numTemporaryNodes pathhandler.html#numTemporaryNodes +Pathfinding.PathHandler.pathID pathhandler.html#pathID Current PathID. \n\n[more in online documentation] +Pathfinding.PathHandler.pathNodes pathhandler.html#pathNodes Reference to the per-node data for this thread. \n\n[more in online documentation]\nBe careful when storing copies of this array, as it may be re-allocated by the AddTemporaryNode method. +Pathfinding.PathHandler.temporaryNodeStartIndex pathhandler.html#temporaryNodeStartIndex All path nodes with an index greater or equal to this are temporary nodes that only exist for the duration of a single path. \n\nThis is a copy of NodeStorage.nextNodeIndex. This is used to avoid having to access the NodeStorage while pathfinding as it's an extra indirection. +Pathfinding.PathHandler.temporaryNodes pathhandler.html#temporaryNodes +Pathfinding.PathHandler.threadID pathhandler.html#threadID +Pathfinding.PathHandler.totalThreadCount pathhandler.html#totalThreadCount +Pathfinding.PathLog pathfinding.html#PathLog How path results are logged by the system. +Pathfinding.PathModifier.Order pathmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.PathModifier.seeker pathmodifier.html#seeker +Pathfinding.PathNode.Default pathnode.html#Default +Pathfinding.PathNode.Flag1Mask pathnode.html#Flag1Mask +Pathfinding.PathNode.Flag1Offset pathnode.html#Flag1Offset Flag 1 is at bit 30. +Pathfinding.PathNode.Flag2Mask pathnode.html#Flag2Mask +Pathfinding.PathNode.Flag2Offset pathnode.html#Flag2Offset Flag 2 is at bit 31. +Pathfinding.PathNode.FractionAlongEdgeMask pathnode.html#FractionAlongEdgeMask +Pathfinding.PathNode.FractionAlongEdgeOffset pathnode.html#FractionAlongEdgeOffset +Pathfinding.PathNode.FractionAlongEdgeQuantization pathnode.html#FractionAlongEdgeQuantization +Pathfinding.PathNode.ParentIndexMask pathnode.html#ParentIndexMask Parent index uses the first 26 bits. +Pathfinding.PathNode.flag1 pathnode.html#flag1 Use as temporary flag during pathfinding. \n\nPath types can use this during pathfinding to mark nodes. When done, this flag should be reverted to its default state (false) to avoid messing up other pathfinding requests. +Pathfinding.PathNode.flag2 pathnode.html#flag2 Use as temporary flag during pathfinding. \n\nPath types can use this during pathfinding to mark nodes. When done, this flag should be reverted to its default state (false) to avoid messing up other pathfinding requests. +Pathfinding.PathNode.flags pathnode.html#flags Bitpacked variable which stores several fields. +Pathfinding.PathNode.fractionAlongEdge pathnode.html#fractionAlongEdge +Pathfinding.PathNode.heapIndex pathnode.html#heapIndex Index of the node in the binary heap. \n\nThe open list in the A* algorithm is backed by a binary heap. To support fast 'decrease key' operations, the index of the node is saved here. +Pathfinding.PathNode.parentIndex pathnode.html#parentIndex +Pathfinding.PathNode.pathID pathnode.html#pathID The path request (in this thread, if multithreading is used) which last used this node. +Pathfinding.PathProcessor.GraphUpdateLock.Held graphupdatelock.html#Held True while this lock is preventing the pathfinding threads from processing more paths. \n\nNote that the pathfinding threads may not be paused yet (if this lock was obtained using PausePathfinding(false)). +Pathfinding.PathProcessor.GraphUpdateLock.id graphupdatelock.html#id +Pathfinding.PathProcessor.GraphUpdateLock.pathProcessor graphupdatelock.html#pathProcessor +Pathfinding.PathProcessor.IsUsingMultithreading pathprocessor.html#IsUsingMultithreading Returns whether or not multithreading is used. +Pathfinding.PathProcessor.MarkerCalculatePath pathprocessor.html#MarkerCalculatePath +Pathfinding.PathProcessor.MarkerPreparePath pathprocessor.html#MarkerPreparePath +Pathfinding.PathProcessor.NumThreads pathprocessor.html#NumThreads Number of parallel pathfinders. \n\nReturns the number of concurrent processes which can calculate paths at once. When using multithreading, this will be the number of threads, if not using multithreading it is always 1 (since only 1 coroutine is used). \n\n[more in online documentation] +Pathfinding.PathProcessor.OnPathPostSearch pathprocessor.html#OnPathPostSearch +Pathfinding.PathProcessor.OnPathPreSearch pathprocessor.html#OnPathPreSearch +Pathfinding.PathProcessor.OnQueueUnblocked pathprocessor.html#OnQueueUnblocked +Pathfinding.PathProcessor.astar pathprocessor.html#astar +Pathfinding.PathProcessor.coroutineReceiver pathprocessor.html#coroutineReceiver +Pathfinding.PathProcessor.locks pathprocessor.html#locks +Pathfinding.PathProcessor.multithreaded pathprocessor.html#multithreaded +Pathfinding.PathProcessor.nextLockID pathprocessor.html#nextLockID +Pathfinding.PathProcessor.pathHandlers pathprocessor.html#pathHandlers +Pathfinding.PathProcessor.queue pathprocessor.html#queue +Pathfinding.PathProcessor.returnQueue pathprocessor.html#returnQueue +Pathfinding.PathProcessor.threadCoroutine pathprocessor.html#threadCoroutine When no multithreading is used, the IEnumerator is stored here. \n\nWhen no multithreading is used, a coroutine is used instead. It is not directly called with StartCoroutine but a separate function has just a while loop which increments the main IEnumerator. This is done so other functions can step the thread forward at any time, without having to wait for Unity to update it. \n\n[more in online documentation] +Pathfinding.PathProcessor.threads pathprocessor.html#threads References to each of the pathfinding threads. +Pathfinding.PathRequestSettings.Default pathrequestsettings.html#Default A PathRequestSettings instance with default values for all fields. +Pathfinding.PathRequestSettings.graphMask pathrequestsettings.html#graphMask Graphs that this agent can use. \n\nThis field determines which graphs will be considered when searching for the start and end nodes of a path. It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units, or one graph for people and one graph for ships.\n\nThis is a bitmask so if you for example want to make the agent only use graph index 3 then you can set this to: [code in online documentation]\n\n[more in online documentation]\nNote that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering then this mask may no longer be correct.\n\nIf you know the name of the graph you can use the Pathfinding.GraphMask.FromGraphName method: [code in online documentation]\n\n[more in online documentation] +Pathfinding.PathRequestSettings.tagCostMultipliers pathrequestsettings.html#tagCostMultipliers Multiplier for the cost of moving some distance across a node with a given tag. \n\nThis will be multiplied by the traversed distance in millimeters (see Int3.Precision) to get the final cost. The default value is 1, which means that the cost will be the same as the distance. If you set this to 2, the cost will be twice as high as the distance.\n\nFor example, moving 1 world unit across a node with tag 0 will cost 1000 * tagCostMultipliers[0] units of movement cost.\n\nIf null (the default), all tags will be treated as having a multiplier of 1.\n\n[more in online documentation]\nIn the inspector, this value is displayed as "Cost per world unit", and is shown as multiplied by 1000 ( Int3.Precision), to make it the cost per world unit. While in code, it is a multiplier of the default cost.\n\n [image in online documentation]\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.PathRequestSettings.tagEntryCosts pathrequestsettings.html#tagEntryCosts How much it costs to enter a node with a given tag. \n\nThis can be used to make agents avoid, or prefer, nodes with certain tags.\n\nFor example, every time a path enters tag 0 (which is the default tag), it will cost an extra tagEntryCosts[0].\n\nFor most pathfinding purposes, you can think of this as just the additional cost of traversing a node with the given tag. The distinction between entering and exiting a node is a very small one, and will not make a difference in most cases.\n\nThe index in the array corresponds to the tag. All entry costs are positive values since the A* algorithm cannot handle negative costs.\n\nIf null (the default), all tags will be treated as having an entry cost of zero. However, the path will still receive cost from the tagCostMultipliers field.\n\nThis cost will be applied for every node that is entered, regardless of if the previous node had the same tag or not.\n\n[more in online documentation]\n[code in online documentation]\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.PathRequestSettings.tagPenalties pathrequestsettings.html#tagPenalties The penalty for each tag. \n\nIf null, all penalties will be treated as zero. Otherwise, the array should always have a length of exactly 32.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.PathRequestSettings.traversableTags pathrequestsettings.html#traversableTags The tags which this agent can traverse. \n\nThis is a bitmask. Each bit indicates that the agent can traverse nodes with the corresponding tag. If a bit is not set, the agent will treat it as if it is not traversable.\n\nThe default value is -1, which sets all bits, and indicates that the agent can traverse all tags.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.PathRequestSettings.traversalProvider pathrequestsettings.html#traversalProvider Filters which nodes the agent can traverse, and can also add penalties to each traversed node. \n\nIn most common situations, this is left as null (which implies the default traversal provider: DefaultITraversalProvider). But if you need custom pathfinding behavior which cannot be done using the graphMask, tagPenalties and traversableTags, then setting an ITraversalProvider is a great option. It provides you a lot more control over how the pathfinding works.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.PathReturnQueue.OnReturnedPaths pathreturnqueue.html#OnReturnedPaths +Pathfinding.PathReturnQueue.pathReturnQueueReading pathreturnqueue.html#pathReturnQueueReading +Pathfinding.PathReturnQueue.pathReturnQueueWriting pathreturnqueue.html#pathReturnQueueWriting Holds all paths which are waiting to be flagged as completed. \n\n[more in online documentation] +Pathfinding.PathReturnQueue.pathsClaimedSilentlyBy pathreturnqueue.html#pathsClaimedSilentlyBy Paths are claimed silently by some object to prevent them from being recycled while still in use. \n\nThis will be set to the AstarPath object. +Pathfinding.PathState pathfinding.html#PathState Internal state of a path in the pipeline. +Pathfinding.PathTracer.MarkerCalculatePortals pathtracer.html#MarkerCalculatePortals +Pathfinding.PathTracer.MarkerClosest pathtracer.html#MarkerClosest +Pathfinding.PathTracer.MarkerContains pathtracer.html#MarkerContains +Pathfinding.PathTracer.MarkerGetNearest pathtracer.html#MarkerGetNearest +Pathfinding.PathTracer.MarkerSetFunnelState pathtracer.html#MarkerSetFunnelState +Pathfinding.PathTracer.MarkerSimplify pathtracer.html#MarkerSimplify +Pathfinding.PathTracer.NODES_TO_CHECK_FOR_DESTRUCTION pathtracer.html#NODES_TO_CHECK_FOR_DESTRUCTION +Pathfinding.PathTracer.PartGraphType pathtracer.html#PartGraphType Type of graph that the current path part is on. +Pathfinding.PathTracer.QueueItem.distance queueitem.html#distance +Pathfinding.PathTracer.QueueItem.node queueitem.html#node +Pathfinding.PathTracer.QueueItem.parent queueitem.html#parent +Pathfinding.PathTracer.RepairQuality pathtracer.html#RepairQuality +Pathfinding.PathTracer.SplittingCoefficients pathtracer.html#SplittingCoefficients +Pathfinding.PathTracer.TempConnectionLists pathtracer.html#TempConnectionLists +Pathfinding.PathTracer.TempQueues pathtracer.html#TempQueues +Pathfinding.PathTracer.desiredCornersForGoodSimplification pathtracer.html#desiredCornersForGoodSimplification The minimum number of corners to request from GetNextCornerIndices to ensure the path can be simplified well. \n\nThe path simplification algorithm requires at least 2 corners on navmesh graphs, but 3 corners on grid graphs. +Pathfinding.PathTracer.endIsUpToDate pathtracer.html#endIsUpToDate +Pathfinding.PathTracer.endPoint pathtracer.html#endPoint End point of the path. \n\nThis is not necessarily the same as the destination, as this point may be clamped to the graph. +Pathfinding.PathTracer.endPointOfFirstPart pathtracer.html#endPointOfFirstPart End point of the current path part. \n\nIf the path has multiple parts, this is typically the start of an off-mesh link. If the path has only one part, this is the same as endPoint. +Pathfinding.PathTracer.firstPartContainsDestroyedNodes pathtracer.html#firstPartContainsDestroyedNodes If true, the first part contains destroyed nodes. \n\nThis can happen if the graph is updated and some nodes are destroyed.\n\nIf this is true, the path is considered stale and should be recalculated.\n\nThe opposite is not necessarily true. If this is false, the path may still be stale.\n\n[more in online documentation] +Pathfinding.PathTracer.firstPartIndex pathtracer.html#firstPartIndex +Pathfinding.PathTracer.funnelState pathtracer.html#funnelState +Pathfinding.PathTracer.hasPath pathtracer.html#hasPath True if there is a path to follow. +Pathfinding.PathTracer.hasValidEndPoint pathtracer.html#hasValidEndPoint True if the end point has been set to a valid value. \n\nThe agent may be on the navmesh, but without a known destination. This can happen both in the obvious case where no destintion is set, but also if the agent was following a path, and suddenly it can no longer find a path to its destination. Then the agent will stay on the navmesh, but should no longer try to follow its previous path.\n\nMore directly, this happens when calling UpdateEnd with the position argument set to infinity.\n\nThis value being false implies isStale being true. +Pathfinding.PathTracer.isCreated pathtracer.html#isCreated True until Dispose is called. +Pathfinding.PathTracer.isNextPartValidLink pathtracer.html#isNextPartValidLink True if the next part in the path exists, and is a valid link. \n\nThis is true if the path has at least 2 parts and the second part is an off-mesh link.\n\nIf any nodes in the second part have been destroyed, this will return false. +Pathfinding.PathTracer.isStale pathtracer.html#isStale True if the path is stale and should be recalculated as quickly as possible. \n\nThis is true if the path has become invalid (e.g. due to a graph update), or if the destination has changed so much that we don't have a path to the destination at all.\n\nFor performance reasons, the agent tries to avoid checking if nodes have been destroyed unless it needs to access them to calculate its movement. Therefore, if a path is invalidated further ahead, the agent may not realize this until it has moved close enough. +Pathfinding.PathTracer.nodeHashes pathtracer.html#nodeHashes Hashes of some important data for each node, to determine if the node has been invalidated in some way. \n\nFor e.g. the grid graph, this is done using the node's index in the grid. This ensures that the node is counted as invalid if the node is for example moved to the other side of the graph using the ProceduralGraphMover.\n\nFor all nodes, this includes if info about if the node has been destroyed, and if it is walkable.\n\nThis will always have the same length as the nodes array, and the absolute indices in this array will correspond to the absolute indices in the nodes array. +Pathfinding.PathTracer.nodes pathtracer.html#nodes All nodes in the path. +Pathfinding.PathTracer.partCount pathtracer.html#partCount Number of parts in the path. \n\nA part is either a sequence of adjacent nodes, or an off-mesh link. +Pathfinding.PathTracer.partGraphType pathtracer.html#partGraphType The type of graph that the current path part is on. \n\nThis is either a grid-like graph, or a navmesh-like graph. +Pathfinding.PathTracer.parts pathtracer.html#parts +Pathfinding.PathTracer.pathEmptyAndUnrepairable pathtracer.html#pathEmptyAndUnrepairable +Pathfinding.PathTracer.pathEmptyAndUnrepairableBacking pathtracer.html#pathEmptyAndUnrepairableBacking +Pathfinding.PathTracer.portalIsNotInnerCorner pathtracer.html#portalIsNotInnerCorner Indicates if portals are definitely not inner corners, or if they may be. \n\nFor each portal, if bit 0 is set then the left side of the portal is definitely not an inner corner. If bit 1 is set that means the same thing but for the right side of the portal.\n\nShould always have the same length as the portals in funnelState. +Pathfinding.PathTracer.scratchList pathtracer.html#scratchList +Pathfinding.PathTracer.startIsUpToDate pathtracer.html#startIsUpToDate +Pathfinding.PathTracer.startNode pathtracer.html#startNode Current start node of the path. \n\nSince the path is updated every time the agent moves, this will be the node which the agent is inside.\n\nIn case the path has become invalid, this will be set to the closest node to the agent, or if no such node could be found, it will be set to null.\n\n[more in online documentation] +Pathfinding.PathTracer.startNodeInternal pathtracer.html#startNodeInternal +Pathfinding.PathTracer.startPoint pathtracer.html#startPoint Start point of the path. +Pathfinding.PathTracer.tmpLeftPortals pathtracer.html#tmpLeftPortals +Pathfinding.PathTracer.tmpRightPortals pathtracer.html#tmpRightPortals +Pathfinding.PathTracer.traversalConstraint pathtracer.html#traversalConstraint +Pathfinding.PathTracer.traversalCosts pathtracer.html#traversalCosts +Pathfinding.PathTracer.unclampedEndPoint pathtracer.html#unclampedEndPoint +Pathfinding.PathTracer.unclampedStartPoint pathtracer.html#unclampedStartPoint +Pathfinding.PathTracer.version pathtracer.html#version Incremented whenever the path is changed. +Pathfinding.PathUtilities.BFSMap pathutilities.html#BFSMap +Pathfinding.PathUtilities.BFSQueue pathutilities.html#BFSQueue +Pathfinding.PathUtilities.FormationMode pathutilities.html#FormationMode +Pathfinding.PathUtilities.JobFormationPacked.DistanceComparer.positions distancecomparer.html#positions +Pathfinding.PathUtilities.JobFormationPacked.agentRadius jobformationpacked.html#agentRadius +Pathfinding.PathUtilities.JobFormationPacked.destination jobformationpacked.html#destination +Pathfinding.PathUtilities.JobFormationPacked.movementPlane jobformationpacked.html#movementPlane +Pathfinding.PathUtilities.JobFormationPacked.positions jobformationpacked.html#positions +Pathfinding.PathfindingEditorSettings.hasShownWelcomeScreen pathfindingeditorsettings.html#hasShownWelcomeScreen +Pathfinding.PathfindingTag.value pathfindingtag.html#value Underlaying tag value. \n\nShould always be between 0 and GraphNode.MaxTagIndex (inclusive). +Pathfinding.Patrol.agent patrol.html#agent +Pathfinding.Patrol.delay patrol.html#delay Time in seconds to wait at each target. +Pathfinding.Patrol.index patrol.html#index Current target index. +Pathfinding.Patrol.switchTime patrol.html#switchTime +Pathfinding.Patrol.targets patrol.html#targets Target points to move to in order. +Pathfinding.Patrol.updateDestinationEveryFrame patrol.html#updateDestinationEveryFrame If true, the agent's destination will be updated every frame instead of only when switching targets. \n\nThis is good if you have moving targets, but is otherwise unnecessary and slightly slower. +Pathfinding.PointGraph.NodeColor pointgraph.html#NodeColor +Pathfinding.PointGraph.NodeDistanceMode pointgraph.html#NodeDistanceMode Distance query mode. \n\n [image in online documentation]\n\nIn the image above there are a few red nodes. Assume the agent is the orange circle. Using the Node mode the closest point on the graph that would be found would be the node at the bottom center which may not be what you want. Using the Connection mode it will find the closest point on the connection between the two nodes in the top half of the image.\n\nWhen using the Connection option you may also want to use the Connection option for the Seeker's Start End Modifier snapping options. This is not strictly necessary, but it most cases it is what you want.\n\n[more in online documentation] +Pathfinding.PointGraph.PointGraphScanPromise.graph pointgraphscanpromise.html#graph +Pathfinding.PointGraph.PointGraphScanPromise.lookupTree pointgraphscanpromise.html#lookupTree +Pathfinding.PointGraph.PointGraphScanPromise.nodes pointgraphscanpromise.html#nodes +Pathfinding.PointGraph.PointGraphUpdatePromise.graph pointgraphupdatepromise.html#graph +Pathfinding.PointGraph.PointGraphUpdatePromise.graphUpdates pointgraphupdatepromise.html#graphUpdates +Pathfinding.PointGraph.isScanned pointgraph.html#isScanned True if the graph has been scanned and contains nodes. \n\nGraphs are typically scanned when the game starts, but they can also be scanned manually.\n\nIf a graph has not been scanned, it does not contain any nodes and it not possible to use it for pathfinding.\n\n[more in online documentation] +Pathfinding.PointGraph.limits pointgraph.html#limits Max distance along the axis for a connection to be valid. \n\n0 = infinity +Pathfinding.PointGraph.lookupTree pointgraph.html#lookupTree +Pathfinding.PointGraph.mask pointgraph.html#mask Layer mask to use for raycasting. \n\nAll objects included in this layer mask will be treated as obstacles.\n\n[more in online documentation] +Pathfinding.PointGraph.maxDistance pointgraph.html#maxDistance Max distance for a connection to be valid. \n\nThe value 0 (zero) will be read as infinity and thus all nodes not restricted by other constraints will be added as connections.\n\nA negative value will disable any neighbours to be added. It will completely stop the connection processing to be done, so it can save you processing power if you don't these connections. +Pathfinding.PointGraph.maximumConnectionLength pointgraph.html#maximumConnectionLength Longest known connection. \n\nIn squared Int3 units.\n\n[more in online documentation] +Pathfinding.PointGraph.nearestNodeDistanceMode pointgraph.html#nearestNodeDistanceMode Distance query mode. \n\n [image in online documentation]\n\nIn the image above there are a few red nodes. Assume the agent is the orange circle. Using the Node mode the closest point on the graph that would be found would be the node at the bottom center which may not be what you want. Using the Connection mode it will find the closest point on the connection between the two nodes in the top half of the image.\n\nWhen using the Connection option you may also want to use the Connection option for the Seeker's Start End Modifier snapping options. This is not strictly necessary, but it most cases it is what you want.\n\n[more in online documentation]\nIf you enable this during runtime, you will need to call RebuildConnectionDistanceLookup to make sure some cache data is properly recalculated. If the graph doesn't have any nodes yet or if you are going to scan the graph afterwards then you do not need to do this. +Pathfinding.PointGraph.nodeCount pointgraph.html#nodeCount Number of nodes in this graph. +Pathfinding.PointGraph.nodes pointgraph.html#nodes All nodes in this graph. \n\nNote that only the first nodeCount will be non-null.\n\nYou can also use the GetNodes method to get all nodes.\n\nThe order of the nodes is unspecified, and may change when nodes are added or removed. +Pathfinding.PointGraph.optimizeForSparseGraph pointgraph.html#optimizeForSparseGraph Optimizes the graph for sparse graphs. \n\nThis can reduce calculation times for both scanning and for normal path requests by huge amounts. It reduces the number of node-node checks that need to be done during scan, and can also optimize getting the nearest node from the graph (such as when querying for a path).\n\nTry enabling and disabling this option, check the scan times logged when you scan the graph to see if your graph is suited for this optimization or if it makes it slower.\n\nThe gain of using this optimization increases with larger graphs, the default scan algorithm is brute force and requires O(n^2) checks, this optimization along with a graph suited for it, requires only O(n) checks during scan (assuming the connection distance limits are reasonable).\n\n[more in online documentation]\n\n\nIf you enable this during runtime, you need to call RebuildNodeLookup to make this take effect. If you are going to scan the graph afterwards then you do not need to do this. +Pathfinding.PointGraph.raycast pointgraph.html#raycast Use raycasts to filter connections. \n\nIf a hit is detected between two nodes, the connection will not be created. +Pathfinding.PointGraph.recursive pointgraph.html#recursive Recursively search for child nodes to the root. \n\nIf false, all direct children of root will be used as nodes. If true, all children of root and their children (recursively) will be used as nodes. +Pathfinding.PointGraph.root pointgraph.html#root Children of this transform are treated as nodes. \n\nIf null, the searchTag will be used instead. +Pathfinding.PointGraph.searchTag pointgraph.html#searchTag If no root is set, all nodes with the tag is used as nodes. +Pathfinding.PointGraph.thickRaycast pointgraph.html#thickRaycast Use thick raycast. \n\nIf enabled, the collision check shape will not be a line segment, but a capsule with a radius of thickRaycastRadius. +Pathfinding.PointGraph.thickRaycastRadius pointgraph.html#thickRaycastRadius Thick raycast radius. \n\n[more in online documentation] +Pathfinding.PointGraph.use2DPhysics pointgraph.html#use2DPhysics Use the 2D Physics API. +Pathfinding.PointGraphEditor.nearestNodeDistanceModeLabels pointgrapheditor.html#nearestNodeDistanceModeLabels +Pathfinding.PointKDTree.LeafArraySize pointkdtree.html#LeafArraySize +Pathfinding.PointKDTree.LeafSize pointkdtree.html#LeafSize +Pathfinding.PointKDTree.Node.count node2.html#count Number of non-null entries in data. +Pathfinding.PointKDTree.Node.data node2.html#data Nodes in this leaf node (null if not a leaf node) +Pathfinding.PointKDTree.Node.split node2.html#split Split point along the splitAxis if not a leaf node. +Pathfinding.PointKDTree.Node.splitAxis node2.html#splitAxis Axis to split along if not a leaf node (x=0, y=1, z=2) +Pathfinding.PointKDTree.arrayCache pointkdtree.html#arrayCache +Pathfinding.PointKDTree.comparers pointkdtree.html#comparers +Pathfinding.PointKDTree.largeList pointkdtree.html#largeList +Pathfinding.PointKDTree.numNodes pointkdtree.html#numNodes +Pathfinding.PointKDTree.tree pointkdtree.html#tree +Pathfinding.PointNode.connections pointnode.html#connections All connections from this node. \n\n[more in online documentation]\n\n\nThis may be null if the node has no connections to other nodes. +Pathfinding.PointNode.gameObject pointnode.html#gameObject GameObject this node was created from (if any). \n\n[more in online documentation]\n[code in online documentation] +Pathfinding.Polygon.BarycentricTriangleInterpolator.barycentricMapping barycentrictriangleinterpolator.html#barycentricMapping +Pathfinding.Polygon.BarycentricTriangleInterpolator.linear1 barycentrictriangleinterpolator.html#linear1 +Pathfinding.Polygon.BarycentricTriangleInterpolator.linear2 barycentrictriangleinterpolator.html#linear2 +Pathfinding.Polygon.BarycentricTriangleInterpolator.linear3 barycentrictriangleinterpolator.html#linear3 +Pathfinding.Polygon.BarycentricTriangleInterpolator.origin barycentrictriangleinterpolator.html#origin +Pathfinding.Polygon.BarycentricTriangleInterpolator.thresholds barycentrictriangleinterpolator.html#thresholds +Pathfinding.Polygon.BarycentricTriangleInterpolator.ys barycentrictriangleinterpolator.html#ys +Pathfinding.Polygon.cached_Int3_int_dict polygon.html#cached_Int3_int_dict Cached dictionary to avoid excessive allocations. +Pathfinding.Pooling.ArrayPool.MaximumExactArrayLength arraypool.html#MaximumExactArrayLength Maximum length of an array pooled using ClaimWithExactLength. \n\nArrays with lengths longer than this will silently not be pooled. +Pathfinding.Pooling.ArrayPool.containsReferenceTypes arraypool.html#containsReferenceTypes +Pathfinding.Pooling.ArrayPool.exactPool arraypool.html#exactPool +Pathfinding.Pooling.ArrayPool.inPool arraypool.html#inPool +Pathfinding.Pooling.ArrayPool.pool arraypool.html#pool Internal pool. \n\nThe arrays in each bucket have lengths of 2^i +Pathfinding.Pooling.ListPool.LargeThreshold listpool.html#LargeThreshold +Pathfinding.Pooling.ListPool.MaxCapacitySearchLength listpool.html#MaxCapacitySearchLength When requesting a list with a specified capacity, search max this many lists in the pool before giving up. \n\nMust be greater or equal to one. +Pathfinding.Pooling.ListPool.MaxLargePoolSize listpool.html#MaxLargePoolSize +Pathfinding.Pooling.ListPool.inPool listpool.html#inPool +Pathfinding.Pooling.ListPool.largePool listpool.html#largePool +Pathfinding.Pooling.ListPool.pool listpool.html#pool Internal pool. +Pathfinding.Pooling.ObjectPoolSimple.inPool objectpoolsimple.html#inPool +Pathfinding.Pooling.ObjectPoolSimple.pool objectpoolsimple.html#pool Internal pool. +Pathfinding.Pooling.PathPool.pool pathpool.html#pool +Pathfinding.Pooling.PathPool.totalCreated pathpool.html#totalCreated +Pathfinding.Pooling.StackPool.pool stackpool.html#pool Internal pool. +Pathfinding.ProceduralGraphMover.graph proceduralgraphmover.html#graph Graph to update. \n\nThis will be set at Start based on graphIndex. During runtime you may set this to any graph or to null to disable updates. +Pathfinding.ProceduralGraphMover.graphIndex proceduralgraphmover.html#graphIndex Index for the graph to update. \n\nThis will be used at Start to set graph.\n\nThis is an index into the AstarPath.active.data.graphs array. +Pathfinding.ProceduralGraphMover.target proceduralgraphmover.html#target Graph will be moved to follow this target. +Pathfinding.ProceduralGraphMover.updateDistance proceduralgraphmover.html#updateDistance Grid graphs will be updated if the target is more than this number of nodes from the graph center. \n\nNote that this is in nodes, not world units.\n\n[more in online documentation] +Pathfinding.ProceduralGraphMover.updatingGraph proceduralgraphmover.html#updatingGraph True while the graph is being updated by this script. +Pathfinding.ProceduralGridMoverEditor.graphLabels proceduralgridmovereditor.html#graphLabels +Pathfinding.Progress.graphCount progress.html#graphCount +Pathfinding.Progress.graphIndex progress.html#graphIndex +Pathfinding.Progress.progress progress.html#progress Current progress as a value between 0 and 1. +Pathfinding.Progress.stage progress.html#stage +Pathfinding.RVO.AgentDebugFlags rvo2.html#AgentDebugFlags +Pathfinding.RVO.ArbitraryMovementPlane.matrix arbitrarymovementplane.html#matrix Maps from 2D (X, Y, 0) coordinates to world coordinates. +Pathfinding.RVO.ArbitraryMovementPlane.plane arbitrarymovementplane.html#plane +Pathfinding.RVO.IAgent.AgentIndex iagent.html#AgentIndex Internal index of the agent. \n\n[more in online documentation] +Pathfinding.RVO.IAgent.AgentTimeHorizon iagent.html#AgentTimeHorizon Max number of estimated seconds to look into the future for collisions with agents. \n\nAs it turns out, this variable is also very good for controling agent avoidance priorities. Agents with lower values will avoid other agents less and thus you can make 'high priority agents' by giving them a lower value. +Pathfinding.RVO.IAgent.AvoidingAnyAgents iagent.html#AvoidingAnyAgents True if the agent's movement is affected by any other agents or obstacles. \n\nIf the agent is all alone, and can just move in a straight line to its target, this will be false. If it has to adjust its velocity, even slightly, to avoid collisions, this will be true. +Pathfinding.RVO.IAgent.CalculatedEffectivelyReachedDestination iagent.html#CalculatedEffectivelyReachedDestination +Pathfinding.RVO.IAgent.CalculatedSpeed iagent.html#CalculatedSpeed Optimal speed of the agent to avoid collisions. \n\nThe movement script should move towards CalculatedTargetPoint with this speed. +Pathfinding.RVO.IAgent.CalculatedTargetPoint iagent.html#CalculatedTargetPoint Optimal point to move towards to avoid collisions. \n\nThe movement script should move towards this point with a speed of CalculatedSpeed.\n\n[more in online documentation] +Pathfinding.RVO.IAgent.CollidesWith iagent.html#CollidesWith Layer mask specifying which layers this agent will avoid. \n\nYou can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...\n\n[more in online documentation] +Pathfinding.RVO.IAgent.DebugFlags iagent.html#DebugFlags Draw debug information in the scene view. +Pathfinding.RVO.IAgent.DestroyedCallback iagent.html#DestroyedCallback Callback which will be called right the agent is removed from the simulation. \n\nThis agent should not be used anymore after this callback has been called. +Pathfinding.RVO.IAgent.FlowFollowingStrength iagent.html#FlowFollowingStrength Determines how strongly this agent just follows the flow instead of making other agents avoid it. \n\nThe default value is 0, if it is greater than zero (up to the maximum value of 1) other agents will not avoid this character as much. However it works in a different way to Priority.\n\nA group of agents with FlowFollowingStrength set to a high value that all try to reach the same point will end up just settling to stationary positions around that point, none will push the others away to any significant extent. This is tricky to achieve with priorities as priorities are all relative, so setting all agents to a low priority is the same thing as not changing priorities at all.\n\nShould be a value in the range [0, 1]. +Pathfinding.RVO.IAgent.Height iagent.html#Height Height of the agent in world units. \n\nAgents are modelled as circles/cylinders. +Pathfinding.RVO.IAgent.HierarchicalNodeIndex iagent.html#HierarchicalNodeIndex +Pathfinding.RVO.IAgent.Layer iagent.html#Layer Specifies the avoidance layer for this agent. \n\nThe CollidesWith mask on other agents will determine if they will avoid this agent. +Pathfinding.RVO.IAgent.Locked iagent.html#Locked Locked agents will be assumed not to move. +Pathfinding.RVO.IAgent.MaxNeighbours iagent.html#MaxNeighbours Max number of agents to take into account. \n\nDecreasing this value can lead to better performance, increasing it can lead to better quality of the simulation. +Pathfinding.RVO.IAgent.MovementPlane iagent.html#MovementPlane Plane in which the agent moves. \n\nLocal avoidance calculations are always done in 2D and this plane determines how to convert from 3D to 2D.\n\nIn a typical 3D game the agents move in the XZ plane and in a 2D game they move in the XY plane. By default this is set to the XZ plane.\n\n[more in online documentation] +Pathfinding.RVO.IAgent.NeighbourCount iagent.html#NeighbourCount Number of neighbours that the agent took into account during the last simulation step. +Pathfinding.RVO.IAgent.ObstacleTimeHorizon iagent.html#ObstacleTimeHorizon Max number of estimated seconds to look into the future for collisions with obstacles. +Pathfinding.RVO.IAgent.Position iagent.html#Position Position of the agent. \n\nThe agent does not move by itself, a movement script has to be responsible for reading the CalculatedTargetPoint and CalculatedSpeed properties and move towards that point with that speed. This property should ideally be set every frame. +Pathfinding.RVO.IAgent.PreCalculationCallback iagent.html#PreCalculationCallback Callback which will be called right before avoidance calculations are started. \n\nUsed to update the other properties with the most up to date values +Pathfinding.RVO.IAgent.Priority iagent.html#Priority How strongly other agents will avoid this agent. \n\nUsually a value between 0 and 1. Agents with similar priorities will avoid each other with an equal strength. If an agent sees another agent with a higher priority than itself it will avoid that agent more strongly. In the extreme case (e.g this agent has a priority of 0 and the other agent has a priority of 1) it will treat the other agent as being a moving obstacle. Similarly if an agent sees another agent with a lower priority than itself it will avoid that agent less.\n\nIn general the avoidance strength for this agent is: [code in online documentation] +Pathfinding.RVO.IAgent.Radius iagent.html#Radius Radius of the agent in world units. \n\nAgents are modelled as circles/cylinders. +Pathfinding.RVO.IMovementPlaneWrapper.matrix imovementplanewrapper.html#matrix Maps from 2D (X, Y, 0) coordinates to world coordinates. +Pathfinding.RVO.JobDestinationReached.MarkerAlloc jobdestinationreached.html#MarkerAlloc +Pathfinding.RVO.JobDestinationReached.MarkerFirstPass jobdestinationreached.html#MarkerFirstPass +Pathfinding.RVO.JobDestinationReached.MarkerInvert jobdestinationreached.html#MarkerInvert +Pathfinding.RVO.JobDestinationReached.TempAgentData.blockedAndSlow tempagentdata.html#blockedAndSlow +Pathfinding.RVO.JobDestinationReached.TempAgentData.distToEndSq tempagentdata.html#distToEndSq +Pathfinding.RVO.JobDestinationReached.agentData jobdestinationreached.html#agentData +Pathfinding.RVO.JobDestinationReached.draw jobdestinationreached.html#draw +Pathfinding.RVO.JobDestinationReached.numAgents jobdestinationreached.html#numAgents +Pathfinding.RVO.JobDestinationReached.output jobdestinationreached.html#output +Pathfinding.RVO.JobDestinationReached.temporaryAgentData jobdestinationreached.html#temporaryAgentData +Pathfinding.RVO.JobHardCollisions.CollisionStrength jobhardcollisions.html#CollisionStrength How aggressively hard collisions are resolved. \n\nShould be a value between 0 and 1. +Pathfinding.RVO.JobHardCollisions.agentData jobhardcollisions.html#agentData +Pathfinding.RVO.JobHardCollisions.allowBoundsChecks jobhardcollisions.html#allowBoundsChecks +Pathfinding.RVO.JobHardCollisions.collisionVelocityOffsets jobhardcollisions.html#collisionVelocityOffsets +Pathfinding.RVO.JobHardCollisions.deltaTime jobhardcollisions.html#deltaTime +Pathfinding.RVO.JobHardCollisions.enabled jobhardcollisions.html#enabled +Pathfinding.RVO.JobHardCollisions.neighbours jobhardcollisions.html#neighbours +Pathfinding.RVO.JobHorizonAvoidancePhase1.agentData jobhorizonavoidancephase1.html#agentData +Pathfinding.RVO.JobHorizonAvoidancePhase1.allowBoundsChecks jobhorizonavoidancephase1.html#allowBoundsChecks +Pathfinding.RVO.JobHorizonAvoidancePhase1.desiredTargetPointInVelocitySpace jobhorizonavoidancephase1.html#desiredTargetPointInVelocitySpace +Pathfinding.RVO.JobHorizonAvoidancePhase1.draw jobhorizonavoidancephase1.html#draw +Pathfinding.RVO.JobHorizonAvoidancePhase1.horizonAgentData jobhorizonavoidancephase1.html#horizonAgentData +Pathfinding.RVO.JobHorizonAvoidancePhase1.neighbours jobhorizonavoidancephase1.html#neighbours +Pathfinding.RVO.JobHorizonAvoidancePhase2.allowBoundsChecks jobhorizonavoidancephase2.html#allowBoundsChecks +Pathfinding.RVO.JobHorizonAvoidancePhase2.desiredTargetPointInVelocitySpace jobhorizonavoidancephase2.html#desiredTargetPointInVelocitySpace +Pathfinding.RVO.JobHorizonAvoidancePhase2.desiredVelocity jobhorizonavoidancephase2.html#desiredVelocity +Pathfinding.RVO.JobHorizonAvoidancePhase2.horizonAgentData jobhorizonavoidancephase2.html#horizonAgentData +Pathfinding.RVO.JobHorizonAvoidancePhase2.movementPlane jobhorizonavoidancephase2.html#movementPlane +Pathfinding.RVO.JobHorizonAvoidancePhase2.neighbours jobhorizonavoidancephase2.html#neighbours +Pathfinding.RVO.JobHorizonAvoidancePhase2.versions jobhorizonavoidancephase2.html#versions +Pathfinding.RVO.JobRVO.LinearProgram2Output.firstFailedLineIndex linearprogram2output.html#firstFailedLineIndex +Pathfinding.RVO.JobRVO.LinearProgram2Output.velocity linearprogram2output.html#velocity +Pathfinding.RVO.JobRVO.MarkerConvertObstacles1 jobrvo.html#MarkerConvertObstacles1 +Pathfinding.RVO.JobRVO.MarkerConvertObstacles2 jobrvo.html#MarkerConvertObstacles2 +Pathfinding.RVO.JobRVO.MaxObstacleCount jobrvo.html#MaxObstacleCount +Pathfinding.RVO.JobRVO.ORCALine.direction orcaline.html#direction +Pathfinding.RVO.JobRVO.ORCALine.point orcaline.html#point +Pathfinding.RVO.JobRVO.SortByKey.keys sortbykey.html#keys +Pathfinding.RVO.JobRVO.agentData jobrvo.html#agentData +Pathfinding.RVO.JobRVO.allowBoundsChecks jobrvo.html#allowBoundsChecks +Pathfinding.RVO.JobRVO.deltaTime jobrvo.html#deltaTime +Pathfinding.RVO.JobRVO.draw jobrvo.html#draw +Pathfinding.RVO.JobRVO.navmeshEdgeData jobrvo.html#navmeshEdgeData +Pathfinding.RVO.JobRVO.output jobrvo.html#output +Pathfinding.RVO.JobRVO.priorityMultiplier jobrvo.html#priorityMultiplier +Pathfinding.RVO.JobRVO.symmetryBreakingBias jobrvo.html#symmetryBreakingBias +Pathfinding.RVO.JobRVO.temporaryAgentData jobrvo.html#temporaryAgentData +Pathfinding.RVO.JobRVO.useNavmeshAsObstacle jobrvo.html#useNavmeshAsObstacle +Pathfinding.RVO.JobRVOCalculateNeighbours.agentData jobrvocalculateneighbours.html#agentData +Pathfinding.RVO.JobRVOCalculateNeighbours.allowBoundsChecks jobrvocalculateneighbours.html#allowBoundsChecks +Pathfinding.RVO.JobRVOCalculateNeighbours.outNeighbours jobrvocalculateneighbours.html#outNeighbours +Pathfinding.RVO.JobRVOCalculateNeighbours.output jobrvocalculateneighbours.html#output +Pathfinding.RVO.JobRVOCalculateNeighbours.quadtree jobrvocalculateneighbours.html#quadtree +Pathfinding.RVO.JobRVOPreprocess.agentData jobrvopreprocess.html#agentData +Pathfinding.RVO.JobRVOPreprocess.endIndex jobrvopreprocess.html#endIndex +Pathfinding.RVO.JobRVOPreprocess.previousOutput jobrvopreprocess.html#previousOutput +Pathfinding.RVO.JobRVOPreprocess.startIndex jobrvopreprocess.html#startIndex +Pathfinding.RVO.JobRVOPreprocess.temporaryAgentData jobrvopreprocess.html#temporaryAgentData +Pathfinding.RVO.MovementPlane rvo2.html#MovementPlane Plane which movement is primarily happening in. +Pathfinding.RVO.ObstacleType rvo2.html#ObstacleType Type of obstacle shape. \n\n[more in online documentation] +Pathfinding.RVO.ObstacleVertexGroup.boundsMn obstaclevertexgroup.html#boundsMn +Pathfinding.RVO.ObstacleVertexGroup.boundsMx obstaclevertexgroup.html#boundsMx +Pathfinding.RVO.ObstacleVertexGroup.type obstaclevertexgroup.html#type Type of obstacle shape. +Pathfinding.RVO.ObstacleVertexGroup.vertexCount obstaclevertexgroup.html#vertexCount Number of vertices that this group consists of. +Pathfinding.RVO.RVOController.AvoidingAnyAgents rvocontroller.html#AvoidingAnyAgents True if the agent's movement is affected by any other agents or obstacles. \n\nIf the agent is all alone, and can just move in a straight line to its target, this will be false. If it has to adjust its velocity, even slightly, to avoid collisions, this will be true. +Pathfinding.RVO.RVOController.RVOControllerMigrations rvocontroller.html#RVOControllerMigrations +Pathfinding.RVO.RVOController.agentTimeHorizon rvocontroller.html#agentTimeHorizon How far into the future to look for collisions with other agents (in seconds) +Pathfinding.RVO.RVOController.ai rvocontroller.html#ai Cached reference to a movement script (if one is used) +Pathfinding.RVO.RVOController.aiBackingField rvocontroller.html#aiBackingField +Pathfinding.RVO.RVOController.center rvocontroller.html#center Center of the agent relative to the pivot point of this game object. \n\n[more in online documentation] +Pathfinding.RVO.RVOController.centerBackingField rvocontroller.html#centerBackingField +Pathfinding.RVO.RVOController.collidesWith rvocontroller.html#collidesWith Layer mask specifying which layers this agent will avoid. \n\nYou can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...\n\nThis can be very useful in games which have multiple teams of some sort. For example you usually want the agents in one team to avoid each other, but you do not want them to avoid the enemies.\n\nThis field only affects which other agents that this agent will avoid, it does not affect how other agents react to this agent.\n\n[more in online documentation] +Pathfinding.RVO.RVOController.debug rvocontroller.html#debug Enables drawing debug information in the scene view. +Pathfinding.RVO.RVOController.flowFollowingStrength rvocontroller.html#flowFollowingStrength Determines how strongly this agent just follows the flow instead of making other agents avoid it. \n\nThe default value is 0, if it is greater than zero (up to the maximum value of 1) other agents will not avoid this character as much. However it works in a different way to Priority.\n\nA group of agents with FlowFollowingStrength set to a high value that all try to reach the same point will end up just settling to stationary positions around that point, none will push the others away to any significant extent. This is tricky to achieve with priorities as priorities are all relative, so setting all agents to a low priority is the same thing as not changing priorities at all.\n\nShould be a value in the range [0, 1]. +Pathfinding.RVO.RVOController.height rvocontroller.html#height Height of the agent in world units. \n\n[more in online documentation] +Pathfinding.RVO.RVOController.heightBackingField rvocontroller.html#heightBackingField +Pathfinding.RVO.RVOController.layer rvocontroller.html#layer Specifies the avoidance layer for this agent. \n\nThe collidesWith mask on other agents will determine if they will avoid this agent. +Pathfinding.RVO.RVOController.lockWhenNotMoving rvocontroller.html#lockWhenNotMoving Automatically set locked to true when desired velocity is approximately zero. \n\nThis prevents other units from pushing them away when they are supposed to e.g block a choke point.\n\nWhen this is true every call to SetTarget or Move will set the locked field to true if the desired velocity was non-zero or false if it was zero. +Pathfinding.RVO.RVOController.locked rvocontroller.html#locked A locked unit cannot move. \n\nOther units will still avoid it but avoidance quality is not the best. +Pathfinding.RVO.RVOController.maxNeighbours rvocontroller.html#maxNeighbours Max number of other agents to take into account. \n\nA smaller value can reduce CPU load, a higher value can lead to better local avoidance quality. +Pathfinding.RVO.RVOController.movementPlane rvocontroller.html#movementPlane Determines if the XY (2D) or XZ (3D) plane is used for movement. +Pathfinding.RVO.RVOController.movementPlaneBackingField rvocontroller.html#movementPlaneBackingField +Pathfinding.RVO.RVOController.movementPlaneMode rvocontroller.html#movementPlaneMode +Pathfinding.RVO.RVOController.obstacleQuery rvocontroller.html#obstacleQuery +Pathfinding.RVO.RVOController.obstacleTimeHorizon rvocontroller.html#obstacleTimeHorizon How far into the future to look for collisions with obstacles (in seconds) +Pathfinding.RVO.RVOController.position rvocontroller.html#position Current position of the agent. \n\nNote that this is only updated every local avoidance simulation step, not every frame. +Pathfinding.RVO.RVOController.priority rvocontroller.html#priority How strongly other agents will avoid this agent. \n\nUsually a value between 0 and 1. Agents with similar priorities will avoid each other with an equal strength. If an agent sees another agent with a higher priority than itself it will avoid that agent more strongly. In the extreme case (e.g this agent has a priority of 0 and the other agent has a priority of 1) it will treat the other agent as being a moving obstacle. Similarly if an agent sees another agent with a lower priority than itself it will avoid that agent less.\n\nIn general the avoidance strength for this agent is: [code in online documentation] +Pathfinding.RVO.RVOController.priorityMultiplier rvocontroller.html#priorityMultiplier Priority multiplier. \n\nThis functions identically to the priority, however it is not exposed in the Unity inspector. It is primarily used by the Pathfinding.RVO.RVODestinationCrowdedBehavior. +Pathfinding.RVO.RVOController.radius rvocontroller.html#radius Radius of the agent in world units. \n\n[more in online documentation] +Pathfinding.RVO.RVOController.radiusBackingField rvocontroller.html#radiusBackingField +Pathfinding.RVO.RVOController.rvoAgent rvocontroller.html#rvoAgent Reference to the internal agent. +Pathfinding.RVO.RVOController.simulator rvocontroller.html#simulator Reference to the rvo simulator. +Pathfinding.RVO.RVOController.tr rvocontroller.html#tr Cached tranform component. +Pathfinding.RVO.RVOController.velocity rvocontroller.html#velocity Current calculated velocity of the agent. \n\nThis is not necessarily the velocity the agent is actually moving with (that is up to the movement script to decide) but it is the velocity that the RVO system has calculated is best for avoiding obstacles and reaching the target.\n\n[more in online documentation]\nYou can also set the velocity of the agent. This will override the local avoidance input completely. It is useful if you have a player controlled character and want other agents to avoid it.\n\nSetting the velocity using this property will mark the agent as being externally controlled for 1 simulation step. Local avoidance calculations will be skipped for the next simulation step but will be resumed after that unless this property is set again.\n\nNote that if you set the velocity the value that can be read from this property will not change until the next simulation step.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.RVO.RVOController.wallAvoidFalloff rvocontroller.html#wallAvoidFalloff How much the wallAvoidForce decreases with distance. \n\nThe strenght of avoidance is: [code in online documentation]\n\n[more in online documentation] +Pathfinding.RVO.RVOController.wallAvoidForce rvocontroller.html#wallAvoidForce An extra force to avoid walls. \n\nThis can be good way to reduce "wall hugging" behaviour.\n\n[more in online documentation] +Pathfinding.RVO.RVODestinationCrowdedBehavior.DefaultPriority rvodestinationcrowdedbehavior.html#DefaultPriority +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.QueryData.agentDestination querydata.html#agentDestination +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.QueryData.agentIndex querydata.html#agentIndex +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.QueryData.densityThreshold querydata.html#densityThreshold +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentDesiredSpeed jobdensitycheck.html#agentDesiredSpeed +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentOutputSpeed jobdensitycheck.html#agentOutputSpeed +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentOutputTargetPoint jobdensitycheck.html#agentOutputTargetPoint +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentPosition jobdensitycheck.html#agentPosition +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentRadius jobdensitycheck.html#agentRadius +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.agentTargetPoint jobdensitycheck.html#agentTargetPoint +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.allowBoundsChecks jobdensitycheck.html#allowBoundsChecks +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.data jobdensitycheck.html#data +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.deltaTime jobdensitycheck.html#deltaTime +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.outThresholdResult jobdensitycheck.html#outThresholdResult +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.progressAverage jobdensitycheck.html#progressAverage +Pathfinding.RVO.RVODestinationCrowdedBehavior.JobDensityCheck.quadtree jobdensitycheck.html#quadtree +Pathfinding.RVO.RVODestinationCrowdedBehavior.MaximumCirclePackingDensity rvodestinationcrowdedbehavior.html#MaximumCirclePackingDensity See https://en.wikipedia.org/wiki/Circle_packing. +Pathfinding.RVO.RVODestinationCrowdedBehavior.MoveBackPriority rvodestinationcrowdedbehavior.html#MoveBackPriority +Pathfinding.RVO.RVODestinationCrowdedBehavior.StoppedPriority rvodestinationcrowdedbehavior.html#StoppedPriority +Pathfinding.RVO.RVODestinationCrowdedBehavior.densityThreshold rvodestinationcrowdedbehavior.html#densityThreshold The threshold for when to stop. \n\nSee the class description for more info. +Pathfinding.RVO.RVODestinationCrowdedBehavior.enabled rvodestinationcrowdedbehavior.html#enabled Enables or disables this module. +Pathfinding.RVO.RVODestinationCrowdedBehavior.lastJobDensityResult rvodestinationcrowdedbehavior.html#lastJobDensityResult +Pathfinding.RVO.RVODestinationCrowdedBehavior.lastShouldStopDestination rvodestinationcrowdedbehavior.html#lastShouldStopDestination +Pathfinding.RVO.RVODestinationCrowdedBehavior.lastShouldStopResult rvodestinationcrowdedbehavior.html#lastShouldStopResult +Pathfinding.RVO.RVODestinationCrowdedBehavior.progressAverage rvodestinationcrowdedbehavior.html#progressAverage +Pathfinding.RVO.RVODestinationCrowdedBehavior.reachedDestination rvodestinationcrowdedbehavior.html#reachedDestination True if the agent has reached its destination. \n\nIf the agents destination changes this may return false until the next frame. Note that changing the destination every frame may cause this value to never return true.\n\nTrue will be returned if the agent has stopped due to being close enough to the destination. This may be quite some distance away if there are many other agents around the destination.\n\n[more in online documentation] +Pathfinding.RVO.RVODestinationCrowdedBehavior.reachedDestinationPoint rvodestinationcrowdedbehavior.html#reachedDestinationPoint +Pathfinding.RVO.RVODestinationCrowdedBehavior.returnAfterBeingPushedAway rvodestinationcrowdedbehavior.html#returnAfterBeingPushedAway If true, the agent will start to move to the destination again if it determines that it is now less crowded. \n\nIf false and the destination becomes less crowded (or if the agent is pushed away from the destination in some way), then the agent will still stay put. +Pathfinding.RVO.RVODestinationCrowdedBehavior.shouldStopDelayTimer rvodestinationcrowdedbehavior.html#shouldStopDelayTimer +Pathfinding.RVO.RVODestinationCrowdedBehavior.timer1 rvodestinationcrowdedbehavior.html#timer1 +Pathfinding.RVO.RVODestinationCrowdedBehavior.wasEnabled rvodestinationcrowdedbehavior.html#wasEnabled +Pathfinding.RVO.RVODestinationCrowdedBehavior.wasStopped rvodestinationcrowdedbehavior.html#wasStopped +Pathfinding.RVO.RVOLayer rvo2.html#RVOLayer +Pathfinding.RVO.RVONavmesh.wallHeight rvonavmesh.html#wallHeight Height of the walls added for each obstacle edge. \n\nIf a graph contains overlapping regions (e.g multiple floor in a building) you should set this low enough so that edges on different levels do not interfere, but high enough so that agents cannot move over them by mistake. +Pathfinding.RVO.RVOObstacle.ExecuteInEditor rvoobstacle.html#ExecuteInEditor Enable executing in editor to draw gizmos. \n\nIf enabled, the CreateObstacles function will be executed in the editor as well in order to draw gizmos. +Pathfinding.RVO.RVOObstacle.Height rvoobstacle.html#Height +Pathfinding.RVO.RVOObstacle.LocalCoordinates rvoobstacle.html#LocalCoordinates If enabled, all coordinates are handled as local. +Pathfinding.RVO.RVOObstacle.ObstacleVertexWinding rvoobstacle.html#ObstacleVertexWinding RVO Obstacle Modes. \n\nDetermines winding order of obstacle vertices +Pathfinding.RVO.RVOObstacle.StaticObstacle rvoobstacle.html#StaticObstacle Static or dynamic. \n\nThis determines if the obstacle can be updated by e.g moving the transform around in the scene. +Pathfinding.RVO.RVOObstacle.layer rvoobstacle.html#layer +Pathfinding.RVO.RVOObstacle.obstacleMode rvoobstacle.html#obstacleMode Mode of the obstacle. \n\nDetermines winding order of the vertices +Pathfinding.RVO.RVOObstacleCache.MarkerAllocate rvoobstaclecache.html#MarkerAllocate +Pathfinding.RVO.RVOObstacleCache.ObstacleSegment.vertex1 obstaclesegment.html#vertex1 +Pathfinding.RVO.RVOObstacleCache.ObstacleSegment.vertex1LinkId obstaclesegment.html#vertex1LinkId +Pathfinding.RVO.RVOObstacleCache.ObstacleSegment.vertex2 obstaclesegment.html#vertex2 +Pathfinding.RVO.RVOObstacleCache.ObstacleSegment.vertex2LinkId obstaclesegment.html#vertex2LinkId +Pathfinding.RVO.RVOQuadtreeBurst.BitPackingMask rvoquadtreeburst.html#BitPackingMask +Pathfinding.RVO.RVOQuadtreeBurst.BitPackingShift rvoquadtreeburst.html#BitPackingShift +Pathfinding.RVO.RVOQuadtreeBurst.ChildLookup rvoquadtreeburst.html#ChildLookup For a given number, contains the index of the first non-zero bit. \n\nOnly the values 0 through 15 are used when movementPlane is XZ or XY.\n\nUse bytes instead of ints to save some precious L1 cache memory. +Pathfinding.RVO.RVOQuadtreeBurst.DebugDrawJob.draw debugdrawjob.html#draw +Pathfinding.RVO.RVOQuadtreeBurst.DebugDrawJob.quadtree debugdrawjob.html#quadtree +Pathfinding.RVO.RVOQuadtreeBurst.DistanceInfinity rvoquadtreeburst.html#DistanceInfinity A very large distance. \n\nUsed as a sentinel value in the QueryKNearest method. We don't use actual infinity, because the code may be compiled using FastMath, which makes the compiler assume that infinities do not exist. This should be much larger than any distance used in practice. +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.agentPositions jobbuild.html#agentPositions +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.agentRadii jobbuild.html#agentRadii +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.agentSpeeds jobbuild.html#agentSpeeds +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.agentVersions jobbuild.html#agentVersions +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.agents jobbuild.html#agents Length should be greater or equal to agentPositions.Length. +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.movementPlane jobbuild.html#movementPlane +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.numAgents jobbuild.html#numAgents +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outAgentCount jobbuild.html#outAgentCount Should have size 1. +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outAgentPositions jobbuild.html#outAgentPositions +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outAgentRadii jobbuild.html#outAgentRadii +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outArea jobbuild.html#outArea Should have size: InnerNodeCountUpperBound(numAgents) +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outBoundingBox jobbuild.html#outBoundingBox Should have size 2. +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outChildPointers jobbuild.html#outChildPointers Should have size: InnerNodeCountUpperBound(numAgents) +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outMaxRadius jobbuild.html#outMaxRadius Should have size: InnerNodeCountUpperBound(numAgents) +Pathfinding.RVO.RVOQuadtreeBurst.JobBuild.outMaxSpeeds jobbuild.html#outMaxSpeeds Should have size: InnerNodeCountUpperBound(numAgents) +Pathfinding.RVO.RVOQuadtreeBurst.LeafNodeBit rvoquadtreeburst.html#LeafNodeBit +Pathfinding.RVO.RVOQuadtreeBurst.LeafSize rvoquadtreeburst.html#LeafSize +Pathfinding.RVO.RVOQuadtreeBurst.MaxAgents rvoquadtreeburst.html#MaxAgents +Pathfinding.RVO.RVOQuadtreeBurst.MaxDepth rvoquadtreeburst.html#MaxDepth +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.agentRadius quadtreequery.html#agentRadius +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.layerMask quadtreequery.html#layerMask +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.layers quadtreequery.html#layers +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.maxCount quadtreequery.html#maxCount +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.outputStartIndex quadtreequery.html#outputStartIndex +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.position quadtreequery.html#position +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.result quadtreequery.html#result +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.resultDistances quadtreequery.html#resultDistances +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.speed quadtreequery.html#speed +Pathfinding.RVO.RVOQuadtreeBurst.QuadtreeQuery.timeHorizon quadtreequery.html#timeHorizon +Pathfinding.RVO.RVOQuadtreeBurst.agentCountBuffer rvoquadtreeburst.html#agentCountBuffer +Pathfinding.RVO.RVOQuadtreeBurst.agentPositions rvoquadtreeburst.html#agentPositions +Pathfinding.RVO.RVOQuadtreeBurst.agentRadii rvoquadtreeburst.html#agentRadii +Pathfinding.RVO.RVOQuadtreeBurst.agents rvoquadtreeburst.html#agents +Pathfinding.RVO.RVOQuadtreeBurst.boundingBoxBuffer rvoquadtreeburst.html#boundingBoxBuffer +Pathfinding.RVO.RVOQuadtreeBurst.bounds rvoquadtreeburst.html#bounds +Pathfinding.RVO.RVOQuadtreeBurst.childPointers rvoquadtreeburst.html#childPointers +Pathfinding.RVO.RVOQuadtreeBurst.maxRadius rvoquadtreeburst.html#maxRadius +Pathfinding.RVO.RVOQuadtreeBurst.maxSpeeds rvoquadtreeburst.html#maxSpeeds +Pathfinding.RVO.RVOQuadtreeBurst.movementPlane rvoquadtreeburst.html#movementPlane +Pathfinding.RVO.RVOQuadtreeBurst.nodeAreas rvoquadtreeburst.html#nodeAreas +Pathfinding.RVO.RVOSimulator.active rvosimulator.html#active First RVOSimulator in the scene (usually there is only one) +Pathfinding.RVO.RVOSimulator.doubleBuffering rvosimulator.html#doubleBuffering Calculate local avoidance in between frames. \n\nIf this is enabled and multithreading is used, the local avoidance calculations will continue to run until the next frame instead of waiting for them to be done the same frame. This can increase the performance but it can make the agents seem a little less responsive.\n\nThis will only be read at Awake. \n\n[more in online documentation] +Pathfinding.RVO.RVOSimulator.drawQuadtree rvosimulator.html#drawQuadtree +Pathfinding.RVO.RVOSimulator.hardCollisions rvosimulator.html#hardCollisions Prevent agent overlap more aggressively. \n\nThis will it much harder for agents to overlap, even in crowded scenarios. It is particularly noticable when running at a low simulation fps. This does not influence agent avoidance when the agents are not overlapping.\n\nEnabling this has a small performance penalty, usually not high enough to care about.\n\nDisabling this may be beneficial if you want softer behaviour when larger groups of agents collide. +Pathfinding.RVO.RVOSimulator.movementPlane rvosimulator.html#movementPlane Determines if the XY (2D) or XZ (3D) plane is used for movement. \n\nFor 2D games you would set this to XY and for 3D games you would usually set it to XZ. +Pathfinding.RVO.RVOSimulator.simulatorBurst rvosimulator.html#simulatorBurst Reference to the internal simulator. +Pathfinding.RVO.RVOSimulator.symmetryBreakingBias rvosimulator.html#symmetryBreakingBias Bias agents to pass each other on the right side. \n\nIf the desired velocity of an agent puts it on a collision course with another agent or an obstacle its desired velocity will be rotated this number of radians (1 radian is approximately 57°) to the right. This helps to break up symmetries and makes it possible to resolve some situations much faster.\n\nWhen many agents have the same goal this can however have the side effect that the group clustered around the target point may as a whole start to spin around the target point.\n\nRecommended values are in the range of 0 to 0.2.\n\nIf this value is negative, the agents will be biased towards passing each other on the left side instead. +Pathfinding.RVO.RVOSimulator.useNavmeshAsObstacle rvosimulator.html#useNavmeshAsObstacle Allows the local avoidance system to take the edges of the navmesh into account. \n\nThis will make agents try to avoid moving into, and getting pushed into the borders of the navmesh.\n\nThis works best on navmesh/recast graphs, but can also be used on grid graphs.\n\nEnabling this has a performance impact.\n\n[more in online documentation]\nIf you are writing your own movement script, you must call RVOController.SetObstacleQuery every frame for the navmesh obstacle detection to work. +Pathfinding.RVO.RVOSimulator.workerThreads rvosimulator.html#workerThreads Number of RVO worker threads. \n\nIf set to None, no multithreading will be used. Using multithreading can significantly improve performance by offloading work to other CPU cores.\n\n[more in online documentation] +Pathfinding.RVO.RVOSquareObstacle.ExecuteInEditor rvosquareobstacle.html#ExecuteInEditor Enable executing in editor to draw gizmos. \n\nIf enabled, the CreateObstacles function will be executed in the editor as well in order to draw gizmos. +Pathfinding.RVO.RVOSquareObstacle.Height rvosquareobstacle.html#Height +Pathfinding.RVO.RVOSquareObstacle.LocalCoordinates rvosquareobstacle.html#LocalCoordinates If enabled, all coordinates are handled as local. +Pathfinding.RVO.RVOSquareObstacle.StaticObstacle rvosquareobstacle.html#StaticObstacle Static or dynamic. \n\nThis determines if the obstacle can be updated by e.g moving the transform around in the scene. +Pathfinding.RVO.RVOSquareObstacle.center rvosquareobstacle.html#center Center of the square. +Pathfinding.RVO.RVOSquareObstacle.height rvosquareobstacle.html#height Height of the obstacle. +Pathfinding.RVO.RVOSquareObstacle.size rvosquareobstacle.html#size Size of the square. +Pathfinding.RVO.ReachedEndOfPath rvo2.html#ReachedEndOfPath Indicates if the agent has reached the end of its path, or been blocked by other agents. \n\nIn the video below, the agents will get a red ring around them for the Reached state, and a brown ring for the ReachedSoon state.\n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.AgentIndex agent.html#AgentIndex Internal index of the agent. \n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.AgentTimeHorizon agent.html#AgentTimeHorizon Max number of estimated seconds to look into the future for collisions with agents. \n\nAs it turns out, this variable is also very good for controling agent avoidance priorities. Agents with lower values will avoid other agents less and thus you can make 'high priority agents' by giving them a lower value. +Pathfinding.RVO.SimulatorBurst.Agent.AvoidingAnyAgents agent.html#AvoidingAnyAgents True if the agent's movement is affected by any other agents or obstacles. \n\nIf the agent is all alone, and can just move in a straight line to its target, this will be false. If it has to adjust its velocity, even slightly, to avoid collisions, this will be true. +Pathfinding.RVO.SimulatorBurst.Agent.CalculatedEffectivelyReachedDestination agent.html#CalculatedEffectivelyReachedDestination +Pathfinding.RVO.SimulatorBurst.Agent.CalculatedSpeed agent.html#CalculatedSpeed Optimal speed of the agent to avoid collisions. \n\nThe movement script should move towards CalculatedTargetPoint with this speed. +Pathfinding.RVO.SimulatorBurst.Agent.CalculatedTargetPoint agent.html#CalculatedTargetPoint Optimal point to move towards to avoid collisions. \n\nThe movement script should move towards this point with a speed of CalculatedSpeed.\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.CollidesWith agent.html#CollidesWith Layer mask specifying which layers this agent will avoid. \n\nYou can set it as CollidesWith = RVOLayer.DefaultAgent | RVOLayer.Layer3 | RVOLayer.Layer6 ...\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.DebugFlags agent.html#DebugFlags Draw debug information in the scene view. +Pathfinding.RVO.SimulatorBurst.Agent.DestroyedCallback agent.html#DestroyedCallback Callback which will be called right the agent is removed from the simulation. \n\nThis agent should not be used anymore after this callback has been called. +Pathfinding.RVO.SimulatorBurst.Agent.FlowFollowingStrength agent.html#FlowFollowingStrength Determines how strongly this agent just follows the flow instead of making other agents avoid it. \n\nThe default value is 0, if it is greater than zero (up to the maximum value of 1) other agents will not avoid this character as much. However it works in a different way to Priority.\n\nA group of agents with FlowFollowingStrength set to a high value that all try to reach the same point will end up just settling to stationary positions around that point, none will push the others away to any significant extent. This is tricky to achieve with priorities as priorities are all relative, so setting all agents to a low priority is the same thing as not changing priorities at all.\n\nShould be a value in the range [0, 1]. +Pathfinding.RVO.SimulatorBurst.Agent.Height agent.html#Height Height of the agent in world units. \n\nAgents are modelled as circles/cylinders. +Pathfinding.RVO.SimulatorBurst.Agent.HierarchicalNodeIndex agent.html#HierarchicalNodeIndex +Pathfinding.RVO.SimulatorBurst.Agent.Layer agent.html#Layer Specifies the avoidance layer for this agent. \n\nThe CollidesWith mask on other agents will determine if they will avoid this agent. +Pathfinding.RVO.SimulatorBurst.Agent.Locked agent.html#Locked Locked agents will be assumed not to move. +Pathfinding.RVO.SimulatorBurst.Agent.MaxNeighbours agent.html#MaxNeighbours Max number of agents to take into account. \n\nDecreasing this value can lead to better performance, increasing it can lead to better quality of the simulation. +Pathfinding.RVO.SimulatorBurst.Agent.MovementPlane agent.html#MovementPlane Plane in which the agent moves. \n\nLocal avoidance calculations are always done in 2D and this plane determines how to convert from 3D to 2D.\n\nIn a typical 3D game the agents move in the XZ plane and in a 2D game they move in the XY plane. By default this is set to the XZ plane.\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.NeighbourCount agent.html#NeighbourCount Number of neighbours that the agent took into account during the last simulation step. +Pathfinding.RVO.SimulatorBurst.Agent.ObstacleTimeHorizon agent.html#ObstacleTimeHorizon Max number of estimated seconds to look into the future for collisions with obstacles. +Pathfinding.RVO.SimulatorBurst.Agent.Position agent.html#Position Position of the agent. \n\nThe agent does not move by itself, a movement script has to be responsible for reading the CalculatedTargetPoint and CalculatedSpeed properties and move towards that point with that speed. This property should ideally be set every frame. +Pathfinding.RVO.SimulatorBurst.Agent.PreCalculationCallback agent.html#PreCalculationCallback Callback which will be called right before avoidance calculations are started. \n\nUsed to update the other properties with the most up to date values +Pathfinding.RVO.SimulatorBurst.Agent.Priority agent.html#Priority How strongly other agents will avoid this agent. \n\nUsually a value between 0 and 1. Agents with similar priorities will avoid each other with an equal strength. If an agent sees another agent with a higher priority than itself it will avoid that agent more strongly. In the extreme case (e.g this agent has a priority of 0 and the other agent has a priority of 1) it will treat the other agent as being a moving obstacle. Similarly if an agent sees another agent with a lower priority than itself it will avoid that agent less.\n\nIn general the avoidance strength for this agent is: [code in online documentation] +Pathfinding.RVO.SimulatorBurst.Agent.Radius agent.html#Radius Radius of the agent in world units. \n\nAgents are modelled as circles/cylinders. +Pathfinding.RVO.SimulatorBurst.Agent.agentIndex agent.html#agentIndex +Pathfinding.RVO.SimulatorBurst.Agent.simulator agent.html#simulator +Pathfinding.RVO.SimulatorBurst.AgentBounds simulatorburst.html#AgentBounds +Pathfinding.RVO.SimulatorBurst.AgentCount simulatorburst.html#AgentCount Number of agents in the simulation. +Pathfinding.RVO.SimulatorBurst.AgentData.agentObstacleMapping agentdata.html#agentObstacleMapping Which obstacle data in the ObstacleData.obstacles array the agent should use for avoidance. +Pathfinding.RVO.SimulatorBurst.AgentData.agentTimeHorizon agentdata.html#agentTimeHorizon +Pathfinding.RVO.SimulatorBurst.AgentData.allowedVelocityDeviationAngles agentdata.html#allowedVelocityDeviationAngles x = signed left angle in radians, y = signed right angle in radians (should be greater than x) +Pathfinding.RVO.SimulatorBurst.AgentData.collidesWith agentdata.html#collidesWith +Pathfinding.RVO.SimulatorBurst.AgentData.collisionNormal agentdata.html#collisionNormal +Pathfinding.RVO.SimulatorBurst.AgentData.debugFlags agentdata.html#debugFlags +Pathfinding.RVO.SimulatorBurst.AgentData.desiredSpeed agentdata.html#desiredSpeed +Pathfinding.RVO.SimulatorBurst.AgentData.endOfPath agentdata.html#endOfPath +Pathfinding.RVO.SimulatorBurst.AgentData.flowFollowingStrength agentdata.html#flowFollowingStrength +Pathfinding.RVO.SimulatorBurst.AgentData.height agentdata.html#height +Pathfinding.RVO.SimulatorBurst.AgentData.hierarchicalNodeIndex agentdata.html#hierarchicalNodeIndex +Pathfinding.RVO.SimulatorBurst.AgentData.layer agentdata.html#layer +Pathfinding.RVO.SimulatorBurst.AgentData.locked agentdata.html#locked +Pathfinding.RVO.SimulatorBurst.AgentData.manuallyControlled agentdata.html#manuallyControlled +Pathfinding.RVO.SimulatorBurst.AgentData.maxNeighbours agentdata.html#maxNeighbours +Pathfinding.RVO.SimulatorBurst.AgentData.maxSpeed agentdata.html#maxSpeed +Pathfinding.RVO.SimulatorBurst.AgentData.movementPlane agentdata.html#movementPlane +Pathfinding.RVO.SimulatorBurst.AgentData.obstacleTimeHorizon agentdata.html#obstacleTimeHorizon +Pathfinding.RVO.SimulatorBurst.AgentData.position agentdata.html#position +Pathfinding.RVO.SimulatorBurst.AgentData.priority agentdata.html#priority +Pathfinding.RVO.SimulatorBurst.AgentData.radius agentdata.html#radius +Pathfinding.RVO.SimulatorBurst.AgentData.targetPoint agentdata.html#targetPoint +Pathfinding.RVO.SimulatorBurst.AgentData.version agentdata.html#version +Pathfinding.RVO.SimulatorBurst.AgentNeighbourLookup.neighbours agentneighbourlookup.html#neighbours +Pathfinding.RVO.SimulatorBurst.AgentOutputData.blockedByAgents agentoutputdata.html#blockedByAgents +Pathfinding.RVO.SimulatorBurst.AgentOutputData.effectivelyReachedDestination agentoutputdata.html#effectivelyReachedDestination +Pathfinding.RVO.SimulatorBurst.AgentOutputData.forwardClearance agentoutputdata.html#forwardClearance +Pathfinding.RVO.SimulatorBurst.AgentOutputData.numNeighbours agentoutputdata.html#numNeighbours +Pathfinding.RVO.SimulatorBurst.AgentOutputData.speed agentoutputdata.html#speed +Pathfinding.RVO.SimulatorBurst.AgentOutputData.targetPoint agentoutputdata.html#targetPoint +Pathfinding.RVO.SimulatorBurst.HardCollisions simulatorburst.html#HardCollisions Use hard collisions. +Pathfinding.RVO.SimulatorBurst.HorizonAgentData.horizonMaxAngle horizonagentdata.html#horizonMaxAngle +Pathfinding.RVO.SimulatorBurst.HorizonAgentData.horizonMinAngle horizonagentdata.html#horizonMinAngle +Pathfinding.RVO.SimulatorBurst.HorizonAgentData.horizonSide horizonagentdata.html#horizonSide +Pathfinding.RVO.SimulatorBurst.MaxBlockingAgentCount simulatorburst.html#MaxBlockingAgentCount +Pathfinding.RVO.SimulatorBurst.MaxNeighbourCount simulatorburst.html#MaxNeighbourCount +Pathfinding.RVO.SimulatorBurst.MaxObstacleVertices simulatorburst.html#MaxObstacleVertices +Pathfinding.RVO.SimulatorBurst.MovementPlane simulatorburst.html#MovementPlane +Pathfinding.RVO.SimulatorBurst.ObstacleData.obstacleVertexGroups obstacledata.html#obstacleVertexGroups Groups of vertices representing obstacles. \n\nAn obstacle is either a cycle or a chain of vertices +Pathfinding.RVO.SimulatorBurst.ObstacleData.obstacleVertices obstacledata.html#obstacleVertices Vertices of all obstacles. +Pathfinding.RVO.SimulatorBurst.ObstacleData.obstacles obstacledata.html#obstacles Obstacle sets, each one is represented as a set of obstacle vertex groups. +Pathfinding.RVO.SimulatorBurst.SymmetryBreakingBias simulatorburst.html#SymmetryBreakingBias Bias agents to pass each other on the right side. \n\nIf the desired velocity of an agent puts it on a collision course with another agent or an obstacle its desired velocity will be rotated this number of radians (1 radian is approximately 57°) to the right. This helps to break up symmetries and makes it possible to resolve some situations much faster.\n\nWhen many agents have the same goal this can however have the side effect that the group clustered around the target point may as a whole start to spin around the target point.\n\nRecommended values are in the range of 0 to 0.2.\n\nIf this value is negative, the agents will be biased towards passing each other on the left side instead. +Pathfinding.RVO.SimulatorBurst.TemporaryAgentData.collisionVelocityOffsets temporaryagentdata.html#collisionVelocityOffsets +Pathfinding.RVO.SimulatorBurst.TemporaryAgentData.currentVelocity temporaryagentdata.html#currentVelocity +Pathfinding.RVO.SimulatorBurst.TemporaryAgentData.desiredTargetPointInVelocitySpace temporaryagentdata.html#desiredTargetPointInVelocitySpace +Pathfinding.RVO.SimulatorBurst.TemporaryAgentData.desiredVelocity temporaryagentdata.html#desiredVelocity +Pathfinding.RVO.SimulatorBurst.TemporaryAgentData.neighbours temporaryagentdata.html#neighbours +Pathfinding.RVO.SimulatorBurst.UseNavmeshAsObstacle simulatorburst.html#UseNavmeshAsObstacle +Pathfinding.RVO.SimulatorBurst.agentDestroyCallbacks simulatorburst.html#agentDestroyCallbacks +Pathfinding.RVO.SimulatorBurst.agentPreCalculationCallbacks simulatorburst.html#agentPreCalculationCallbacks +Pathfinding.RVO.SimulatorBurst.anyAgentsInSimulation simulatorburst.html#anyAgentsInSimulation +Pathfinding.RVO.SimulatorBurst.debugDrawingScope simulatorburst.html#debugDrawingScope Scope for drawing gizmos even on frames during which the simulation is not running. \n\nThis is used to draw the obstacles, quadtree and agent debug lines. +Pathfinding.RVO.SimulatorBurst.drawQuadtree simulatorburst.html#drawQuadtree +Pathfinding.RVO.SimulatorBurst.freeAgentIndices simulatorburst.html#freeAgentIndices +Pathfinding.RVO.SimulatorBurst.horizonAgentData simulatorburst.html#horizonAgentData +Pathfinding.RVO.SimulatorBurst.movementPlane simulatorburst.html#movementPlane Determines if the XY (2D) or XZ (3D) plane is used for movement. +Pathfinding.RVO.SimulatorBurst.numAgents simulatorburst.html#numAgents Number of agents in this simulation. +Pathfinding.RVO.SimulatorBurst.outputData simulatorburst.html#outputData Internal simulation data. \n\nCan be used if you need very high performance access to the agent data. Normally you would use the SimulatorBurst.Agent class instead (implements the IAgent interface).\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.quadtree simulatorburst.html#quadtree Quadtree for this simulation. \n\nUsed internally by the simulation to perform fast neighbour lookups for each agent. Please only read from this tree, do not rebuild it since that can interfere with the simulation. It is rebuilt when necessary.\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.rwLock simulatorburst.html#rwLock Used to synchronize access to the simulation data. +Pathfinding.RVO.SimulatorBurst.simulationData simulatorburst.html#simulationData Internal simulation data. \n\nCan be used if you need very high performance access to the agent data. Normally you would use the SimulatorBurst.Agent class instead (implements the IAgent interface).\n\n[more in online documentation] +Pathfinding.RVO.SimulatorBurst.temporaryAgentData simulatorburst.html#temporaryAgentData +Pathfinding.RVO.UnmanagedObstacle.groupsAllocation unmanagedobstacle.html#groupsAllocation The allocation in ObstacleData.obstacles which represents the obstacle groups. +Pathfinding.RVO.UnmanagedObstacle.verticesAllocation unmanagedobstacle.html#verticesAllocation The allocation in ObstacleData.obstacleVertices which represents all vertices used for these obstacles. +Pathfinding.RVO.XYMovementPlane.matrix xymovementplane.html#matrix Maps from 2D (X, Y, 0) coordinates to world coordinates. +Pathfinding.RVO.XZMovementPlane.matrix xzmovementplane.html#matrix Maps from 2D (X, Y, 0) coordinates to world coordinates. +Pathfinding.RVOSimulatorEditor.movementPlaneOptions rvosimulatoreditor.html#movementPlaneOptions +Pathfinding.RadiusModifier.Order radiusmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.RadiusModifier.TangentType radiusmodifier.html#TangentType +Pathfinding.RadiusModifier.a1 radiusmodifier.html#a1 +Pathfinding.RadiusModifier.a2 radiusmodifier.html#a2 +Pathfinding.RadiusModifier.detail radiusmodifier.html#detail Detail of generated circle segments. \n\nMeasured as steps per full circle.\n\nIt is more performant to use a low value. For movement, using a high value will barely improve path quality. +Pathfinding.RadiusModifier.dir radiusmodifier.html#dir +Pathfinding.RadiusModifier.radi radiusmodifier.html#radi +Pathfinding.RadiusModifier.radius radiusmodifier.html#radius Radius of the circle segments generated. \n\nUsually similar to the character radius. +Pathfinding.RandomPath.aim randompath.html#aim An aim can be used to guide the pathfinder to not take totally random paths. \n\nFor example you might want your AI to continue in generally the same direction as before, then you can specify aim to be transform.postion + transform.forward*10 which will make it more often take paths nearer that point \n\n[more in online documentation] +Pathfinding.RandomPath.aimStrength randompath.html#aimStrength If an aim is set, the higher this value is, the more it will try to reach aim. \n\nRecommended values are between 0 and 10. +Pathfinding.RandomPath.chosenPathNodeGScore randompath.html#chosenPathNodeGScore +Pathfinding.RandomPath.chosenPathNodeIndex randompath.html#chosenPathNodeIndex Currently chosen end node. +Pathfinding.RandomPath.endPointKnownBeforeCalculation randompath.html#endPointKnownBeforeCalculation True if this path type has a well defined end point, even before calculation starts. \n\nThis is for example true for the ABPath type, but false for the RandomPath type. +Pathfinding.RandomPath.hasEndPoint randompath.html#hasEndPoint Determines if a search for an end node should be done. \n\nSet by different path types. \n\n[more in online documentation] +Pathfinding.RandomPath.maxGScore randompath.html#maxGScore The G score of maxGScorePathNodeIndex. +Pathfinding.RandomPath.maxGScorePathNodeIndex randompath.html#maxGScorePathNodeIndex The node with the highest G score which is still lower than searchLength. \n\nUsed as a backup if a node with a G score higher than searchLength could not be found +Pathfinding.RandomPath.nodesEvaluatedRep randompath.html#nodesEvaluatedRep +Pathfinding.RandomPath.rnd randompath.html#rnd Random number generator source. \n\nYou may supply a custom random number generator here in order to get reproducible paths by using a fixed seed. If not, a new System.Random instance will be created when the path is constructed.\n\nKeep in mind that multiple paths should never share the same System.Random instance, since paths are calculated in parallel. +Pathfinding.RandomPath.searchLength randompath.html#searchLength G score to stop searching at. \n\nThe G score is rougly the distance to get from the start node to a node multiplied by 1000 (per default, see Pathfinding.Int3.Precision), plus any penalties.\n\n [video in online documentation] +Pathfinding.RandomPath.spread randompath.html#spread All G scores between searchLength and searchLength+spread are valid end points, a random one of them is chosen as the final point. \n\nOn grid graphs a low spread usually works (but keep it higher than nodeSize*1000 since that it the default cost of moving between two nodes), on NavMesh graphs I would recommend a higher spread so it can evaluate more nodes.\n\n [video in online documentation] +Pathfinding.RaycastModifier.DPCosts raycastmodifier.html#DPCosts +Pathfinding.RaycastModifier.DPParents raycastmodifier.html#DPParents +Pathfinding.RaycastModifier.Order raycastmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.RaycastModifier.Quality raycastmodifier.html#Quality +Pathfinding.RaycastModifier.buffer raycastmodifier.html#buffer +Pathfinding.RaycastModifier.iterationsByQuality raycastmodifier.html#iterationsByQuality +Pathfinding.RaycastModifier.mask raycastmodifier.html#mask Layer mask used for physics raycasting. \n\nAll objects with layers that are included in this mask will be treated as obstacles. If you are using a grid graph you usually want this to be the same as the mask in the grid graph's 'Collision Testing' settings. +Pathfinding.RaycastModifier.quality raycastmodifier.html#quality Higher quality modes will try harder to find a shorter path. \n\nHigher qualities may be significantly slower than low quality. [image in online documentation] +Pathfinding.RaycastModifier.raycastOffset raycastmodifier.html#raycastOffset Offset from the original positions to perform the raycast. \n\nCan be useful to avoid the raycast intersecting the ground or similar things you do not want to it intersect +Pathfinding.RaycastModifier.thickRaycast raycastmodifier.html#thickRaycast Checks around the line between two points, not just the exact line. \n\nMake sure the ground is either too far below or is not inside the mask since otherwise the raycast might always hit the ground.\n\n[more in online documentation] +Pathfinding.RaycastModifier.thickRaycastRadius raycastmodifier.html#thickRaycastRadius Distance from the ray which will be checked for colliders. +Pathfinding.RaycastModifier.use2DPhysics raycastmodifier.html#use2DPhysics Check for intersections with 2D colliders instead of 3D colliders. \n\nUseful for 2D games.\n\n[more in online documentation] +Pathfinding.RaycastModifier.useGraphRaycasting raycastmodifier.html#useGraphRaycasting Use raycasting on the graphs. \n\nOnly currently works with GridGraph and NavmeshGraph and RecastGraph. [more in online documentation] +Pathfinding.RaycastModifier.useRaycasting raycastmodifier.html#useRaycasting Use Physics.Raycast to simplify the path. +Pathfinding.RecastGraph.BackgroundTraversability recastgraph.html#BackgroundTraversability Whether the base of the graph should default to being walkable or unwalkable. \n\n[more in online documentation] +Pathfinding.RecastGraph.CharacterRadiusInVoxels recastgraph.html#CharacterRadiusInVoxels Convert character radius to a number of voxels. +Pathfinding.RecastGraph.CollectionSettings.FilterMode collectionsettings.html#FilterMode Determines how the initial filtering of objects is done. +Pathfinding.RecastGraph.CollectionSettings.collectionMode collectionsettings.html#collectionMode Determines how the initial filtering of objects is done. \n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.colliderRasterizeDetail collectionsettings.html#colliderRasterizeDetail Controls detail on rasterization of sphere and capsule colliders. \n\nThe colliders will be approximated with polygons so that the max distance to the theoretical surface is less than 1/(this number of voxels).\n\nA higher value does not necessarily increase quality of the mesh, but a lower value will often speed it up.\n\nYou should try to keep this value as low as possible without affecting the mesh quality since that will yield the fastest scan times.\n\nThe default value is 1, which corresponds to a maximum error of 1 voxel. In most cases, increasing this to a value higher than 2 (corresponding to a maximum error of 0.5 voxels) is not useful.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.layerMask collectionsettings.html#layerMask Objects in all of these layers will be rasterized. \n\nWill only be used if collectionMode is set to Layers.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.onCollectMeshes collectionsettings.html#onCollectMeshes Callback for collecting custom scene meshes. \n\nThis callback will be called once when scanning the graph, to allow you to add custom meshes to the graph, and once every time a graph update happens. Use the RecastMeshGatherer class to add meshes that are to be rasterized.\n\n[more in online documentation]\n[code in online documentation] +Pathfinding.RecastGraph.CollectionSettings.physicsScene collectionsettings.html#physicsScene The physics scene for collecting colliders when scanning the graph. \n\nIf null (the default), the physics scene that the AstarPath component is part of will be used.\n\nYou typically don't have to set this, but it can be useful in some rare situations.\n\n[more in online documentation]\nOnly used if rasterizeColliders is enabled.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.physicsScene2D collectionsettings.html#physicsScene2D The physics scene for collecting 2D colliders when scanning the graph. \n\nIf null (the default), the physics scene that the AstarPath component is part of will be used.\n\nYou typically don't have to set this, but it can be useful in some rare situations.\n\n[more in online documentation]\nOnly used if rasterizeColliders is enabled.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.rasterizeColliders collectionsettings.html#rasterizeColliders Use colliders to calculate the navmesh. \n\nDepending on the dimensionMode, either 3D or 2D colliders will be rasterized.\n\nSphere/Capsule/Circle colliders will be approximated using polygons, with the precision specified in colliderRasterizeDetail.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.rasterizeMeshes collectionsettings.html#rasterizeMeshes Use scene meshes to calculate the navmesh. \n\nThis can get you higher precision than colliders, since colliders are typically very simplified versions of the mesh. However, it is often slower to scan, and graph updates can be particularly slow.\n\nThe reason that graph updates are slower is that there's no efficient way to find all meshes that intersect a given tile, so the graph has to iterate over all meshes in the scene just to find the ones relevant for the tiles that you want to update. Colliders, on the other hand, can be efficiently queried using the physics system.\n\nYou can disable this and attach a RecastNavmeshModifier component (with dynamic=false) to all meshes that you want to be included in the navmesh instead. That way they will be able to be efficiently queried for, without having to iterate through all meshes in the scene.\n\nIn 2D mode, this setting has no effect. +Pathfinding.RecastGraph.CollectionSettings.rasterizeTerrain collectionsettings.html#rasterizeTerrain Use terrains to calculate the navmesh. \n\nIn 2D mode, this setting has no effect. +Pathfinding.RecastGraph.CollectionSettings.rasterizeTrees collectionsettings.html#rasterizeTrees Rasterize tree colliders on terrains. \n\nIf the tree prefab has a collider, that collider will be rasterized. Otherwise a simple box collider will be used and the script will try to adjust it to the tree's scale, it might not do a very good job though so an attached collider is preferable.\n\n[more in online documentation]\nIn 2D mode, this setting has no effect.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.tagMask collectionsettings.html#tagMask Objects tagged with any of these tags will be rasterized. \n\nWill only be used if collectionMode is set to Tags.\n\n[more in online documentation] +Pathfinding.RecastGraph.CollectionSettings.terrainHeightmapDownsamplingFactor collectionsettings.html#terrainHeightmapDownsamplingFactor Controls how much to downsample the terrain's heightmap before generating the input mesh used for rasterization. \n\nA higher value is faster to scan but less accurate. +Pathfinding.RecastGraph.DimensionMode recastgraph.html#DimensionMode Whether to use 3D or 2D mode. +Pathfinding.RecastGraph.MaxTileConnectionEdgeDistance recastgraph.html#MaxTileConnectionEdgeDistance Maximum (vertical) distance between the sides of two nodes for them to be connected across a tile edge. \n\nWhen tiles are connected to each other, the nodes sometimes do not line up perfectly so some allowance must be made to allow tiles that do not match exactly to be connected with each other. +Pathfinding.RecastGraph.NavmeshCuttingCharacterRadius recastgraph.html#NavmeshCuttingCharacterRadius +Pathfinding.RecastGraph.PerLayerModification.Default perlayermodification.html#Default +Pathfinding.RecastGraph.PerLayerModification.layer perlayermodification.html#layer Unity layer that this modification applies to. +Pathfinding.RecastGraph.PerLayerModification.mode perlayermodification.html#mode Surface rasterization mode. \n\n[more in online documentation] +Pathfinding.RecastGraph.PerLayerModification.surfaceID perlayermodification.html#surfaceID Voxel area for mesh. \n\nThis area (not to be confused with pathfinding areas, this is only used when rasterizing meshes for the recast graph) field can be used to explicitly insert edges in the navmesh geometry or to make some parts of the mesh unwalkable.\n\nWhen rasterizing the world and two objects with different surface id values are adjacent to each other, a split in the navmesh geometry will be added between them, characters will still be able to walk between them, but this can be useful when working with navmesh updates.\n\nNavmesh updates which recalculate a whole tile (updatePhysics=True) are very slow So if there are special places which you know are going to be updated quite often, for example at a door opening (opened/closed door) you can use surface IDs to create splits on the navmesh for easier updating using normal graph updates (updatePhysics=False). See the below video for more information.\n\n [video in online documentation]\n\nWhen mode is set to Mode.WalkableSurfaceWithTag then this value will be interpreted as a pathfinding tag. See Working with tags.\n\n[more in online documentation] +Pathfinding.RecastGraph.PerTerrainLayerModification.Default perterrainlayermodification.html#Default +Pathfinding.RecastGraph.PerTerrainLayerModification.layer perterrainlayermodification.html#layer Unity terrain layer that this modification applies to. +Pathfinding.RecastGraph.PerTerrainLayerModification.mode perterrainlayermodification.html#mode Surface rasterization mode. \n\n[more in online documentation] +Pathfinding.RecastGraph.PerTerrainLayerModification.surfaceID perterrainlayermodification.html#surfaceID Voxel area for mesh. \n\nThis area (not to be confused with pathfinding areas, this is only used when rasterizing meshes for the recast graph) field can be used to explicitly insert edges in the navmesh geometry or to make some parts of the mesh unwalkable.\n\nWhen rasterizing the world and two objects with different surface id values are adjacent to each other, a split in the navmesh geometry will be added between them, characters will still be able to walk between them, but this can be useful when working with navmesh updates.\n\nNavmesh updates which recalculate a whole tile (updatePhysics=True) are very slow So if there are special places which you know are going to be updated quite often, for example at a door opening (opened/closed door) you can use surface IDs to create splits on the navmesh for easier updating using normal graph updates (updatePhysics=False). See the below video for more information.\n\n [video in online documentation]\n\nWhen mode is set to Mode.WalkableSurfaceWithTag then this value will be interpreted as a pathfinding tag. See Working with tags.\n\n[more in online documentation] +Pathfinding.RecastGraph.PerTerrainLayerModification.threshold perterrainlayermodification.html#threshold Threshold for which alpha the terrain layer needs to have for this rule to be applied. \n\nIn transitions between textures, the most applicable rule will be used. Given a layer alpha of A, and a threshold of T, the rule with the highest value of A - T will be used.\n\nIf there's no rule for a given terrain layer, it is treated as having a threshold of 0.5. +Pathfinding.RecastGraph.RecalculateNormals recastgraph.html#RecalculateNormals Determines how normals are calculated. \n\nDisable for spherical graphs or other complicated surfaces that allow the agents to e.g walk on walls or ceilings.\n\nBy default the normals of the mesh will be flipped so that they point as much as possible in the upwards direction. The normals are important when connecting adjacent nodes. Two adjacent nodes will only be connected if they are oriented the same way. This is particularly important if you have a navmesh on the walls or even on the ceiling of a room. Or if you are trying to make a spherical navmesh. If you do one of those things then you should set disable this setting and make sure the normals in your source mesh are properly set.\n\nIf you for example take a look at the image below. In the upper case then the nodes on the bottom half of the mesh haven't been connected with the nodes on the upper half because the normals on the lower half will have been modified to point inwards (as that is the direction that makes them face upwards the most) while the normals on the upper half point outwards. This causes the nodes to not connect properly along the seam. When this option is set to false instead the nodes are connected properly as in the original mesh all normals point outwards. [image in online documentation]\n\nThe default value of this field is true to reduce the risk for errors in the common case. If a mesh is supplied that has all normals pointing downwards and this option is false, then some methods like PointOnNavmesh will not work correctly as they assume that the normals point upwards. For a more complicated surface like a spherical graph those methods make no sense anyway as there is no clear definition of what it means to be "inside" a triangle when there is no clear up direction. +Pathfinding.RecastGraph.RecastGraphScanPromise.Progress recastgraphscanpromise.html#Progress Returns the progress of the update. \n\nThis should be a value between 0 and 1. +Pathfinding.RecastGraph.RecastGraphScanPromise.cutSettings recastgraphscanpromise.html#cutSettings +Pathfinding.RecastGraph.RecastGraphScanPromise.emptyGraph recastgraphscanpromise.html#emptyGraph +Pathfinding.RecastGraph.RecastGraphScanPromise.graph recastgraphscanpromise.html#graph +Pathfinding.RecastGraph.RecastGraphScanPromise.meshesUnreadableAtRuntime recastgraphscanpromise.html#meshesUnreadableAtRuntime +Pathfinding.RecastGraph.RecastGraphScanPromise.progressSource recastgraphscanpromise.html#progressSource +Pathfinding.RecastGraph.RecastGraphScanPromise.tileLayout recastgraphscanpromise.html#tileLayout +Pathfinding.RecastGraph.RecastGraphScanPromise.tiles recastgraphscanpromise.html#tiles +Pathfinding.RecastGraph.RecastGraphUpdatePromise.graph recastgraphupdatepromise.html#graph +Pathfinding.RecastGraph.RecastGraphUpdatePromise.graphHash recastgraphupdatepromise.html#graphHash +Pathfinding.RecastGraph.RecastGraphUpdatePromise.graphUpdates recastgraphupdatepromise.html#graphUpdates +Pathfinding.RecastGraph.RecastGraphUpdatePromise.promises recastgraphupdatepromise.html#promises +Pathfinding.RecastGraph.RecastMovePromise.delta recastmovepromise.html#delta +Pathfinding.RecastGraph.RecastMovePromise.graph recastmovepromise.html#graph +Pathfinding.RecastGraph.RecastMovePromise.newTileRect recastmovepromise.html#newTileRect +Pathfinding.RecastGraph.RecastMovePromise.tileMeshes recastmovepromise.html#tileMeshes +Pathfinding.RecastGraph.RelevantGraphSurfaceMode recastgraph.html#RelevantGraphSurfaceMode +Pathfinding.RecastGraph.TileBorderSizeInVoxels recastgraph.html#TileBorderSizeInVoxels Number of extra voxels on each side of a tile to ensure accurate navmeshes near the tile border. \n\nThe width of a tile is expanded by 2 times this value (1x to the left and 1x to the right) +Pathfinding.RecastGraph.TileBorderSizeInWorldUnits recastgraph.html#TileBorderSizeInWorldUnits +Pathfinding.RecastGraph.TileWorldSizeX recastgraph.html#TileWorldSizeX Size of a tile in world units along the X axis. +Pathfinding.RecastGraph.TileWorldSizeZ recastgraph.html#TileWorldSizeZ Size of a tile in world units along the Z axis. +Pathfinding.RecastGraph.backgroundTraversability recastgraph.html#backgroundTraversability Whether the base of the graph should default to being walkable or unwalkable. \n\nThis is only used in 2D mode. In 3D mode, this setting has no effect.\n\nFor 2D games, it can be very useful to set the background to be walkable by default, and then constrain walkability using colliders.\n\nIf you don't want to use a walkable background, you can instead create colliders and attach a RecastNavmeshModifier with Surface Type set to Walkable Surface. These will then create walkable regions.\n\n[more in online documentation] +Pathfinding.RecastGraph.bounds recastgraph.html#bounds World bounding box for the graph. \n\nThis always contains the whole graph.\n\n[more in online documentation]\n [image in online documentation] +Pathfinding.RecastGraph.cellSize recastgraph.html#cellSize Voxel sample size (x,z). \n\nWhen generating a recast graph what happens is that the world is voxelized. You can think of this as constructing an approximation of the world out of lots of boxes. If you have played Minecraft it looks very similar (but with smaller boxes). [image in online documentation]\n\nLower values will yield higher quality navmeshes, however the graph will be slower to scan.\n\n [image in online documentation] +Pathfinding.RecastGraph.characterRadius recastgraph.html#characterRadius Radius of the agent which will traverse the navmesh. \n\nThe navmesh will be eroded with this radius.\n\nThis value will be rounded up to the nearest multiple of cellSize.\n\n [image in online documentation] +Pathfinding.RecastGraph.collectionSettings recastgraph.html#collectionSettings Determines which objects are used to build the graph, when it is scanned. \n\nAlso contains some settings for how to convert objects into meshes. Spherical colliders, for example, need to be converted into a triangular mesh before they can be used in the graph.\n\n[more in online documentation] +Pathfinding.RecastGraph.colliderRasterizeDetail recastgraph.html#colliderRasterizeDetail Controls detail on rasterization of sphere and capsule colliders. \n\nThe colliders will be approximated with polygons so that the max distance to the theoretical surface is less than 1/(this number of voxels).\n\nA higher value does not necessarily increase quality of the mesh, but a lower value will often speed it up.\n\nYou should try to keep this value as low as possible without affecting the mesh quality since that will yield the fastest scan times.\n\nThe default value is 1, which corresponds to a maximum error of 1 voxel. In most cases, increasing this to a value higher than 2 (corresponding to a maximum error of 0.5 voxels) is not useful.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.RecastGraph.contourMaxError recastgraph.html#contourMaxError Max distance from simplified edge to real edge. \n\nThis value is measured in voxels. So with the default value of 2 it means that the final navmesh contour may be at most 2 voxels (i.e 2 times cellSize) away from the border that was calculated when voxelizing the world. A higher value will yield a more simplified and cleaner navmesh while a lower value may capture more details. However a too low value will cause the individual voxels to be visible (see image below).\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.RecastGraph.dimensionMode recastgraph.html#dimensionMode Whether to use 3D or 2D mode. \n\n[more in online documentation] +Pathfinding.RecastGraph.editorTileSize recastgraph.html#editorTileSize Size in voxels of a single tile. \n\nThis is the width of the tile.\n\n [image in online documentation]\n\nA large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world) smaller tile sizes are (much) faster to update.\n\nDifferent tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for better quality paths, but too small tiles can also lead to effects looking like invisible obstacles. For more information about this take a look at Notes about navmesh graphs. Usually it is best to experiment and see what works best for your game.\n\nWhen scanning a recast graphs individual tiles can be calculated in parallel which can make it much faster to scan large worlds. When you want to recalculate a part of a recast graph, this can only be done on a tile-by-tile basis which means that if you often try to update a region of the recast graph much smaller than the tile size, then you will be doing a lot of unnecessary calculations. However if you on the other hand update regions of the recast graph that are much larger than the tile size then it may be slower than necessary as there is some overhead in having lots of tiles instead of a few larger ones (not that much though).\n\nRecommended values are between 64 and 256, but these are very soft limits. It is possible to use both larger and smaller values. +Pathfinding.RecastGraph.forcedBoundsCenter recastgraph.html#forcedBoundsCenter Center of the bounding box. \n\nScanning will only be done inside the bounding box +Pathfinding.RecastGraph.hasExtendedInX recastgraph.html#hasExtendedInX +Pathfinding.RecastGraph.hasExtendedInZ recastgraph.html#hasExtendedInZ +Pathfinding.RecastGraph.mask recastgraph.html#mask Layer mask which filters which objects to include. \n\n[more in online documentation] +Pathfinding.RecastGraph.maxEdgeLength recastgraph.html#maxEdgeLength Longer edges will be subdivided. \n\nReducing this value can sometimes improve path quality since similarly sized triangles yield better paths than really large and really triangles small next to each other. However it will also add a lot more nodes which will make pathfinding slower. For more information about this take a look at Notes about navmesh graphs.\n\n [image in online documentation] +Pathfinding.RecastGraph.maxSlope recastgraph.html#maxSlope Max slope in degrees the character can traverse. \n\n [image in online documentation] +Pathfinding.RecastGraph.meshesUnreadableAtRuntime recastgraph.html#meshesUnreadableAtRuntime Internal field used to warn users when the mesh includes meshes that are not readable at runtime. +Pathfinding.RecastGraph.minRegionSize recastgraph.html#minRegionSize Minumum region size. \n\nSmall regions will be removed from the navmesh. Measured in voxels.\n\n [image in online documentation]\n\nIf a region is adjacent to a tile border, it will not be removed even though it is small since the adjacent tile might join it to form a larger region.\n\n [image in online documentation][image in online documentation] +Pathfinding.RecastGraph.pendingGraphUpdateArena recastgraph.html#pendingGraphUpdateArena +Pathfinding.RecastGraph.perLayerModifications recastgraph.html#perLayerModifications List of rules that modify the graph based on the layer of the rasterized object. \n\n [image in online documentation]\n\nBy default, all layers are treated as walkable surfaces. But by adding rules to this list, one can for example make all surfaces with a specific layer get a specific pathfinding tag.\n\nEach layer should be modified at most once in this list.\n\nIf an object has a RecastNavmeshModifier component attached, the settings on that component will override the settings in this list.\n\n[more in online documentation] +Pathfinding.RecastGraph.perTerrainLayerModifications recastgraph.html#perTerrainLayerModifications List of rules that modify the graph based on the painted textures on terrains. \n\n [image in online documentation]\n\nBy default, all terrain layers are treated as walkable surfaces. But by adding rules to this list, one can for example make all surfaces with a specific terrain layer get a specific pathfinding tag.\n\nIn the following image and video, a dirt road has been painted on the terrain and tagged with a custom tag. The agent has been configured to prefer that tag, and so it will try to walk on the road instead of the grass.\n\n [image in online documentation]\n\n [video in online documentation]\n\nEach terrain layer should be modified at most once in this list.\n\nIf the terrain has a RecastNavmeshModifier component attached, it will be ignored, and these rules will be used instead.\n\nThe collectionSettings.terrainHeightmapDownsamplingFactor is very important when these adjustments are used. If the terrain heightmap is downsampled too much, the accuracy of the generated navmesh will be reduced. It will also be the most accurate when terrainHeightmapDownsamplingFactor is a factor or two, i.e. 1, 2, 4, 8, etc.\n\n[more in online documentation] +Pathfinding.RecastGraph.rasterizeColliders recastgraph.html#rasterizeColliders Use colliders to calculate the navmesh. \n\nDepending on the dimensionMode, either 3D or 2D colliders will be rasterized.\n\nSphere/Capsule/Circle colliders will be approximated using polygons, with the precision specified in colliderRasterizeDetail. \n\n[more in online documentation] +Pathfinding.RecastGraph.rasterizeMeshes recastgraph.html#rasterizeMeshes Use scene meshes to calculate the navmesh. \n\nThis can get you higher precision than colliders, since colliders are typically very simplified versions of the mesh. However, it is often slower to scan, and graph updates can be particularly slow.\n\nThe reason that graph updates are slower is that there's no efficient way to find all meshes that intersect a given tile, so the graph has to iterate over all meshes in the scene just to find the ones relevant for the tiles that you want to update. Colliders, on the other hand, can be efficiently queried using the physics system.\n\nYou can disable this and attach a RecastNavmeshModifier component (with dynamic=false) to all meshes that you want to be included in the navmesh instead. That way they will be able to be efficiently queried for, without having to iterate through all meshes in the scene.\n\nIn 2D mode, this setting has no effect. \n\n[more in online documentation] +Pathfinding.RecastGraph.rasterizeTerrain recastgraph.html#rasterizeTerrain Use terrains to calculate the navmesh. \n\nIn 2D mode, this setting has no effect. \n\n[more in online documentation] +Pathfinding.RecastGraph.rasterizeTrees recastgraph.html#rasterizeTrees Rasterize tree colliders on terrains. \n\nIf the tree prefab has a collider, that collider will be rasterized. Otherwise a simple box collider will be used and the script will try to adjust it to the tree's scale, it might not do a very good job though so an attached collider is preferable.\n\n[more in online documentation]\nIn 2D mode, this setting has no effect.\n\n[more in online documentation] +Pathfinding.RecastGraph.relevantGraphSurfaceMode recastgraph.html#relevantGraphSurfaceMode Require every region to have a RelevantGraphSurface component inside it. \n\nA RelevantGraphSurface component placed in the scene specifies that the navmesh region it is inside should be included in the navmesh.\n\nIf this is set to OnlyForCompletelyInsideTile a navmesh region is included in the navmesh if it has a RelevantGraphSurface inside it, or if it is adjacent to a tile border. This can leave some small regions which you didn't want to have included because they are adjacent to tile borders, but it removes the need to place a component in every single tile, which can be tedious (see below).\n\nIf this is set to RequireForAll a navmesh region is included only if it has a RelevantGraphSurface inside it. Note that even though the navmesh looks continous between tiles, the tiles are computed individually and therefore you need a RelevantGraphSurface component for each region and for each tile.\n\n [image in online documentation]\n\n [image in online documentation]\n\n [image in online documentation]\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.RecastGraph.rotation recastgraph.html#rotation Rotation of the graph in degrees. +Pathfinding.RecastGraph.scanEmptyGraph recastgraph.html#scanEmptyGraph If true, scanning the graph will yield a completely empty graph. \n\nUseful if you want to replace the graph with a custom navmesh for example\n\n[more in online documentation] +Pathfinding.RecastGraph.tagMask recastgraph.html#tagMask Objects tagged with any of these tags will be rasterized. \n\nNote that this extends the layer mask, so if you only want to use tags, set mask to 'Nothing'.\n\n[more in online documentation] +Pathfinding.RecastGraph.terrainSampleSize recastgraph.html#terrainSampleSize Controls how large the sample size for the terrain is. \n\nA higher value is faster to scan but less accurate.\n\nThe heightmap resolution is effectively divided by this value, before the terrain is rasterized.\n\n[more in online documentation] +Pathfinding.RecastGraph.tileSizeX recastgraph.html#tileSizeX Size of a tile along the X axis in voxels. \n\nThis is the width of the tile.\n\n [image in online documentation]\n\nA large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world) smaller tile sizes are (much) faster to update.\n\nDifferent tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for better quality paths, but too small tiles can also lead to effects looking like invisible obstacles. For more information about this take a look at Notes about navmesh graphs. Usually it is best to experiment and see what works best for your game.\n\nWhen scanning a recast graphs individual tiles can be calculated in parallel which can make it much faster to scan large worlds. When you want to recalculate a part of a recast graph, this can only be done on a tile-by-tile basis which means that if you often try to update a region of the recast graph much smaller than the tile size, then you will be doing a lot of unnecessary calculations. However if you on the other hand update regions of the recast graph that are much larger than the tile size then it may be slower than necessary as there is some overhead in having lots of tiles instead of a few larger ones (not that much though).\n\nRecommended values are between 64 and 256, but these are very soft limits. It is possible to use both larger and smaller values.\n\n[more in online documentation] +Pathfinding.RecastGraph.tileSizeZ recastgraph.html#tileSizeZ Size of a tile along the Z axis in voxels. \n\nThis is the width of the tile.\n\n [image in online documentation]\n\nA large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world) smaller tile sizes are (much) faster to update.\n\nDifferent tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for better quality paths, but too small tiles can also lead to effects looking like invisible obstacles. For more information about this take a look at Notes about navmesh graphs. Usually it is best to experiment and see what works best for your game.\n\nWhen scanning a recast graphs individual tiles can be calculated in parallel which can make it much faster to scan large worlds. When you want to recalculate a part of a recast graph, this can only be done on a tile-by-tile basis which means that if you often try to update a region of the recast graph much smaller than the tile size, then you will be doing a lot of unnecessary calculations. However if you on the other hand update regions of the recast graph that are much larger than the tile size then it may be slower than necessary as there is some overhead in having lots of tiles instead of a few larger ones (not that much though).\n\nRecommended values are between 64 and 256, but these are very soft limits. It is possible to use both larger and smaller values.\n\n[more in online documentation] +Pathfinding.RecastGraph.useTiles recastgraph.html#useTiles If true, divide the graph into tiles, otherwise use a single tile covering the whole graph. \n\nUsing tiles is useful for a number of things. But it also has some drawbacks.\n- Using tiles allows you to update only a part of the graph at a time. When doing graph updates on a recast graph, it will always recalculate whole tiles (or the whole graph if there are no tiles). NavmeshCut components also work on a tile-by-tile basis.\n\n- Using tiles allows you to use NavmeshPrefabs.\n\n- Using tiles can break up very large triangles, which can improve path quality in some cases, and make the navmesh more closely follow the y-coordinates of the ground.\n\n- Using tiles can make it much faster to generate the navmesh, because each tile can be calculated in parallel. But if the tiles are made too small, then the overhead of having many tiles can make it slower than having fewer tiles.\n\n- Using small tiles can make the path quality worse in some cases, but setting the FunnelModifiers quality setting to high (or using RichAI.funnelSimplification) will mostly mitigate this.\n\n\n\n[more in online documentation] +Pathfinding.RecastGraph.walkableClimb recastgraph.html#walkableClimb Height the character can climb. \n\n [image in online documentation] +Pathfinding.RecastGraph.walkableHeight recastgraph.html#walkableHeight Character height. \n\n [image in online documentation] +Pathfinding.RecastGraphEditor.DimensionModeLabels recastgrapheditor.html#DimensionModeLabels +Pathfinding.RecastGraphEditor.UseTiles recastgrapheditor.html#UseTiles +Pathfinding.RecastGraphEditor.handlePoints recastgrapheditor.html#handlePoints +Pathfinding.RecastGraphEditor.meshesUnreadableAtRuntimeFoldout recastgrapheditor.html#meshesUnreadableAtRuntimeFoldout +Pathfinding.RecastGraphEditor.perLayerModificationsList recastgrapheditor.html#perLayerModificationsList +Pathfinding.RecastGraphEditor.perTerrainLayerModificationsList recastgrapheditor.html#perTerrainLayerModificationsList +Pathfinding.RecastGraphEditor.tagMaskFoldout recastgrapheditor.html#tagMaskFoldout +Pathfinding.RecastGraphEditor.tagMaskList recastgrapheditor.html#tagMaskList +Pathfinding.RecastMeshObj.dynamic recastmeshobj.html#dynamic +Pathfinding.RecastMeshObj.enabled recastmeshobj.html#enabled +Pathfinding.RecastMeshObj.geometrySource recastmeshobj.html#geometrySource +Pathfinding.RecastMeshObj.includeInScan recastmeshobj.html#includeInScan +Pathfinding.RecastMeshObj.mode recastmeshobj.html#mode +Pathfinding.RecastMeshObj.solid recastmeshobj.html#solid +Pathfinding.RecastMeshObj.surfaceID recastmeshobj.html#surfaceID +Pathfinding.RecastNavmeshModifier.GeometrySource recastnavmeshmodifier.html#GeometrySource Source of geometry when voxelizing this object. +Pathfinding.RecastNavmeshModifier.Mode recastnavmeshmodifier.html#Mode +Pathfinding.RecastNavmeshModifier.ScanInclusion recastnavmeshmodifier.html#ScanInclusion +Pathfinding.RecastNavmeshModifier.area recastnavmeshmodifier.html#area Voxel area for mesh. \n\nThis area (not to be confused with pathfinding areas, this is only used when rasterizing meshes for the recast graph) field can be used to explicitly insert edges in the navmesh geometry or to make some parts of the mesh unwalkable.\n\nWhen rasterizing the world and two objects with different surface id values are adjacent to each other, a split in the navmesh geometry will be added between them, characters will still be able to walk between them, but this can be useful when working with navmesh updates.\n\nNavmesh updates which recalculate a whole tile (updatePhysics=True) are very slow So if there are special places which you know are going to be updated quite often, for example at a door opening (opened/closed door) you can use surface IDs to create splits on the navmesh for easier updating using normal graph updates (updatePhysics=False). See the below video for more information.\n\n [video in online documentation]\n\n[more in online documentation] +Pathfinding.RecastNavmeshModifier.dynamic recastnavmeshmodifier.html#dynamic Enable if the object will move during runtime. \n\nIf disabled, the object will be assumed to stay in the same position, and keep the same size, until the component is disabled or destroyed.\n\nDisabling this will provide a small performance boost when doing graph updates, as the graph no longer has to check if this RecastNavmeshModifier might have moved.\n\nEven you set dynamic=false, you can disable the component, move the object, and enable it at the new position. +Pathfinding.RecastNavmeshModifier.geometrySource recastnavmeshmodifier.html#geometrySource Source of geometry when voxelizing this object. +Pathfinding.RecastNavmeshModifier.includeInScan recastnavmeshmodifier.html#includeInScan Determines if the object should be included in scans or not. \n\n[more in online documentation] +Pathfinding.RecastNavmeshModifier.mode recastnavmeshmodifier.html#mode Surface rasterization mode. \n\n[more in online documentation] +Pathfinding.RecastNavmeshModifier.solid recastnavmeshmodifier.html#solid If true then the mesh will be treated as solid and its interior will be unwalkable. \n\nThe unwalkable region will be the minimum to maximum y coordinate in each cell.\n\nIf you enable this on a mesh that is actually hollow then the hollow region will also be treated as unwalkable. +Pathfinding.RecastNavmeshModifier.surfaceID recastnavmeshmodifier.html#surfaceID Voxel area for mesh. \n\nThis area (not to be confused with pathfinding areas, this is only used when rasterizing meshes for the recast graph) field can be used to explicitly insert edges in the navmesh geometry or to make some parts of the mesh unwalkable.\n\nWhen rasterizing the world and two objects with different surface id values are adjacent to each other, a split in the navmesh geometry will be added between them, characters will still be able to walk between them, but this can be useful when working with navmesh updates.\n\nNavmesh updates which recalculate a whole tile (updatePhysics=True) are very slow So if there are special places which you know are going to be updated quite often, for example at a door opening (opened/closed door) you can use surface IDs to create splits on the navmesh for easier updating using normal graph updates (updatePhysics=False). See the below video for more information.\n\n [video in online documentation]\n\nWhen mode is set to Mode.WalkableSurfaceWithTag then this value will be interpreted as a pathfinding tag. See Working with tags.\n\n[more in online documentation] +Pathfinding.RecastNavmeshModifier.tree recastnavmeshmodifier.html#tree Components are stored in a tree for fast lookups. +Pathfinding.RecastNavmeshModifier.treeKey recastnavmeshmodifier.html#treeKey +Pathfinding.RecastTileUpdate.OnNeedUpdates recasttileupdate.html#OnNeedUpdates +Pathfinding.RecastTileUpdateHandler.anyDirtyTiles recasttileupdatehandler.html#anyDirtyTiles True if any elements in dirtyTiles are true. +Pathfinding.RecastTileUpdateHandler.dirtyTiles recasttileupdatehandler.html#dirtyTiles True for a tile if it needs updating. +Pathfinding.RecastTileUpdateHandler.earliestDirty recasttileupdatehandler.html#earliestDirty Earliest update request we are handling right now. +Pathfinding.RecastTileUpdateHandler.graph recasttileupdatehandler.html#graph Graph that handles the updates. +Pathfinding.RecastTileUpdateHandler.maxThrottlingDelay recasttileupdatehandler.html#maxThrottlingDelay All tile updates will be performed within (roughly) this number of seconds. +Pathfinding.RichAI.GizmoColorPath richai.html#GizmoColorPath +Pathfinding.RichAI.acceleration richai.html#acceleration Max acceleration of the agent. \n\nIn world units per second per second. +Pathfinding.RichAI.approachingPartEndpoint richai.html#approachingPartEndpoint True if approaching the last waypoint in the current part of the path. \n\nPath parts are separated by off-mesh links.\n\n[more in online documentation] +Pathfinding.RichAI.approachingPathEndpoint richai.html#approachingPathEndpoint True if approaching the last waypoint of all parts in the current path. \n\nPath parts are separated by off-mesh links.\n\n[more in online documentation] +Pathfinding.RichAI.canSearch richai.html#canSearch Enables or disables recalculating the path at regular intervals. \n\nSetting this to false does not stop any active path requests from being calculated or stop it from continuing to follow the current path.\n\nNote that this only disables automatic path recalculations. If you call the SearchPath() method a path will still be calculated.\n\n[more in online documentation] +Pathfinding.RichAI.delayUpdatePath richai.html#delayUpdatePath +Pathfinding.RichAI.distanceToSteeringTarget richai.html#distanceToSteeringTarget Distance to steeringTarget in the movement plane. +Pathfinding.RichAI.endOfPath richai.html#endOfPath End point of path the agent is currently following. \n\nIf the agent has no path (or it might not be calculated yet), this will return the destination instead. If the agent has no destination it will return the agent's current position.\n\nThe end of the path is usually identical or very close to the destination, but it may differ if the path for example was blocked by a wall so that the agent couldn't get any closer.\n\nThis is only updated when the path is recalculated. +Pathfinding.RichAI.funnelSimplification richai.html#funnelSimplification Use funnel simplification. \n\nOn tiled navmesh maps, but sometimes on normal ones as well, it can be good to simplify the funnel as a post-processing step to make the paths straighter.\n\nThis has a moderate performance impact during frames when a path calculation is completed.\n\nThe RichAI script uses its own internal funnel algorithm, so you never need to attach the FunnelModifier component.\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.RichAI.hasPath richai.html#hasPath True if this agent currently has a path that it follows. +Pathfinding.RichAI.height richai.html#height Height of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nThis value is currently only used if an RVOController is attached to the same GameObject, otherwise it is only used for drawing nice gizmos in the scene view. However since the height value is used for some things, the radius field is always visible for consistency and easier visualization of the character. That said, it may be used for something in a future release.\n\n[more in online documentation] +Pathfinding.RichAI.lastCorner richai.html#lastCorner +Pathfinding.RichAI.maxSpeed richai.html#maxSpeed Max speed in world units per second. +Pathfinding.RichAI.movementPlane richai.html#movementPlane The plane the agent is moving in. \n\nThis is typically the ground plane, which will be the XZ plane in a 3D game, and the XY plane in a 2D game. Ultimately it depends on the graph orientation.\n\nIf you are doing pathfinding on a spherical world (see Spherical Worlds), the the movement plane will be the tangent plane of the sphere at the agent's position. +Pathfinding.RichAI.nextCorners richai.html#nextCorners +Pathfinding.RichAI.onTraverseOffMeshLink richai.html#onTraverseOffMeshLink Called when the agent starts to traverse an off-mesh link. \n\nRegister to this callback to handle off-mesh links in a custom way.\n\nIf this event is set to null then the agent will fall back to traversing off-mesh links using a very simple linear interpolation.\n\n[code in online documentation] +Pathfinding.RichAI.pathPending richai.html#pathPending True if a path is currently being calculated. +Pathfinding.RichAI.preventMovingBackwards richai.html#preventMovingBackwards Prevent the velocity from being too far away from the forward direction of the character. \n\nIf the character is ordered to move in the opposite direction from where it is facing then enabling this will cause it to make a small loop instead of turning on the spot.\n\nThis setting only has an effect if slowWhenNotFacingTarget is enabled. +Pathfinding.RichAI.radius richai.html#radius Radius of the agent in world units. \n\nThis is visualized in the scene view as a yellow cylinder around the character.\n\nNote that this does not affect pathfinding in any way. The graph used completely determines where the agent can move.\n\n[more in online documentation] +Pathfinding.RichAI.reachedDestination richai.html#reachedDestination True if the ai has reached the destination. \n\nThis is a best effort calculation to see if the destination has been reached. For the AIPath/RichAI scripts, this is when the character is within AIPath.endReachedDistance world units from the destination. For the AILerp script it is when the character is at the destination (±a very small margin).\n\nThis value will be updated immediately when the destination is changed (in contrast to reachedEndOfPath), however since path requests are asynchronous it will use an approximation until it sees the real path result. What this property does is to check the distance to the end of the current path, and add to that the distance from the end of the path to the destination (i.e. is assumes it is possible to move in a straight line between the end of the current path to the destination) and then checks if that total distance is less than AIPath.endReachedDistance. This property is therefore only a best effort, but it will work well for almost all use cases.\n\nFurthermore it will not report that the destination is reached if the destination is above the head of the character or more than half the height of the character below its feet (so if you have a multilevel building, it is important that you configure the height of the character correctly).\n\nThe cases which could be problematic are if an agent is standing next to a very thin wall and the destination suddenly changes to the other side of that thin wall. During the time that it takes for the path to be calculated the agent may see itself as alredy having reached the destination because the destination only moved a very small distance (the wall was thin), even though it may actually be quite a long way around the wall to the other side.\n\nIn contrast to reachedEndOfPath, this property is immediately updated when the destination is changed.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.RichAI.reachedEndOfPath richai.html#reachedEndOfPath True if the agent has reached the end of the current path. \n\nNote that setting the destination does not immediately update the path, nor is there any guarantee that the AI will actually be able to reach the destination that you set. The AI will try to get as close as possible. Often you want to use reachedDestination instead which is easier to work with.\n\nIt is very hard to provide a method for detecting if the AI has reached the destination that works across all different games because the destination may not even lie on the navmesh and how that is handled differs from game to game (see also the code snippet in the docs for destination).\n\n[more in online documentation] +Pathfinding.RichAI.remainingDistance richai.html#remainingDistance Approximate remaining distance along the current path to the end of the path. \n\nThe RichAI movement script approximates this distance since it is quite expensive to calculate the real distance. However it will be accurate when the agent is within 1 corner of the destination. You can use GetRemainingPath to calculate the actual remaining path more precisely.\n\nThe AIPath and AILerp scripts use a more accurate distance calculation at all times.\n\nIf the agent does not currently have a path, then positive infinity will be returned.\n\n[more in online documentation]\n\n\n\n[more in online documentation] +Pathfinding.RichAI.richPath richai.html#richPath Holds the current path that this agent is following. +Pathfinding.RichAI.rotation richai.html#rotation Rotation of the agent. \n\nIf updateRotation is true then this value is identical to transform.rotation. +Pathfinding.RichAI.rotationFilterState richai.html#rotationFilterState Internal state used for filtering out noise in the agent's rotation. +Pathfinding.RichAI.rotationFilterState2 richai.html#rotationFilterState2 +Pathfinding.RichAI.rotationSpeed richai.html#rotationSpeed Max rotation speed of the agent. \n\nIn degrees per second. +Pathfinding.RichAI.shouldRecalculatePath richai.html#shouldRecalculatePath True if the path should be automatically recalculated as soon as possible. +Pathfinding.RichAI.simulateMovement richai.html#simulateMovement Enables or disables movement simulations completely. \n\nThis is primarily useful if you want to override the default movement behavior. If disabled, the agent will stop all its movement calculations and may not update many other properties either (e.g. reachedDestination). Depending on the movement script, you can then call the internal movement methods manually to control the agent's movement.\n\nIf you want the agent to smoothly come to a stand still, but still react to local avoidance and use gravity: use isStopped instead. If you want to make the agent stop immediately, disable the whole component instead.\n\n[more in online documentation] +Pathfinding.RichAI.slowWhenNotFacingTarget richai.html#slowWhenNotFacingTarget Slow down when not facing the target direction. \n\nIncurs at a small performance overhead.\n\nThis setting only has an effect if enableRotation is enabled. +Pathfinding.RichAI.slowdownTime richai.html#slowdownTime How long before reaching the end of the path to start to slow down. \n\nA lower value will make the agent stop more abruptly.\n\n[more in online documentation]\nIf set to zero the agent will not even attempt to slow down. This can be useful if the target point is not a point you want the agent to stop at but it might for example be the player and you want the AI to slam into the player.\n\n[more in online documentation]\n [video in online documentation] +Pathfinding.RichAI.steeringTarget richai.html#steeringTarget Point on the path which the agent is currently moving towards. \n\nThis is usually a point a small distance ahead of the agent or the end of the path.\n\nIf the agent does not have a path at the moment, then the agent's current position will be returned. +Pathfinding.RichAI.traversingOffMeshLink richai.html#traversingOffMeshLink +Pathfinding.RichAI.wallBuffer richai.html#wallBuffer +Pathfinding.RichAI.wallDist richai.html#wallDist Walls within this range will be used for avoidance. \n\nSetting this to zero disables wall avoidance and may improve performance slightly\n\n[more in online documentation] +Pathfinding.RichAI.wallForce richai.html#wallForce Force to avoid walls with. \n\nThe agent will try to steer away from walls slightly.\n\n[more in online documentation] +Pathfinding.RichFunnel.CurrentNode richfunnel.html#CurrentNode +Pathfinding.RichFunnel.DistanceToEndOfPath richfunnel.html#DistanceToEndOfPath Approximate distance (as the crow flies) to the endpoint of this path part. \n\n[more in online documentation] +Pathfinding.RichFunnel.checkForDestroyedNodesCounter richfunnel.html#checkForDestroyedNodesCounter +Pathfinding.RichFunnel.currentNode richfunnel.html#currentNode +Pathfinding.RichFunnel.currentPosition richfunnel.html#currentPosition +Pathfinding.RichFunnel.exactEnd richfunnel.html#exactEnd +Pathfinding.RichFunnel.exactStart richfunnel.html#exactStart +Pathfinding.RichFunnel.funnelSimplification richfunnel.html#funnelSimplification Post process the funnel corridor or not. +Pathfinding.RichFunnel.graph richfunnel.html#graph +Pathfinding.RichFunnel.left richfunnel.html#left +Pathfinding.RichFunnel.navmeshClampDict richfunnel.html#navmeshClampDict Cached object to avoid unnecessary allocations. +Pathfinding.RichFunnel.navmeshClampList richfunnel.html#navmeshClampList Cached object to avoid unnecessary allocations. +Pathfinding.RichFunnel.navmeshClampQueue richfunnel.html#navmeshClampQueue Cached object to avoid unnecessary allocations. +Pathfinding.RichFunnel.nodes richfunnel.html#nodes +Pathfinding.RichFunnel.path richfunnel.html#path +Pathfinding.RichFunnel.right richfunnel.html#right +Pathfinding.RichFunnel.triBuffer richfunnel.html#triBuffer +Pathfinding.RichPath.CompletedAllParts richpath.html#CompletedAllParts True if we have completed (called NextPart for) the last part in the path. +Pathfinding.RichPath.Endpoint richpath.html#Endpoint +Pathfinding.RichPath.IsLastPart richpath.html#IsLastPart True if we are traversing the last part of the path. +Pathfinding.RichPath.currentPart richpath.html#currentPart +Pathfinding.RichPath.parts richpath.html#parts +Pathfinding.RichPath.seeker richpath.html#seeker +Pathfinding.RichPath.transform richpath.html#transform Transforms points from path space to world space. \n\nIf null the identity transform will be used.\n\nThis is used when the world position of the agent does not match the corresponding position on the graph. This is the case in the example scene called 'Moving'.\n\n[more in online documentation] +Pathfinding.RichSpecial.first richspecial.html#first +Pathfinding.RichSpecial.nodeLink richspecial.html#nodeLink +Pathfinding.RichSpecial.reverse richspecial.html#reverse +Pathfinding.RichSpecial.second richspecial.html#second +Pathfinding.RuleElevationPenaltyEditor.GizmoColorMax ruleelevationpenaltyeditor.html#GizmoColorMax +Pathfinding.RuleElevationPenaltyEditor.GizmoColorMin ruleelevationpenaltyeditor.html#GizmoColorMin +Pathfinding.RuleElevationPenaltyEditor.lastChangedTime ruleelevationpenaltyeditor.html#lastChangedTime +Pathfinding.RuleTextureEditor.ChannelUseNames ruletextureeditor.html#ChannelUseNames +Pathfinding.ScanningStage pathfinding.html#ScanningStage Info about where in the scanning process a graph is. +Pathfinding.Seeker.ModifierPass seeker.html#ModifierPass +Pathfinding.Seeker.detailedGizmos seeker.html#detailedGizmos Enables drawing of the non-postprocessed path using Gizmos. \n\nThe path will show up in orange.\n\nRequires that drawGizmos is true.\n\nThis will show the path before any post processing such as smoothing is applied.\n\n[more in online documentation] +Pathfinding.Seeker.drawGizmos seeker.html#drawGizmos Enables drawing of the last calculated path using Gizmos. \n\nThe path will show up in green.\n\n[more in online documentation] +Pathfinding.Seeker.graphMask seeker.html#graphMask Graphs that this Seeker can use. \n\nThis field determines which graphs will be considered when searching for the start and end nodes of a path. It is useful in numerous situations, for example if you want to make one graph for small units and one graph for large units.\n\nThis is a bitmask so if you for example want to make the agent only use graph index 3 then you can set this to: [code in online documentation]\n\n[more in online documentation]\nNote that this field only stores which graph indices that are allowed. This means that if the graphs change their ordering then this mask may no longer be correct.\n\nIf you know the name of the graph you can use the GraphMask.FromGraphName method: [code in online documentation]\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.Seeker.graphMaskCompatibility seeker.html#graphMaskCompatibility Used for serialization backwards compatibility. +Pathfinding.Seeker.lastCompletedNodePath seeker.html#lastCompletedNodePath Used for drawing gizmos. +Pathfinding.Seeker.lastCompletedVectorPath seeker.html#lastCompletedVectorPath Used for drawing gizmos. +Pathfinding.Seeker.lastPathID seeker.html#lastPathID The path ID of the last path queried. +Pathfinding.Seeker.modifiers seeker.html#modifiers Internal list of all modifiers. +Pathfinding.Seeker.onPartialPathDelegate seeker.html#onPartialPathDelegate Cached delegate to avoid allocating one every time a path is started. +Pathfinding.Seeker.onPathDelegate seeker.html#onPathDelegate Cached delegate to avoid allocating one every time a path is started. +Pathfinding.Seeker.path seeker.html#path The current path. +Pathfinding.Seeker.pathCallback seeker.html#pathCallback Callback for when a path is completed. \n\nMovement scripts should register to this delegate.\n\nA temporary callback can also be set when calling StartPath, but that delegate will only be called for that path\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.Seeker.postProcessPath seeker.html#postProcessPath Called after a path has been calculated, right before modifiers are executed. +Pathfinding.Seeker.preProcessPath seeker.html#preProcessPath Called before pathfinding is started. +Pathfinding.Seeker.prevPath seeker.html#prevPath Previous path. \n\nUsed to draw gizmos +Pathfinding.Seeker.startEndModifier seeker.html#startEndModifier Path modifier which tweaks the start and end points of a path. +Pathfinding.Seeker.tagCostMultipliers seeker.html#tagCostMultipliers Multiplier for the cost of moving some distance across a node with a given tag. \n\nThis will be multiplied by the traversed distance in millimeters (see Int3.Precision) to get the final cost. The default value is 1, which means that the cost will be the same as the distance. If you set this to 2, the cost will be twice as high as the distance.\n\nFor example, moving 1 world unit across a node with tag 0 will cost 1000 * tagCostMultipliers[0] units of movement cost.\n\nIf null (the default), all tags will be treated as having a multiplier of 1.\n\n[more in online documentation]\nIn the inspector, this value is displayed as "Cost per world unit", and is shown as multiplied by 1000 ( Int3.Precision), to make it the cost per world unit. While in code, it is a multiplier of the default cost.\n\n [image in online documentation]\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.Seeker.tagEntryCosts seeker.html#tagEntryCosts How much it costs to enter a node with a given tag. \n\nThis can be used to make agents avoid, or prefer, nodes with certain tags.\n\nFor example, every time a path enters tag 0 (which is the default tag), it will cost an extra tagEntryCosts[0].\n\nFor most pathfinding purposes, you can think of this as just the additional cost of traversing a node with the given tag. The distinction between entering and exiting a node is a very small one, and will not make a difference in most cases.\n\nThe index in the array corresponds to the tag. All entry costs are positive values since the A* algorithm cannot handle negative costs.\n\nIf null (the default), all tags will be treated as having an entry cost of zero. However, the path will still receive cost from the tagCostMultipliers field.\n\nThis cost will be applied for every node that is entered, regardless of if the previous node had the same tag or not.\n\n[more in online documentation]\n[code in online documentation]\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.Seeker.tagPenalties seeker.html#tagPenalties Penalties for each tag. \n\nTag 0 which is the default tag, will have added a penalty of tagPenalties[0]. These should only be positive values since the A* algorithm cannot handle negative penalties.\n\nThe length of this array should be exactly 32, one for each tag.\n\n[more in online documentation] +Pathfinding.Seeker.tmpPathCallback seeker.html#tmpPathCallback Temporary callback only called for the current path. \n\nThis value is set by the StartPath functions +Pathfinding.Seeker.traversableTags seeker.html#traversableTags The tags which the Seeker can traverse. \n\nThis is a bitmask, i.e bit 0 indicates that tag 0 is traversable, bit 3 indicates tag 3 is traversable etc.\n\nBy default all tags are traversable, which corresponds to the value -1.\n\n [image in online documentation]\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.Seeker.traversalProvider seeker.html#traversalProvider Custom traversal provider to calculate which nodes are traversable and their penalties. \n\nThis can be used to override the built-in pathfinding logic.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.SeekerEditor.exactnessLabels seekereditor.html#exactnessLabels +Pathfinding.SeekerEditor.scripts seekereditor.html#scripts +Pathfinding.SeekerEditor.tagPenaltiesOpen seekereditor.html#tagPenaltiesOpen +Pathfinding.Serialization.AstarSerializer.V3_8_3 astarserializer.html#V3_8_3 Cached version object for 3.8.3. +Pathfinding.Serialization.AstarSerializer.V3_9_0 astarserializer.html#V3_9_0 Cached version object for 3.9.0. +Pathfinding.Serialization.AstarSerializer.V4_1_0 astarserializer.html#V4_1_0 Cached version object for 4.1.0. +Pathfinding.Serialization.AstarSerializer.V4_3_12 astarserializer.html#V4_3_12 Cached version object for 4.3.12. +Pathfinding.Serialization.AstarSerializer.V4_3_2 astarserializer.html#V4_3_2 Cached version object for 4.3.2. +Pathfinding.Serialization.AstarSerializer.V4_3_37 astarserializer.html#V4_3_37 Cached version object for 4.3.37. +Pathfinding.Serialization.AstarSerializer.V4_3_6 astarserializer.html#V4_3_6 Cached version object for 4.3.6. +Pathfinding.Serialization.AstarSerializer.V4_3_68 astarserializer.html#V4_3_68 Cached version object for 4.3.68. +Pathfinding.Serialization.AstarSerializer.V4_3_74 astarserializer.html#V4_3_74 Cached version object for 4.3.74. +Pathfinding.Serialization.AstarSerializer.V4_3_80 astarserializer.html#V4_3_80 Cached version object for 4.3.80. +Pathfinding.Serialization.AstarSerializer.V4_3_83 astarserializer.html#V4_3_83 Cached version object for 4.3.83. +Pathfinding.Serialization.AstarSerializer.V4_3_85 astarserializer.html#V4_3_85 Cached version object for 4.3.85. +Pathfinding.Serialization.AstarSerializer.V4_3_87 astarserializer.html#V4_3_87 Cached version object for 4.3.87. +Pathfinding.Serialization.AstarSerializer.V5_1_0 astarserializer.html#V5_1_0 Cached version object for 5.1.0. +Pathfinding.Serialization.AstarSerializer.V5_2_0 astarserializer.html#V5_2_0 Cached version object for 5.2.0. +Pathfinding.Serialization.AstarSerializer._stringBuilder astarserializer.html#_stringBuilder Cached StringBuilder to avoid excessive allocations. +Pathfinding.Serialization.AstarSerializer.binaryExt astarserializer.html#binaryExt Extension to use for binary files. +Pathfinding.Serialization.AstarSerializer.checksum astarserializer.html#checksum Checksum for the serialized data. \n\nUsed to provide a quick equality check in editor code +Pathfinding.Serialization.AstarSerializer.contextRoot astarserializer.html#contextRoot Root GameObject used for deserialization. \n\nThis should be the GameObject which holds the AstarPath component. Important when deserializing when the component is on a prefab. +Pathfinding.Serialization.AstarSerializer.data astarserializer.html#data +Pathfinding.Serialization.AstarSerializer.encoding astarserializer.html#encoding +Pathfinding.Serialization.AstarSerializer.graphIndexInZip astarserializer.html#graphIndexInZip Index used for the graph in the file. \n\nIf some graphs were null in the file then graphIndexInZip[graphs[i]] may not equal i. Used for deserialization. +Pathfinding.Serialization.AstarSerializer.graphs astarserializer.html#graphs Graphs that are being serialized or deserialized. +Pathfinding.Serialization.AstarSerializer.jsonExt astarserializer.html#jsonExt Extension to use for json files. +Pathfinding.Serialization.AstarSerializer.meta astarserializer.html#meta Graph metadata. +Pathfinding.Serialization.AstarSerializer.persistentGraphs astarserializer.html#persistentGraphs True if the graph should be serialized, indexed by graph index. +Pathfinding.Serialization.AstarSerializer.settings astarserializer.html#settings Settings for serialization. +Pathfinding.Serialization.AstarSerializer.zip astarserializer.html#zip Zip which the data is loaded from. +Pathfinding.Serialization.AstarSerializer.zipStream astarserializer.html#zipStream Memory stream with the zip data. +Pathfinding.Serialization.GraphMeta.graphs graphmeta.html#graphs Number of graphs serialized. +Pathfinding.Serialization.GraphMeta.guids graphmeta.html#guids Guids for all graphs. +Pathfinding.Serialization.GraphMeta.typeNames graphmeta.html#typeNames Type names for all graphs. +Pathfinding.Serialization.GraphMeta.version graphmeta.html#version Project version it was saved with. +Pathfinding.Serialization.GraphSerializationContext.graphIndex graphserializationcontext.html#graphIndex Index of the graph which is currently being processed. \n\n[more in online documentation] +Pathfinding.Serialization.GraphSerializationContext.id2NodeMapping graphserializationcontext.html#id2NodeMapping +Pathfinding.Serialization.GraphSerializationContext.meta graphserializationcontext.html#meta Metadata about graphs being deserialized. +Pathfinding.Serialization.GraphSerializationContext.persistentGraphs graphserializationcontext.html#persistentGraphs True if a graph should be persisted. \n\nIndexed by graph index +Pathfinding.Serialization.GraphSerializationContext.reader graphserializationcontext.html#reader Deserialization stream. \n\nWill only be set when deserializing +Pathfinding.Serialization.GraphSerializationContext.writer graphserializationcontext.html#writer Serialization stream. \n\nWill only be set when serializing +Pathfinding.Serialization.JsonDynamicTypeAliasAttribute.alias jsondynamictypealiasattribute.html#alias +Pathfinding.Serialization.JsonDynamicTypeAliasAttribute.type jsondynamictypealiasattribute.html#type +Pathfinding.Serialization.Migrations.IsLegacyFormat migrations.html#IsLegacyFormat +Pathfinding.Serialization.Migrations.LegacyVersion migrations.html#LegacyVersion +Pathfinding.Serialization.Migrations.MIGRATE_TO_BITFIELD migrations.html#MIGRATE_TO_BITFIELD A special migration flag which is used to mark that the version has been migrated to the bitfield format, from the legacy linear version format. +Pathfinding.Serialization.Migrations.allMigrations migrations.html#allMigrations Bitfield of all migrations that the component supports. \n\nA newly created component will be initialized with this value. +Pathfinding.Serialization.Migrations.finishedMigrations migrations.html#finishedMigrations Bitfield of all migrations that have been run. +Pathfinding.Serialization.Migrations.ignore migrations.html#ignore +Pathfinding.Serialization.SerializableAnimationCurve.keys serializableanimationcurve.html#keys +Pathfinding.Serialization.SerializableAnimationCurve.postWrapMode serializableanimationcurve.html#postWrapMode +Pathfinding.Serialization.SerializableAnimationCurve.preWrapMode serializableanimationcurve.html#preWrapMode +Pathfinding.Serialization.SerializeSettings.NodesAndSettings serializesettings.html#NodesAndSettings Serialization settings for serializing nodes and settings. +Pathfinding.Serialization.SerializeSettings.Settings serializesettings.html#Settings Serialization settings for only saving graph settings. +Pathfinding.Serialization.SerializeSettings.nodes serializesettings.html#nodes Enable to include node data. \n\nIf false, only settings will be saved +Pathfinding.Serialization.TinyJsonDeserializer.builder tinyjsondeserializer.html#builder +Pathfinding.Serialization.TinyJsonDeserializer.contextRoot tinyjsondeserializer.html#contextRoot +Pathfinding.Serialization.TinyJsonDeserializer.fullTextDebug tinyjsondeserializer.html#fullTextDebug +Pathfinding.Serialization.TinyJsonDeserializer.numberFormat tinyjsondeserializer.html#numberFormat +Pathfinding.Serialization.TinyJsonDeserializer.reader tinyjsondeserializer.html#reader +Pathfinding.Serialization.TinyJsonSerializer.invariantCulture tinyjsonserializer.html#invariantCulture +Pathfinding.Serialization.TinyJsonSerializer.output tinyjsonserializer.html#output +Pathfinding.Serialization.TinyJsonSerializer.serializers tinyjsonserializer.html#serializers +Pathfinding.Side pathfinding.html#Side Indicates the side of a line that a point lies on. +Pathfinding.SimpleSmoothModifier.Order simplesmoothmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.SimpleSmoothModifier.SmoothType simplesmoothmodifier.html#SmoothType +Pathfinding.SimpleSmoothModifier.bezierTangentLength simplesmoothmodifier.html#bezierTangentLength Length factor of the bezier curves' tangents'. +Pathfinding.SimpleSmoothModifier.factor simplesmoothmodifier.html#factor Roundness factor used for CurvedNonuniform. +Pathfinding.SimpleSmoothModifier.iterations simplesmoothmodifier.html#iterations Number of times to apply smoothing. +Pathfinding.SimpleSmoothModifier.maxSegmentLength simplesmoothmodifier.html#maxSegmentLength The length of the segments in the smoothed path when using uniformLength. \n\nA high value yields rough paths and low value yields very smooth paths, but is slower +Pathfinding.SimpleSmoothModifier.offset simplesmoothmodifier.html#offset Offset to apply in each smoothing iteration when using Offset Simple. \n\n[more in online documentation] +Pathfinding.SimpleSmoothModifier.smoothType simplesmoothmodifier.html#smoothType Type of smoothing to use. +Pathfinding.SimpleSmoothModifier.strength simplesmoothmodifier.html#strength Determines how much smoothing to apply in each smooth iteration. \n\n0.5 usually produces the nicest looking curves. +Pathfinding.SimpleSmoothModifier.subdivisions simplesmoothmodifier.html#subdivisions Number of times to subdivide when not using a uniform length. +Pathfinding.SimpleSmoothModifier.uniformLength simplesmoothmodifier.html#uniformLength Toggle to divide all lines in equal length segments. \n\n[more in online documentation] +Pathfinding.SingleNodeBlocker.lastBlocked singlenodeblocker.html#lastBlocked +Pathfinding.SingleNodeBlocker.manager singlenodeblocker.html#manager +Pathfinding.StartEndModifier.Exactness startendmodifier.html#Exactness Sets where the start and end points of a path should be placed. \n\nHere is a legend showing what the different items in the above images represent. The images above show a path coming in from the top left corner and ending at a node next to an obstacle as well as 2 different possible end points of the path and how they would be modified. [image in online documentation] +Pathfinding.StartEndModifier.Order startendmodifier.html#Order Modifiers will be executed from lower order to higher order. \n\nThis value is assumed to stay constant. +Pathfinding.StartEndModifier.addPoints startendmodifier.html#addPoints Add points to the path instead of replacing them. \n\nIf for example exactEndPoint is set to ClosestOnNode then the path will be modified so that the path goes first to the center of the last node in the path and then goes to the closest point on the node to the end point in the path request.\n\nIf this is false however then the relevant points in the path will simply be replaced. In the above example the path would go directly to the closest point on the node without passing through the center of the node. +Pathfinding.StartEndModifier.adjustStartPoint startendmodifier.html#adjustStartPoint Will be called when a path is processed. \n\nThe value which is returned will be used as the start point of the path and potentially clamped depending on the value of the exactStartPoint field. Only used for the Original, Interpolate and NodeConnection modes. +Pathfinding.StartEndModifier.connectionBuffer startendmodifier.html#connectionBuffer +Pathfinding.StartEndModifier.connectionBufferAddDelegate startendmodifier.html#connectionBufferAddDelegate +Pathfinding.StartEndModifier.exactEndPoint startendmodifier.html#exactEndPoint How the end point of the path will be determined. \n\n[more in online documentation] +Pathfinding.StartEndModifier.exactStartPoint startendmodifier.html#exactStartPoint How the start point of the path will be determined. \n\n[more in online documentation] +Pathfinding.StartEndModifier.mask startendmodifier.html#mask +Pathfinding.StartEndModifier.useGraphRaycasting startendmodifier.html#useGraphRaycasting Do a straight line check from the node's center to the point determined by the Exactness. \n\n[more in online documentation] +Pathfinding.StartEndModifier.useRaycasting startendmodifier.html#useRaycasting Do a straight line check from the node's center to the point determined by the Exactness. \n\nThere are very few cases where you will want to use this. It is mostly here for backwards compatibility reasons.\n\n[more in online documentation] +Pathfinding.Sync.BlockableChannel.Receiver.channel receiver.html#channel +Pathfinding.Sync.BlockableChannel.allReceiversBlocked blockablechannel.html#allReceiversBlocked True if blocking and all receivers are waiting for unblocking. +Pathfinding.Sync.BlockableChannel.blocked blockablechannel.html#blocked +Pathfinding.Sync.BlockableChannel.isBlocked blockablechannel.html#isBlocked If true, all calls to Receive will block until this property is set to false. +Pathfinding.Sync.BlockableChannel.isClosed blockablechannel.html#isClosed True if Close has been called. +Pathfinding.Sync.BlockableChannel.isEmpty blockablechannel.html#isEmpty True if the queue is empty. +Pathfinding.Sync.BlockableChannel.isStarving blockablechannel.html#isStarving +Pathfinding.Sync.BlockableChannel.lockObj blockablechannel.html#lockObj +Pathfinding.Sync.BlockableChannel.numReceivers blockablechannel.html#numReceivers +Pathfinding.Sync.BlockableChannel.queue blockablechannel.html#queue +Pathfinding.Sync.BlockableChannel.starving blockablechannel.html#starving +Pathfinding.Sync.BlockableChannel.waitingReceivers blockablechannel.html#waitingReceivers +Pathfinding.Sync.IProgress.Progress iprogress.html#Progress +Pathfinding.Sync.Promise.IsCompleted promise.html#IsCompleted +Pathfinding.Sync.Promise.Progress promise.html#Progress +Pathfinding.Sync.Promise.handle promise.html#handle +Pathfinding.Sync.Promise.result promise.html#result +Pathfinding.Sync.RWLock.CombinedReadLockAsync.dependency combinedreadlockasync.html#dependency +Pathfinding.Sync.RWLock.CombinedReadLockAsync.lock1 combinedreadlockasync.html#lock1 +Pathfinding.Sync.RWLock.CombinedReadLockAsync.lock2 combinedreadlockasync.html#lock2 +Pathfinding.Sync.RWLock.LockSync.inner locksync.html#inner +Pathfinding.Sync.RWLock.ReadLockAsync.dependency readlockasync.html#dependency +Pathfinding.Sync.RWLock.ReadLockAsync.inner readlockasync.html#inner +Pathfinding.Sync.RWLock.WriteLockAsync.dependency writelockasync.html#dependency +Pathfinding.Sync.RWLock.WriteLockAsync.inner writelockasync.html#inner +Pathfinding.Sync.RWLock.lastRead rwlock.html#lastRead +Pathfinding.Sync.RWLock.lastWrite rwlock.html#lastWrite +Pathfinding.Sync.SpinLock.locked spinlock.html#locked +Pathfinding.TargetMover.Trigger targetmover.html#Trigger +Pathfinding.TargetMover.cam targetmover.html#cam +Pathfinding.TargetMover.clickEffect targetmover.html#clickEffect +Pathfinding.TargetMover.formationMode targetmover.html#formationMode +Pathfinding.TargetMover.mask targetmover.html#mask Mask for the raycast placement. +Pathfinding.TargetMover.onlyOnDoubleClick targetmover.html#onlyOnDoubleClick Determines if the target position should be updated every frame or only on double-click. +Pathfinding.TargetMover.target targetmover.html#target +Pathfinding.TargetMover.trigger targetmover.html#trigger +Pathfinding.TargetMover.use2D targetmover.html#use2D +Pathfinding.TemporaryNode.associatedNode temporarynode.html#associatedNode +Pathfinding.TemporaryNode.position temporarynode.html#position +Pathfinding.TemporaryNode.targetIndex temporarynode.html#targetIndex +Pathfinding.TemporaryNode.type temporarynode.html#type +Pathfinding.TemporaryNodeType pathfinding.html#TemporaryNodeType +Pathfinding.ThreadCount pathfinding.html#ThreadCount Number of threads to use. +Pathfinding.TraversalConstraint.FilterType traversalconstraint.html#FilterType +Pathfinding.TraversalConstraint.None traversalconstraint.html#None Traversal constraint which allows all (walkable) nodes on all graphs to be traversed. \n\n[more in online documentation]\nThe returned struct's CanTraverse(GraphNode) method will return true for all nodes except those with the GraphNode.Walkable property set to false. +Pathfinding.TraversalConstraint.filter traversalconstraint.html#filter Allow traversing only nodes which the given filter function returns true for. \n\nThe other constraints on this struct will still apply, in addition to the filter function.\n\n[more in online documentation]\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.TraversalConstraint.filterDiagonalGridConnections traversalconstraint.html#filterDiagonalGridConnections Filter diagonal connections on grid graphs using GridGraph.cutCorners for effects applied by any assigned ITraversalProvider. \n\nIf a traversal provider is used, returns ITraversalProvider.filterDiagonalGridConnections for it. Otherwise returns true.\n\n[more in online documentation] +Pathfinding.TraversalConstraint.filterObj traversalconstraint.html#filterObj +Pathfinding.TraversalConstraint.filterType traversalconstraint.html#filterType +Pathfinding.TraversalConstraint.graphMask traversalconstraint.html#graphMask Graphs which are traversable. \n\nThis is a bitmask, meaning that bit 0 specifies whether or not the first graph in the graphs list should be included in the search, bit 1 specifies whether or not the second graph should be included, and so on.\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.TraversalConstraint.tags traversalconstraint.html#tags Set of tags which are traversable. \n\nThis is a bitmask, i.e bit 0 indicates that tag 0 is traversable, bit 3 indicates tag 3 is traversable etc.\n\nBy default all tags are traversable, which corresponds to the value -1.\n\n[code in online documentation][image in online documentation]\n\n[more in online documentation] +Pathfinding.TraversalConstraint.traversalProvider traversalconstraint.html#traversalProvider Allow traversing only nodes that are also traversable by the given ITraversalProvider. \n\nThe other constraints on this struct will still apply, in addition to the ITraversalProvider.\n\n[more in online documentation] +Pathfinding.TraversalCosts.hasCosts traversalcosts.html#hasCosts True if traversal costs might be non-default, and false if they are guaranteed to be default for all nodes. \n\nIf false, then GetTraversalCostMultiplier will always return 1, and GetConnectionCost will always return the node's penalty. +Pathfinding.TraversalCosts.tagCostMultipliers traversalcosts.html#tagCostMultipliers Multiplier for the cost of moving some distance across a node with a given tag. \n\nThis will be multiplied by the traversed distance in millimeters (see Int3.Precision) to get the final cost. The default value is 1, which means that the cost will be the same as the distance. If you set this to 2, the cost will be twice as high as the distance.\n\nFor example, moving 1 world unit across a node with tag 0 will cost 1000 * tagCostMultipliers[0] units of movement cost.\n\nIf null (the default), all tags will be treated as having a multiplier of 1.\n\n[more in online documentation]\nIn the inspector, this value is displayed as "Cost per world unit", and is shown as multiplied by 1000 ( Int3.Precision), to make it the cost per world unit. While in code, it is a multiplier of the default cost.\n\n [image in online documentation]\n\n[code in online documentation]\n\n[more in online documentation] +Pathfinding.TraversalCosts.tagCostMultipliersInternal traversalcosts.html#tagCostMultipliersInternal +Pathfinding.TraversalCosts.tagEntryCosts traversalcosts.html#tagEntryCosts How much it costs to enter a node with a given tag. \n\nThis can be used to make agents avoid, or prefer, nodes with certain tags.\n\nFor example, every time a path enters tag 0 (which is the default tag), it will cost an extra tagEntryCosts[0].\n\nFor most pathfinding purposes, you can think of this as just the additional cost of traversing a node with the given tag. The distinction between entering and exiting a node is a very small one, and will not make a difference in most cases.\n\nThe index in the array corresponds to the tag. All entry costs are positive values since the A* algorithm cannot handle negative costs.\n\nIf null (the default), all tags will be treated as having an entry cost of zero. However, the path will still receive cost from the tagCostMultipliers field.\n\nThis cost will be applied for every node that is entered, regardless of if the previous node had the same tag or not.\n\n[more in online documentation]\n[code in online documentation]\n\n [image in online documentation]\n\n[more in online documentation] +Pathfinding.TraversalCosts.tagEntryCostsInternal traversalcosts.html#tagEntryCostsInternal +Pathfinding.TraversalCosts.traversalProvider traversalcosts.html#traversalProvider Traversal provider to forward all cost calculations to. \n\nIf not null, this will override the GetTraversalCost method's output completely. The traversal provider will be called for each node that is explored by a path search. +Pathfinding.TriangleMeshNode.InaccuratePathSearch trianglemeshnode.html#InaccuratePathSearch Legacy compatibility. \n\nEnabling this will make pathfinding use node centers, which leads to less accurate paths (but it's faster). +Pathfinding.TriangleMeshNode.MarkerClosest trianglemeshnode.html#MarkerClosest +Pathfinding.TriangleMeshNode.MarkerDecode trianglemeshnode.html#MarkerDecode +Pathfinding.TriangleMeshNode.MarkerGetVertices trianglemeshnode.html#MarkerGetVertices +Pathfinding.TriangleMeshNode.PathNodeVariants trianglemeshnode.html#PathNodeVariants +Pathfinding.TriangleMeshNode.TileIndex trianglemeshnode.html#TileIndex Tile index in the recast or navmesh graph that this node is part of. \n\n[more in online documentation] +Pathfinding.TriangleMeshNode._navmeshHolders trianglemeshnode.html#_navmeshHolders Holds INavmeshHolder references for all graph indices to be able to access them in a performant manner. +Pathfinding.TriangleMeshNode.lockObject trianglemeshnode.html#lockObject Used for synchronised access to the _navmeshHolders array. +Pathfinding.TriangleMeshNode.v0 trianglemeshnode.html#v0 Internal vertex index for the first vertex. +Pathfinding.TriangleMeshNode.v1 trianglemeshnode.html#v1 Internal vertex index for the second vertex. +Pathfinding.TriangleMeshNode.v2 trianglemeshnode.html#v2 Internal vertex index for the third vertex. +Pathfinding.UniqueComponentAttribute.tag uniquecomponentattribute.html#tag +Pathfinding.UnityReferenceHelper.guid unityreferencehelper.html#guid +Pathfinding.Util.BatchedEvents.Archetype.action archetype.html#action +Pathfinding.Util.BatchedEvents.Archetype.archetypeIndex archetype.html#archetypeIndex +Pathfinding.Util.BatchedEvents.Archetype.events archetype.html#events +Pathfinding.Util.BatchedEvents.Archetype.objectCount archetype.html#objectCount +Pathfinding.Util.BatchedEvents.Archetype.objects archetype.html#objects +Pathfinding.Util.BatchedEvents.Archetype.sampler archetype.html#sampler +Pathfinding.Util.BatchedEvents.Archetype.transforms archetype.html#transforms +Pathfinding.Util.BatchedEvents.Archetype.type archetype.html#type +Pathfinding.Util.BatchedEvents.Archetype.variant archetype.html#variant +Pathfinding.Util.BatchedEvents.ArchetypeMask batchedevents.html#ArchetypeMask +Pathfinding.Util.BatchedEvents.ArchetypeOffset batchedevents.html#ArchetypeOffset +Pathfinding.Util.BatchedEvents.Event batchedevents.html#Event +Pathfinding.Util.BatchedEvents.data batchedevents.html#data +Pathfinding.Util.BatchedEvents.instance batchedevents.html#instance +Pathfinding.Util.BatchedEvents.isIterating batchedevents.html#isIterating +Pathfinding.Util.BatchedEvents.isIteratingOverTypeIndex batchedevents.html#isIteratingOverTypeIndex +Pathfinding.Util.DependencyCheck.Dependency.name dependency.html#name +Pathfinding.Util.DependencyCheck.Dependency.version dependency.html#version +Pathfinding.Util.EditorGUILayoutHelper.tagNamesAndEditTagsButton editorguilayouthelper.html#tagNamesAndEditTagsButton Tag names and an additional 'Edit Tags...' entry. \n\nUsed for SingleTagField +Pathfinding.Util.EditorGUILayoutHelper.tagValues editorguilayouthelper.html#tagValues +Pathfinding.Util.EditorGUILayoutHelper.timeLastUpdatedTagNames editorguilayouthelper.html#timeLastUpdatedTagNames Last time tagNamesAndEditTagsButton was updated. \n\nUses EditorApplication.timeSinceStartup +Pathfinding.Util.GraphGizmoHelper.builder graphgizmohelper.html#builder +Pathfinding.Util.GraphGizmoHelper.debugData graphgizmohelper.html#debugData +Pathfinding.Util.GraphGizmoHelper.debugFloor graphgizmohelper.html#debugFloor +Pathfinding.Util.GraphGizmoHelper.debugMode graphgizmohelper.html#debugMode +Pathfinding.Util.GraphGizmoHelper.debugPathID graphgizmohelper.html#debugPathID +Pathfinding.Util.GraphGizmoHelper.debugPathNodes graphgizmohelper.html#debugPathNodes +Pathfinding.Util.GraphGizmoHelper.debugRoof graphgizmohelper.html#debugRoof +Pathfinding.Util.GraphGizmoHelper.drawConnection graphgizmohelper.html#drawConnection +Pathfinding.Util.GraphGizmoHelper.drawConnectionColor graphgizmohelper.html#drawConnectionColor +Pathfinding.Util.GraphGizmoHelper.drawConnectionStart graphgizmohelper.html#drawConnectionStart +Pathfinding.Util.GraphGizmoHelper.hasher graphgizmohelper.html#hasher +Pathfinding.Util.GraphGizmoHelper.nodeStorage graphgizmohelper.html#nodeStorage +Pathfinding.Util.GraphGizmoHelper.showSearchTree graphgizmohelper.html#showSearchTree +Pathfinding.Util.GraphSnapshot.inner graphsnapshot.html#inner +Pathfinding.Util.GraphTransform.i3translation graphtransform.html#i3translation +Pathfinding.Util.GraphTransform.identity graphtransform.html#identity True if this transform is the identity transform (i.e it does not do anything) +Pathfinding.Util.GraphTransform.identityTransform graphtransform.html#identityTransform +Pathfinding.Util.GraphTransform.inverseMatrix graphtransform.html#inverseMatrix +Pathfinding.Util.GraphTransform.inverseRotation graphtransform.html#inverseRotation +Pathfinding.Util.GraphTransform.isIdentity graphtransform.html#isIdentity +Pathfinding.Util.GraphTransform.isOnlyTranslational graphtransform.html#isOnlyTranslational +Pathfinding.Util.GraphTransform.isXY graphtransform.html#isXY +Pathfinding.Util.GraphTransform.isXZ graphtransform.html#isXZ +Pathfinding.Util.GraphTransform.matrix graphtransform.html#matrix +Pathfinding.Util.GraphTransform.onlyTranslational graphtransform.html#onlyTranslational True if this transform is a pure translation without any scaling or rotation. +Pathfinding.Util.GraphTransform.rotation graphtransform.html#rotation +Pathfinding.Util.GraphTransform.translation graphtransform.html#translation +Pathfinding.Util.GraphTransform.up graphtransform.html#up +Pathfinding.Util.GraphTransform.xyPlane graphtransform.html#xyPlane Transforms from the XZ plane to the XY plane. +Pathfinding.Util.GraphTransform.xzPlane graphtransform.html#xzPlane Transforms from the XZ plane to the XZ plane (i.e. \n\nan identity transformation) +Pathfinding.Util.Guid._a guid.html#_a +Pathfinding.Util.Guid._b guid.html#_b +Pathfinding.Util.Guid.hex guid.html#hex +Pathfinding.Util.Guid.random guid.html#random +Pathfinding.Util.Guid.text guid.html#text +Pathfinding.Util.Guid.zero guid.html#zero +Pathfinding.Util.Guid.zeroString guid.html#zeroString +Pathfinding.Util.IEntityIndex.EntityIndex ientityindex.html#EntityIndex +Pathfinding.Util.MeshUtility.JobMergeNearbyVertices.CoordinateSorter.vertices coordinatesorter.html#vertices +Pathfinding.Util.MeshUtility.JobMergeNearbyVertices.mergeRadiusSq jobmergenearbyvertices.html#mergeRadiusSq +Pathfinding.Util.MeshUtility.JobMergeNearbyVertices.triangles jobmergenearbyvertices.html#triangles +Pathfinding.Util.MeshUtility.JobMergeNearbyVertices.vertices jobmergenearbyvertices.html#vertices +Pathfinding.Util.MeshUtility.JobRemoveDegenerateTriangles.tags jobremovedegeneratetriangles.html#tags +Pathfinding.Util.MeshUtility.JobRemoveDegenerateTriangles.triangles jobremovedegeneratetriangles.html#triangles +Pathfinding.Util.MeshUtility.JobRemoveDegenerateTriangles.verbose jobremovedegeneratetriangles.html#verbose +Pathfinding.Util.MeshUtility.JobRemoveDegenerateTriangles.vertices jobremovedegeneratetriangles.html#vertices +Pathfinding.Util.NativeMovementPlane.rotation nativemovementplane.html#rotation The rotation of the plane. \n\nThe plane is defined by the XZ-plane rotated by this quaternion.\n\nShould always be normalized. +Pathfinding.Util.NativeMovementPlane.up nativemovementplane.html#up Normalized normal of the plane. +Pathfinding.Util.NodeHasher.debugData nodehasher.html#debugData +Pathfinding.Util.NodeHasher.hasher nodehasher.html#hasher +Pathfinding.Util.NodeHasher.includeAreaInfo nodehasher.html#includeAreaInfo +Pathfinding.Util.NodeHasher.includeHierarchicalNodeInfo nodehasher.html#includeHierarchicalNodeInfo +Pathfinding.Util.NodeHasher.includePathSearchInfo nodehasher.html#includePathSearchInfo +Pathfinding.Util.PathInterpolator.Cursor.currentDistance cursor.html#currentDistance +Pathfinding.Util.PathInterpolator.Cursor.currentSegmentLength cursor.html#currentSegmentLength +Pathfinding.Util.PathInterpolator.Cursor.curvatureDirection cursor.html#curvatureDirection A vector parallel to the local curvature. \n\nThis will be zero on straight line segments, and in the same direction as the rotation axis when on a corner.\n\nSince this interpolator follows a polyline, the curvature is always either 0 or infinite. Therefore the magnitude of this vector has no meaning when non-zero. Only the direction matters. +Pathfinding.Util.PathInterpolator.Cursor.distance cursor.html#distance Traversed distance from the start of the path. +Pathfinding.Util.PathInterpolator.Cursor.distanceToSegmentStart cursor.html#distanceToSegmentStart +Pathfinding.Util.PathInterpolator.Cursor.endPoint cursor.html#endPoint Last point in the path. +Pathfinding.Util.PathInterpolator.Cursor.fractionAlongCurrentSegment cursor.html#fractionAlongCurrentSegment Fraction of the way along the current segment. \n\n0 is at the start of the segment, 1 is at the end of the segment. +Pathfinding.Util.PathInterpolator.Cursor.interpolator cursor.html#interpolator +Pathfinding.Util.PathInterpolator.Cursor.position cursor.html#position Current position. +Pathfinding.Util.PathInterpolator.Cursor.remainingDistance cursor.html#remainingDistance Remaining distance until the end of the path. +Pathfinding.Util.PathInterpolator.Cursor.segmentCount cursor.html#segmentCount +Pathfinding.Util.PathInterpolator.Cursor.segmentIndex cursor.html#segmentIndex Current segment. \n\nThe start and end points of the segment are path[value] and path[value+1]. +Pathfinding.Util.PathInterpolator.Cursor.tangent cursor.html#tangent Tangent of the curve at the current position. \n\nNot necessarily normalized. +Pathfinding.Util.PathInterpolator.Cursor.valid cursor.html#valid True if this instance has a path set. \n\n[more in online documentation] +Pathfinding.Util.PathInterpolator.Cursor.version cursor.html#version +Pathfinding.Util.PathInterpolator.path pathinterpolator.html#path +Pathfinding.Util.PathInterpolator.start pathinterpolator.html#start +Pathfinding.Util.PathInterpolator.totalDistance pathinterpolator.html#totalDistance +Pathfinding.Util.PathInterpolator.valid pathinterpolator.html#valid True if this instance has a path set. \n\n[more in online documentation] +Pathfinding.Util.PathInterpolator.version pathinterpolator.html#version +Pathfinding.Util.PathPartWithLinkInfo.endIndex pathpartwithlinkinfo.html#endIndex Index of the last point in the path that this part represents. \n\nFor off-mesh links, this will refer to the first point in the part after the off-mesh link. +Pathfinding.Util.PathPartWithLinkInfo.linkInfo pathpartwithlinkinfo.html#linkInfo The off-mesh link that this part represents. \n\nWill contain a null link if this part is not an off-mesh link +Pathfinding.Util.PathPartWithLinkInfo.startIndex pathpartwithlinkinfo.html#startIndex Index of the first point in the path that this part represents. \n\nFor off-mesh links, this will refer to the last point in the part before the off-mesh link. +Pathfinding.Util.PathPartWithLinkInfo.type pathpartwithlinkinfo.html#type Specifies if this is a sequence of nodes, or an off-mesh link. +Pathfinding.Util.SimpleMovementPlane.XYPlane simplemovementplane.html#XYPlane A plane that spans the X and Y axes. +Pathfinding.Util.SimpleMovementPlane.XZPlane simplemovementplane.html#XZPlane A plane that spans the X and Z axes. +Pathfinding.Util.SimpleMovementPlane.inverseRotation simplemovementplane.html#inverseRotation +Pathfinding.Util.SimpleMovementPlane.isXY simplemovementplane.html#isXY +Pathfinding.Util.SimpleMovementPlane.isXZ simplemovementplane.html#isXZ +Pathfinding.Util.SimpleMovementPlane.plane simplemovementplane.html#plane +Pathfinding.Util.SimpleMovementPlane.rotation simplemovementplane.html#rotation +Pathfinding.Util.ToPlaneMatrix.matrix toplanematrix.html#matrix +Pathfinding.Util.ToWorldMatrix.matrix toworldmatrix.html#matrix +Pathfinding.VersionedMonoBehaviour.EntityIndex versionedmonobehaviour.html#EntityIndex Internal entity index used by BatchedEvents. \n\nShould never be modified by other scripts. +Pathfinding.VersionedMonoBehaviour.version versionedmonobehaviour.html#version Version of the serialized data. \n\nUsed for script upgrades. +Pathfinding.WelcomeScreen.FirstSceneToLoad welcomescreen.html#FirstSceneToLoad +Pathfinding.WelcomeScreen.askedAboutQuitting welcomescreen.html#askedAboutQuitting +Pathfinding.WelcomeScreen.isImportingSamples welcomescreen.html#isImportingSamples +Pathfinding.WelcomeScreen.m_VisualTreeAsset welcomescreen.html#m_VisualTreeAsset +Pathfinding.WorkItemProcessor.BoundsVisualization.BoundsVisualizationType boundsvisualization.html#BoundsVisualizationType +Pathfinding.WorkItemProcessor.BoundsVisualization.bounds boundsvisualization.html#bounds +Pathfinding.WorkItemProcessor.BoundsVisualization.createdFrame boundsvisualization.html#createdFrame +Pathfinding.WorkItemProcessor.BoundsVisualization.createdTime boundsvisualization.html#createdTime +Pathfinding.WorkItemProcessor.BoundsVisualization.type boundsvisualization.html#type +Pathfinding.WorkItemProcessor.IndexedQueue.Count indexedqueue.html#Count +Pathfinding.WorkItemProcessor.IndexedQueue.buffer indexedqueue.html#buffer +Pathfinding.WorkItemProcessor.IndexedQueue.start indexedqueue.html#start +Pathfinding.WorkItemProcessor.IndexedQueue.this[int index] indexedqueue.html#thisintindex +Pathfinding.WorkItemProcessor.OnGraphsUpdated workitemprocessor.html#OnGraphsUpdated +Pathfinding.WorkItemProcessor.anyGraphsDirty workitemprocessor.html#anyGraphsDirty +Pathfinding.WorkItemProcessor.anyQueued workitemprocessor.html#anyQueued True if any work items are queued right now. +Pathfinding.WorkItemProcessor.astar workitemprocessor.html#astar +Pathfinding.WorkItemProcessor.boundsVisualizations workitemprocessor.html#boundsVisualizations +Pathfinding.WorkItemProcessor.preUpdateEventSent workitemprocessor.html#preUpdateEventSent +Pathfinding.WorkItemProcessor.workItems workitemprocessor.html#workItems +Pathfinding.WorkItemProcessor.workItemsInProgress workitemprocessor.html#workItemsInProgress True while a batch of work items are being processed. \n\nSet to true when a work item is started to be processed, reset to false when all work items are complete.\n\nWork item updates are often spread out over several frames, this flag will be true during the whole time the updates are in progress. +Pathfinding.WorkItemProcessor.workItemsInProgressRightNow workitemprocessor.html#workItemsInProgressRightNow Used to prevent waiting for work items to complete inside other work items as that will cause the program to hang. \ No newline at end of file diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv.meta b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv.meta new file mode 100644 index 0000000..71b7031 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorAssets/tooltips.tsv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 89a6dfad301c7e922a96ffe5a465add6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs new file mode 100644 index 0000000..c77d351 --- /dev/null +++ b/FishROV/Assets/ThirdParty/AstarPro/Editor/EditorHelpers.cs @@ -0,0 +1,104 @@ +using UnityEngine; +using UnityEditor; + +namespace Pathfinding.Util { + ///