PlaneFinding.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. namespace Urho.SharpReality.HoloToolkit
  5. {
  6. [StructLayout(LayoutKind.Sequential)]
  7. public struct OrientedBoundingBox
  8. {
  9. public Vector3 Center;
  10. public Vector3 Extents;
  11. public Quaternion Rotation;
  12. };
  13. [StructLayout(LayoutKind.Sequential)]
  14. public struct BoundedPlane
  15. {
  16. public Plane Plane;
  17. public OrientedBoundingBox Bounds;
  18. public float Area;
  19. };
  20. [StructLayout(LayoutKind.Sequential)]
  21. public struct Vector3
  22. {
  23. public float x;
  24. public float y;
  25. public float z;
  26. public Vector3(float x, float y, float z)
  27. {
  28. this.x = x;
  29. this.y = y;
  30. this.z = z;
  31. }
  32. public static Vector3 operator +(Vector3 a, Vector3 b)
  33. {
  34. return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
  35. }
  36. public float Length()
  37. {
  38. return (float)Math.Sqrt(x * x + y * y + z * z);
  39. }
  40. public override string ToString()
  41. {
  42. return string.Format("{{{0}, {1}, {2}}}", x, y, z);
  43. }
  44. public string ToString(string format)
  45. {
  46. return string.Format("{{{0}, {1}, {2}}}", x.ToString(format), y.ToString(format), z.ToString(format));
  47. }
  48. public static readonly Vector3 zero = new Vector3(0, 0, 0);
  49. public static readonly Vector3 one = new Vector3(1, 1, 1);
  50. }
  51. [StructLayout(LayoutKind.Sequential)]
  52. public struct Quaternion
  53. {
  54. public float x;
  55. public float y;
  56. public float z;
  57. public float w;
  58. public Quaternion(float x, float y, float z, float w)
  59. {
  60. this.x = x;
  61. this.y = y;
  62. this.z = z;
  63. this.w = w;
  64. }
  65. public static readonly Quaternion identity = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
  66. }
  67. [StructLayout(LayoutKind.Sequential)]
  68. public struct Matrix4x4
  69. {
  70. public float m00; public float m10; public float m20; public float m30;
  71. public float m01; public float m11; public float m21; public float m31;
  72. public float m02; public float m12; public float m22; public float m32;
  73. public float m03; public float m13; public float m23; public float m33;
  74. public Matrix4x4(
  75. float m00, float m01, float m02, float m03,
  76. float m10, float m11, float m12, float m13,
  77. float m20, float m21, float m22, float m23,
  78. float m30, float m31, float m32, float m33)
  79. {
  80. this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03;
  81. this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13;
  82. this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23;
  83. this.m30 = m30; this.m31 = m31; this.m32 = m32; this.m33 = m33;
  84. }
  85. public Vector3 TransformDirection(Vector3 v)
  86. {
  87. return new Vector3(
  88. v.x * m00 + v.y * m01 + v.z * m02,
  89. v.x * m10 + v.y * m11 + v.z * m12,
  90. v.x * m20 + v.y * m21 + v.z * m22);
  91. }
  92. public Vector3 TransformPoint(Vector3 v)
  93. {
  94. return TransformDirection(v) + new Vector3(m03, m13, m23);
  95. }
  96. public static readonly Matrix4x4 identity = new Matrix4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
  97. }
  98. [StructLayout(LayoutKind.Sequential)]
  99. public struct Plane
  100. {
  101. public Vector3 normal;
  102. public float distance;
  103. };
  104. [StructLayout(LayoutKind.Sequential)]
  105. public struct Transform
  106. {
  107. public Matrix4x4 localToWorldMatrix;
  108. public Quaternion rotation;
  109. public Vector3 TransformPoint(Vector3 point)
  110. {
  111. return localToWorldMatrix.TransformPoint(point);
  112. }
  113. }
  114. [StructLayout(LayoutKind.Sequential)]
  115. public struct Bounds
  116. {
  117. public Vector3 center;
  118. public Vector3 extents;
  119. }
  120. [StructLayout(LayoutKind.Sequential)]
  121. public struct Mesh
  122. {
  123. public Bounds bounds;
  124. public Vector3[] vertices;
  125. public Vector3[] normals;
  126. public int[] triangles;
  127. }
  128. [StructLayout(LayoutKind.Sequential)]
  129. public struct MeshFilter
  130. {
  131. public Transform transform;
  132. public Mesh sharedMesh;
  133. }
  134. public class PlaneFinding
  135. {
  136. #region Public APIs
  137. /// <summary>
  138. /// PlaneFinding is an expensive task that should not be run from Unity's main thread as it
  139. /// will stall the thread and cause a framerate dip. Instead, the PlaneFinding APIs should be
  140. /// exclusively called from background threads. Unfortunately, Unity's built-in data types
  141. /// (such as MeshFilter) are not thread safe and cannot be accessed from background threads.
  142. /// The MeshData struct exists to work-around this limitation. When you want to find planes
  143. /// in a collection of MeshFilter objects, start by constructing a list of MeshData structs
  144. /// from those MeshFilters. You can then take the resulting list of MeshData structs, and
  145. /// safely pass it to the FindPlanes() API from a background thread.
  146. /// </summary>
  147. public struct MeshData
  148. {
  149. public Matrix4x4 Transform;
  150. public Vector3[] Verts;
  151. public Vector3[] Normals;
  152. public Int32[] Indices;
  153. public MeshData(MeshFilter meshFilter)
  154. {
  155. Transform = meshFilter.transform.localToWorldMatrix;
  156. Verts = meshFilter.sharedMesh.vertices;
  157. Normals = meshFilter.sharedMesh.normals;
  158. Indices = meshFilter.sharedMesh.triangles;
  159. }
  160. }
  161. /// <summary>
  162. /// Finds small planar patches that are contained within individual meshes. The output of this
  163. /// API can then be passed to MergeSubPlanes() in order to find larger planar surfaces that
  164. /// potentially span across multiple meshes.
  165. /// </summary>
  166. /// <param name="meshes">
  167. /// List of meshes to run the plane finding algorithm on.
  168. /// </param>
  169. /// <param name="snapToGravityThreshold">
  170. /// Planes whose normal vectors are within this threshold (in degrees) from vertical/horizontal
  171. /// will be snapped to be perfectly gravity aligned. When set to something other than zero, the
  172. /// bounding boxes for each plane will be gravity aligned as well, rather than rotated for an
  173. /// optimally tight fit. Pass 0.0 for this parameter to completely disable the gravity alignment
  174. /// logic.
  175. /// </param>
  176. public static BoundedPlane[] FindSubPlanes(List<MeshData> meshes, float snapToGravityThreshold = 0.0f)
  177. {
  178. StartPlaneFinding();
  179. try
  180. {
  181. int planeCount;
  182. IntPtr planesPtr;
  183. IntPtr pinnedMeshData = PinMeshDataForMarshalling(meshes);
  184. DLLImports.FindSubPlanes(meshes.Count, pinnedMeshData, snapToGravityThreshold, out planeCount, out planesPtr);
  185. return MarshalBoundedPlanesFromIntPtr(planesPtr, planeCount);
  186. }
  187. finally
  188. {
  189. FinishPlaneFinding();
  190. }
  191. }
  192. /// <summary>
  193. /// Takes the subplanes returned by one or more previous calls to FindSubPlanes() and merges
  194. /// them together into larger planes that can potentially span across multiple meshes.
  195. /// Overlapping subplanes that have similar plane equations will be merged together to form
  196. /// larger planes.
  197. /// </summary>
  198. /// <param name="subPlanes">
  199. /// The output from one or more previous calls to FindSubPlanes().
  200. /// </param>
  201. /// <param name="snapToGravityThreshold">
  202. /// Planes whose normal vectors are within this threshold (in degrees) from vertical/horizontal
  203. /// will be snapped to be perfectly gravity aligned. When set to something other than zero, the
  204. /// bounding boxes for each plane will be gravity aligned as well, rather than rotated for an
  205. /// optimally tight fit. Pass 0.0 for this parameter to completely disable the gravity alignment
  206. /// logic.
  207. /// </param>
  208. /// <param name="minArea">
  209. /// While merging subplanes together, any candidate merged plane whose constituent mesh
  210. /// triangles have a total area less than this threshold are ignored.
  211. /// </param>
  212. public static BoundedPlane[] MergeSubPlanes(BoundedPlane[] subPlanes, float snapToGravityThreshold = 0.0f, float minArea = 0.0f)
  213. {
  214. StartPlaneFinding();
  215. try
  216. {
  217. int planeCount;
  218. IntPtr planesPtr;
  219. DLLImports.MergeSubPlanes(subPlanes.Length, PinObject(subPlanes), minArea, snapToGravityThreshold, out planeCount, out planesPtr);
  220. return MarshalBoundedPlanesFromIntPtr(planesPtr, planeCount);
  221. }
  222. finally
  223. {
  224. FinishPlaneFinding();
  225. }
  226. }
  227. /// <summary>
  228. /// Convenience wrapper that executes FindSubPlanes followed by MergeSubPlanes via a single
  229. /// call into native code (which improves performance by avoiding a bunch of unnecessary data
  230. /// marshalling and a managed-to-native transition).
  231. /// </summary>
  232. /// <param name="meshes">
  233. /// List of meshes to run the plane finding algorithm on.
  234. /// </param>
  235. /// <param name="snapToGravityThreshold">
  236. /// Planes whose normal vectors are within this threshold (in degrees) from vertical/horizontal
  237. /// will be snapped to be perfectly gravity aligned. When set to something other than zero, the
  238. /// bounding boxes for each plane will be gravity aligned as well, rather than rotated for an
  239. /// optimally tight fit. Pass 0.0 for this parameter to completely disable the gravity alignment
  240. /// logic.
  241. /// </param>
  242. /// <param name="minArea">
  243. /// While merging subplanes together, any candidate merged plane whose constituent mesh
  244. /// triangles have a total area less than this threshold are ignored.
  245. /// </param>
  246. public static BoundedPlane[] FindPlanes(List<MeshData> meshes, float snapToGravityThreshold = 0.0f, float minArea = 0.0f)
  247. {
  248. StartPlaneFinding();
  249. try
  250. {
  251. int planeCount;
  252. IntPtr planesPtr;
  253. IntPtr pinnedMeshData = PinMeshDataForMarshalling(meshes);
  254. DLLImports.FindPlanes(meshes.Count, pinnedMeshData, minArea, snapToGravityThreshold, out planeCount, out planesPtr);
  255. return MarshalBoundedPlanesFromIntPtr(planesPtr, planeCount);
  256. }
  257. finally
  258. {
  259. FinishPlaneFinding();
  260. }
  261. }
  262. #endregion
  263. #region Internal
  264. private static bool findPlanesRunning = false;
  265. private static System.Object findPlanesLock = new System.Object();
  266. private static DLLImports.MeshData[] reusedMeshesForMarshalling = null;
  267. private static List<GCHandle> reusedPinnedMemoryHandles = new List<GCHandle>();
  268. /// <summary>
  269. /// Validate that no other PlaneFinding API call is currently in progress. As a performance
  270. /// optimization to avoid unnecessarily thrashing the garbage collector, each call into the
  271. /// PlaneFinding DLL reuses a couple of static data stuctures. As a result, we can't handle
  272. /// multiple concurrent calls into these APIs.
  273. /// </summary>
  274. private static void StartPlaneFinding()
  275. {
  276. lock (findPlanesLock)
  277. {
  278. if (findPlanesRunning)
  279. {
  280. throw new Exception("PlaneFinding is already running. You can not call these APIs from multiple threads.");
  281. }
  282. findPlanesRunning = true;
  283. }
  284. }
  285. /// <summary>
  286. /// Cleanup after finishing a PlaneFinding API call by unpinning any memory that was pinned
  287. /// for the call into the driver, and then reset the findPlanesRunning bool.
  288. /// </summary>
  289. private static void FinishPlaneFinding()
  290. {
  291. UnpinAllObjects();
  292. findPlanesRunning = false;
  293. }
  294. /// <summary>
  295. /// Pins the specified object so that the backing memory can not be relocated, adds the pinned
  296. /// memory handle to the tracking list, and then returns that address of the pinned memory so
  297. /// that it can be passed into the DLL to be access directly from native code.
  298. /// </summary>
  299. private static IntPtr PinObject(System.Object obj)
  300. {
  301. GCHandle h = GCHandle.Alloc(obj, GCHandleType.Pinned);
  302. reusedPinnedMemoryHandles.Add(h);
  303. return h.AddrOfPinnedObject();
  304. }
  305. /// <summary>
  306. /// Unpins all of the memory previously pinned by calls to PinObject().
  307. /// </summary>
  308. private static void UnpinAllObjects()
  309. {
  310. for (int i = 0; i < reusedPinnedMemoryHandles.Count; ++i)
  311. {
  312. reusedPinnedMemoryHandles[i].Free();
  313. }
  314. reusedPinnedMemoryHandles.Clear();
  315. }
  316. /// <summary>
  317. /// Copies the supplied mesh data into the reusedMeshesForMarhsalling array. All managed arrays
  318. /// are pinned so that the marshalling only needs to pass a pointer and the native code can
  319. /// reference the memory in place without needing the marshaller to create a complete copy of
  320. /// the data.
  321. /// </summary>
  322. private static IntPtr PinMeshDataForMarshalling(List<MeshData> meshes)
  323. {
  324. // if we have a big enough array reuse it, otherwise create new
  325. if (reusedMeshesForMarshalling == null || reusedMeshesForMarshalling.Length < meshes.Count)
  326. {
  327. reusedMeshesForMarshalling = new DLLImports.MeshData[meshes.Count];
  328. }
  329. for (int i = 0; i < meshes.Count; ++i)
  330. {
  331. reusedMeshesForMarshalling[i] = new DLLImports.MeshData()
  332. {
  333. transform = meshes[i].Transform,
  334. vertCount = meshes[i].Verts.Length,
  335. indexCount = meshes[i].Indices.Length,
  336. verts = PinObject(meshes[i].Verts),
  337. normals = PinObject(meshes[i].Normals),
  338. indices = PinObject(meshes[i].Indices),
  339. };
  340. }
  341. return PinObject(reusedMeshesForMarshalling);
  342. }
  343. /// <summary>
  344. /// Marshals BoundedPlane data returned from a DLL API call into a managed BoundedPlane array
  345. /// and then frees the memory that was allocated within the DLL.
  346. /// </summary>
  347. /// <remarks>Disabling warning 618 when calling Marshal.SizeOf(), because
  348. /// Unity does not support .Net 4.5.1+ for using the preferred Marshal.SizeOf(T) method."/>, </remarks>
  349. private static BoundedPlane[] MarshalBoundedPlanesFromIntPtr(IntPtr outArray, int size)
  350. {
  351. BoundedPlane[] resArray = new BoundedPlane[size];
  352. #pragma warning disable 618
  353. int structsize = Marshal.SizeOf(typeof(BoundedPlane));
  354. #pragma warning restore 618
  355. IntPtr current = outArray;
  356. for (int i = 0; i < size; i++)
  357. {
  358. #pragma warning disable 618
  359. resArray[i] = (BoundedPlane)Marshal.PtrToStructure(current, typeof(BoundedPlane));
  360. #pragma warning restore 618
  361. current = (IntPtr)((long)current + structsize);
  362. }
  363. Marshal.FreeCoTaskMem(outArray);
  364. return resArray;
  365. }
  366. /// <summary>
  367. /// Raw PlaneFinding.dll imports
  368. /// </summary>
  369. private class DLLImports
  370. {
  371. [StructLayout(LayoutKind.Sequential)]
  372. public struct MeshData
  373. {
  374. public Matrix4x4 transform;
  375. public Int32 vertCount;
  376. public Int32 indexCount;
  377. public IntPtr verts;
  378. public IntPtr normals;
  379. public IntPtr indices;
  380. };
  381. [DllImport(Consts.NativeImport)]
  382. public static extern void FindPlanes(
  383. [In] int meshCount,
  384. [In] IntPtr meshes,
  385. [In] float minArea,
  386. [In] float snapToGravityThreshold,
  387. [Out] out int planeCount,
  388. [Out] out IntPtr planesPtr);
  389. [DllImport(Consts.NativeImport)]
  390. public static extern void FindSubPlanes(
  391. [In] int meshCount,
  392. [In] IntPtr meshes,
  393. [In] float snapToGravityThreshold,
  394. [Out] out int planeCount,
  395. [Out] out IntPtr planesPtr);
  396. [DllImport(Consts.NativeImport)]
  397. public static extern void MergeSubPlanes(
  398. [In] int subPlaneCount,
  399. [In] IntPtr subPlanes,
  400. [In] float minArea,
  401. [In] float snapToGravityThreshold,
  402. [Out] out int planeCount,
  403. [Out] out IntPtr planesPtr);
  404. }
  405. #endregion
  406. }
  407. }