GraphTransform.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using UnityEngine;
  2. namespace Pathfinding.Util {
  3. /// <summary>
  4. /// Transforms to and from world space to a 2D movement plane.
  5. /// The transformation is guaranteed to be purely a rotation
  6. /// so no scale or offset is used. This interface is primarily
  7. /// used to make it easier to write movement scripts which can
  8. /// handle movement both in the XZ plane and in the XY plane.
  9. ///
  10. /// See: <see cref="Pathfinding.Util.GraphTransform"/>
  11. /// </summary>
  12. public interface IMovementPlane {
  13. Vector2 ToPlane(Vector3 p);
  14. Vector2 ToPlane(Vector3 p, out float elevation);
  15. Vector3 ToWorld(Vector2 p, float elevation = 0);
  16. }
  17. /// <summary>Generic 3D coordinate transformation</summary>
  18. public interface ITransform {
  19. Vector3 Transform(Vector3 position);
  20. Vector3 InverseTransform(Vector3 position);
  21. }
  22. /// <summary>
  23. /// Defines a transformation from graph space to world space.
  24. /// This is essentially just a simple wrapper around a matrix, but it has several utilities that are useful.
  25. /// </summary>
  26. public class GraphTransform : IMovementPlane, ITransform {
  27. /// <summary>True if this transform is the identity transform (i.e it does not do anything)</summary>
  28. public readonly bool identity;
  29. /// <summary>True if this transform is a pure translation without any scaling or rotation</summary>
  30. public readonly bool onlyTranslational;
  31. readonly bool isXY;
  32. readonly bool isXZ;
  33. readonly Matrix4x4 matrix;
  34. readonly Matrix4x4 inverseMatrix;
  35. readonly Vector3 up;
  36. readonly Vector3 translation;
  37. readonly Int3 i3translation;
  38. readonly Quaternion rotation;
  39. readonly Quaternion inverseRotation;
  40. public static readonly GraphTransform identityTransform = new GraphTransform(Matrix4x4.identity);
  41. public GraphTransform (Matrix4x4 matrix) {
  42. this.matrix = matrix;
  43. inverseMatrix = matrix.inverse;
  44. identity = matrix.isIdentity;
  45. onlyTranslational = MatrixIsTranslational(matrix);
  46. up = matrix.MultiplyVector(Vector3.up).normalized;
  47. translation = matrix.MultiplyPoint3x4(Vector3.zero);
  48. i3translation = (Int3)translation;
  49. // Extract the rotation from the matrix. This is only correct if the matrix has no skew, but we only
  50. // want to use it for the movement plane so as long as the Up axis is parpendicular to the Forward
  51. // axis everything should be ok. In fact the only case in the project when all three axes are not
  52. // perpendicular is when hexagon or isometric grid graphs are used, but in those cases only the
  53. // X and Z axes are not perpendicular.
  54. rotation = Quaternion.LookRotation(TransformVector(Vector3.forward), TransformVector(Vector3.up));
  55. inverseRotation = Quaternion.Inverse(rotation);
  56. // Some short circuiting code for the movement plane calculations
  57. isXY = rotation == Quaternion.Euler(-90, 0, 0);
  58. isXZ = rotation == Quaternion.Euler(0, 0, 0);
  59. }
  60. public Vector3 WorldUpAtGraphPosition (Vector3 point) {
  61. return up;
  62. }
  63. static bool MatrixIsTranslational (Matrix4x4 matrix) {
  64. return matrix.GetColumn(0) == new Vector4(1, 0, 0, 0) && matrix.GetColumn(1) == new Vector4(0, 1, 0, 0) && matrix.GetColumn(2) == new Vector4(0, 0, 1, 0) && matrix.m33 == 1;
  65. }
  66. public Vector3 Transform (Vector3 point) {
  67. if (onlyTranslational) return point + translation;
  68. return matrix.MultiplyPoint3x4(point);
  69. }
  70. public Vector3 TransformVector (Vector3 point) {
  71. if (onlyTranslational) return point;
  72. return matrix.MultiplyVector(point);
  73. }
  74. public void Transform (Int3[] arr) {
  75. if (onlyTranslational) {
  76. for (int i = arr.Length - 1; i >= 0; i--) arr[i] += i3translation;
  77. } else {
  78. for (int i = arr.Length - 1; i >= 0; i--) arr[i] = (Int3)matrix.MultiplyPoint3x4((Vector3)arr[i]);
  79. }
  80. }
  81. public void Transform (Vector3[] arr) {
  82. if (onlyTranslational) {
  83. for (int i = arr.Length - 1; i >= 0; i--) arr[i] += translation;
  84. } else {
  85. for (int i = arr.Length - 1; i >= 0; i--) arr[i] = matrix.MultiplyPoint3x4(arr[i]);
  86. }
  87. }
  88. public Vector3 InverseTransform (Vector3 point) {
  89. if (onlyTranslational) return point - translation;
  90. return inverseMatrix.MultiplyPoint3x4(point);
  91. }
  92. public Int3 InverseTransform (Int3 point) {
  93. if (onlyTranslational) return point - i3translation;
  94. return (Int3)inverseMatrix.MultiplyPoint3x4((Vector3)point);
  95. }
  96. public void InverseTransform (Int3[] arr) {
  97. for (int i = arr.Length - 1; i >= 0; i--) arr[i] = (Int3)inverseMatrix.MultiplyPoint3x4((Vector3)arr[i]);
  98. }
  99. public static GraphTransform operator * (GraphTransform lhs, Matrix4x4 rhs) {
  100. return new GraphTransform(lhs.matrix * rhs);
  101. }
  102. public static GraphTransform operator * (Matrix4x4 lhs, GraphTransform rhs) {
  103. return new GraphTransform(lhs * rhs.matrix);
  104. }
  105. public Bounds Transform (Bounds bounds) {
  106. if (onlyTranslational) return new Bounds(bounds.center + translation, bounds.size);
  107. var corners = ArrayPool<Vector3>.Claim(8);
  108. var extents = bounds.extents;
  109. corners[0] = Transform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
  110. corners[1] = Transform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
  111. corners[2] = Transform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
  112. corners[3] = Transform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
  113. corners[4] = Transform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
  114. corners[5] = Transform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
  115. corners[6] = Transform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
  116. corners[7] = Transform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
  117. var min = corners[0];
  118. var max = corners[0];
  119. for (int i = 1; i < 8; i++) {
  120. min = Vector3.Min(min, corners[i]);
  121. max = Vector3.Max(max, corners[i]);
  122. }
  123. ArrayPool<Vector3>.Release(ref corners);
  124. return new Bounds((min+max)*0.5f, max - min);
  125. }
  126. public Bounds InverseTransform (Bounds bounds) {
  127. if (onlyTranslational) return new Bounds(bounds.center - translation, bounds.size);
  128. var corners = ArrayPool<Vector3>.Claim(8);
  129. var extents = bounds.extents;
  130. corners[0] = InverseTransform(bounds.center + new Vector3(extents.x, extents.y, extents.z));
  131. corners[1] = InverseTransform(bounds.center + new Vector3(extents.x, extents.y, -extents.z));
  132. corners[2] = InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, extents.z));
  133. corners[3] = InverseTransform(bounds.center + new Vector3(extents.x, -extents.y, -extents.z));
  134. corners[4] = InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, extents.z));
  135. corners[5] = InverseTransform(bounds.center + new Vector3(-extents.x, extents.y, -extents.z));
  136. corners[6] = InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, extents.z));
  137. corners[7] = InverseTransform(bounds.center + new Vector3(-extents.x, -extents.y, -extents.z));
  138. var min = corners[0];
  139. var max = corners[0];
  140. for (int i = 1; i < 8; i++) {
  141. min = Vector3.Min(min, corners[i]);
  142. max = Vector3.Max(max, corners[i]);
  143. }
  144. ArrayPool<Vector3>.Release(ref corners);
  145. return new Bounds((min+max)*0.5f, max - min);
  146. }
  147. #region IMovementPlane implementation
  148. /// <summary>
  149. /// Transforms from world space to the 'ground' plane of the graph.
  150. /// The transformation is purely a rotation so no scale or offset is used.
  151. ///
  152. /// For a graph rotated with the rotation (-90, 0, 0) this will transform
  153. /// a coordinate (x,y,z) to (x,y). For a graph with the rotation (0,0,0)
  154. /// this will tranform a coordinate (x,y,z) to (x,z). More generally for
  155. /// a graph with a quaternion rotation R this will transform a vector V
  156. /// to R * V (i.e rotate the vector V using the rotation R).
  157. /// </summary>
  158. Vector2 IMovementPlane.ToPlane (Vector3 point) {
  159. // These special cases cover most graph orientations used in practice.
  160. // Having them here improves performance in those cases by a factor of
  161. // 2.5 without impacting the generic case in any significant way.
  162. if (isXY) return new Vector2(point.x, point.y);
  163. if (!isXZ) point = inverseRotation * point;
  164. return new Vector2(point.x, point.z);
  165. }
  166. /// <summary>
  167. /// Transforms from world space to the 'ground' plane of the graph.
  168. /// The transformation is purely a rotation so no scale or offset is used.
  169. /// </summary>
  170. Vector2 IMovementPlane.ToPlane (Vector3 point, out float elevation) {
  171. if (!isXZ) point = inverseRotation * point;
  172. elevation = point.y;
  173. return new Vector2(point.x, point.z);
  174. }
  175. /// <summary>
  176. /// Transforms from the 'ground' plane of the graph to world space.
  177. /// The transformation is purely a rotation so no scale or offset is used.
  178. /// </summary>
  179. Vector3 IMovementPlane.ToWorld (Vector2 point, float elevation) {
  180. return rotation * new Vector3(point.x, elevation, point.y);
  181. }
  182. #endregion
  183. }
  184. }