summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs293
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs81
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs76
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs79
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta11
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs181
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta11
10 files changed, 765 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs
new file mode 100644
index 0000000..174b7d2
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs
@@ -0,0 +1,293 @@
+using System.Collections.Generic;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Serialization;
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Mathematics;
+
+ public class CustomGridGraphRuleEditorAttribute : System.Attribute {
+ public System.Type type;
+ public string name;
+ public CustomGridGraphRuleEditorAttribute(System.Type type, string name) {
+ this.type = type;
+ this.name = name;
+ }
+ }
+
+ /// <summary>
+ /// Container for all rules in a grid graph.
+ ///
+ /// <code>
+ /// // Get the first grid graph in the scene
+ /// var gridGraph = AstarPath.active.data.gridGraph;
+ ///
+ /// gridGraph.rules.AddRule(new Pathfinding.Graphs.Grid.Rules.RuleAnglePenalty {
+ /// penaltyScale = 10000,
+ /// curve = AnimationCurve.Linear(0, 0, 90, 1),
+ /// });
+ /// </code>
+ ///
+ /// See: <see cref="Pathfinding.GridGraph.rules"/>
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [JsonOptIn]
+ public class GridGraphRules {
+ List<System.Action<Context> >[] jobSystemCallbacks;
+ List<System.Action<Context> >[] mainThreadCallbacks;
+
+ /// <summary>List of all rules</summary>
+ [JsonMember]
+ List<GridGraphRule> rules = new List<GridGraphRule>();
+
+ long lastHash;
+
+ /// <summary>Context for when scanning or updating a graph</summary>
+ public class Context {
+ /// <summary>Graph which is being scanned or updated</summary>
+ public GridGraph graph;
+ /// <summary>Data for all the nodes as NativeArrays</summary>
+ public GridGraphScanData data;
+ /// <summary>
+ /// Tracks dependencies between jobs to allow parallelism without tediously specifying dependencies manually.
+ /// Always use when scheduling jobs.
+ /// </summary>
+ public JobDependencyTracker tracker => data.dependencyTracker;
+ }
+
+ public void AddRule (GridGraphRule rule) {
+ rules.Add(rule);
+ lastHash = -1;
+ }
+
+ public void RemoveRule (GridGraphRule rule) {
+ rules.Remove(rule);
+ lastHash = -1;
+ }
+
+ public IReadOnlyList<GridGraphRule> GetRules () {
+ if (rules == null) rules = new List<GridGraphRule>();
+ return rules.AsReadOnly();
+ }
+
+ long Hash () {
+ long hash = 196613;
+
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i] != null && rules[i].enabled) hash = hash * 1572869 ^ (long)rules[i].Hash;
+ }
+ return hash;
+ }
+
+ public void RebuildIfNecessary () {
+ var newHash = Hash();
+
+ if (newHash == lastHash && jobSystemCallbacks != null && mainThreadCallbacks != null) return;
+ lastHash = newHash;
+ Rebuild();
+ }
+
+ public void Rebuild () {
+ rules = rules ?? new List<GridGraphRule>();
+ jobSystemCallbacks = jobSystemCallbacks ?? new List<System.Action<Context> >[6];
+ for (int i = 0; i < jobSystemCallbacks.Length; i++) {
+ if (jobSystemCallbacks[i] != null) jobSystemCallbacks[i].Clear();
+ }
+ mainThreadCallbacks = mainThreadCallbacks ?? new List<System.Action<Context> >[6];
+ for (int i = 0; i < mainThreadCallbacks.Length; i++) {
+ if (mainThreadCallbacks[i] != null) mainThreadCallbacks[i].Clear();
+ }
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i].enabled) rules[i].Register(this);
+ }
+ }
+
+ public void DisposeUnmanagedData () {
+ if (rules != null) {
+ for (int i = 0; i < rules.Count; i++) {
+ if (rules[i] != null) {
+ rules[i].DisposeUnmanagedData();
+ rules[i].SetDirty();
+ }
+ }
+ }
+ }
+
+ static void CallActions (List<System.Action<Context> > actions, Context context) {
+ if (actions != null) {
+ try {
+ for (int i = 0; i < actions.Count; i++) actions[i](context);
+ } catch (System.Exception e) {
+ UnityEngine.Debug.LogException(e);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Executes the rules for the given pass.
+ /// Call handle.Complete on, or wait for, all yielded job handles.
+ /// </summary>
+ public IEnumerator<JobHandle> ExecuteRule (GridGraphRule.Pass rule, Context context) {
+ if (jobSystemCallbacks == null) Rebuild();
+ CallActions(jobSystemCallbacks[(int)rule], context);
+
+ if (mainThreadCallbacks[(int)rule] != null && mainThreadCallbacks[(int)rule].Count > 0) {
+ if (!context.tracker.forceLinearDependencies) yield return context.tracker.AllWritesDependency;
+ CallActions(mainThreadCallbacks[(int)rule], context);
+ }
+ }
+
+ public void ExecuteRuleMainThread (GridGraphRule.Pass rule, Context context) {
+ if (jobSystemCallbacks[(int)rule] != null && jobSystemCallbacks[(int)rule].Count > 0) throw new System.Exception("A job system pass has been added for the " + rule + " pass. " + rule + " only supports main thread callbacks.");
+ if (context.tracker != null) context.tracker.AllWritesDependency.Complete();
+ CallActions(mainThreadCallbacks[(int)rule], context);
+ }
+
+ /// <summary>
+ /// Adds a pass callback that uses the job system.
+ /// This rule should only schedule jobs using the `Context.tracker` dependency tracker. Data is not safe to access directly in the callback
+ ///
+ /// This method should only be called from rules in their Register method.
+ /// </summary>
+ public void AddJobSystemPass (GridGraphRule.Pass pass, System.Action<Context> action) {
+ var index = (int)pass;
+
+ if (jobSystemCallbacks[index] == null) {
+ jobSystemCallbacks[index] = new List<System.Action<Context> >();
+ }
+ jobSystemCallbacks[index].Add(action);
+ }
+
+ /// <summary>
+ /// Adds a pass callback that runs in the main thread.
+ /// The callback may access and modify any data in the context.
+ /// You do not need to schedule jobs in order to access the data.
+ ///
+ /// Warning: Not all data in the Context is valid for every pass. For example you cannot access node connections in the BeforeConnections pass
+ /// since they haven't been calculated yet.
+ ///
+ /// This is a bit slower than <see cref="AddJobSystemPass"/> since parallelism and the burst compiler cannot be used.
+ /// But if you need to use non-thread-safe APIs or data then this is a good choice.
+ ///
+ /// This method should only be called from rules in their Register method.
+ /// </summary>
+ public void AddMainThreadPass (GridGraphRule.Pass pass, System.Action<Context> action) {
+ var index = (int)pass;
+
+ if (mainThreadCallbacks[index] == null) {
+ mainThreadCallbacks[index] = new List<System.Action<Context> >();
+ }
+ mainThreadCallbacks[index].Add(action);
+ }
+
+ /// <summary>Deprecated: Use AddJobSystemPass or AddMainThreadPass instead</summary>
+ [System.Obsolete("Use AddJobSystemPass or AddMainThreadPass instead")]
+ public void Add (GridGraphRule.Pass pass, System.Action<Context> action) {
+ AddJobSystemPass(pass, action);
+ }
+ }
+
+ /// <summary>
+ /// Custom rule for a grid graph.
+ /// See: <see cref="GridGraphRules"/>
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [JsonDynamicType]
+ // Compatibility with old versions
+ [JsonDynamicTypeAlias("Pathfinding.RuleTexture", typeof(RuleTexture))]
+ [JsonDynamicTypeAlias("Pathfinding.RuleAnglePenalty", typeof(RuleAnglePenalty))]
+ [JsonDynamicTypeAlias("Pathfinding.RuleElevationPenalty", typeof(RuleElevationPenalty))]
+ [JsonDynamicTypeAlias("Pathfinding.RulePerLayerModifications", typeof(RulePerLayerModifications))]
+ public abstract class GridGraphRule {
+ /// <summary>Only enabled rules are executed</summary>
+ [JsonMember]
+ public bool enabled = true;
+ int dirty = 1;
+
+ /// <summary>
+ /// Where in the scanning process a rule will be executed.
+ /// Check the documentation for <see cref="GridGraphScanData"/> to see which data fields are valid in which passes.
+ /// </summary>
+ public enum Pass {
+ /// <summary>
+ /// Before the collision testing phase but after height testing.
+ /// This is very early. Most data is not valid by this point.
+ ///
+ /// You can use this if you want to modify the node positions and still have it picked up by the collision testing code.
+ /// </summary>
+ BeforeCollision,
+ /// <summary>
+ /// Before connections are calculated.
+ /// At this point height testing and collision testing has been done (if they are enabled).
+ ///
+ /// This is the most common pass to use.
+ /// If you are modifying walkability here then connections and erosion will be calculated properly.
+ /// </summary>
+ BeforeConnections,
+ /// <summary>
+ /// After connections are calculated.
+ ///
+ /// If you are modifying connections directly you should do that in this pass.
+ ///
+ /// Note: If erosion is used then this pass will be executed twice. One time before erosion and one time after erosion
+ /// when the connections are calculated again.
+ /// </summary>
+ AfterConnections,
+ /// <summary>
+ /// After erosion is calculated but before connections have been recalculated.
+ ///
+ /// If no erosion is used then this pass will not be executed.
+ /// </summary>
+ AfterErosion,
+ /// <summary>
+ /// After everything else.
+ /// This pass is executed after everything else is done.
+ /// You should not modify walkability in this pass because then the node connections will not be up to date.
+ /// </summary>
+ PostProcess,
+ /// <summary>
+ /// After the graph update has been applied to the graph.
+ ///
+ /// This pass can only be added as a main-thread pass.
+ ///
+ /// Warning: No native data in the context is valid at this point. It has all been disposed.
+ /// You cannot modify any data in this pass.
+ /// </summary>
+ AfterApplied,
+ }
+
+ /// <summary>
+ /// Hash of the settings for this rule.
+ /// The <see cref="Register"/> method will be called again whenever the hash changes.
+ /// If the hash does not change it is assumed that the <see cref="Register"/> method does not need to be called again.
+ /// </summary>
+ public virtual int Hash => dirty;
+
+ /// <summary>
+ /// Call if you have changed any setting of the rule.
+ /// This will ensure that any cached data the rule uses is rebuilt.
+ /// If you do not do this then any settings changes may not affect the graph when it is rescanned or updated.
+ ///
+ /// The purpose of this method call is to cause the <see cref="Hash"/> property to change. If your custom rule overrides the Hash property to
+ /// return a hash of some settings, then you do not need to call this method for the changes the hash function already accounts for.
+ /// </summary>
+ public virtual void SetDirty () {
+ dirty++;
+ }
+
+ /// <summary>
+ /// Called when the rule is removed or the graph is destroyed.
+ /// Use this to e.g. clean up any NativeArrays that the rule uses.
+ ///
+ /// Note: The rule should remain valid after this method has been called.
+ /// However the <see cref="Register"/> method is guaranteed to be called before the rule is executed again.
+ /// </summary>
+ public virtual void DisposeUnmanagedData () {
+ }
+
+ /// <summary>Does preprocessing and adds callbacks to the <see cref="GridGraphRules"/> object</summary>
+ public virtual void Register (GridGraphRules rules) {
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta
new file mode 100644
index 0000000..a73e50c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/GridGraphRules.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4d90c9a7bca49464796933f43b5506fc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs
new file mode 100644
index 0000000..1322770
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs
@@ -0,0 +1,81 @@
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Burst;
+ using UnityEngine;
+ using Unity.Mathematics;
+
+ /// <summary>
+ /// Applies penalty based on the slope of the surface below the node.
+ ///
+ /// This is useful if you for example want to discourage agents from walking on steep slopes.
+ ///
+ /// The penalty applied is equivalent to:
+ ///
+ /// <code>
+ /// penalty = curve.evaluate(slope angle in degrees) * penaltyScale
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [Pathfinding.Util.Preserve]
+ public class RuleAnglePenalty : GridGraphRule {
+ public float penaltyScale = 10000;
+ public AnimationCurve curve = AnimationCurve.Linear(0, 0, 90, 1);
+ NativeArray<float> angleToPenalty;
+
+ public override void Register (GridGraphRules rules) {
+ if (!angleToPenalty.IsCreated) angleToPenalty = new NativeArray<float>(32, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < angleToPenalty.Length; i++) {
+ angleToPenalty[i] = Mathf.Max(0, curve.Evaluate(90.0f * i / (angleToPenalty.Length - 1)) * penaltyScale);
+ }
+
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ new JobPenaltyAngle {
+ angleToPenalty = angleToPenalty,
+ up = context.data.up,
+ nodeNormals = context.data.nodes.normals,
+ penalty = context.data.nodes.penalties,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (angleToPenalty.IsCreated) angleToPenalty.Dispose();
+ }
+
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobPenaltyAngle : IJob {
+ public Vector3 up;
+
+ [ReadOnly]
+ public NativeArray<float> angleToPenalty;
+
+ [ReadOnly]
+ public NativeArray<float4> nodeNormals;
+
+ public NativeArray<uint> penalty;
+
+ public void Execute () {
+ float4 up = new float4(this.up.x, this.up.y, this.up.z, 0);
+
+ for (int i = 0; i < penalty.Length; i++) {
+ float4 normal = nodeNormals[i];
+ if (math.any(normal)) {
+ float angle = math.acos(math.dot(normal, up));
+ // Take the dot product to find out the cosinus of the angle it has
+ // Add penalty based on the angle from a precalculated array
+ float x = angle*(angleToPenalty.Length - 1)/math.PI;
+ int ix = (int)x;
+ float p1 = angleToPenalty[math.max(ix, 0)];
+ float p2 = angleToPenalty[math.min(ix + 1, angleToPenalty.Length - 1)];
+ penalty[i] += (uint)math.lerp(p1, p2, x - ix);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta
new file mode 100644
index 0000000..d047088
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleAnglePenalty.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 40d5c4aeb2276457f8fe040e4c5d71fe
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs
new file mode 100644
index 0000000..6f62660
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs
@@ -0,0 +1,76 @@
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+ using Unity.Jobs;
+ using Unity.Collections;
+ using Unity.Burst;
+ using UnityEngine;
+ using Unity.Mathematics;
+
+ /// <summary>
+ /// Applies penalty based on the elevation of the node.
+ ///
+ /// This is useful if you for example want to discourage agents from walking high up in mountain regions.
+ ///
+ /// The penalty applied is equivalent to:
+ ///
+ /// <code>
+ /// penalty = curve.evaluate(Mathf.Clamp01(Mathf.InverseLerp(lower elevation range, upper elevation range, elevation))) * penaltyScale
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [Pathfinding.Util.Preserve]
+ public class RuleElevationPenalty : GridGraphRule {
+ public float penaltyScale = 10000;
+ public Vector2 elevationRange = new Vector2(0, 100);
+ public AnimationCurve curve = AnimationCurve.Linear(0, 0, 1, 1);
+ NativeArray<float> elevationToPenalty;
+
+ public override void Register (GridGraphRules rules) {
+ if (!elevationToPenalty.IsCreated) elevationToPenalty = new NativeArray<float>(64, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
+ for (int i = 0; i < elevationToPenalty.Length; i++) {
+ elevationToPenalty[i] = Mathf.Max(0, penaltyScale * curve.Evaluate(i * 1.0f / (elevationToPenalty.Length - 1)));
+ }
+
+ var clampedElevationRange = new Vector2(math.max(0, elevationRange.x), math.max(1, elevationRange.y));
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ //var elevationRangeScale = Matrix4x4.TRS(new Vector3(0, -clampedElevationRange.x, 0), Quaternion.identity, new Vector3(1, 1/(clampedElevationRange.y - clampedElevationRange.x), 1));
+ var elevationRangeScale = Matrix4x4.Scale(new Vector3(1, 1/(clampedElevationRange.y - clampedElevationRange.x), 1)) * Matrix4x4.Translate(new Vector3(0, -clampedElevationRange.x, 0));
+ new JobElevationPenalty {
+ elevationToPenalty = elevationToPenalty,
+ nodePositions = context.data.nodes.positions,
+ worldToGraph = elevationRangeScale * context.data.transform.matrix.inverse,
+ penalty = context.data.nodes.penalties,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (elevationToPenalty.IsCreated) elevationToPenalty.Dispose();
+ }
+
+ [BurstCompile(FloatMode = FloatMode.Fast)]
+ public struct JobElevationPenalty : IJob {
+ [ReadOnly]
+ public NativeArray<float> elevationToPenalty;
+
+ [ReadOnly]
+ public NativeArray<Vector3> nodePositions;
+
+ public Matrix4x4 worldToGraph;
+ public NativeArray<uint> penalty;
+
+ public void Execute () {
+ for (int i = 0; i < penalty.Length; i++) {
+ float y = math.clamp(worldToGraph.MultiplyPoint3x4(nodePositions[i]).y, 0, 1) * (elevationToPenalty.Length - 1);
+ int iy = (int)y;
+ float p1 = elevationToPenalty[iy];
+ float p2 = elevationToPenalty[math.min(iy + 1, elevationToPenalty.Length - 1)];
+ penalty[i] += (uint)math.lerp(p1, p2, y - iy);
+ }
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta
new file mode 100644
index 0000000..5878475
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleElevationPenalty.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d2933158d922e49e39a332d795d26d68
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs
new file mode 100644
index 0000000..1684b04
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs
@@ -0,0 +1,79 @@
+using Pathfinding.Jobs;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ /// <summary>
+ /// Modifies nodes based on the layer of the surface under the node.
+ ///
+ /// You can for example make all surfaces with a specific layer make the nodes get a specific tag.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [Pathfinding.Util.Preserve]
+ public class RulePerLayerModifications : GridGraphRule {
+ public PerLayerRule[] layerRules = new PerLayerRule[0];
+ const int SetTagBit = 1 << 30;
+
+ public struct PerLayerRule {
+ /// <summary>Layer this rule applies to</summary>
+ public int layer;
+ /// <summary>The action to apply to matching nodes</summary>
+ public RuleAction action;
+ /// <summary>
+ /// Tag for the RuleAction.SetTag action.
+ /// Must be between 0 and <see cref="Pathfinding.GraphNode.MaxTagIndex"/>
+ /// </summary>
+ public int tag;
+ }
+
+ public enum RuleAction {
+ /// <summary>Sets the tag of all affected nodes to <see cref="PerLayerRule.tag"/></summary>
+ SetTag,
+ /// <summary>Makes all affected nodes unwalkable</summary>
+ MakeUnwalkable,
+ }
+
+ public override void Register (GridGraphRules rules) {
+ int[] layerToTag = new int[32];
+ bool[] layerToUnwalkable = new bool[32];
+ for (int i = 0; i < layerRules.Length; i++) {
+ var rule = layerRules[i];
+ if (rule.action == RuleAction.SetTag) {
+ layerToTag[rule.layer] = SetTagBit | rule.tag;
+ } else {
+ layerToUnwalkable[rule.layer] = true;
+ }
+ }
+
+ rules.AddMainThreadPass(Pass.BeforeConnections, context => {
+ if (!context.data.heightHits.IsCreated) {
+ UnityEngine.Debug.LogError("RulePerLayerModifications requires height testing to be enabled on the grid graph", context.graph.active);
+ return;
+ }
+
+ var raycastHits = context.data.heightHits;
+ var nodeWalkable = context.data.nodes.walkable;
+ var nodeTags = context.data.nodes.tags;
+ var slice = new Slice3D(context.data.nodes.bounds, context.data.heightHitsBounds);
+ var size = slice.slice.size;
+ for (int y = 0; y < size.y; y++) {
+ for (int z = 0; z < size.z; z++) {
+ var rowOffset = y * size.x * size.z + z * size.x;
+ for (int x = 0; x < size.x; x++) {
+ var innerIndex = rowOffset + x;
+ var outerIndex = slice.InnerCoordinateToOuterIndex(x, y, z);
+ var coll = raycastHits[innerIndex].collider;
+ if (coll != null) {
+ var layer = coll.gameObject.layer;
+ if (layerToUnwalkable[layer]) nodeWalkable[outerIndex] = false;
+ var tag = layerToTag[layer];
+ if ((tag & SetTagBit) != 0) nodeTags[outerIndex] = tag & 0xFF;
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta
new file mode 100644
index 0000000..bfa3859
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RulePerLayerModifications.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4869e55551c0e4e1abaaf19bcc3d44a1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs
new file mode 100644
index 0000000..ddc6a9c
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs
@@ -0,0 +1,181 @@
+using UnityEngine;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Jobs;
+using Unity.Mathematics;
+
+namespace Pathfinding.Graphs.Grid.Rules {
+ using Pathfinding.Jobs;
+
+ /// <summary>
+ /// Modifies nodes based on the contents of a texture.
+ ///
+ /// This can be used to "paint" penalties or walkability using an external program such as Photoshop.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: grid-rules (view in online documentation for working links)
+ /// </summary>
+ [Pathfinding.Util.Preserve]
+ public class RuleTexture : GridGraphRule {
+ public Texture2D texture;
+
+ public ChannelUse[] channels = new ChannelUse[4];
+ public float[] channelScales = { 1000, 1000, 1000, 1000 };
+
+ public ScalingMode scalingMode = ScalingMode.StretchToFitGraph;
+ public float nodesPerPixel = 1;
+
+ NativeArray<int> colors;
+
+ public enum ScalingMode {
+ FixedScale,
+ StretchToFitGraph,
+ }
+
+ public override int Hash {
+ get {
+ var h = base.Hash ^ (texture != null ? (int)texture.updateCount : 0);
+#if UNITY_EDITOR
+ if (texture != null) h ^= (int)texture.imageContentsHash.GetHashCode();
+#endif
+ return h;
+ }
+ }
+
+ public enum ChannelUse {
+ None,
+ /// <summary>Penalty goes from 0 to channelScale depending on the channel value</summary>
+ Penalty,
+ /// <summary>Node Y coordinate goes from 0 to channelScale depending on the channel value</summary>
+ Position,
+ /// <summary>If channel value is zero the node is made unwalkable, penalty goes from 0 to channelScale depending on the channel value</summary>
+ WalkablePenalty,
+ /// <summary>If channel value is zero the node is made unwalkable</summary>
+ Walkable,
+ }
+
+ public override void Register (GridGraphRules rules) {
+ if (texture == null) return;
+
+ if (!texture.isReadable) {
+ Debug.LogError("Texture for the texture rule on a grid graph is not marked as readable.", texture);
+ return;
+ }
+
+ if (colors.IsCreated) colors.Dispose();
+ colors = new NativeArray<Color32>(texture.GetPixels32(), Allocator.Persistent).Reinterpret<int>();
+
+ // Make sure this is done outside the delegate, just in case the texture is later resized
+ var textureSize = new int2(texture.width, texture.height);
+
+ float4 channelPenaltiesCombined = float4.zero;
+ bool4 channelDeterminesWalkability = false;
+ float4 channelPositionScalesCombined = float4.zero;
+ for (int i = 0; i < 4; i++) {
+ channelPenaltiesCombined[i] = channels[i] == ChannelUse.Penalty || channels[i] == ChannelUse.WalkablePenalty ? channelScales[i] : 0;
+ channelDeterminesWalkability[i] = channels[i] == ChannelUse.Walkable || channels[i] == ChannelUse.WalkablePenalty;
+ channelPositionScalesCombined[i] = channels[i] == ChannelUse.Position ? channelScales[i] : 0;
+ }
+
+ channelPositionScalesCombined /= 255.0f;
+ channelPenaltiesCombined /= 255.0f;
+
+ if (math.any(channelPositionScalesCombined)) {
+ rules.AddJobSystemPass(Pass.BeforeCollision, context => {
+ new JobTexturePosition {
+ colorData = colors,
+ nodePositions = context.data.nodes.positions,
+ nodeNormals = context.data.nodes.normals,
+ bounds = context.data.nodes.bounds,
+ colorDataSize = textureSize,
+ scale = scalingMode == ScalingMode.FixedScale ? 1.0f/math.max(0.01f, nodesPerPixel) : textureSize / new float2(context.graph.width, context.graph.depth),
+ channelPositionScale = channelPositionScalesCombined,
+ graphToWorld = context.data.transform.matrix,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ rules.AddJobSystemPass(Pass.BeforeConnections, context => {
+ new JobTexturePenalty {
+ colorData = colors,
+ penalty = context.data.nodes.penalties,
+ walkable = context.data.nodes.walkable,
+ nodeNormals = context.data.nodes.normals,
+ bounds = context.data.nodes.bounds,
+ colorDataSize = textureSize,
+ scale = scalingMode == ScalingMode.FixedScale ? 1.0f/math.max(0.01f, nodesPerPixel) : textureSize / new float2(context.graph.width, context.graph.depth),
+ channelPenalties = channelPenaltiesCombined,
+ channelDeterminesWalkability = channelDeterminesWalkability,
+ }.Schedule(context.tracker);
+ });
+ }
+
+ public override void DisposeUnmanagedData () {
+ if (colors.IsCreated) colors.Dispose();
+ }
+
+ [BurstCompile]
+ public struct JobTexturePosition : IJob, GridIterationUtilities.INodeModifier {
+ [ReadOnly]
+ public NativeArray<int> colorData;
+ [WriteOnly]
+ public NativeArray<Vector3> nodePositions;
+ [ReadOnly]
+ public NativeArray<float4> nodeNormals;
+
+ public Matrix4x4 graphToWorld;
+ public IntBounds bounds;
+ public int2 colorDataSize;
+ public float2 scale;
+ public float4 channelPositionScale;
+
+ public void ModifyNode (int dataIndex, int dataX, int dataLayer, int dataZ) {
+ var offset = bounds.min.xz;
+ int2 colorPos = math.clamp((int2)math.round((new float2(dataX, dataZ) + offset) * scale), int2.zero, colorDataSize - new int2(1, 1));
+ int colorIndex = colorPos.y*colorDataSize.x + colorPos.x;
+
+ int4 color = new int4((colorData[colorIndex] >> 0) & 0xFF, (colorData[colorIndex] >> 8) & 0xFF, (colorData[colorIndex] >> 16) & 0xFF, (colorData[colorIndex] >> 24) & 0xFF);
+
+ float y = math.dot(channelPositionScale, color);
+
+ nodePositions[dataIndex] = graphToWorld.MultiplyPoint3x4(new Vector3((bounds.min.x + dataX) + 0.5f, y, (bounds.min.z + dataZ) + 0.5f));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachNode(bounds.size, nodeNormals, ref this);
+ }
+ }
+
+ [BurstCompile]
+ public struct JobTexturePenalty : IJob, GridIterationUtilities.INodeModifier {
+ [ReadOnly]
+ public NativeArray<int> colorData;
+ public NativeArray<uint> penalty;
+ public NativeArray<bool> walkable;
+ [ReadOnly]
+ public NativeArray<float4> nodeNormals;
+
+ public IntBounds bounds;
+ public int2 colorDataSize;
+ public float2 scale;
+ public float4 channelPenalties;
+ public bool4 channelDeterminesWalkability;
+
+ public void ModifyNode (int dataIndex, int dataX, int dataLayer, int dataZ) {
+ var offset = bounds.min.xz;
+ int2 colorPos = math.clamp((int2)math.round((new float2(dataX, dataZ) + offset) * scale), int2.zero, colorDataSize - new int2(1, 1));
+ int colorIndex = colorPos.y*colorDataSize.x + colorPos.x;
+
+ int4 color = new int4((colorData[colorIndex] >> 0) & 0xFF, (colorData[colorIndex] >> 8) & 0xFF, (colorData[colorIndex] >> 16) & 0xFF, (colorData[colorIndex] >> 24) & 0xFF);
+
+ penalty[dataIndex] += (uint)math.dot(channelPenalties, color);
+ walkable[dataIndex] = walkable[dataIndex] & !math.any(channelDeterminesWalkability & (color == 0));
+ }
+
+ public void Execute () {
+ GridIterationUtilities.ForEachNode(bounds.size, nodeNormals, ref this);
+ }
+ }
+ }
+}
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta
new file mode 100644
index 0000000..6b3c4aa
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Grid/Rules/RuleTexture.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 42c128143490d447fa6420a4f35fe9bb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: