RecastMeshObj.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding {
  4. /// <summary>
  5. /// Explicit mesh object for recast graphs.
  6. /// Adding this component to an object will make sure it is included in any recast graphs.
  7. /// It will be included even if the Rasterize Meshes toggle is set to false.
  8. ///
  9. /// Using RecastMeshObjs instead of relying on the Rasterize Meshes option is good for several reasons.
  10. /// - Rasterize Meshes is slow. If you are using a tiled graph and you are updating it, every time something is recalculated
  11. /// the graph will have to search all meshes in your scene for ones to rasterize, in contrast, RecastMeshObjs are stored
  12. /// in a tree for extremely fast lookup (O(log n + k) compared to O(n) where n is the number of meshes in your scene and k is the number of meshes
  13. /// which should be rasterized, if you know Big-O notation).
  14. /// - The RecastMeshObj exposes some options which can not be accessed using the Rasterize Meshes toggle. See member documentation for more info.
  15. /// This can for example be used to include meshes in the recast graph rasterization, but make sure that the character cannot walk on them.
  16. ///
  17. /// Since the objects are stored in a tree, and trees are slow to update, there is an enforcement that objects are not allowed to move
  18. /// unless the <see cref="dynamic"/> option is enabled. When the dynamic option is enabled, the object will be stored in an array instead of in the tree.
  19. /// This will reduce the performance improvement over 'Rasterize Meshes' but is still faster.
  20. ///
  21. /// If a mesh filter and a mesh renderer is attached to this GameObject, those will be used in the rasterization
  22. /// otherwise if a collider is attached, that will be used.
  23. /// </summary>
  24. [AddComponentMenu("Pathfinding/Navmesh/RecastMeshObj")]
  25. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_recast_mesh_obj.php")]
  26. public class RecastMeshObj : VersionedMonoBehaviour {
  27. /// <summary>Static objects are stored in a tree for fast bounds lookups</summary>
  28. protected static RecastBBTree tree = new RecastBBTree();
  29. /// <summary>Dynamic objects are stored in a list since it is costly to update the tree every time they move</summary>
  30. protected static List<RecastMeshObj> dynamicMeshObjs = new List<RecastMeshObj>();
  31. /// <summary>Fills the buffer with all RecastMeshObjs which intersect the specified bounds</summary>
  32. public static void GetAllInBounds (List<RecastMeshObj> buffer, Bounds bounds) {
  33. if (!Application.isPlaying) {
  34. var objs = FindObjectsOfType(typeof(RecastMeshObj)) as RecastMeshObj[];
  35. for (int i = 0; i < objs.Length; i++) {
  36. objs[i].RecalculateBounds();
  37. if (objs[i].GetBounds().Intersects(bounds)) {
  38. buffer.Add(objs[i]);
  39. }
  40. }
  41. return;
  42. } else if (Time.timeSinceLevelLoad == 0) {
  43. // Is is not guaranteed that all RecastMeshObj OnEnable functions have been called, so if it is the first frame since loading a new level
  44. // try to initialize all RecastMeshObj objects.
  45. var objs = FindObjectsOfType(typeof(RecastMeshObj)) as RecastMeshObj[];
  46. for (int i = 0; i < objs.Length; i++) objs[i].Register();
  47. }
  48. for (int q = 0; q < dynamicMeshObjs.Count; q++) {
  49. if (dynamicMeshObjs[q].GetBounds().Intersects(bounds)) {
  50. buffer.Add(dynamicMeshObjs[q]);
  51. }
  52. }
  53. Rect r = Rect.MinMaxRect(bounds.min.x, bounds.min.z, bounds.max.x, bounds.max.z);
  54. tree.QueryInBounds(r, buffer);
  55. }
  56. [HideInInspector]
  57. public Bounds bounds;
  58. /// <summary>
  59. /// Check if the object will move.
  60. /// Recalculation of bounding box trees is expensive so if this is true, the object
  61. /// will simply be stored in an array. Easier to move, but slower lookup, so use wisely.
  62. /// If you for some reason want to move it, but don't want it dynamic (maybe you will only move it veery seldom and have lots of similar
  63. /// objects so they would add overhead by being dynamic). You can enable and disable the component every time you move it.
  64. /// Disabling it will remove it from the bounding box tree and enabling it will add it to the bounding box tree again.
  65. ///
  66. /// The object should never move unless being dynamic or disabling/enabling it as described above.
  67. /// </summary>
  68. public bool dynamic = true;
  69. /// <summary>
  70. /// Voxel area for mesh.
  71. /// This area (not to be confused with pathfinding areas, this is only used when rasterizing meshes for the recast graph) field
  72. /// can be used to explicitly insert edges in the navmesh geometry or to make some parts of the mesh unwalkable.
  73. /// If the area is set to -1, it will be removed from the resulting navmesh. This is useful if you have some object that you want to be included in the rasterization,
  74. /// but you don't want to let the character walk on it.
  75. ///
  76. /// When rasterizing the world and two objects with different area values are adjacent to each other, a split in the navmesh geometry
  77. /// will be added between them, characters will still be able to walk between them, but this can be useful when working with navmesh updates.
  78. ///
  79. /// Navmesh updates which recalculate a whole tile (updatePhysics=True) are very slow So if there are special places
  80. /// which you know are going to be updated quite often, for example at a door opening (opened/closed door) you
  81. /// can use areas to create splits on the navmesh for easier updating using normal graph updates (updatePhysics=False).
  82. /// See the below video for more information.
  83. ///
  84. /// Video: https://www.youtube.com/watch?v=CS6UypuEMwM
  85. /// </summary>
  86. public int area = 0;
  87. bool _dynamic;
  88. bool registered;
  89. void OnEnable () {
  90. Register();
  91. }
  92. void Register () {
  93. if (registered) return;
  94. registered = true;
  95. //Clamp area, upper limit isn't really a hard limit, but if it gets much higher it will start to interfere with other stuff
  96. area = Mathf.Clamp(area, -1, 1 << 25);
  97. Renderer rend = GetComponent<Renderer>();
  98. Collider coll = GetComponent<Collider>();
  99. if (rend == null && coll == null) throw new System.Exception("A renderer or a collider should be attached to the GameObject");
  100. MeshFilter filter = GetComponent<MeshFilter>();
  101. if (rend != null && filter == null) throw new System.Exception("A renderer was attached but no mesh filter");
  102. // Default to renderer
  103. bounds = rend != null ? rend.bounds : coll.bounds;
  104. _dynamic = dynamic;
  105. if (_dynamic) {
  106. dynamicMeshObjs.Add(this);
  107. } else {
  108. tree.Insert(this);
  109. }
  110. }
  111. /// <summary>Recalculates the internally stored bounds of the object</summary>
  112. private void RecalculateBounds () {
  113. Renderer rend = GetComponent<Renderer>();
  114. Collider coll = GetCollider();
  115. if (rend == null && coll == null) throw new System.Exception("A renderer or a collider should be attached to the GameObject");
  116. MeshFilter filter = GetComponent<MeshFilter>();
  117. if (rend != null && filter == null) throw new System.Exception("A renderer was attached but no mesh filter");
  118. // Default to renderer
  119. bounds = rend != null ? rend.bounds : coll.bounds;
  120. }
  121. /// <summary>Bounds completely enclosing the mesh for this object</summary>
  122. public Bounds GetBounds () {
  123. if (_dynamic) {
  124. RecalculateBounds();
  125. }
  126. return bounds;
  127. }
  128. public MeshFilter GetMeshFilter () {
  129. return GetComponent<MeshFilter>();
  130. }
  131. public Collider GetCollider () {
  132. return GetComponent<Collider>();
  133. }
  134. void OnDisable () {
  135. registered = false;
  136. if (_dynamic) {
  137. dynamicMeshObjs.Remove(this);
  138. } else {
  139. if (!tree.Remove(this)) {
  140. throw new System.Exception("Could not remove RecastMeshObj from tree even though it should exist in it. Has the object moved without being marked as dynamic?");
  141. }
  142. }
  143. _dynamic = dynamic;
  144. }
  145. }
  146. }