summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs18
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs30
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs15
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs388
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs97
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs26
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs16
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs91
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs222
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs85
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs113
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs196
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs17
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs58
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs94
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs10
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs35
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs46
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs35
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs223
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs9
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs43
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs75
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs146
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs104
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs45
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs86
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs95
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs47
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs277
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs33
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs47
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems.meta8
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs268
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs194
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs51
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs322
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs188
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs84
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs254
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs28
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs91
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta11
88 files changed, 4807 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components.meta
new file mode 100644
index 0000000..662ec62
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c496a3fafab9fab4a8c139fc7295f219
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs
new file mode 100644
index 0000000..9909d0f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs
@@ -0,0 +1,18 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.ECS.RVO;
+
+ /// <summary>An agent's shape represented as a cylinder</summary>
+ [System.Serializable]
+ public struct AgentCylinderShape : IComponentData {
+ /// <summary>Radius of the agent in world units</summary>
+ public float radius;
+
+ /// <summary>Height of the agent in world units</summary>
+ public float height;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentCylinderShape.cs.meta
new file mode 100644
index 0000000..aa81963
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs
new file mode 100644
index 0000000..3321294
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs
@@ -0,0 +1,30 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.Util;
+ using Unity.Mathematics;
+
+ /// <summary>Holds an agent's movement plane</summary>
+ [System.Serializable]
+ public struct AgentMovementPlane : IComponentData {
+ /// <summary>
+ /// 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)
+ /// </summary>
+ public NativeMovementPlane value;
+
+ /// <summary>Create a movement plane aligned with the XZ plane of the specified rotation</summary>
+ public AgentMovementPlane (quaternion rotation) {
+ value = new NativeMovementPlane(rotation);
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlane.cs.meta
new file mode 100644
index 0000000..9e6ebba
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs
new file mode 100644
index 0000000..1bcfbb4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs
@@ -0,0 +1,15 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ /// <summary>
+ /// The movement plane source for an agent.
+ ///
+ /// See: <see cref="MovementPlaneSource"/>
+ /// </summary>
+ [System.Serializable]
+ public struct AgentMovementPlaneSource : ISharedComponentData {
+ public MovementPlaneSource value;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentMovementPlaneSource.cs.meta
new file mode 100644
index 0000000..e3a1b88
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs
new file mode 100644
index 0000000..0b79f59
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs
@@ -0,0 +1,388 @@
+#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;
+
+ /// <summary>
+ /// 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: <see cref="ManagedAgentOffMeshLinkTraversal"/>
+ /// </summary>
+ public struct AgentOffMeshLinkTraversal : IComponentData {
+ /// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}</summary>
+ public float3 relativeStart;
+
+ /// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}</summary>
+ public float3 relativeEnd;
+
+ /// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeStart}. Deprecated: Use relativeStart instead</summary>
+ [System.Obsolete("Use relativeStart instead")]
+ public float3 firstPosition => relativeStart;
+
+ /// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.relativeEnd}. Deprecated: Use relativeEnd instead</summary>
+ [System.Obsolete("Use relativeEnd instead")]
+ public float3 secondPosition => relativeEnd;
+
+ /// <summary>\copydocref{OffMeshLinks.OffMeshLinkTracer.isReverse}</summary>
+ public bool isReverse;
+
+ public AgentOffMeshLinkTraversal (OffMeshLinks.OffMeshLinkTracer linkInfo) {
+ relativeStart = linkInfo.relativeStart;
+ relativeEnd = linkInfo.relativeEnd;
+ isReverse = linkInfo.isReverse;
+ }
+ }
+
+ /// <summary>
+ /// 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: <see cref="AgentOffMeshLinkTraversal"/>
+ /// </summary>
+ public class ManagedAgentOffMeshLinkTraversal : IComponentData, System.ICloneable, ICleanupComponentData {
+ /// <summary>Internal context used to pass component data to the coroutine</summary>
+ public AgentOffMeshLinkTraversalContext context;
+
+ /// <summary>Coroutine which is used to traverse the link</summary>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// 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 <see cref="IOffMeshLinkStateMachine"/> interface.
+ /// </summary>
+ 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;
+
+ /// <summary>The entity that is traversing the off-mesh link</summary>
+ public Entity entity;
+
+ /// <summary>Some internal state of the agent</summary>
+ [Unity.Properties.DontCreateProperty]
+ public ManagedState managedState;
+
+ /// <summary>
+ /// The off-mesh link that is being traversed.
+ ///
+ /// See: <see cref="link"/>
+ /// </summary>
+ [Unity.Properties.DontCreateProperty]
+ internal OffMeshLinks.OffMeshLinkConcrete concreteLink;
+
+ protected bool disabledRVO;
+ protected float backupRotationSmoothing = float.NaN;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float deltaTime;
+
+ protected GameObject gameObjectCache;
+
+ /// <summary>
+ /// 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 <see cref="FollowerEntity"/> component, this property may return null.
+ ///
+ /// Note: When directly modifying the agent's transform during a link traversal, you should use the <see cref="transform"/> property instead of modifying the GameObject's transform.
+ /// </summary>
+ public virtual GameObject gameObject {
+ get {
+ if (gameObjectCache == null) {
+ var follower = BatchedEvents.Find<FollowerEntity, Entity>(entity, (follower, entity) => follower.entity == entity);
+ if (follower != null) gameObjectCache = follower.gameObject;
+ }
+ return gameObjectCache;
+ }
+ }
+
+ /// <summary>ECS LocalTransform component attached to the agent</summary>
+ public ref LocalTransform transform {
+ get {
+ unsafe {
+ return ref *transformPtr;
+ }
+ }
+ }
+
+ /// <summary>The movement settings for the agent</summary>
+ public ref MovementSettings movementSettings {
+ get {
+ unsafe {
+ return ref *movementSettingsPtr;
+ }
+ }
+ }
+
+ /// <summary>
+ /// How the agent should move.
+ ///
+ /// The agent will move according to this data, every frame.
+ /// </summary>
+ public ref MovementControl movementControl {
+ get {
+ unsafe {
+ return ref *movementControlPtr;
+ }
+ }
+ }
+
+ /// <summary>Information about the off-mesh link that the agent is traversing</summary>
+ public OffMeshLinks.OffMeshLinkTracer link {
+ get {
+ unsafe {
+ return new OffMeshLinks.OffMeshLinkTracer(concreteLink, linkInfoPtr->relativeStart, linkInfoPtr->relativeEnd, linkInfoPtr->isReverse);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Information about the off-mesh link that the agent is traversing.
+ ///
+ /// Deprecated: Use the <see cref="link"/> property instead
+ /// </summary>
+ [System.Obsolete("Use the link property instead")]
+ public AgentOffMeshLinkTraversal linkInfo {
+ get {
+ unsafe {
+ return *linkInfoPtr;
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public ref NativeMovementPlane movementPlane {
+ get {
+ unsafe {
+ return ref movementPlanePtr->value;
+ }
+ }
+ }
+
+ public AgentOffMeshLinkTraversalContext (OffMeshLinks.OffMeshLinkConcrete link) {
+ this.concreteLink = link;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public virtual unsafe void SetInternalData (Entity entity, ref LocalTransform transform, ref AgentMovementPlane movementPlane, ref MovementControl movementControl, ref MovementSettings movementSettings, ref AgentOffMeshLinkTraversal linkInfo, 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.managedState = state;
+ this.deltaTime = deltaTime;
+ this.entity = entity;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public void DisableLocalAvoidance () {
+ if (managedState.enableLocalAvoidance) {
+ disabledRVO = true;
+ managedState.enableLocalAvoidance = false;
+ }
+ }
+
+ /// <summary>
+ /// Disables rotation smoothing for the agent.
+ ///
+ /// This disables the effect of <see cref="MovementSettings.rotationSmoothing"/> 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 <see cref="MoveTowards"/> method automatically disables rotation smoothing when called.
+ /// </summary>
+ public void DisableRotationSmoothing () {
+ if (float.IsNaN(backupRotationSmoothing) && movementSettings.rotationSmoothing > 0) {
+ backupRotationSmoothing = movementSettings.rotationSmoothing;
+ movementSettings.rotationSmoothing = 0;
+ }
+ }
+
+ /// <summary>
+ /// Restores the agent's settings to what it was before the link traversal started.
+ ///
+ /// This undos the changes made by <see cref="DisableLocalAvoidance"/> and <see cref="DisableRotationSmoothing"/>.
+ ///
+ /// This method is automatically called when the agent finishes traversing the link.
+ /// </summary>
+ public virtual void Restore () {
+ if (disabledRVO) {
+ managedState.enableLocalAvoidance = true;
+ disabledRVO = false;
+ }
+ if (!float.IsNaN(backupRotationSmoothing)) {
+ movementSettings.rotationSmoothing = backupRotationSmoothing;
+ backupRotationSmoothing = float.NaN;
+ }
+ }
+
+ /// <summary>Teleports the agent to the given position</summary>
+ public virtual void Teleport (float3 position) {
+ transform.Position = position;
+ }
+
+ /// <summary>
+ /// Thrown when the off-mesh link traversal should be aborted.
+ ///
+ /// See: <see cref="AgentOffMeshLinkTraversal.Abort"/>
+ /// </summary>
+ public class AbortOffMeshLinkTraversal : System.Exception {}
+
+ /// <summary>
+ /// 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 <see cref="Teleport"/>, and then use 'yield break;' from your coroutine.
+ /// </summary>
+ /// <param name="teleportToStart">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.</param>
+ 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.SetFromSingleNode(managedState.pathTracer.startNode, transform.Position, movementPlane);
+ throw new AbortOffMeshLinkTraversal();
+ }
+
+ /// <summary>
+ /// Move towards a point while ignoring the navmesh.
+ /// This method should be called repeatedly until the returned <see cref="MovementTarget.reached"/> property is true.
+ ///
+ /// Returns: A <see cref="MovementTarget"/> 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.
+ /// </summary>
+ /// <param name="position">The position to move towards.</param>
+ /// <param name="rotation">The rotation to rotate towards.</param>
+ /// <param name="gravity">If true, gravity will be applied to the agent.</param>
+ /// <param name="slowdown">If true, the agent will slow down as it approaches the target.</param>
+ 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();
+
+ 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;
+ unsafe {
+ linkInfoPtr = null;
+ movementControlPtr = null;
+ movementSettingsPtr = null;
+ transformPtr = null;
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AgentOffMeshLinkTraversal.cs.meta
new file mode 100644
index 0000000..28f8e71
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs
new file mode 100644
index 0000000..a9324c1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs
@@ -0,0 +1,97 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ /// <summary>
+ /// Policy for how often to recalculate an agent's path.
+ ///
+ /// See: <see cref="FollowerEntity.autoRepath"/>
+ ///
+ /// This is the unmanaged equivalent of <see cref="Pathfinding.AutoRepathPolicy"/>.
+ /// </summary>
+ [System.Serializable]
+ public struct AutoRepathPolicy : IComponentData {
+ /// <summary>
+ /// 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: <see cref="AutoRepathPolicy.Mode"/>
+ /// </summary>
+ public const float Sensitivity = 10.0f;
+
+ /// <summary>
+ /// Policy to use when recalculating paths.
+ ///
+ /// See: <see cref="Pathfinding.AutoRepathPolicy.Mode"/> for more details.
+ /// </summary>
+ public Pathfinding.AutoRepathPolicy.Mode mode;
+
+ /// <summary>Number of seconds between each automatic path recalculation for Mode.EveryNSeconds, and the maximum interval for Mode.Dynamic</summary>
+ 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;
+ }
+
+ /// <summary>
+ /// True if the path should be recalculated according to the policy
+ ///
+ /// The above parameters are relevant only if <see cref="mode"/> is <see cref="Mode.Dynamic"/>.
+ /// </summary>
+ /// <param name="position">The current position of the agent.</param>
+ /// <param name="radius">The radius of the agent. You may pass 0.0 if the agent doesn't have a radius.</param>
+ /// <param name="destination">The goal of the agent right now</param>
+ /// <param name="time">The current time in seconds</param>
+ public bool ShouldRecalculatePath (float3 position, float radius, float3 destination, float time) {
+ if (mode == Pathfinding.AutoRepathPolicy.Mode.Never || float.IsPositiveInfinity(destination.x)) return false;
+
+ float timeSinceLast = time - lastRepathTime;
+ if (mode == Pathfinding.AutoRepathPolicy.Mode.EveryNSeconds) {
+ return timeSinceLast >= period;
+ } else {
+ // cost = change in destination / max(distance to destination, radius)
+ 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 >= period*(1 - math.sqrt(fraction));
+ }
+ }
+
+ public void Reset () {
+ lastDestination = float.PositiveInfinity;
+ lastRepathTime = float.NegativeInfinity;
+ }
+
+ /// <summary>Must be called when a path request has been scheduled</summary>
+ public void DidRecalculatePath (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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/AutoRepathPolicy.cs.meta
new file mode 100644
index 0000000..0f99f79
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs
new file mode 100644
index 0000000..d3ff623
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs
@@ -0,0 +1,26 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ /// <summary>Holds an agent's destination point</summary>
+ public struct DestinationPoint : IComponentData {
+ /// <summary>
+ /// 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 cref="AIDestinationSetter"/>
+ /// See: <see cref="IAstarAI.destination"/>
+ /// </summary>
+ public float3 destination;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float3 facingDirection;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/DestinationPoint.cs.meta
new file mode 100644
index 0000000..ca2370d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs
new file mode 100644
index 0000000..4ab8700
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs
@@ -0,0 +1,16 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ /// <summary>Agent state related to gravity</summary>
+ public struct GravityState : IComponentData, IEnableableComponent {
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float verticalVelocity;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/GravityState.cs.meta
new file mode 100644
index 0000000..2275bbb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs
new file mode 100644
index 0000000..4f45dab
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs
@@ -0,0 +1,91 @@
+#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);
+
+ /// <summary>
+ /// 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: <see cref="FollowerEntity.movementOverrides"/>
+ /// </summary>
+ public ref struct ManagedMovementOverrides {
+ Entity entity;
+ World world;
+
+ public ManagedMovementOverrides (Entity entity, World world) {
+ this.entity = entity;
+ this.world = world;
+ }
+
+ public void AddBeforeControlCallback (BeforeControlDelegate value) {
+ AddCallback<ManagedMovementOverrideBeforeControl, BeforeControlDelegate>(value);
+ }
+ public void RemoveBeforeControlCallback (BeforeControlDelegate value) {
+ RemoveCallback<ManagedMovementOverrideBeforeControl, BeforeControlDelegate>(value);
+ }
+
+ public void AddAfterControlCallback (AfterControlDelegate value) {
+ AddCallback<ManagedMovementOverrideAfterControl, AfterControlDelegate>(value);
+ }
+ public void RemoveAfterControlCallback (AfterControlDelegate value) {
+ RemoveCallback<ManagedMovementOverrideAfterControl, AfterControlDelegate>(value);
+ }
+
+ public void AddBeforeMovementCallback (BeforeMovementDelegate value) {
+ AddCallback<ManagedMovementOverrideBeforeMovement, BeforeMovementDelegate>(value);
+ }
+ public void RemoveBeforeMovementCallback (BeforeMovementDelegate value) {
+ RemoveCallback<ManagedMovementOverrideBeforeMovement, BeforeMovementDelegate>(value);
+ }
+
+ void AddCallback<C, T>(T callback) where T : System.Delegate where C : ManagedMovementOverride<T>, 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<C>(entity)) world.EntityManager.AddComponentData(entity, new C());
+ world.EntityManager.GetComponentData<C>(entity).AddCallback(callback);
+ }
+
+ void RemoveCallback<C, T>(T callback) where T : System.Delegate where C : ManagedMovementOverride<T>, IComponentData, new() {
+ if (callback == null) throw new System.ArgumentNullException(nameof(callback));
+ if (world == null || !world.EntityManager.Exists(entity)) return;
+ if (!world.EntityManager.HasComponent<C>(entity)) return;
+
+ var comp = world.EntityManager.GetComponentData<C>(entity);
+ if (!comp.RemoveCallback(callback)) {
+ world.EntityManager.RemoveComponent<C>(entity);
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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: <see cref="FollowerEntity.movementOverrides"/>
+ /// </summary>
+ public class ManagedMovementOverride<T> : 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<BeforeControlDelegate>, System.ICloneable {
+ // No fields in this class can be cloned safely
+ public object Clone() => new ManagedMovementOverrideBeforeControl();
+ }
+ public class ManagedMovementOverrideAfterControl : ManagedMovementOverride<AfterControlDelegate> {
+ public object Clone() => new ManagedMovementOverrideAfterControl();
+ }
+ public class ManagedMovementOverrideBeforeMovement : ManagedMovementOverride<BeforeMovementDelegate> {
+ public object Clone() => new ManagedMovementOverrideBeforeMovement();
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedMovementOverride.cs.meta
new file mode 100644
index 0000000..409fc88
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs
new file mode 100644
index 0000000..d219541
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs
@@ -0,0 +1,222 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.ECS.RVO;
+ using UnityEngine.Serialization;
+
+ /// <summary>
+ /// Settings for agent movement that cannot be put anywhere else.
+ ///
+ /// The Unity ECS in general wants everything in components to be unmanaged types.
+ /// However, some things cannot be unmanaged types, for example delegates and interfaces.
+ /// 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.
+ ///
+ /// It can also be used for things that are not used often, and so are best kept out-of-band to avoid bloating the ECS chunks too much.
+ /// </summary>
+ [System.Serializable]
+ public class ManagedState : IComponentData, System.IDisposable, System.ICloneable {
+ /// <summary>
+ /// Settings for when to recalculate the path.
+ ///
+ /// Deprecated: Use <see cref="FollowerEntity.autoRepath"/>, or the <see cref="Pathfinding.ECS.AutoRepathPolicy"/> component instead.
+ /// </summary>
+ [System.Obsolete("Use FollowerEntity.autoRepath, or the Pathfinding.ECS.AutoRepathPolicy component instead")]
+ public Pathfinding.AutoRepathPolicy autoRepath = new Pathfinding.AutoRepathPolicy();
+
+ /// <summary>Calculates in which direction to move to follow the path</summary>
+ public PathTracer pathTracer;
+
+ /// <summary>
+ /// Local avoidance settings.
+ ///
+ /// When the agent has local avoidance enabled, these settings will be copied into a <see cref="Pathfinding.ECS.RVO.RVOAgent"/> component which is attached to the agent.
+ ///
+ /// See: <see cref="enableLocalAvoidance"/>
+ /// </summary>
+ [FormerlySerializedAs("rvoAgent")]
+ public RVOAgent rvoSettings = RVOAgent.Default;
+
+ /// <summary>Callback for when the agent starts to traverse an off-mesh link</summary>
+ [System.NonSerialized]
+ public IOffMeshLinkHandler onTraverseOffMeshLink;
+
+ public PathRequestSettings pathfindingSettings = PathRequestSettings.Default;
+
+ /// <summary>
+ /// True if local avoidance is enabled for this agent.
+ ///
+ /// Enabling this will automatically add a <see cref="Pathfinding.ECS.RVO.RVOAgent"/> component to the entity.
+ ///
+ /// See: local-avoidance (view in online documentation for working links)
+ /// </summary>
+ [FormerlySerializedAs("rvoEnabled")]
+ public bool enableLocalAvoidance;
+
+ /// <summary>
+ /// 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 add the <see cref="GravityState"/> component to the entity.
+ /// </summary>
+ public bool enableGravity = true;
+
+ /// <summary>Path that is being calculated, if any</summary>
+ // 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; }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ // 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; }
+
+ /// <summary>
+ /// \copydocref{IAstarAI.SetPath}.
+ ///
+ /// Warning: In almost all cases you should use <see cref="FollowerEntity.SetPath"/> instead of this method.
+ /// </summary>
+ 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 {
+ abPath.Release(state);
+ }
+ } 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;
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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, pathfindingSettings.traversalProvider, activePath);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ object System.ICloneable.Clone () {
+ return new ManagedState {
+ #pragma warning disable 618
+ autoRepath = autoRepath.Clone(),
+ #pragma warning restore 618
+ pathTracer = pathTracer.Clone(),
+ rvoSettings = rvoSettings,
+ pathfindingSettings = new PathRequestSettings {
+ graphMask = pathfindingSettings.graphMask,
+ tagPenalties = pathfindingSettings.tagPenalties != null ? (int[])pathfindingSettings.tagPenalties.Clone() : null,
+ traversableTags = pathfindingSettings.traversableTags,
+ traversalProvider = null, // Cannot be safely cloned or copied
+ },
+ enableLocalAvoidance = enableLocalAvoidance,
+ enableGravity = enableGravity,
+ onTraverseOffMeshLink = null, // Cannot be safely cloned or copied
+ pendingPath = null, // Cannot be safely cloned or copied
+ activePath = null, // Cannot be safely cloned or copied
+ };
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ManagedState.cs.meta
new file mode 100644
index 0000000..6320c9e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs
new file mode 100644
index 0000000..dfd03eb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs
@@ -0,0 +1,85 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.Util;
+
+ /// <summary>
+ /// 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 cref="ResolvedMovement"/>.
+ ///
+ /// See: <see cref="ResolvedMovement"/>
+ /// </summary>
+ public struct MovementControl : IComponentData {
+ /// <summary>The point the agent should move towards</summary>
+ public float3 targetPoint;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float3 endOfPath;
+
+ /// <summary>The speed at which the agent should move towards <see cref="targetPoint"/>, in meters per second</summary>
+ public float speed;
+
+ /// <summary>
+ /// The maximum speed at which the agent may move, in meters per second.
+ ///
+ /// It is recommended to keep this slightly above <see cref="speed"/>, to allow the local avoidance system to move agents around more efficiently when necessary.
+ /// </summary>
+ public float maxSpeed;
+
+ /// <summary>
+ /// 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 cref="HierarchicalGraph"/>
+ /// </summary>
+ public int hierarchicalNodeIndex;
+
+ /// <summary>
+ /// The desired rotation of the agent, in radians, relative to the current movement plane.
+ /// See: <see cref="NativeMovementPlane.ToWorldRotation"/>
+ /// </summary>
+ public float targetRotation;
+
+ /// <summary>
+ /// The desired rotation of the agent, in radians, over a longer time horizon, relative to the current movement plane.
+ ///
+ /// The <see cref="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.
+ ///
+ /// If no better hint is available, this should be set to the same value as <see cref="targetRotation"/>.
+ ///
+ /// See: <see cref="NativeMovementPlane.ToWorldRotation"/>
+ /// </summary>
+ public float targetRotationHint;
+
+ /// <summary>
+ /// Additive modifier to <see cref="targetRotation"/>, 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.
+ /// </summary>
+ public float targetRotationOffset;
+
+ /// <summary>The speed at which the agent should rotate towards <see cref="targetRotation"/> + <see cref="targetRotationOffset"/>, in radians per second</summary>
+ public float rotationSpeed;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public bool overrideLocalAvoidance;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementControl.cs.meta
new file mode 100644
index 0000000..4c0b87a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs
new file mode 100644
index 0000000..b6e2bef
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs
@@ -0,0 +1,113 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using UnityEngine;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding.PID;
+
+ /// <summary>How to calculate which direction is "up" for the agent</summary>
+ public enum MovementPlaneSource : byte {
+ /// <summary>
+ /// The graph's natural up direction will be used to align the agent.
+ /// This is the most common option.
+ /// </summary>
+ Graph,
+ /// <summary>
+ /// 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 <see cref="FollowerEntity.updateRotation"/> 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)
+ /// </summary>
+ NavmeshNormal,
+ /// <summary>
+ /// 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 <see cref="FollowerEntity.updateRotation"/> and then align the transform using a custom script.
+ ///
+ /// Warning: Using this option has a performance penalty.
+ /// </summary>
+ Raycast,
+ }
+
+ [System.Serializable]
+ public struct MovementSettings : IComponentData {
+ /// <summary>Additional movement settings</summary>
+ public PIDMovement follower;
+
+ /// <summary>Flags for enabling debug rendering in the scene view</summary>
+ public PIDMovement.DebugFlags debugFlags;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float stopDistance;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float rotationSmoothing;
+ public float positionSmoothing;
+
+ /// <summary>
+ /// Layer mask to use for ground placement.
+ /// Make sure this does not include the layer of any colliders attached to this gameobject.
+ ///
+ /// See: <see cref="GravityState"/>
+ /// See: https://docs.unity3d.com/Manual/Layers.html
+ /// </summary>
+ public LayerMask groundMask;
+
+ /// <summary>
+ /// How to calculate which direction is "up" for the agent.
+ /// See: <see cref="MovementPlaneSource"/>
+ ///
+ /// Deprecated: Use the AgentMovementPlaneSource component instead, or the movementPlaneSource property on the FollowerEntity component
+ /// </summary>
+ [System.Obsolete("Use the AgentMovementPlaneSource component instead, or the movementPlaneSource property on the FollowerEntity component")]
+ public MovementPlaneSource movementPlaneSource;
+
+ /// <summary>\copydocref{IAstarAI.isStopped}</summary>
+ public bool isStopped;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementSettings.cs.meta
new file mode 100644
index 0000000..734fcc4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs
new file mode 100644
index 0000000..0ea83ad
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs
@@ -0,0 +1,196 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.PID;
+
+ public struct MovementState : IComponentData {
+ /// <summary>State of the PID controller for the movement</summary>
+ public PIDMovement.PersistentState followerState;
+
+ /// <summary>The next corner in the path</summary>
+ public float3 nextCorner;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public float3 endOfPath;
+
+ /// <summary>
+ /// The closest point on the navmesh to the agent.
+ /// The agent will be snapped to this point.
+ /// </summary>
+ public float3 closestOnNavmesh;
+
+ /// <summary>
+ /// Offset from the agent's internal position to its visual position.
+ ///
+ /// This is used when position smoothing is enabled. Otherwise it is zero.
+ /// </summary>
+ public float3 positionOffset;
+
+ /// <summary>
+ /// 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 <see cref="AIMovementSystemGroup"/>. It is not guaranteed to be valid after that group has finished running, as graph updates may have changed the graph.
+ ///
+ /// See: <see cref="HierarchicalGraph"/>
+ /// </summary>
+ public int hierarchicalNodeIndex;
+
+ /// <summary>The remaining distance until the end of the path, or the next off-mesh link</summary>
+ public float remainingDistanceToEndOfPart;
+
+ /// <summary>
+ /// 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: <see cref="ResolvedMovement.targetRotationOffset"/>
+ /// </summary>
+ public float rotationOffset;
+
+ /// <summary>
+ /// An additional, purely visual, rotation offset.
+ /// This is used for rotation smoothing, but does not affect the movement of the agent.
+ /// </summary>
+ public float rotationOffset2;
+
+ /// <summary>
+ /// Version number of <see cref="PathTracer.version"/> when the movement state was last updated.
+ /// In particular, <see cref="closestOnNavmesh"/>, <see cref="nextCorner"/>, <see cref="endOfPath"/>, <see cref="remainingDistanceToEndOfPart"/>, <see cref="reachedDestination"/> and <see cref="reachedEndOfPath"/> will only
+ /// be considered up to date if this is equal to the current version number of the path tracer.
+ /// </summary>
+ public ushort pathTracerVersion;
+
+ /// <summary>Bitmask for various flags</summary>
+ 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;
+
+ /// <summary>
+ /// 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 <see cref="MovementSettings.stopDistance"/>.
+ /// </summary>
+ public bool reachedDestination {
+ get => (flags & ReachedDestinationFlag) != 0;
+ set => flags = (ushort)((flags & ~ReachedDestinationFlag) | (value ? ReachedDestinationFlag : 0));
+ }
+
+ /// <summary>
+ /// 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:
+ /// - <see cref="reachedDestination"/> is true
+ /// - The agent is facing the desired facing direction as specified in <see cref="DestinationPoint.facingDirection"/>.
+ /// </summary>
+ public bool reachedDestinationAndOrientation {
+ get => (flags & reachedDestinationAndOrientationFlag) != 0;
+ set => flags = (ushort)((flags & ~reachedDestinationAndOrientationFlag) | (value ? reachedDestinationAndOrientationFlag : 0));
+ }
+
+ /// <summary>
+ /// 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 <see cref="MovementSettings.stopDistance"/>.
+ /// </summary>
+ public bool reachedEndOfPath {
+ get => (flags & ReachedEndOfPathFlag) != 0;
+ set => flags = (ushort)((flags & ~ReachedEndOfPathFlag) | (value ? ReachedEndOfPathFlag : 0));
+ }
+
+ /// <summary>
+ /// 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:
+ /// - <see cref="reachedEndOfPath"/> is true
+ /// - The agent is facing the desired facing direction as specified in <see cref="DestinationPoint.facingDirection"/>.
+ /// </summary>
+ public bool reachedEndOfPathAndOrientation {
+ get => (flags & reachedEndOfPathAndOrientationFlag) != 0;
+ set => flags = (ushort)((flags & ~reachedEndOfPathAndOrientationFlag) | (value ? reachedEndOfPathAndOrientationFlag : 0));
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public bool reachedEndOfPart {
+ get => (flags & ReachedEndOfPartFlag) != 0;
+ set => flags = (ushort)((flags & ~ReachedEndOfPartFlag) | (value ? ReachedEndOfPartFlag : 0));
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public bool traversingLastPart {
+ get => (flags & TraversingLastPartFlag) != 0;
+ set => flags = (ushort)((flags & ~TraversingLastPartFlag) | (value ? TraversingLastPartFlag : 0));
+ }
+
+ /// <summary>
+ /// The index of the graph that the agent is currently traversing.
+ ///
+ /// Will be <see cref="GraphNode.InvalidGraphIndex"/> if the agent has no path, or the node that the agent is traversing has been destroyed.
+ /// </summary>
+ public uint graphIndex {
+ get => (uint)(flags >> 8);
+ internal set => flags = (ushort)((flags & 0xFF) | (ushort)(value << 8));
+ }
+
+ /// <summary>
+ /// 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 <see cref="hierarchicalNodeIndex"/> and <see cref="graphIndex"/> fields are invalid.
+ /// </summary>
+ public bool isOnValidNode => hierarchicalNodeIndex != -1;
+
+ public MovementState(UnityEngine.Vector3 agentPosition) {
+ this = default;
+ SetPathIsEmpty(agentPosition);
+ }
+
+ /// <summary>Sets the appropriate fields to indicate that the agent has no path</summary>
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementState.cs.meta
new file mode 100644
index 0000000..71f1c38
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs
new file mode 100644
index 0000000..d29781c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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 {
+ /// <summary>
+ /// The estimated velocity that the agent is moving with.
+ /// This includes all form of movement, including local avoidance and gravity.
+ /// </summary>
+ public float3 estimatedVelocity;
+
+ /// <summary>The position of the agent at the end of the last movement simulation step</summary>
+ public float3 lastPosition;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/MovementStatistics.cs.meta
new file mode 100644
index 0000000..3b36a89
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO.meta
new file mode 100644
index 0000000..30598c4
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 233cdeb50c94c714ab4c82711f977368
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs
new file mode 100644
index 0000000..72d4c4c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs
@@ -0,0 +1,58 @@
+using Pathfinding.RVO;
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Transforms;
+#endif
+using UnityEngine;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS.RVO {
+ using Pathfinding.RVO;
+
+ /// <summary>
+ /// 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 <see cref="RVOSystem"/> is responsible for adding and removing this component as necessary.
+ /// Any other systems should only concern themselves with the <see cref="RVOAgent"/> 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.
+ /// </summary>
+#if MODULE_ENTITIES
+ [WriteGroup(typeof(ResolvedMovement))]
+#endif
+ public readonly struct AgentIndex
+#if MODULE_ENTITIES
+ : Unity.Entities.ICleanupComponentData
+#endif
+ {
+ internal const int DeletedBit = 1 << 31;
+ internal const int IndexMask = (1 << 24) - 1;
+ internal const int VersionOffset = 24;
+ internal const int VersionMask = 0b1111_111 << VersionOffset;
+
+ public readonly int packedAgentIndex;
+ public int Index => packedAgentIndex & IndexMask;
+ public int Version => packedAgentIndex & VersionMask;
+ public bool Valid => (packedAgentIndex & DeletedBit) == 0;
+
+ public AgentIndex(int packedAgentIndex) {
+ this.packedAgentIndex = packedAgentIndex;
+ }
+
+ public AgentIndex(int version, int index) {
+ version <<= VersionOffset;
+ UnityEngine.Assertions.Assert.IsTrue((index & IndexMask) == index);
+ packedAgentIndex = (version & VersionMask) | (index & IndexMask);
+ }
+
+ public AgentIndex WithIncrementedVersion () {
+ return new AgentIndex((((packedAgentIndex & VersionMask) + (1 << VersionOffset)) & VersionMask) | Index);
+ }
+
+ public AgentIndex WithDeleted () {
+ return new AgentIndex(packedAgentIndex | DeletedBit);
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/AgentIndex.cs.meta
new file mode 100644
index 0000000..b67b67e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs
new file mode 100644
index 0000000..5049190
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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;
+
+ /// <summary>
+ /// Agent data for the local avoidance system.
+ ///
+ /// See: local-avoidance (view in online documentation for working links)
+ /// </summary>
+ [System.Serializable]
+ public struct RVOAgent : IComponentData {
+ /// <summary>How far into the future to look for collisions with other agents (in seconds)</summary>
+ [Tooltip("How far into the future to look for collisions with other agents (in seconds)")]
+ public float agentTimeHorizon;
+
+ /// <summary>How far into the future to look for collisions with obstacles (in seconds)</summary>
+ [Tooltip("How far into the future to look for collisions with obstacles (in seconds)")]
+ public float obstacleTimeHorizon;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [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;
+
+ /// <summary>
+ /// Specifies the avoidance layer for this agent.
+ /// The <see cref="collidesWith"/> mask on other agents will determine if they will avoid this agent.
+ /// </summary>
+ public RVOLayer layer;
+
+ /// <summary>
+ /// 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)
+ /// </summary>
+ [Pathfinding.EnumFlag]
+ public RVOLayer collidesWith;
+
+ /// <summary>\copydocref{Pathfinding.RVO.IAgent.Priority}</summary>
+ [Tooltip("How strongly other agents will avoid this agent")]
+ [UnityEngine.Range(0, 1)]
+ public float priority;
+
+ /// <summary>
+ /// Priority multiplier.
+ /// This functions identically to the <see cref="priority"/>, however it is not exposed in the Unity inspector.
+ /// It is primarily used by the <see cref="Pathfinding.RVO.RVODestinationCrowdedBehavior"/>.
+ /// </summary>
+ [System.NonSerialized]
+ public float priorityMultiplier;
+
+ [System.NonSerialized]
+ public float flowFollowingStrength;
+
+ /// <summary>Enables drawing debug information in the scene view</summary>
+ public AgentDebugFlags debug;
+
+ /// <summary>A locked unit cannot move. Other units will still avoid it but avoidance quality is not the best.</summary>
+ [Tooltip("A locked unit cannot move. Other units will still avoid it. But avoidance quality is not the best")]
+ public bool locked;
+
+ /// <summary>Good default settings for an RVO agent</summary>
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/RVO/RVOAgent.cs.meta
new file mode 100644
index 0000000..046cc35
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs
new file mode 100644
index 0000000..43e179e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs
@@ -0,0 +1,10 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ /// <summary>Enabled if the agnet is ready to start traversing an off-mesh link</summary>
+ [System.Serializable]
+ public struct ReadyToTraverseOffMeshLink : IComponentData, IEnableableComponent {
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ReadyToTraverseOffMeshLink.cs.meta
new file mode 100644
index 0000000..bcfbe64
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs
new file mode 100644
index 0000000..d91a621
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs
@@ -0,0 +1,35 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ using Pathfinding.Util;
+
+ /// <summary>
+ /// Holds the final movement data for an entity.
+ /// This is the data that is used by the movement system to move the entity.
+ /// </summary>
+ public struct ResolvedMovement : IComponentData {
+ /// <summary>\copydocref{MovementControl.targetPoint}</summary>
+ public float3 targetPoint;
+
+ /// <summary>\copydocref{MovementControl.speed}</summary>
+ public float speed;
+
+ public float turningRadiusMultiplier;
+
+ /// <summary>\copydocref{MovementControl.targetRotation}</summary>
+ public float targetRotation;
+
+ /// <summary>\copydocref{MovementControl.targetRotationHint}</summary>
+ public float targetRotationHint;
+
+ /// <summary>\copydocref{MovementControl.targetRotationOffset}</summary>
+ public float targetRotationOffset;
+
+ /// <summary>\copydocref{MovementControl.rotationSpeed}</summary>
+ public float rotationSpeed;
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/ResolvedMovement.cs.meta
new file mode 100644
index 0000000..74542a5
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs
new file mode 100644
index 0000000..44c5eac
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SearchState.cs.meta
new file mode 100644
index 0000000..5198363
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs
new file mode 100644
index 0000000..49d316d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs
@@ -0,0 +1,46 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+
+ /// <summary>
+ /// 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:
+ /// - <see cref="SimulateMovementRepair"/>
+ /// - <see cref="SimulateMovementControl"/>
+ /// - <see cref="SimulateMovementFinalize"/>
+ ///
+ /// Removing one of the above components can be useful if you want to override the movement of an agent in some way.
+ /// </summary>
+ public struct SimulateMovement : IComponentData {
+ }
+
+ /// <summary>
+ /// Tag component to allow the agent to repair its path and recalculate various statistics.
+ ///
+ /// Allows the <see cref="JobRepairPath"/> to run.
+ /// </summary>
+ public struct SimulateMovementRepair : IComponentData {
+ }
+
+ /// <summary>
+ /// Tag component to allow the agent to calculate how it wants to move.
+ ///
+ /// Allows the <see cref="ControlJob"/> to run.
+ /// </summary>
+ public struct SimulateMovementControl : IComponentData {
+ }
+
+ /// <summary>
+ /// Tag component to allow the agent to move according to its desired movement parameters.
+ ///
+ /// Allows <see cref="AIMoveSystem"/> to run the <see cref="JobApplyGravity"/>, <see cref="JobAlignAgentWithMovementDirection"/> and <see cref="JobMoveAgent"/> jobs.
+ /// </summary>
+ public struct SimulateMovementFinalize : IComponentData {
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SimulateMovement.cs.meta
new file mode 100644
index 0000000..485bb36
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs
new file mode 100644
index 0000000..497b8b5
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs
@@ -0,0 +1,35 @@
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Mathematics;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+
+ /// <summary>
+ /// Tag component to enable syncing between an agent's Transform and the agent entity's position.
+ ///
+ /// See: <see cref="FollowerEntity.updatePosition"/>
+ /// </summary>
+ public struct SyncPositionWithTransform : IComponentData {
+ }
+
+ /// <summary>
+ /// Tag component to enable syncing between an agent's Transform and the agent entity's rotation.
+ ///
+ /// See: <see cref="FollowerEntity.updateRotation"/>
+ /// </summary>
+ public struct SyncRotationWithTransform : IComponentData {
+ }
+
+ /// <summary>
+ /// 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 cref="FollowerEntity.orientation"/>
+ /// See: <see cref="OrientationMode"/>
+ /// </summary>
+ public struct OrientationYAxisForward : IComponentData {
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Components/SyncWithTransform.cs.meta
new file mode 100644
index 0000000..c3f179f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs
new file mode 100644
index 0000000..8264ac6
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs
@@ -0,0 +1,223 @@
+#if MODULE_ENTITIES
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Entities;
+
+namespace Pathfinding.ECS {
+ /// <summary>Helper for EntityAccess</summary>
+ static class EntityAccessHelper {
+ public static readonly int GlobalSystemVersionOffset = UnsafeUtility.GetFieldOffset(typeof(ComponentTypeHandle<int>).GetField("m_GlobalSystemVersion", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
+ }
+
+ /// <summary>
+ /// Wrapper for a pointer.
+ ///
+ /// Very similar to the entities package RefRW<T> struct. But unfortunately that one cannot be used because the required constructor is not exposed.
+ /// </summary>
+ public ref struct ComponentRef<T> 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;
+ }
+ }
+ }
+ }
+
+ /// <summary>Utility for efficient random access to entity storage data from the main thread</summary>
+ public struct EntityStorageCache {
+ EntityStorageInfo storage;
+ Entity entity;
+ int lastWorldHash;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ // 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) {
+ entityManager = default;
+ storage = this.storage;
+ if (world == null) 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;
+ }
+
+ /// <summary>
+ /// Retrieves a component for a given entity.
+ ///
+ /// This is a convenience method to call <see cref="Update"/> 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.
+ ///
+ /// Returns: True if the entity exists, and false if it does not.
+ /// Throws: An exception if the entity does not have the given component.
+ /// </summary>
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public bool GetComponentData<A>(Entity entity, ref EntityAccess<A> access, out ComponentRef<A> value) where A : unmanaged, IComponentData {
+ if (Update(World.DefaultGameObjectInjectionWorld, entity, out var entityManager, out var storage)) {
+ access.Update(entityManager);
+ unsafe {
+ value = new ComponentRef<A>((byte*)UnsafeUtility.AddressOf(ref access[storage]));
+ }
+ return true;
+ } else {
+ value = default;
+ return false;
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public struct EntityAccess<T> where T : unmanaged, IComponentData {
+ public ComponentTypeHandle<T> 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;
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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<T>(readOnly);
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ entityManager.WorldUnmanaged.IsSystemValid(systemHandle);
+ systemHandle = entityManager.WorldUnmanaged.GetExistingUnmanagedSystem<AIMoveSystem>();
+#endif
+ }
+
+ lastSystemVersion = systemVersion;
+ worldSequenceNumber = sequenceNumber;
+ if (readOnly) entityManager.CompleteDependencyBeforeRO<T>();
+ else entityManager.CompleteDependencyBeforeRW<T>();
+ }
+
+#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<T>(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;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public struct ManagedEntityAccess<T> where T : class, IComponentData {
+ EntityManager entityManager;
+ ComponentTypeHandle<T> 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<T>();
+ else entityManager.CompleteDependencyBeforeRW<T>();
+ handle = entityManager.GetComponentTypeHandle<T>(readOnly);
+ this.entityManager = entityManager;
+ }
+
+ public T this[EntityStorageInfo storage] {
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ get {
+ return storage.Chunk.GetManagedComponentAccessor<T>(ref handle, entityManager)[storage.IndexInChunk];
+ }
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ set {
+ var accessor = storage.Chunk.GetManagedComponentAccessor<T>(ref handle, entityManager);
+ accessor[storage.IndexInChunk] = value;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/EntityAccess.cs.meta
new file mode 100644
index 0000000..5a10e58
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs
new file mode 100644
index 0000000..077ba9e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/IRuntimeBaker.cs.meta
new file mode 100644
index 0000000..afec79b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs.meta
new file mode 100644
index 0000000..d357f8a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fabe887d69022ae459e7619f5b0fbdf6
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs
new file mode 100644
index 0000000..4553462
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs
@@ -0,0 +1,43 @@
+#if MODULE_ENTITIES
+using Unity.Burst;
+using Unity.Entities;
+using Unity.Mathematics;
+using Unity.Transforms;
+
+namespace Pathfinding.ECS {
+ [BurstCompile]
+ 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) {
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobAlignAgentWithMovementDirection.cs.meta
new file mode 100644
index 0000000..e8059c1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs
new file mode 100644
index 0000000..347fbc9
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs
@@ -0,0 +1,75 @@
+#if MODULE_ENTITIES
+using Pathfinding.Drawing;
+using Pathfinding.Util;
+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<RaycastHit> raycastHits;
+ [ReadOnly]
+ public NativeArray<RaycastCommand> raycastCommands;
+ public CommandBuilder draw;
+ public float dt;
+
+ public static void UpdateMovementPlaneFromNormal (float3 normal, ref AgentMovementPlane movementPlane) {
+ // Calculate a new movement plane that is perpendicular to the surface normal
+ // and is as similar to the previous movement plane as possible.
+ var forward = math.normalizesafe(math.mul(movementPlane.value.rotation, new float3(0, 0, 1)));
+ normal = math.normalizesafe(normal);
+ // TODO: This doesn't guarantee an orthogonal basis? forward and normal may not be perpendicular
+ movementPlane.value = new NativeMovementPlane(new quaternion(new float3x3(
+ math.cross(normal, forward),
+ normal,
+ forward
+ )));
+ }
+
+ 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];
+ var hitAnything = math.any((float3)hit.normal != 0f);
+ if (hitAnything && movementPlaneSource.value == MovementPlaneSource.Raycast) {
+ UpdateMovementPlaneFromNormal(hit.normal, ref movementPlane);
+ }
+ ResolveGravity(hit, hitAnything, ref transform, in movementPlane, ref gravityState);
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobApplyGravity.cs.meta
new file mode 100644
index 0000000..a53f5e1
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs
new file mode 100644
index 0000000..49c804f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs
@@ -0,0 +1,146 @@
+#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]
+ public partial struct JobControl : IJobEntity, IJobEntityChunkBeginEnd {
+ public float dt;
+ public CommandBuilder draw;
+ [ReadOnly]
+ [NativeDisableContainerSafetyRestriction]
+ public NavmeshEdges.NavmeshBorderData navmeshEdgeData;
+
+ [NativeDisableContainerSafetyRestriction]
+ public NativeList<float2> edgesScratch;
+
+ 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.
+ var clamped2D = movementPlane.value.ToPlane(closestOnNavmesh, out float clampedElevation);
+ movementPlane.value.ToPlane(position, out float currentElevation);
+ currentElevation = math.max(currentElevation, clampedElevation - shape.height * 0.4f);
+ position = movementPlane.value.ToWorld(clamped2D, currentElevation);
+ return position;
+ }
+
+ public bool OnChunkBegin (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) {
+ if (!edgesScratch.IsCreated) edgesScratch = new NativeList<float2>(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, position, state.nextCorner, shape.height, movementPlane.value);
+ navmeshEdgeData.GetEdgesInRange(state.hierarchicalNodeIndex, localBounds, edgesScratch, 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,
+ rotationSpeed = settings.follower.maxRotationSpeed,
+ targetRotationOffset = state.rotationOffset, // May be modified by other systems
+ };
+ } else if (settings.isStopped) {
+ // 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 = resolvedMovement.targetRotation,
+ 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,
+ debugFlags = settings.debugFlags,
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobControl.cs.meta
new file mode 100644
index 0000000..c4b5992
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs
new file mode 100644
index 0000000..df4652d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs
@@ -0,0 +1,104 @@
+#if MODULE_ENTITIES
+using System.Runtime.InteropServices;
+using Pathfinding.Drawing;
+using Pathfinding.PID;
+using Pathfinding.Util;
+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<float3> vertices, ref AgentCylinderShape shape) {
+ draw.PushColor(Palette.Colorbrewer.Set1.Orange);
+ // 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]);
+ }
+ draw.PopColor();
+ }
+ }
+
+ public partial struct JobDrawFollowerGizmos : IJobChunk {
+ public CommandBuilder draw;
+ public GCHandle entityManagerHandle;
+ [ReadOnly]
+ public ComponentTypeHandle<LocalTransform> LocalTransformTypeHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<AgentCylinderShape> AgentCylinderShapeHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<MovementSettings> MovementSettingsHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<AgentMovementPlane> 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<ManagedState> ManagedStateHandleRW;
+ [ReadOnly]
+ public ComponentTypeHandle<MovementState> MovementStateHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<ResolvedMovement> ResolvedMovementHandleRO;
+
+ [NativeDisableContainerSafetyRestriction]
+ public NativeList<float3> scratchBuffer1;
+ [NativeDisableContainerSafetyRestriction]
+ public NativeArray<int> scratchBuffer2;
+
+ public void Execute (in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in Unity.Burst.Intrinsics.v128 chunkEnabledMask) {
+ if (!scratchBuffer1.IsCreated) scratchBuffer1 = new NativeList<float3>(32, Allocator.Temp);
+ if (!scratchBuffer2.IsCreated) scratchBuffer2 = new NativeArray<int>(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 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, Palette.Colorbrewer.Set1.Blue);
+ draw.xz.ArrowheadArc(p2D, new float2(c.y, s.y), shape.radius * 1.1f, Palette.Colorbrewer.Set1.Purple);
+ draw.xz.ArrowheadArc(p2D, new float2(c.z, s.z), shape.radius * 1.1f, Palette.Colorbrewer.Set1.Orange);
+ 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, Palette.Colorbrewer.Set1.Yellow);
+ draw.xz.ArrowheadArc(p2D, new float2(c2.y, s2.y), shape.radius * 1.2f, Palette.Colorbrewer.Set1.Pink);
+ draw.PopMatrix();
+ }
+ if ((settings.debugFlags & PIDMovement.DebugFlags.Path) != 0 && managedState.pathTracer.hasPath) {
+ scratchBuffer1.Clear();
+ managedState.pathTracer.GetNextCorners(scratchBuffer1, int.MaxValue, ref scratchBuffer2, Allocator.Temp, managedState.pathfindingSettings.traversalProvider, managedState.activePath);
+ var span = scratchBuffer1.AsUnsafeSpan();
+ DrawGizmosJobUtils.DrawPath(ref draw, ref span, ref shape);
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.cs.meta
new file mode 100644
index 0000000..377d6ac
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobDrawFollowerGizmos.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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs
new file mode 100644
index 0000000..abdd98e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedMovementOverride.cs.meta
new file mode 100644
index 0000000..24d261a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs
new file mode 100644
index 0000000..8055cc8
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs
@@ -0,0 +1,86 @@
+#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;
+
+ 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) {
+ if (!MoveNext(entity, state, ref transform, ref movementPlane, ref movementControl, ref movementSettings, ref linkInfo, managedLinkInfo, deltaTime)) {
+ commandBuffer.RemoveComponent<AgentOffMeshLinkTraversal>(entity);
+ commandBuffer.RemoveComponent<ManagedAgentOffMeshLinkTraversal>(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, float deltaTime) {
+ unsafe {
+ managedLinkInfo.context.SetInternalData(entity, ref transform, ref movementPlane, ref movementControl, ref movementSettings, ref linkInfo, 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();
+ }
+
+ bool finished;
+ bool error = false;
+ bool popParts = 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) {
+ managedLinkInfo.stateMachine.OnAbortTraversingOffMeshLink();
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.cs.meta
new file mode 100644
index 0000000..1c23899
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobManagedOffMeshLinkTransition.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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs
new file mode 100644
index 0000000..331ab82
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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]
+ public partial struct JobMoveAgent : IJobEntity {
+ public float dt;
+
+ static void UpdateVelocityEstimate (ref LocalTransform transform, ref MovementStatistics movementStatistics, float dt) {
+ if (dt > 0.000001f) {
+ movementStatistics.estimatedVelocity = (transform.Position - movementStatistics.lastPosition) / dt;
+ }
+ }
+
+ static void ResolveRotation (ref LocalTransform transform, ref MovementState state, in ResolvedMovement resolvedMovement, in MovementSettings movementSettings, in AgentMovementPlane movementPlane, float dt) {
+ var currentRotation = movementPlane.value.ToPlane(transform.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);
+ }
+
+ transform.Rotation = movementPlane.value.ToWorldRotation(newInternalRotation + state.rotationOffset + state.rotationOffset2);
+ }
+
+ public static float3 MoveWithoutGravity (ref LocalTransform transform, 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 - transform.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(ref transform, 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;
+ ResolvePositionSmoothing(delta, ref state, in movementSettings, dt);
+ ResolveRotation(ref transform, ref state, in resolvedMovement, in movementSettings, in movementPlane, dt);
+ UpdateVelocityEstimate(ref transform, ref movementStatistics, dt);
+ movementStatistics.lastPosition = transform.Position;
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobMoveAgent.cs.meta
new file mode 100644
index 0000000..c989a0d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs
new file mode 100644
index 0000000..c6c4081
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs
@@ -0,0 +1,47 @@
+#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<RaycastCommand> raycastCommands;
+ public QueryParameters raycastQueryParameters;
+ public CommandBuilder draw;
+ 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) {
+ // 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(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, [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);
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobPrepareAgentRaycasts.cs.meta
new file mode 100644
index 0000000..7d0a011
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs
new file mode 100644
index 0000000..7f0e193
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs
@@ -0,0 +1,277 @@
+#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 Unity.Burst;
+ using Unity.Jobs;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public struct JobRepairPath : IJobChunk {
+ public struct Scheduler {
+ [ReadOnly]
+ public ComponentTypeHandle<LocalTransform> LocalTransformTypeHandleRO;
+ public ComponentTypeHandle<MovementState> MovementStateTypeHandleRW;
+ [ReadOnly]
+ public ComponentTypeHandle<AgentCylinderShape> AgentCylinderShapeTypeHandleRO;
+ // NativeDisableContainerSafetyRestriction seems to be necessary because otherwise we will get an error:
+ // "The ComponentTypeHandle<Pathfinding.ECS.ManagedState> ... 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<ManagedState> ManagedStateTypeHandleRW;
+ [ReadOnly]
+ public ComponentTypeHandle<MovementSettings> MovementSettingsTypeHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<DestinationPoint> DestinationPointTypeHandleRO;
+ [ReadOnly]
+ public ComponentTypeHandle<AgentMovementPlane> AgentMovementPlaneTypeHandleRO;
+ public ComponentTypeHandle<ReadyToTraverseOffMeshLink> ReadyToTraverseOffMeshLinkTypeHandleRW;
+ public GCHandle entityManagerHandle;
+ public bool onlyApplyPendingPaths;
+
+ public EntityQueryBuilder GetEntityQuery (Allocator allocator) {
+ return new EntityQueryBuilder(Allocator.Temp)
+ .WithAllRW<MovementState>()
+ .WithAllRW<ManagedState>()
+ .WithAllRW<LocalTransform>()
+ .WithAll<MovementSettings, DestinationPoint, AgentMovementPlane, AgentCylinderShape>()
+ // .WithAny<ReadyToTraverseOffMeshLink>() // TODO: Use WithPresent in newer versions
+ .WithAbsent<AgentOffMeshLinkTraversal>();
+ }
+
+ public Scheduler(ref SystemState systemState) {
+ entityManagerHandle = GCHandle.Alloc(systemState.EntityManager);
+ LocalTransformTypeHandleRO = systemState.GetComponentTypeHandle<LocalTransform>(true);
+ MovementStateTypeHandleRW = systemState.GetComponentTypeHandle<MovementState>(false);
+ AgentCylinderShapeTypeHandleRO = systemState.GetComponentTypeHandle<AgentCylinderShape>(true);
+ DestinationPointTypeHandleRO = systemState.GetComponentTypeHandle<DestinationPoint>(true);
+ AgentMovementPlaneTypeHandleRO = systemState.GetComponentTypeHandle<AgentMovementPlane>(true);
+ MovementSettingsTypeHandleRO = systemState.GetComponentTypeHandle<MovementSettings>(true);
+ ReadyToTraverseOffMeshLinkTypeHandleRW = systemState.GetComponentTypeHandle<ReadyToTraverseOffMeshLink>(false);
+ // Need to bypass the T : unmanaged check in systemState.GetComponentTypeHandle
+ ManagedStateTypeHandleRW = systemState.EntityManager.GetComponentTypeHandle<ManagedState>(false);
+ onlyApplyPendingPaths = false;
+ }
+
+ public void Dispose () {
+ entityManagerHandle.Free();
+ }
+
+ void Update (ref SystemState systemState) {
+ LocalTransformTypeHandleRO.Update(ref systemState);
+ MovementStateTypeHandleRW.Update(ref systemState);
+ AgentCylinderShapeTypeHandleRO.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<int> indicesScratch;
+ [NativeDisableContainerSafetyRestriction]
+ public NativeList<float3> 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<float3>(Allocator.Temp);
+ indicesScratch = new NativeArray<int>(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 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 destinationPoints[i],
+ mask.GetEnabledRefRW<ReadyToTraverseOffMeshLink>(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 DestinationPoint destination, EnabledRefRW<ReadyToTraverseOffMeshLink> readyToTraverseOffMeshLink, ManagedState managedState, in MovementSettings settings, NativeList<float3> nextCornersScratch, ref NativeArray<int> 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.
+ 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, managedState.pathfindingSettings.traversalProvider, managedState.activePath);
+ state.endOfPath = pathTracer.UpdateEnd(destination.destination, PathTracer.RepairQuality.High, movementPlane.value, managedState.pathfindingSettings.traversalProvider, managedState.activePath);
+ 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, out bool lastCorner, managedState.pathfindingSettings.traversalProvider, managedState.activePath);
+ 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,
+ 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 use a byte instead. Very ugly, but it is what it is.
+ byte isStaleBacking;
+ public bool isStale {
+ get => isStaleBacking != 0;
+ set => isStaleBacking = value ? (byte)1 : (byte)0;
+ }
+ }
+
+ /// <summary>Checks if the agent has reached its destination, or the end of the path</summary>
+ [BurstCompile]
+ public static void UpdateReachedEndInfo (ref UnsafeSpan<float3> 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;
+ UpdateReachedOrientation(ref state, ref transform, ref movementPlane, ref destination);
+ }
+
+ /// <summary>Checks if the agent is oriented towards the desired facing direction</summary>
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.cs.meta
new file mode 100644
index 0000000..d1ba13b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobRepairPath.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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs
new file mode 100644
index 0000000..994c68d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ public static System.Collections.Generic.IEnumerable<object> 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.cs.meta
new file mode 100644
index 0000000..69baaa9
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobStartOffMeshLinkTransition.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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs
new file mode 100644
index 0000000..041f03d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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<Entity> entities;
+ [ReadOnly]
+ public ComponentLookup<LocalTransform> entityPositions;
+ [ReadOnly]
+ public ComponentLookup<MovementState> movementState;
+ [ReadOnly]
+ public ComponentLookup<SyncPositionWithTransform> syncPositionWithTransform;
+ [ReadOnly]
+ public ComponentLookup<SyncRotationWithTransform> syncRotationWithTransform;
+ [ReadOnly]
+ public ComponentLookup<OrientationYAxisForward> 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Jobs/JobSyncEntitiesToTransforms.cs.meta
new file mode 100644
index 0000000..8edba2e
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems.meta
new file mode 100644
index 0000000..ad610e5
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 380dbe46ad736aa4ab00113f94f0ddf2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs
new file mode 100644
index 0000000..c68ad7f
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs
@@ -0,0 +1,268 @@
+#pragma warning disable CS0282
+#if MODULE_ENTITIES
+using Unity.Entities;
+using Unity.Transforms;
+using Unity.Burst;
+using Unity.Jobs;
+using Unity.Collections;
+using UnityEngine;
+using UnityEngine.Jobs;
+using GCHandle = System.Runtime.InteropServices.GCHandle;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+ 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 entityQueryPrepareMovement;
+ EntityQuery entityQueryWithGravity;
+ EntityQuery entityQueryMove;
+ EntityQuery entityQueryRotation;
+ EntityQuery entityQueryGizmos;
+ EntityQuery entityQueryMovementOverride;
+ JobRepairPath.Scheduler jobRepairPathScheduler;
+ ComponentTypeHandle<MovementState> MovementStateTypeHandleRO;
+ ComponentTypeHandle<ResolvedMovement> ResolvedMovementHandleRO;
+
+ public static EntityQueryBuilder EntityQueryPrepareMovement () {
+ return new EntityQueryBuilder(Allocator.Temp)
+ .WithAllRW<MovementState>()
+ .WithAllRW<ManagedState>()
+ .WithAllRW<LocalTransform>()
+ .WithAll<MovementSettings, DestinationPoint, AgentMovementPlane, AgentCylinderShape>()
+ // .WithAny<ReadyToTraverseOffMeshLink>() // TODO: Use WithPresent in newer versions
+ .WithAbsent<AgentOffMeshLinkTraversal>();
+ }
+
+ public void OnCreate (ref SystemState state) {
+ jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state);
+ MovementStateTypeHandleRO = state.GetComponentTypeHandle<MovementState>(true);
+ ResolvedMovementHandleRO = state.GetComponentTypeHandle<ResolvedMovement>(true);
+
+ entityQueryRotation = state.GetEntityQuery(
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadOnly<MovementSettings>(),
+ ComponentType.ReadOnly<MovementState>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<AgentMovementPlane>(),
+ ComponentType.ReadOnly<MovementControl>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementFinalize>()
+ );
+
+ entityQueryMove = state.GetEntityQuery(
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<AgentMovementPlane>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadOnly<MovementSettings>(),
+ ComponentType.ReadOnly<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementStatistics>(),
+
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementFinalize>()
+ );
+
+ entityQueryWithGravity = state.GetEntityQuery(
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadOnly<MovementSettings>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementStatistics>(),
+ ComponentType.ReadOnly<MovementControl>(),
+ ComponentType.ReadWrite<GravityState>(),
+
+ ComponentType.ReadOnly<AgentMovementPlaneSource>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementFinalize>()
+ );
+
+ entityQueryPrepareMovement = jobRepairPathScheduler.GetEntityQuery(Allocator.Temp).WithAll<SimulateMovement, SimulateMovementRepair>().Build(ref state);
+
+ entityQueryGizmos = state.GetEntityQuery(
+ ComponentType.ReadOnly<LocalTransform>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<MovementSettings>(),
+ ComponentType.ReadOnly<AgentMovementPlane>(),
+ ComponentType.ReadOnly<ManagedState>(),
+ ComponentType.ReadOnly<MovementState>(),
+ ComponentType.ReadOnly<ResolvedMovement>(),
+
+ ComponentType.ReadOnly<SimulateMovement>()
+ );
+
+ entityQueryMovementOverride = state.GetEntityQuery(
+ ComponentType.ReadWrite<ManagedMovementOverrideBeforeMovement>(),
+
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadWrite<AgentCylinderShape>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadWrite<DestinationPoint>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadWrite<MovementStatistics>(),
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadWrite<MovementSettings>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementControl>(),
+
+ ComponentType.Exclude<AgentOffMeshLinkTraversal>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementControl>()
+ );
+ }
+
+ static readonly ProfilerMarker MarkerMovementOverride = new ProfilerMarker("MovementOverrideBeforeMovement");
+
+ public void OnDestroy (ref SystemState state) {
+ jobRepairPathScheduler.Dispose();
+ }
+
+ public void OnUpdate (ref SystemState systemState) {
+ var draw = DrawingManager.GetBuilder();
+
+ // 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(entityQueryRotation, systemState.Dependency);
+
+ RunMovementOverrideBeforeMovement(ref systemState, dt);
+
+ // Move all agents which do not have a GravityState component
+ systemState.Dependency = new JobMoveAgent {
+ dt = dt,
+ }.ScheduleParallel(entityQueryMove, systemState.Dependency);
+
+ ScheduleApplyGravity(ref systemState, draw, dt);
+ var gizmosDependency = systemState.Dependency;
+
+ UpdateTypeHandles(ref systemState);
+
+ systemState.Dependency = ScheduleRepairPaths(ref systemState, systemState.Dependency);
+
+ // Draw gizmos only in the editor, and at most once per frame.
+ // The movement calculations may run multiple times per frame when using high time-scales,
+ // but rendering gizmos more than once would just lead to clutter.
+ if (Application.isEditor && AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep) {
+ gizmosDependency = ScheduleDrawGizmos(draw, systemState.Dependency);
+ }
+
+ // Render gizmos as soon as all relevant jobs are done
+ draw.DisposeAfter(gizmosDependency);
+ systemState.Dependency = ScheduleSyncEntitiesToTransforms(ref systemState, systemState.Dependency);
+ systemState.Dependency = JobHandle.CombineDependencies(systemState.Dependency, gizmosDependency);
+ }
+
+ void ScheduleApplyGravity (ref SystemState systemState, CommandBuilder draw, float dt) {
+ Profiler.BeginSample("Gravity");
+ // Note: We cannot use CalculateEntityCountWithoutFiltering here, because the GravityState component can be disabled
+ var count = entityQueryWithGravity.CalculateEntityCount();
+ var raycastCommands = CollectionHelper.CreateNativeArray<RaycastCommand>(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory);
+ var raycastHits = CollectionHelper.CreateNativeArray<RaycastHit>(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,
+ draw = draw,
+ 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,
+ draw = draw,
+ 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();
+ }
+ }
+
+ void UpdateTypeHandles (ref SystemState systemState) {
+ MovementStateTypeHandleRO.Update(ref systemState);
+ ResolvedMovementHandleRO.Update(ref systemState);
+ }
+
+ 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 ScheduleDrawGizmos (CommandBuilder commandBuilder, JobHandle dependency) {
+ // Note: The ScheduleRepairPaths job runs right before this, so those handles are still valid
+ return new JobDrawFollowerGizmos {
+ draw = commandBuilder,
+ entityManagerHandle = jobRepairPathScheduler.entityManagerHandle,
+ LocalTransformTypeHandleRO = jobRepairPathScheduler.LocalTransformTypeHandleRO,
+ AgentCylinderShapeHandleRO = jobRepairPathScheduler.AgentCylinderShapeTypeHandleRO,
+ MovementSettingsHandleRO = jobRepairPathScheduler.MovementSettingsTypeHandleRO,
+ AgentMovementPlaneHandleRO = jobRepairPathScheduler.AgentMovementPlaneTypeHandleRO,
+ ManagedStateHandleRW = jobRepairPathScheduler.ManagedStateTypeHandleRW,
+ MovementStateHandleRO = MovementStateTypeHandleRO,
+ ResolvedMovementHandleRO = ResolvedMovementHandleRO,
+ }.ScheduleParallel(entityQueryGizmos, dependency);
+ }
+
+ JobHandle ScheduleSyncEntitiesToTransforms (ref SystemState systemState, JobHandle dependency) {
+ Profiler.BeginSample("SyncEntitiesToTransforms");
+ int numComponents = BatchedEvents.GetComponents<FollowerEntity>(BatchedEvents.Event.None, out var transforms, out var components);
+ if (numComponents == 0) {
+ Profiler.EndSample();
+ return dependency;
+ }
+
+ var entities = CollectionHelper.CreateNativeArray<Entity>(numComponents, systemState.WorldUpdateAllocator);
+ for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity;
+
+ dependency = new JobSyncEntitiesToTransforms {
+ entities = entities,
+ syncPositionWithTransform = SystemAPI.GetComponentLookup<SyncPositionWithTransform>(true),
+ syncRotationWithTransform = SystemAPI.GetComponentLookup<SyncRotationWithTransform>(true),
+ orientationYAxisForward = SystemAPI.GetComponentLookup<OrientationYAxisForward>(true),
+ entityPositions = SystemAPI.GetComponentLookup<LocalTransform>(true),
+ movementState = SystemAPI.GetComponentLookup<MovementState>(true),
+ }.Schedule(transforms, dependency);
+ Profiler.EndSample();
+ return dependency;
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMoveSystem.cs.meta
new file mode 100644
index 0000000..31a2d28
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs
new file mode 100644
index 0000000..a97172c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs
@@ -0,0 +1,194 @@
+#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 {
+ /// <summary>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</summary>
+ public class TimeScaledRateManager : IRateManager, System.IDisposable {
+ int numUpdatesThisFrame;
+ int updateIndex;
+ float stepDt;
+ float maximumDt = 1.0f / 30.0f;
+ NativeList<TimeData> cheapTimeDataQueue;
+ NativeList<TimeData> timeDataQueue;
+ double lastFullSimulation;
+ double lastCheapSimulation;
+ static bool cheapSimulationOnly;
+ static bool isLastSubstep;
+ static bool inGroup;
+ static TimeData cheapTimeData;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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;
+ }
+ }
+
+ /// <summary>True when this is the last substep of the current simulation</summary>
+ public static bool IsLastSubstep {
+ get {
+ if (!inGroup) throw new System.InvalidOperationException("Cannot call this method outside of a simulation group using TimeScaledRateManager");
+ return isLastSubstep;
+ }
+ }
+
+ public TimeScaledRateManager () {
+ cheapTimeDataQueue = new NativeList<TimeData>(Allocator.Persistent);
+ timeDataQueue = new NativeList<TimeData>(Allocator.Persistent);
+ }
+
+ public void Dispose () {
+ cheapTimeDataQueue.Dispose();
+ timeDataQueue.Dispose();
+ }
+
+ 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 fullDt = (float)(group.World.Time.ElapsedTime - 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.
+ numUpdatesThisFrame = Mathf.FloorToInt(Mathf.Pow(fullDt / maximumDt, 0.8f));
+ var currentTime = group.World.Time.ElapsedTime;
+ 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;
+
+ return true;
+ }
+
+ public float Timestep {
+ get => maximumDt;
+ set => maximumDt = 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
+
+ base.OnUpdate();
+
+ 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/AIMovementSystemGroup.cs.meta
new file mode 100644
index 0000000..da24779
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs
new file mode 100644
index 0000000..a4d590a
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs
@@ -0,0 +1,51 @@
+#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;
+
+ /// <summary>Copies <see cref="MovementControl"/> to <see cref="ResolvedMovement"/> when no local avoidance is used</summary>
+ [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 {
+ EntityQuery entityQuery;
+
+ public void OnCreate (ref SystemState state) {
+ entityQuery = state.GetEntityQuery(new EntityQueryDesc {
+ All = new ComponentType[] {
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadOnly<MovementControl>(),
+ ComponentType.ReadOnly<SimulateMovement>()
+ },
+ Options = EntityQueryOptions.FilterWriteGroup
+ });
+ }
+
+ public void OnDestroy (ref SystemState state) { }
+
+ public void OnUpdate (ref SystemState systemState) {
+ new CopyJob {}.Schedule(entityQuery);
+ }
+
+ [BurstCompile]
+ 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;
+ resolved.targetRotation = control.targetRotation;
+ resolved.targetRotationHint = control.targetRotationHint;
+ resolved.targetRotationOffset = control.targetRotationOffset;
+ resolved.rotationSpeed = control.rotationSpeed;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FallbackResolveMovementSystem.cs.meta
new file mode 100644
index 0000000..fbac068
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs
new file mode 100644
index 0000000..bc50184
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs
@@ -0,0 +1,322 @@
+#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 entityQueryPrepare;
+ EntityQuery entityQueryControl;
+ EntityQuery entityQueryControlManaged;
+ EntityQuery entityQueryControlManaged2;
+ EntityQuery entityQueryOffMeshLink;
+ EntityQuery entityQueryOffMeshLinkCleanup;
+ public JobRepairPath.Scheduler jobRepairPathScheduler;
+ RedrawScope redrawScope;
+
+ static readonly ProfilerMarker MarkerMovementOverrideBeforeControl = new ProfilerMarker("MovementOverrideBeforeControl");
+ static readonly ProfilerMarker MarkerMovementOverrideAfterControl = new ProfilerMarker("MovementOverrideAfterControl");
+
+ public void OnCreate (ref SystemState state) {
+ jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state);
+
+ redrawScope = DrawingManager.GetRedrawScope();
+
+ entityQueryPrepare = jobRepairPathScheduler.GetEntityQuery(Unity.Collections.Allocator.Temp).WithAll<SimulateMovement, SimulateMovementRepair>().Build(ref state);
+ entityQueryControl = state.GetEntityQuery(
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<AgentMovementPlane>(),
+ ComponentType.ReadOnly<DestinationPoint>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadOnly<MovementStatistics>(),
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadOnly<MovementSettings>(),
+ ComponentType.ReadOnly<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementControl>(),
+
+ ComponentType.Exclude<AgentOffMeshLinkTraversal>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementControl>()
+ );
+
+ entityQueryControlManaged = state.GetEntityQuery(
+ ComponentType.ReadWrite<ManagedMovementOverrideBeforeControl>(),
+
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadWrite<AgentCylinderShape>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadWrite<DestinationPoint>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadWrite<MovementStatistics>(),
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadWrite<MovementSettings>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementControl>(),
+
+ ComponentType.Exclude<AgentOffMeshLinkTraversal>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementControl>()
+ );
+
+ entityQueryControlManaged2 = state.GetEntityQuery(
+ ComponentType.ReadWrite<ManagedMovementOverrideAfterControl>(),
+
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadWrite<AgentCylinderShape>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadWrite<DestinationPoint>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadWrite<MovementStatistics>(),
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadWrite<MovementSettings>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementControl>(),
+
+ ComponentType.Exclude<AgentOffMeshLinkTraversal>(),
+ ComponentType.ReadOnly<SimulateMovement>(),
+ ComponentType.ReadOnly<SimulateMovementControl>()
+ );
+
+ entityQueryOffMeshLink = state.GetEntityQuery(
+ ComponentType.ReadWrite<LocalTransform>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadOnly<DestinationPoint>(),
+ ComponentType.ReadWrite<MovementState>(),
+ ComponentType.ReadOnly<MovementStatistics>(),
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadWrite<MovementSettings>(),
+ ComponentType.ReadOnly<ResolvedMovement>(),
+ ComponentType.ReadWrite<MovementControl>(),
+ ComponentType.ReadWrite<AgentOffMeshLinkTraversal>(),
+ ComponentType.ReadWrite<ManagedAgentOffMeshLinkTraversal>(),
+ ComponentType.ReadOnly<SimulateMovement>()
+ );
+
+ 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.ReadWrite<ManagedAgentOffMeshLinkTraversal>(),
+ ComponentType.Exclude<AgentOffMeshLinkTraversal>()
+ );
+ }
+
+ public void OnDestroy (ref SystemState state) {
+ redrawScope.Dispose();
+ jobRepairPathScheduler.Dispose();
+ }
+
+ public void OnUpdate (ref SystemState systemState) {
+ if (AstarPath.active == null) return;
+
+ var commandBuffer = new EntityCommandBuffer(systemState.WorldUpdateAllocator);
+
+ SyncLocalAvoidanceComponents(ref systemState, commandBuffer);
+ SchedulePaths(ref systemState);
+ StartOffMeshLinkTraversal(ref systemState, commandBuffer);
+
+ commandBuffer.Playback(systemState.EntityManager);
+ commandBuffer.Dispose();
+
+ ProcessActiveOffMeshLinkTraversal(ref systemState);
+ RepairPaths(ref systemState);
+
+ // The full movement calculations do not necessarily need to be done every frame if the fps is high
+ if (!AIMovementSystemGroup.TimeScaledRateManager.CheapSimulationOnly) {
+ ProcessControlLoop(ref systemState, SystemAPI.Time.DeltaTime);
+ }
+ }
+
+ void SyncLocalAvoidanceComponents (ref SystemState systemState, EntityCommandBuffer commandBuffer) {
+ var simulator = RVOSimulator.active?.GetSimulator();
+ // First check if we have a simulator. If not, we can skip handling RVO components
+ if (simulator == null) return;
+
+ Profiler.BeginSample("AddRVOComponents");
+ foreach (var(managedState, entity) in SystemAPI.Query<ManagedState>().WithNone<RVOAgent>().WithEntityAccess()) {
+ if (managedState.enableLocalAvoidance) {
+ commandBuffer.AddComponent<RVOAgent>(entity, managedState.rvoSettings);
+ }
+ }
+ Profiler.EndSample();
+ Profiler.BeginSample("CopyRVOSettings");
+ foreach (var(managedState, rvoAgent, entity) in SystemAPI.Query<ManagedState, RefRW<RVOAgent> >().WithEntityAccess()) {
+ rvoAgent.ValueRW = managedState.rvoSettings;
+ if (!managedState.enableLocalAvoidance) {
+ commandBuffer.RemoveComponent<RVOAgent>(entity);
+ }
+ }
+
+ Profiler.EndSample();
+ }
+
+ void RepairPaths (ref SystemState systemState) {
+ Profiler.BeginSample("RepairPaths");
+ // 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);
+ Profiler.EndSample();
+ }
+
+ [BurstCompile]
+ [WithAbsent(typeof(ManagedAgentOffMeshLinkTraversal))] // Do not recalculate the path of agents that are currently traversing an off-mesh link.
+ partial struct JobShouldRecalculatePaths : IJobEntity {
+ public float time;
+ public NativeBitArray shouldRecalculatePath;
+ int index;
+
+ public void Execute (ref ECS.AutoRepathPolicy autoRepathPolicy, in LocalTransform transform, in AgentCylinderShape shape, in DestinationPoint destination) {
+ if (index >= shouldRecalculatePath.Length) {
+ shouldRecalculatePath.Resize(shouldRecalculatePath.Length * 2, NativeArrayOptions.ClearMemory);
+ }
+ shouldRecalculatePath.Set(index++, autoRepathPolicy.ShouldRecalculatePath(transform.Position, shape.radius, destination.destination, time));
+ }
+ }
+
+ [WithAbsent(typeof(ManagedAgentOffMeshLinkTraversal))] // Do not recalculate the path of agents that are currently traversing an off-mesh link.
+ public partial struct JobRecalculatePaths : IJobEntity {
+ public float time;
+ public NativeBitArray shouldRecalculatePath;
+ int index;
+
+ public void Execute (ManagedState state, ref ECS.AutoRepathPolicy autoRepathPolicy, ref LocalTransform transform, ref DestinationPoint destination, ref AgentMovementPlane movementPlane) {
+ MaybeRecalculatePath(state, ref autoRepathPolicy, ref transform, ref destination, ref movementPlane, time, shouldRecalculatePath.IsSet(index++));
+ }
+
+ public static void MaybeRecalculatePath (ManagedState state, ref ECS.AutoRepathPolicy autoRepathPolicy, ref LocalTransform transform, ref DestinationPoint destination, ref AgentMovementPlane movementPlane, float time, bool wantsToRecalculatePath) {
+ if ((state.pathTracer.isStale || wantsToRecalculatePath) && state.pendingPath == null) {
+ if (autoRepathPolicy.mode != Pathfinding.AutoRepathPolicy.Mode.Never && float.IsFinite(destination.destination.x)) {
+ var path = ABPath.Construct(transform.Position, destination.destination, null);
+ path.UseSettings(state.pathfindingSettings);
+ path.nnConstraint.distanceMetric = DistanceMetric.ClosestAsSeenFromAboveSoft(movementPlane.value.up);
+ ManagedState.SetPath(path, state, in movementPlane, ref destination);
+ autoRepathPolicy.DidRecalculatePath(destination.destination, time);
+ }
+ }
+ }
+ }
+
+ void SchedulePaths (ref SystemState systemState) {
+ Profiler.BeginSample("Schedule search");
+ // 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 bits = new NativeBitArray(512, Allocator.TempJob);
+ systemState.CompleteDependency();
+ var pathfindingLock = AstarPath.active.PausePathfindingSoon();
+ // Calculate which agents want to recalculate their path (using burst)
+ new JobShouldRecalculatePaths {
+ time = (float)SystemAPI.Time.ElapsedTime,
+ shouldRecalculatePath = bits,
+ }.Run();
+ // Schedule the path calculations
+ new JobRecalculatePaths {
+ time = (float)SystemAPI.Time.ElapsedTime,
+ shouldRecalculatePath = bits,
+ }.Run();
+ pathfindingLock.Release();
+ bits.Dispose();
+ Profiler.EndSample();
+ }
+
+ void StartOffMeshLinkTraversal (ref SystemState systemState, EntityCommandBuffer commandBuffer) {
+ Profiler.BeginSample("Start off-mesh link traversal");
+ foreach (var(state, entity) in SystemAPI.Query<ManagedState>().WithAll<ReadyToTraverseOffMeshLink>()
+ .WithEntityAccess()
+ // Do not try to add another off-mesh link component to agents that already have one.
+ .WithNone<AgentOffMeshLinkTraversal>()) {
+ // UnityEngine.Assertions.Assert.IsTrue(movementState.ValueRO.reachedEndOfPart && state.pathTracer.isNextPartValidLink);
+ 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(state, ctx)));
+ }
+ Profiler.EndSample();
+ }
+
+ public static OffMeshLinks.OffMeshLinkTracer NextLinkToTraverse (ManagedState state) {
+ return state.pathTracer.GetLinkInfo(1);
+ }
+
+ public static IOffMeshLinkHandler ResolveOffMeshLinkHandler (ManagedState state, AgentOffMeshLinkTraversalContext ctx) {
+ var handler = state.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(entityQueryOffMeshLink);
+
+ new JobManagedOffMeshLinkTransitionCleanup().Run(entityQueryOffMeshLinkCleanup);
+#if MODULE_ENTITIES_1_0_8_OR_NEWER
+ commandBuffer.RemoveComponent<ManagedAgentOffMeshLinkTraversal>(entityQueryOffMeshLinkCleanup, EntityQueryCaptureMode.AtPlayback);
+#else
+ commandBuffer.RemoveComponent<ManagedAgentOffMeshLinkTraversal>(entityQueryOffMeshLinkCleanup);
+#endif
+ commandBuffer.Playback(systemState.EntityManager);
+ commandBuffer.Dispose();
+ }
+
+ 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(entityQueryControl, 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/FollowerControlSystem.cs.meta
new file mode 100644
index 0000000..653caa6
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs
new file mode 100644
index 0000000..de40655
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs
@@ -0,0 +1,188 @@
+#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.Drawing;
+ using Pathfinding.Util;
+ using Unity.Transforms;
+ using UnityEngine.Profiling;
+
+ [UpdateBefore(typeof(FollowerControlSystem))]
+ [UpdateInGroup(typeof(AIMovementSystemGroup))]
+ [RequireMatchingQueriesForUpdate]
+ [BurstCompile]
+ public partial struct MovementPlaneFromGraphSystem : ISystem {
+ public EntityQuery entityQueryGraph;
+ public EntityQuery entityQueryNormal;
+ NativeArray<float3> sphereSamplePoints;
+ // Store the queue in a GCHandle to avoid restrictions on ISystem
+ GCHandle graphNodeQueue;
+
+ public void OnCreate (ref SystemState state) {
+ entityQueryGraph = state.GetEntityQuery(ComponentType.ReadOnly<MovementState>(), ComponentType.ReadWrite<AgentMovementPlane>(), ComponentType.ReadOnly<AgentMovementPlaneSource>());
+ entityQueryGraph.SetSharedComponentFilter(new AgentMovementPlaneSource { value = MovementPlaneSource.Graph });
+ entityQueryNormal = state.GetEntityQuery(
+ ComponentType.ReadWrite<ManagedState>(),
+ ComponentType.ReadOnly<LocalTransform>(),
+ ComponentType.ReadWrite<AgentMovementPlane>(),
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<AgentMovementPlaneSource>()
+ );
+ entityQueryNormal.AddSharedComponentFilter(new AgentMovementPlaneSource { value = MovementPlaneSource.NavmeshNormal });
+
+ // Number of samples to use when approximating the normal, when using the NavmeshNormal mode.
+ const int Samples = 16;
+ sphereSamplePoints = new NativeArray<float3>(Samples, Allocator.Persistent);
+ UnityEngine.Random.InitState(0);
+ for (int i = 0; i < Samples; i++) {
+ sphereSamplePoints[i] = (float3)UnityEngine.Random.insideUnitSphere;
+ }
+
+ graphNodeQueue = GCHandle.Alloc(new List<GraphNode>(32));
+ }
+
+ public void OnDestroy (ref SystemState state) {
+ sphereSamplePoints.Dispose();
+ graphNodeQueue.Free();
+ }
+
+ public void OnUpdate (ref SystemState systemState) {
+ var graphs = AstarPath.active?.data.graphs;
+ if (graphs == null) return;
+
+ var movementPlanes = CollectionHelper.CreateNativeArray<AgentMovementPlane>(graphs.Length, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < graphs.Length; i++) {
+ var graph = graphs[i];
+ var plane = new NativeMovementPlane(quaternion.identity);
+ if (graph is NavmeshBase navmesh) {
+ plane = new NativeMovementPlane(navmesh.transform.rotation);
+ } else if (graph is GridGraph grid) {
+ plane = new NativeMovementPlane(grid.transform.rotation);
+ }
+ movementPlanes[i] = new AgentMovementPlane {
+ value = plane,
+ };
+ }
+
+ if (!entityQueryNormal.IsEmpty) {
+ systemState.CompleteDependency();
+ var vertices = new NativeList<float3>(16, Allocator.Temp);
+ new JobMovementPlaneFromNavmeshNormal {
+ dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime,
+ sphereSamplePoints = sphereSamplePoints,
+ vertices = vertices,
+ que = (List<GraphNode>)graphNodeQueue.Target,
+ }.Run(entityQueryNormal);
+ }
+
+ systemState.Dependency = new JobMovementPlaneFromGraph {
+ movementPlanes = movementPlanes,
+ }.Schedule(entityQueryGraph, systemState.Dependency);
+ }
+
+ partial struct JobMovementPlaneFromNavmeshNormal : IJobEntity {
+ public float dt;
+ [ReadOnly]
+ public NativeArray<float3> sphereSamplePoints;
+ public NativeList<float3> vertices;
+ public List<GraphNode> que;
+
+ public void Execute (ManagedState managedState, in LocalTransform localTransform, ref AgentMovementPlane agentMovementPlane, in AgentCylinderShape shape) {
+ var sphereSamplePointsSpan = sphereSamplePoints.AsUnsafeSpan();
+ var node = managedState.pathTracer.startNode;
+ // TODO: Expose these parameters?
+ float size = shape.radius * 1.5f;
+ const float InverseSmoothness = 20f;
+ if (node != null) {
+ vertices.Clear();
+ que.Clear();
+ var position = localTransform.Position;
+ var bounds = new UnityEngine.Bounds(position, new float3(size, size, size));
+ int queStart = 0;
+ node.TemporaryFlag1 = true;
+ que.Add(node);
+
+ while (queStart < que.Count) {
+ var current = que[queStart++] as TriangleMeshNode;
+
+ current.GetVertices(out var v0, out var v1, out var v2);
+ var p0 = (float3)v0;
+ var p1 = (float3)v1;
+ var p2 = (float3)v2;
+ Polygon.ClosestPointOnTriangleByRef(in p0, in p1, in p2, in position, out var closest);
+ if (math.lengthsq(closest - position) < size*size) {
+ vertices.Add(p0);
+ vertices.Add(p1);
+ vertices.Add(p2);
+ current.GetConnections((GraphNode con, ref List<GraphNode> 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();
+ SampleTriangleNormals(ref sphereSamplePointsSpan, ref position, size, ref verticesSpan, ref agentMovementPlane, dt * InverseSmoothness);
+ }
+ }
+ }
+
+ [BurstCompile]
+ partial struct JobMovementPlaneFromGraph : IJobEntity {
+ [ReadOnly]
+ public NativeArray<AgentMovementPlane> 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.
+ }
+ }
+ }
+
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ static void SampleTriangleNormals (ref UnsafeSpan<float3> samplePoints, ref float3 sampleOrigin, float sampleScale, ref UnsafeSpan<float3> triangleVertices, ref AgentMovementPlane agentMovementPlane, float alpha) {
+ var targetNormal = float3.zero;
+ int normalWeight = 0;
+ for (int i = 0; i < triangleVertices.Length; i += 3) {
+ var p0 = triangleVertices[i + 0];
+ var p1 = triangleVertices[i + 1];
+ var p2 = triangleVertices[i + 2];
+ var triangleNormal = math.normalizesafe(math.cross(p1 - p0, p2 - p0));
+
+ for (int j = 0; j < samplePoints.Length; j++) {
+ var p = samplePoints[j] * sampleScale + sampleOrigin;
+ if (Polygon.ClosestPointOnTriangleByRef(in p0, in p1, in p2, in p, out var closest) && math.lengthsq(closest - sampleOrigin) < sampleScale*sampleScale) {
+ targetNormal += triangleNormal;
+ normalWeight++;
+ }
+ }
+ }
+
+ if (normalWeight > 0) {
+ targetNormal = math.normalizesafe(targetNormal / normalWeight);
+
+ var currentNormal = agentMovementPlane.value.up;
+ var nextNormal = math.lerp(currentNormal, targetNormal, math.clamp(0, 1, alpha));
+ JobApplyGravity.UpdateMovementPlaneFromNormal(nextNormal, ref agentMovementPlane);
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/MovementPlaneFromGraphSystem.cs.meta
new file mode 100644
index 0000000..f8563f3
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs
new file mode 100644
index 0000000..35ea267
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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 {
+ /// <summary>
+ /// Checks if paths have been calculated, and updates the agent's paths if they have.
+ ///
+ /// This is essentially a replacement for <see cref="Path.callback"/> 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 <see cref="AstarPath.OnPathsCalculated"/> 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: <see cref="AstarPath.OnPathsCalculated"/>
+ /// </summary>
+ [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<PollPendingPathsSystem>().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<ManagedState>().WithAll<AgentOffMeshLinkTraversal>()) 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/PollPendingPathsSystem.cs.meta
new file mode 100644
index 0000000..6f6c2c7
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs
new file mode 100644
index 0000000..3f6b217
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs
@@ -0,0 +1,254 @@
+#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;
+
+ [BurstCompile]
+ [UpdateAfter(typeof(FollowerControlSystem))]
+ [UpdateInGroup(typeof(AIMovementSystemGroup))]
+ public partial struct RVOSystem : ISystem {
+ EntityQuery entityQuery;
+ /// <summary>
+ /// Keeps track of the last simulator that this RVOSystem saw.
+ /// This is a weak GCHandle to allow it to be stored in an ISystem.
+ /// </summary>
+ GCHandle lastSimulator;
+ EntityQuery withAgentIndex;
+ EntityQuery shouldBeAddedToSimulation;
+ EntityQuery shouldBeRemovedFromSimulation;
+ ComponentLookup<AgentOffMeshLinkTraversal> agentOffMeshLinkTraversalLookup;
+
+ public void OnCreate (ref SystemState state) {
+ entityQuery = state.GetEntityQuery(
+ ComponentType.ReadOnly<AgentCylinderShape>(),
+ ComponentType.ReadOnly<LocalTransform>(),
+ ComponentType.ReadOnly<RVOAgent>(),
+ ComponentType.ReadOnly<AgentIndex>(),
+ ComponentType.ReadOnly<AgentMovementPlane>(),
+ ComponentType.ReadOnly<MovementControl>(),
+ ComponentType.ReadWrite<ResolvedMovement>(),
+ ComponentType.ReadOnly<SimulateMovement>()
+ );
+ withAgentIndex = state.GetEntityQuery(
+ ComponentType.ReadWrite<AgentIndex>()
+ );
+ shouldBeAddedToSimulation = state.GetEntityQuery(
+ ComponentType.ReadOnly<RVOAgent>(),
+ ComponentType.Exclude<AgentIndex>()
+ );
+ shouldBeRemovedFromSimulation = state.GetEntityQuery(
+ ComponentType.ReadOnly<AgentIndex>(),
+ ComponentType.Exclude<RVOAgent>()
+ );
+ lastSimulator = GCHandle.Alloc(null, System.Runtime.InteropServices.GCHandleType.Weak);
+ agentOffMeshLinkTraversalLookup = state.GetComponentLookup<AgentOffMeshLinkTraversal>(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
+ systemState.Dependency = simulator.Update(
+ systemState.Dependency,
+ SystemAPI.Time.DeltaTime,
+ AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep,
+ systemState.WorldUpdateAllocator
+ );
+
+ CopyFromRVOSimulatorToEntities(ref systemState, simulator);
+ simulator.LockSimulationDataReadOnly(systemState.Dependency);
+ }
+
+ void RemoveAllAgentsFromSimulation (ref SystemState systemState) {
+ var buffer = new EntityCommandBuffer(Allocator.Temp);
+ var entities = withAgentIndex.ToEntityArray(systemState.WorldUpdateAllocator);
+ buffer.RemoveComponent<AgentIndex>(entities);
+ buffer.Playback(systemState.EntityManager);
+ buffer.Dispose();
+ }
+
+ void AddAndRemoveAgentsFromSimulation (ref SystemState systemState, SimulatorBurst simulator) {
+ // Remove all agents from the simulation that do not have an RVOAgent component, but have an AgentIndex
+ var indicesToRemove = shouldBeRemovedFromSimulation.ToComponentDataArray<AgentIndex>(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 || entitiesToAdd.Length > 0) {
+ var buffer = new EntityCommandBuffer(Allocator.Temp);
+#if MODULE_ENTITIES_1_0_8_OR_NEWER
+ buffer.RemoveComponent<AgentIndex>(shouldBeRemovedFromSimulation, EntityQueryCaptureMode.AtPlayback);
+#else
+ buffer.RemoveComponent<AgentIndex>(shouldBeRemovedFromSimulation);
+#endif
+ for (int i = 0; i < indicesToRemove.Length; i++) {
+ simulator.RemoveAgent(indicesToRemove[i]);
+ }
+ for (int i = 0; i < entitiesToAdd.Length; i++) {
+ buffer.AddComponent<AgentIndex>(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);
+ systemState.Dependency = new JobCopyFromEntitiesToRVOSimulator {
+ agentData = simulator.simulationData,
+ agentOutputData = simulator.outputData,
+ movementPlaneMode = simulator.movementPlane,
+ agentOffMeshLinkTraversalLookup = agentOffMeshLinkTraversalLookup,
+ dt = dt,
+ }.ScheduleParallel(entityQuery, systemState.Dependency);
+ }
+
+ void CopyFromRVOSimulatorToEntities (ref SystemState systemState, SimulatorBurst simulator) {
+ systemState.Dependency = new JobCopyFromRVOSimulatorToEntities {
+ quadtree = simulator.quadtree,
+ agentData = simulator.simulationData,
+ agentOutputData = simulator.outputData,
+ }.ScheduleParallel(entityQuery, systemState.Dependency);
+ }
+
+ [BurstCompile]
+ public partial struct JobCopyFromEntitiesToRVOSimulator : IJobEntity {
+ [NativeDisableParallelForRestriction]
+ public SimulatorBurst.AgentData agentData;
+ [ReadOnly]
+ public SimulatorBurst.AgentOutputData agentOutputData;
+ public MovementPlane movementPlaneMode;
+ [ReadOnly]
+ public ComponentLookup<AgentOffMeshLinkTraversal> 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);
+ var index = agentIndex.Index;
+
+ if (agentData.version[index].Version != agentIndex.Version) 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;
+ // control.endOfPath // TODO
+ 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);
+ var center = 0.5f * shape.height;
+ 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 {
+ 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);
+ if (reached == ReachedEndOfPath.Reached) {
+ flow = math.lerp(agentData.flowFollowingStrength[index], 1.0f, 6.0f * dt);
+ prio *= 0.3f;
+ } else if (reached == ReachedEndOfPath.ReachedSoon) {
+ 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 SimulatorBurst.AgentData agentData;
+ [ReadOnly]
+ public RVOQuadtreeBurst quadtree;
+ [ReadOnly]
+ public SimulatorBurst.AgentOutputData agentOutputData;
+
+ /// <summary>See https://en.wikipedia.org/wiki/Circle_packing</summary>
+ 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) {
+ var index = agentIndex.Index;
+
+ if (agentData.version[index].Version != agentIndex.Version) return;
+
+ var scale = math.abs(transform.Scale);
+ var r = shape.radius * scale * 3f;
+ var area = quadtree.QueryArea(transform.Position, r);
+ var density = 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);
+
+ // Pure copy
+ resolved.targetRotation = control.targetRotation;
+ resolved.targetRotationHint = control.targetRotationHint;
+ resolved.targetRotationOffset = control.targetRotationOffset;
+ resolved.rotationSpeed = control.rotationSpeed;
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/RVOSystem.cs.meta
new file mode 100644
index 0000000..a291705
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs
new file mode 100644
index 0000000..30c0205
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs
@@ -0,0 +1,28 @@
+#pragma warning disable CS0282
+#if MODULE_ENTITIES
+using Unity.Entities;
+using UnityEngine;
+
+namespace Pathfinding.ECS {
+ using Pathfinding;
+
+ [UpdateBefore(typeof(FollowerControlSystem))]
+ [UpdateInGroup(typeof(AIMovementSystemGroup))]
+ [RequireMatchingQueriesForUpdate]
+ public partial struct SyncDestinationTransformSystem : ISystem {
+ public void OnCreate (ref SystemState state) {}
+ public void OnDestroy (ref SystemState state) {}
+
+ public void OnUpdate (ref SystemState systemState) {
+ foreach (var(point, destinationSetter) in SystemAPI.Query<RefRW<DestinationPoint>, AIDestinationSetter>()) {
+ if (destinationSetter.target != null) {
+ point.ValueRW = new DestinationPoint {
+ destination = destinationSetter.target.position,
+ facingDirection = destinationSetter.useRotation ? destinationSetter.target.forward : Vector3.zero
+ };
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncDestinationTransformSystem.cs.meta
new file mode 100644
index 0000000..274915b
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs
new file mode 100644
index 0000000..0fcbf60
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs
@@ -0,0 +1,91 @@
+#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 OnCreate (ref SystemState state) {}
+ public void OnDestroy (ref SystemState state) {}
+
+ public void OnUpdate (ref SystemState systemState) {
+ int numComponents = BatchedEvents.GetComponents<FollowerEntity>(BatchedEvents.Event.None, out var transforms, out var components);
+ if (numComponents > 0) {
+ var entities = new NativeArray<Entity>(numComponents, Allocator.TempJob);
+
+ for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity;
+
+ systemState.Dependency = new SyncTransformsToEntitiesJob {
+ entities = entities,
+ entityPositions = SystemAPI.GetComponentLookup<LocalTransform>(),
+ syncPositionWithTransform = SystemAPI.GetComponentLookup<SyncPositionWithTransform>(true),
+ syncRotationWithTransform = SystemAPI.GetComponentLookup<SyncRotationWithTransform>(true),
+ orientationYAxisForward = SystemAPI.GetComponentLookup<OrientationYAxisForward>(true),
+ movementState = SystemAPI.GetComponentLookup<MovementState>(true),
+ }.Schedule(transforms, systemState.Dependency);
+ }
+ }
+
+ [BurstCompile]
+ struct SyncTransformsToEntitiesJob : IJobParallelForTransform {
+ [ReadOnly]
+ [DeallocateOnJobCompletion]
+ public NativeArray<Entity> entities;
+
+ // Safety: All entities are unique
+ [NativeDisableParallelForRestriction]
+ public ComponentLookup<LocalTransform> entityPositions;
+ [ReadOnly]
+ public ComponentLookup<SyncPositionWithTransform> syncPositionWithTransform;
+ [ReadOnly]
+ public ComponentLookup<SyncRotationWithTransform> syncRotationWithTransform;
+ [ReadOnly]
+ public ComponentLookup<OrientationYAxisForward> orientationYAxisForward;
+ [ReadOnly]
+ public ComponentLookup<MovementState> 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/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/ECS/Systems/SyncTransformsToEntitiesSystem.cs.meta
new file mode 100644
index 0000000..70b3391
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/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: