ZEDPlaneGameObject.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.Threading;
  5. using System.Runtime.InteropServices;
  6. #if ZED_URP || ZED_HDRP
  7. using UnityEngine.Rendering;
  8. #endif
  9. /// <summary>
  10. /// Represents an individual plane that was detected by ZEDPlaneDetectionManager.
  11. /// When created, it converts plane data from the ZED SDK into a mesh with proper world position/rotation.
  12. /// This is necessary as the ZED SDK provides plane data relative to the camera.
  13. /// It's also used to enable/disable collisions and visibility.
  14. /// </summary>
  15. public class ZEDPlaneGameObject : MonoBehaviour
  16. {
  17. /// <summary>
  18. /// Type of the plane, determined by its orientation and whether detected by ZEDPlaneDetectionManager's
  19. /// DetectFloorPlane() or DetectPlaneAtHit().
  20. /// </summary>
  21. public enum PLANE_TYPE
  22. {
  23. /// <summary>
  24. /// Floor plane of a scene. Retrieved by ZEDPlaneDetectionManager.DetectFloorPlane().
  25. /// </summary>
  26. FLOOR,
  27. /// <summary>
  28. /// Horizontal plane, such as a tabletop, floor, etc. Detected with DetectPlaneAtHit() using screen-space coordinates.
  29. /// </summary>
  30. HIT_HORIZONTAL,
  31. /// <summary>
  32. /// Vertical plane, such as a wall. Detected with DetectPlaneAtHit() using screen-space coordinates.
  33. /// </summary>
  34. HIT_VERTICAL,
  35. /// <summary>
  36. /// Plane at an angle neither parallel nor perpendicular to the floor. Detected with DetectPlaneAtHit() using screen-space coordinates.
  37. /// </summary>
  38. HIT_UNKNOWN
  39. };
  40. /// <summary>
  41. /// Structure that defines a new plane, holding information directly from the ZED SDK.
  42. /// Data within is relative to the camera; use ZEDPlaneGameObject's public fields for world-space values.
  43. /// </summary>
  44. [StructLayout(LayoutKind.Sequential)]
  45. public struct PlaneData
  46. {
  47. /// <summary>
  48. /// Error code returned by the ZED SDK when the plane detection was attempted.
  49. /// </summary>
  50. public sl.ERROR_CODE ErrorCode;
  51. /// <summary>
  52. /// Type of the plane (floor, hit_vertical, etc.)
  53. /// </summary>
  54. public ZEDPlaneGameObject.PLANE_TYPE Type;
  55. /// <summary>
  56. /// Normalized vector of the direction the plane is facing.
  57. /// </summary>
  58. public Vector3 PlaneNormal;
  59. /// <summary>
  60. /// Camera-space position of the center of the plane.
  61. /// </summary>
  62. public Vector3 PlaneCenter;
  63. /// <summary>
  64. /// Camera-space position of the center of the plane.
  65. /// </summary>
  66. public Vector3 PlaneTransformPosition;
  67. /// <summary>
  68. /// Camera-space rotation/orientation of the plane.
  69. /// </summary>
  70. public Quaternion PlaneTransformOrientation;
  71. /// <summary>
  72. /// The mathematical Vector4 equation of the plane.
  73. /// </summary>
  74. public Vector4 PlaneEquation;
  75. /// <summary>
  76. /// How wide and long/tall the plane is in meters.
  77. /// </summary>
  78. public Vector2 Extents;
  79. /// <summary>
  80. /// How many points make up the plane's bounds, eg. the array length of Bounds.
  81. /// </summary>
  82. public int BoundsSize;
  83. [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
  84. ///Positions of the points that make up the edges of the plane's mesh.
  85. public Vector3[] Bounds; //max 256 points
  86. }
  87. /// <summary>
  88. /// Copy of the PlaneData structure provided by the ZED SDK when the plane was first detected.
  89. /// Position, orientation, normal and equation are relative to the ZED camera at time of detection, not the world.
  90. /// </summary>
  91. public ZEDPlaneGameObject.PlaneData planeData;
  92. /// <summary>
  93. /// Whether the plane has had Create() called yet to build its mesh.
  94. /// </summary>
  95. private bool isCreated = false;
  96. /// <summary>
  97. /// Public accessor for isCreated, which is hether the plane has had Create() called yet to build its mesh.
  98. /// </summary>
  99. public bool IsCreated
  100. {
  101. get { return isCreated; }
  102. }
  103. /// <summary>
  104. /// Normalized vector representing the direction the plane is facing in world space.
  105. /// </summary>
  106. public Vector3 worldNormal { get; private set; }
  107. /// <summary>
  108. /// Position of the plane's center in world space.
  109. /// </summary>
  110. public Vector3 worldCenter
  111. {
  112. get
  113. {
  114. return gameObject.transform.position;
  115. }
  116. }
  117. /// <summary>
  118. /// The MeshFilter used to display the plane.
  119. /// </summary>
  120. MeshFilter mfilter;
  121. /// <summary>
  122. /// The MeshRenderer attached to this object.
  123. /// </summary>
  124. MeshRenderer rend;
  125. /// <summary>
  126. /// Enabled state of the attached Renderer prior to Unity's rendering stage.
  127. /// <para>Used so that manually disabling the object's MeshRenderer won't be undone by this script.</para>
  128. /// </summary>
  129. private bool lastRenderState = true;
  130. /// <summary>
  131. /// Creates a mesh from given plane data and assigns it to new MeshFilter, MeshRenderer and MeshCollider components.
  132. /// </summary>
  133. /// <param name="plane"></param>
  134. /// <param name="vertices"></param>
  135. /// <param name="triangles"></param>
  136. /// <param name="rendermaterial"></param>
  137. private void SetComponents(PlaneData plane, Vector3[] vertices, int[] triangles, Material rendermaterial)
  138. {
  139. //Create the MeshFilter to render the mesh
  140. mfilter = gameObject.GetComponent<MeshFilter>();
  141. if (mfilter == null)
  142. mfilter = gameObject.AddComponent<MeshFilter>();
  143. //Eliminate superfluous vertices.
  144. int highestvertindex = 0;
  145. for (int i = 0; i < triangles.Length; i++)
  146. {
  147. if (triangles[i] > highestvertindex) highestvertindex = triangles[i];
  148. }
  149. System.Array.Resize(ref vertices, highestvertindex + 1);
  150. //Calculate the UVs for the vertices based on world space, so they line up with other planes.
  151. Vector2[] uvs = new Vector2[vertices.Length];
  152. Quaternion rotatetobackward = Quaternion.FromToRotation(worldNormal, Vector3.back);
  153. for (int i = 0; i < vertices.Length; i++)
  154. {
  155. Vector3 upwardcoords = rotatetobackward * (vertices[i] + worldCenter);
  156. uvs[i] = new Vector2(upwardcoords.x, upwardcoords.y);
  157. }
  158. //Apply the new data to the MeshFilter's mesh and update it.
  159. mfilter.mesh.Clear();
  160. mfilter.mesh.vertices = vertices;
  161. mfilter.mesh.triangles = triangles;
  162. mfilter.mesh.uv = uvs;
  163. mfilter.mesh.RecalculateNormals();
  164. mfilter.mesh.RecalculateBounds();
  165. //Get the MeshRenderer and set properties.
  166. rend = gameObject.GetComponent<MeshRenderer>();
  167. if (rend == null)
  168. rend = gameObject.AddComponent<MeshRenderer>();
  169. rend.material = rendermaterial;
  170. //Turn off light and shadow effects, as the planes are meant to highlight a real-world object, not be a distinct object.
  171. rend.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  172. rend.receiveShadows = false;
  173. rend.enabled = true;
  174. rend.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
  175. rend.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
  176. //Get the MeshCollider and apply the new mesh to it.
  177. MeshCollider mc = gameObject.GetComponent<MeshCollider>();
  178. if (mc == null)
  179. mc = gameObject.AddComponent<MeshCollider>();
  180. // Set the mesh for the collider.
  181. mc.sharedMesh = mfilter.mesh;
  182. lastRenderState = true;
  183. }
  184. /// <summary>
  185. /// Create the plane as a GameObject, and fills the internal PlaneData structure for future access.
  186. /// </summary>
  187. /// <param name="holder">Scene's holder object to which all planes are parented.</param>
  188. /// <param name="plane">PlaneData filled by the ZED SDK.</param>
  189. /// <param name="vertices">Vertices of the mesh.</param>
  190. /// <param name="triangles">Triangles of the mesh.</param>
  191. /// <param name="opt_count">If a hit plane, the total number of hit planes detected prior to and including this one.</param>
  192. public void Create(Camera cam, PlaneData plane, Vector3[] vertices, int[] triangles, int opt_count)
  193. {
  194. Create(cam, plane, vertices, triangles, opt_count, GetDefaultMaterial(plane.Type));
  195. }
  196. /// <summary>
  197. /// Create the plane as a GameObject with a custom material, and fills the internal PlaneData structure for future access.
  198. /// </summary>
  199. /// <param name="holder">Scene's holder object to which all planes are parented.</param>
  200. /// <param name="plane">PlaneData filled by the ZED SDK.</param>
  201. /// <param name="vertices">Vertices of the mesh.</param>
  202. /// <param name="triangles">Triangles of the mesh.</param>
  203. /// <param name="opt_count">If a hit plane, the total number of hit planes detected prior to and including this one.</param>
  204. /// <param name="rendermaterial">Material to replace the default wireframe plane material.</param>
  205. public void Create(Camera cam, PlaneData plane, Vector3[] vertices, int[] triangles, int opt_count, Material rendermaterial)
  206. {
  207. //Copy the supplied PlaneData into this component's own PlaneData, for accessing later.
  208. planeData.ErrorCode = plane.ErrorCode;
  209. planeData.Type = plane.Type;
  210. planeData.PlaneNormal = plane.PlaneNormal;
  211. planeData.PlaneCenter = plane.PlaneCenter;
  212. planeData.PlaneTransformPosition = plane.PlaneTransformPosition;
  213. planeData.PlaneTransformOrientation = plane.PlaneTransformOrientation;
  214. planeData.PlaneEquation = plane.PlaneEquation;
  215. planeData.Extents = plane.Extents;
  216. planeData.BoundsSize = plane.BoundsSize;
  217. planeData.Bounds = new Vector3[plane.BoundsSize];
  218. System.Array.Copy(plane.Bounds, planeData.Bounds, plane.BoundsSize);
  219. //Calculate the world space normal.
  220. Camera leftCamera = cam;
  221. worldNormal = cam.transform.TransformDirection(planeData.PlaneNormal);
  222. //Create the MeshCollider.
  223. gameObject.AddComponent<MeshCollider>().sharedMesh = null;
  224. if (plane.Type != PLANE_TYPE.FLOOR) //Give it a name.
  225. gameObject.name = "Hit Plane " + opt_count;
  226. else
  227. gameObject.name = "Floor Plane";
  228. gameObject.layer = 12;//sl.ZEDCamera.TagOneObject;
  229. SetComponents(plane, vertices, triangles, rendermaterial);
  230. isCreated = true;
  231. //Subscribe to events that let you govern visibility in the scene and game.
  232. #if !ZED_URP && !ZED_HDRP
  233. Camera.onPreCull += PreCull;
  234. Camera.onPostRender += PostRender;
  235. #else
  236. RenderPipelineManager.beginFrameRendering += SRPFrameBegin;
  237. #endif
  238. }
  239. /// <summary>
  240. /// Updates the floor plane with new plane data, if
  241. /// </summary>
  242. /// <returns><c>true</c>, if floor plane was updated, <c>false</c> otherwise.</returns>
  243. /// <param name="force">If set to <c>true</c> force the update. Is set to false, update only if new plane/mesh is bigger or contains the old one</param>
  244. /// <param name="plane">PlaneData returned from the ZED SDK.</param>
  245. /// <param name="vertices">Vertices of the new plane mesh.</param>
  246. /// <param name="triangles">Triangles of the new plane mesh.</param>
  247. public bool UpdateFloorPlane(bool force, ZEDPlaneGameObject.PlaneData plane, Vector3[] vertices, int[] triangles, Material rendermaterial = null)
  248. {
  249. bool need_update = false;
  250. if (!force) //Not force mode. Check if the new mesh contains or is larger than the old one.
  251. {
  252. if (!gameObject.GetComponent<MeshRenderer>().isVisible)
  253. need_update = true;
  254. else
  255. {
  256. Mesh tmp = new Mesh();
  257. tmp.vertices = vertices;
  258. tmp.SetTriangles(triangles, 0, false);
  259. tmp.RecalculateBounds();
  260. Bounds tmpNBnds = tmp.bounds;
  261. MeshFilter mf = gameObject.GetComponent<MeshFilter>();
  262. if ((tmpNBnds.Contains(mf.mesh.bounds.min) && tmpNBnds.Contains(mf.mesh.bounds.max)) || (tmpNBnds.size.x * tmpNBnds.size.y > 1.1 * mf.mesh.bounds.size.x * mf.mesh.bounds.size.y))
  263. need_update = true;
  264. tmp.Clear();
  265. }
  266. }
  267. else //Force mode. Update the mesh regardless of the existing mesh.
  268. need_update = true;
  269. if (need_update)
  270. SetComponents(plane, vertices, triangles, gameObject.GetComponent<MeshRenderer>().material);
  271. MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
  272. if (mr != null)
  273. {
  274. if (rendermaterial != null)
  275. {
  276. mr.material = rendermaterial;
  277. }
  278. else
  279. {
  280. mr.material = GetDefaultMaterial(plane.Type);
  281. }
  282. }
  283. return need_update;
  284. }
  285. /// <summary>
  286. /// Enable/disable the MeshCollider component to turn on/off collisions.
  287. /// </summary>
  288. /// <param name="c">If set to <c>true</c>, collisions will be enabled.</param>
  289. public void SetPhysics(bool c)
  290. {
  291. MeshCollider mc = gameObject.GetComponent<MeshCollider>();
  292. if (mc != null)
  293. mc.enabled = c;
  294. }
  295. /// <summary>
  296. /// Enable/disable the MeshRenderer component to turn on/off visibility.
  297. /// </summary>
  298. /// <param name="c">If set to <c>true</c> c.</param>
  299. public void SetVisible(bool c)
  300. {
  301. MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
  302. if (mr != null)
  303. mr.enabled = c;
  304. }
  305. /// <summary>
  306. /// Gets the size of the bounding rect that fits the plane (aka 'extents').
  307. /// </summary>
  308. /// <returns>The scale.</returns>
  309. public Vector2 GetScale()
  310. {
  311. return planeData.Extents;
  312. }
  313. /// <summary>
  314. /// Returns the bounds of the plane from the MeshFilter.
  315. /// </summary>
  316. /// <returns></returns>
  317. public Bounds GetBounds()
  318. {
  319. MeshFilter mf = gameObject.GetComponent<MeshFilter>();
  320. if (mf != null)
  321. return mf.mesh.bounds;
  322. else
  323. return new Bounds(gameObject.transform.position, Vector3.zero);
  324. }
  325. /// <summary>
  326. /// Gets the minimum distance to plane boundaries of a given 3D point (in world space)
  327. /// </summary>
  328. /// <returns>The minimum distance to boundaries.</returns>
  329. /// <param name="worldPosition">World position.</param>
  330. public float getMinimumDistanceToBoundaries(Camera cam, Vector3 worldPosition, out Vector3 minimumBoundsPosition)
  331. {
  332. Camera leftCamera = cam;
  333. float minimal_distance = ZEDSupportFunctions.DistancePointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[0]), leftCamera.transform.TransformPoint(planeData.Bounds[1]));
  334. Vector3 BestFoundPoint = new Vector3(0.0f, 0.0f, 0.0f);
  335. if (planeData.BoundsSize > 2)
  336. {
  337. for (int i = 1; i < planeData.BoundsSize - 1; i++)
  338. {
  339. float currentDistance = ZEDSupportFunctions.DistancePointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[i]), leftCamera.transform.TransformPoint(planeData.Bounds[i + 1]));
  340. if (currentDistance < minimal_distance)
  341. {
  342. minimal_distance = currentDistance;
  343. BestFoundPoint = ZEDSupportFunctions.ProjectPointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[i]), leftCamera.transform.TransformPoint(planeData.Bounds[i + 1]));
  344. }
  345. }
  346. }
  347. minimumBoundsPosition = BestFoundPoint;
  348. return minimal_distance;
  349. }
  350. /// <summary>
  351. /// Determines whether this floor plane is visible by any camera.
  352. /// </summary>
  353. /// <returns><c>true</c> if this instance is floor plane visible; otherwise, <c>false</c>.</returns>
  354. public bool IsFloorPlaneVisible()
  355. {
  356. return gameObject.GetComponent<MeshRenderer>().isVisible;
  357. }
  358. /// <summary>
  359. /// Loads the default material for a plane, given its plane type.
  360. /// Blue wireframe for floor planes and pink wireframe for hit planes.
  361. /// </summary>
  362. /// <param name="type">Type of plane based on its orientation and if it's the scene's floor plane.</param>
  363. /// <returns></returns>
  364. private Material GetDefaultMaterial(PLANE_TYPE type)
  365. {
  366. //Find the default material for the plane type
  367. Material defaultmaterial = new Material(Resources.Load("Materials/PlaneDetection/Mat_ZED_Geometry_WirePlane") as Material);
  368. switch (type)
  369. {
  370. case PLANE_TYPE.FLOOR:
  371. //Floor planes are blue
  372. defaultmaterial.SetColor("_WireColor", new Color(44.0f / 255.0f, 157.0f / 255.0f, 222.0f / 255.0f, 174.0f / 255.0f));
  373. break;
  374. case PLANE_TYPE.HIT_HORIZONTAL:
  375. case PLANE_TYPE.HIT_VERTICAL:
  376. case PLANE_TYPE.HIT_UNKNOWN:
  377. // Hit planes are pink
  378. defaultmaterial.SetColor("_WireColor", new Color(221.0f / 255.0f, 20.0f / 255.0f, 149.0f / 255.0f, 174.0f / 255.0f));
  379. break;
  380. default:
  381. //Misc. planes are white
  382. defaultmaterial.SetColor("_WireColor", new Color(1, 1, 1, 174.0f / 255.0f));
  383. break;
  384. }
  385. return defaultmaterial;
  386. }
  387. #if! ZED_URP && !ZED_HDRP
  388. /// <summary>
  389. /// Disables the MeshRenderer object for rendering a single camera, depending on display settings in ZEDPlaneDetectionManager.
  390. /// </summary>
  391. /// <param name="currentcam"></param>
  392. private void PreCull(Camera currentcam)
  393. {
  394. lastRenderState = rend.enabled;
  395. if (!rend.enabled) return; //We weren't going to render this object anyway, so skip the rest of the logic.
  396. if (currentcam.name.ToLower().Contains("scenecamera"))
  397. {
  398. rend.enabled = ZEDPlaneDetectionManager.isSceneDisplay;
  399. }
  400. else
  401. {
  402. rend.enabled = ZEDPlaneDetectionManager.isGameDisplay;
  403. }
  404. }
  405. /// <summary>
  406. /// Re-enables the MeshRenderer after PreCull may disable it each time a camera renders.
  407. /// </summary>
  408. /// <param name="currentcam"></param>
  409. private void PostRender(Camera currentcam)
  410. {
  411. rend.enabled = lastRenderState;
  412. }
  413. #else
  414. private void SRPFrameBegin(ScriptableRenderContext context, Camera[] rendercams)
  415. {
  416. rend.enabled = false; //We'll only draw for certain cameras.
  417. foreach (Camera rendcam in rendercams)
  418. {
  419. if (rendcam.name.ToLower().Contains("scenecamera"))
  420. {
  421. if (ZEDPlaneDetectionManager.isSceneDisplay) DrawPlane(rendcam);
  422. }
  423. else if (ZEDPlaneDetectionManager.isGameDisplay) DrawPlane(rendcam);
  424. }
  425. }
  426. private void DrawPlane(Camera drawcam)
  427. {
  428. Matrix4x4 canvastrs = Matrix4x4.TRS(transform.position, transform.rotation, transform.localScale);
  429. Graphics.DrawMesh(mfilter.mesh, canvastrs, rend.material, 0, drawcam);
  430. }
  431. #endif
  432. private void OnDestroy()
  433. {
  434. #if !ZED_URP && !ZED_HDRP
  435. Camera.onPreCull -= PreCull;
  436. Camera.onPostRender -= PostRender;
  437. #else
  438. RenderPipelineManager.beginFrameRendering -= SRPFrameBegin;
  439. #endif
  440. }
  441. }