/* This file is part of the "NavMesh Extension" project by Rebound Games. * You are only allowed to use these resources if you've bought them directly or indirectly * from Rebound Games. You shall not license, sublicense, sell, resell, transfer, assign, * distribute or otherwise make available to any third party the Service or the Content. */ using UnityEngine; using System.Collections; using System.Collections.Generic; namespace NavMeshExtension { /// /// Stores portal path trees and provides methods for getting the shortest path. /// public class PortalManager : MonoBehaviour { //manager reference private static PortalManager instance; /// /// Dictionary for portal containers and its children, the actual portal transforms. /// public static readonly Dictionary> portals = new Dictionary>(); //initializes the portal tree void Awake() { instance = this; Init(); } //static dictionaries keep their variables between scenes, //we don't want that to happen - clear the path dictionary //whenever this object gets destroyed (e.g. on scene change) void OnDestroy() { portals.Clear(); } /// /// Populate portal dictionary with transforms. /// public static void Init() { foreach (Transform parent in instance.transform) { if (!portals.ContainsKey(parent)) { portals.Add(parent, new List()); foreach (Transform child in parent) { portals[parent].Add(child); } } } } //returns the complement position of a portal static Vector3 GetComplement(Transform parent, Vector3 portal) { Vector3 pos; if (portals[parent][0].position == portal) pos = portals[parent][1].position; else pos = portals[parent][0].position; return pos; } //returns the nearest portal of a container, based on the position static Vector3? FindNearest(Transform parent, Vector3 pos) { //initialize variables UnityEngine.AI.NavMeshPath path = new UnityEngine.AI.NavMeshPath(); Vector3? nearest = null; float distance = Mathf.Infinity; //don't continue if the parent does not exist if (!portals.ContainsKey(parent)) return null; //loop over portals of this portal container, //find the shortest NavMesh path to a portal for (int i = 0; i < portals[parent].Count; i++) { Vector3 portal = portals[parent][i].position; float length = Mathf.Infinity; //let Unity calculate the path and set length, if valid if (UnityEngine.AI.NavMesh.CalculatePath(pos, portal, -1, path) && path.status == UnityEngine.AI.NavMeshPathStatus.PathComplete) length = PathLength(path); if (length < distance) { distance = length; nearest = portal; } } return nearest; } //returns the length of a Unity-constructed NavMesh path static float PathLength(UnityEngine.AI.NavMeshPath path) { //passed in path is too short if (path.corners.Length < 2) return 0f; //initialize variables Vector3 previousCorner = path.corners[0]; float lengthSoFar = 0.0f; int i = 1; //iterate over corners for the full path length while (i < path.corners.Length) { Vector3 currentCorner = path.corners[i]; lengthSoFar += Vector3.Distance(previousCorner, currentCorner); previousCorner = currentCorner; i++; } return lengthSoFar; } /// /// Returns the shortest path based on all portals available. /// public static Vector3[] GetPath(Vector3 start, Vector3 target) { PortalPath path = new PortalPath(); path.pos = start; path.final = target; return path.GetShortestPath(); } /// /// Class that receives start and end points to build path nodes. /// [System.Serializable] public class PortalPath { /// /// Starting point of the path. /// public Vector3 pos; /// /// Desired target destination of the path. /// public Vector3 final; /// /// Calculates the shortest path of all portal tree branches. /// public Vector3[] GetShortestPath() { //get list of all portal transforms List myNodes = new List(); foreach (Transform portal in portals.Keys) myNodes.Add(portal); //get the full tree of possible path branches List tree = new List(); tree.Add(new PortalNode(this, pos)); for (int i = 0; i < myNodes.Count; i++) tree.Add(new PortalNode(pos, myNodes, i)); //for (int i = 0; i < tree.Count; i++) // Debug.Log("Tree " + i + ": " + tree[i].PrintPath()); //let path branches calculate its length for (int i = 0; i < tree.Count; i++) tree[i].CalculateLength(0f); //get the shortest path length of all branches, //then cache the best index of the total path float min = Mathf.Infinity; int minIndex = 0; for (int i = 0; i < tree.Count; i++) { float len = tree[i].GetShortestLength(); if (len < min) { min = len; minIndex = i; } //Debug.Log("Shortest of " + i + ": " + len); } return tree[minIndex].GetShortestPath().ToArray(); } /// /// Class for branch length calculation of the full path. /// [System.Serializable] public class PortalNode { //path instance reference private static PortalPath path; /// /// Starting point of the node. /// public Vector3 start; /// /// Desired target destination of the node. /// public Vector3? target; /// /// Calculated node length. /// public float length = 0f; /// /// Children nodes of this branch/node. /// public List childs = new List(); /// /// Constructor for setting up initial starting point and final destination. /// public PortalNode(PortalPath instance, Vector3 start) { path = instance; this.start = start; //initialize variables UnityEngine.AI.NavMeshPath p = new UnityEngine.AI.NavMeshPath(); //let Unity calculate this path and set target, if valid if (UnityEngine.AI.NavMesh.CalculatePath(start, instance.final, -1, p) && p.status == UnityEngine.AI.NavMeshPathStatus.PathComplete) { this.target = instance.final; } } /// /// Constructor for setting up deeper branches of this branch. /// public PortalNode(Vector3 tar, List nodes, int index) { start = tar; //receive portal parents from the upper branch List childNodes = new List(nodes); Transform portalParent = childNodes[index]; //set the nearest target to the portal destination, //then find the complement and remove this portal node target = FindNearest(portalParent, start); if (!target.HasValue) return; Vector3 com = GetComplement(portalParent, target.Value); childNodes.Remove(portalParent); //add direct path to the complement as a new node //set up deeper branches without the previous portal container childs.Add(new PortalNode(path, com)); for (int i = 0; i < childNodes.Count; i++) childs.Add(new PortalNode(com, childNodes, i)); } /// /// Recursively logs node positions of this branch. /// public string PrintPath() { string str = " " + start + "->" + target; for (int i = 0; i < childs.Count; i++) { str += childs[i].PrintPath(); } return str; } /// /// Sets the length of this branch and invokes it for all childs. /// public void CalculateLength(float parLength) { //initialize variables UnityEngine.AI.NavMeshPath path = new UnityEngine.AI.NavMeshPath(); float myLength = Mathf.Infinity; //let Unity calculate this path if (target.HasValue && UnityEngine.AI.NavMesh.CalculatePath(start, target.Value, -1, path)) { myLength = parLength + PathLength(path); } length = myLength; //loop over children to do the same for (int i = 0; i < childs.Count; i++) { childs[i].CalculateLength(length); } } /// /// Loops over all child branches to find the total shortest path length. /// public float GetShortestLength() { //don't continue at the last node/leaf if (childs.Count == 0) return length; float min = Mathf.Infinity; for (int i = 0; i < childs.Count; i++) { float len = childs[i].GetShortestLength(); if (len < min) min = len; } return min; } /// /// Recursively gets node positions of the shortest branch. /// public List GetShortestPath() { //initialize list with positions of this node List nodes = new List(); if (target.HasValue) { nodes.Add(start); nodes.Add(target.Value); } //we're done without target or children if (!target.HasValue || childs.Count == 0) return nodes; //do the same as in GetShortestLength(), //but here we also keep the shortest node index float min = Mathf.Infinity; int index = 0; for (int i = 0; i < childs.Count; i++) { float len = childs[i].GetShortestLength(); if (len < min) { min = len; index = i; } } //add best/shortest branch to the final list nodes.AddRange(childs[index].GetShortestPath()); return nodes; } } } } }