123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- //======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============
- using System.Collections.Generic;
- using UnityEngine;
- using System.Threading;
- using System.Runtime.InteropServices;
- #if ZED_URP || ZED_HDRP
- using UnityEngine.Rendering;
- #endif
- /// <summary>
- /// Represents an individual plane that was detected by ZEDPlaneDetectionManager.
- /// When created, it converts plane data from the ZED SDK into a mesh with proper world position/rotation.
- /// This is necessary as the ZED SDK provides plane data relative to the camera.
- /// It's also used to enable/disable collisions and visibility.
- /// </summary>
- public class ZEDPlaneGameObject : MonoBehaviour
- {
- /// <summary>
- /// Type of the plane, determined by its orientation and whether detected by ZEDPlaneDetectionManager's
- /// DetectFloorPlane() or DetectPlaneAtHit().
- /// </summary>
- public enum PLANE_TYPE
- {
- /// <summary>
- /// Floor plane of a scene. Retrieved by ZEDPlaneDetectionManager.DetectFloorPlane().
- /// </summary>
- FLOOR,
- /// <summary>
- /// Horizontal plane, such as a tabletop, floor, etc. Detected with DetectPlaneAtHit() using screen-space coordinates.
- /// </summary>
- HIT_HORIZONTAL,
- /// <summary>
- /// Vertical plane, such as a wall. Detected with DetectPlaneAtHit() using screen-space coordinates.
- /// </summary>
- HIT_VERTICAL,
- /// <summary>
- /// Plane at an angle neither parallel nor perpendicular to the floor. Detected with DetectPlaneAtHit() using screen-space coordinates.
- /// </summary>
- HIT_UNKNOWN
- };
- /// <summary>
- /// Structure that defines a new plane, holding information directly from the ZED SDK.
- /// Data within is relative to the camera; use ZEDPlaneGameObject's public fields for world-space values.
- /// </summary>
- [StructLayout(LayoutKind.Sequential)]
- public struct PlaneData
- {
- /// <summary>
- /// Error code returned by the ZED SDK when the plane detection was attempted.
- /// </summary>
- public sl.ERROR_CODE ErrorCode;
- /// <summary>
- /// Type of the plane (floor, hit_vertical, etc.)
- /// </summary>
- public ZEDPlaneGameObject.PLANE_TYPE Type;
- /// <summary>
- /// Normalized vector of the direction the plane is facing.
- /// </summary>
- public Vector3 PlaneNormal;
- /// <summary>
- /// Camera-space position of the center of the plane.
- /// </summary>
- public Vector3 PlaneCenter;
- /// <summary>
- /// Camera-space position of the center of the plane.
- /// </summary>
- public Vector3 PlaneTransformPosition;
- /// <summary>
- /// Camera-space rotation/orientation of the plane.
- /// </summary>
- public Quaternion PlaneTransformOrientation;
- /// <summary>
- /// The mathematical Vector4 equation of the plane.
- /// </summary>
- public Vector4 PlaneEquation;
- /// <summary>
- /// How wide and long/tall the plane is in meters.
- /// </summary>
- public Vector2 Extents;
- /// <summary>
- /// How many points make up the plane's bounds, eg. the array length of Bounds.
- /// </summary>
- public int BoundsSize;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
- ///Positions of the points that make up the edges of the plane's mesh.
- public Vector3[] Bounds; //max 256 points
- }
- /// <summary>
- /// Copy of the PlaneData structure provided by the ZED SDK when the plane was first detected.
- /// Position, orientation, normal and equation are relative to the ZED camera at time of detection, not the world.
- /// </summary>
- public ZEDPlaneGameObject.PlaneData planeData;
- /// <summary>
- /// Whether the plane has had Create() called yet to build its mesh.
- /// </summary>
- private bool isCreated = false;
- /// <summary>
- /// Public accessor for isCreated, which is hether the plane has had Create() called yet to build its mesh.
- /// </summary>
- public bool IsCreated
- {
- get { return isCreated; }
- }
- /// <summary>
- /// Normalized vector representing the direction the plane is facing in world space.
- /// </summary>
- public Vector3 worldNormal { get; private set; }
- /// <summary>
- /// Position of the plane's center in world space.
- /// </summary>
- public Vector3 worldCenter
- {
- get
- {
- return gameObject.transform.position;
- }
- }
- /// <summary>
- /// The MeshFilter used to display the plane.
- /// </summary>
- MeshFilter mfilter;
- /// <summary>
- /// The MeshRenderer attached to this object.
- /// </summary>
- MeshRenderer rend;
- /// <summary>
- /// Enabled state of the attached Renderer prior to Unity's rendering stage.
- /// <para>Used so that manually disabling the object's MeshRenderer won't be undone by this script.</para>
- /// </summary>
- private bool lastRenderState = true;
- /// <summary>
- /// Creates a mesh from given plane data and assigns it to new MeshFilter, MeshRenderer and MeshCollider components.
- /// </summary>
- /// <param name="plane"></param>
- /// <param name="vertices"></param>
- /// <param name="triangles"></param>
- /// <param name="rendermaterial"></param>
- private void SetComponents(PlaneData plane, Vector3[] vertices, int[] triangles, Material rendermaterial)
- {
- //Create the MeshFilter to render the mesh
- mfilter = gameObject.GetComponent<MeshFilter>();
- if (mfilter == null)
- mfilter = gameObject.AddComponent<MeshFilter>();
- //Eliminate superfluous vertices.
- int highestvertindex = 0;
- for (int i = 0; i < triangles.Length; i++)
- {
- if (triangles[i] > highestvertindex) highestvertindex = triangles[i];
- }
- System.Array.Resize(ref vertices, highestvertindex + 1);
- //Calculate the UVs for the vertices based on world space, so they line up with other planes.
- Vector2[] uvs = new Vector2[vertices.Length];
- Quaternion rotatetobackward = Quaternion.FromToRotation(worldNormal, Vector3.back);
- for (int i = 0; i < vertices.Length; i++)
- {
- Vector3 upwardcoords = rotatetobackward * (vertices[i] + worldCenter);
- uvs[i] = new Vector2(upwardcoords.x, upwardcoords.y);
- }
- //Apply the new data to the MeshFilter's mesh and update it.
- mfilter.mesh.Clear();
- mfilter.mesh.vertices = vertices;
- mfilter.mesh.triangles = triangles;
- mfilter.mesh.uv = uvs;
- mfilter.mesh.RecalculateNormals();
- mfilter.mesh.RecalculateBounds();
- //Get the MeshRenderer and set properties.
- rend = gameObject.GetComponent<MeshRenderer>();
- if (rend == null)
- rend = gameObject.AddComponent<MeshRenderer>();
- rend.material = rendermaterial;
- //Turn off light and shadow effects, as the planes are meant to highlight a real-world object, not be a distinct object.
- rend.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- rend.receiveShadows = false;
- rend.enabled = true;
- rend.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
- rend.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
- //Get the MeshCollider and apply the new mesh to it.
- MeshCollider mc = gameObject.GetComponent<MeshCollider>();
- if (mc == null)
- mc = gameObject.AddComponent<MeshCollider>();
- // Set the mesh for the collider.
- mc.sharedMesh = mfilter.mesh;
- lastRenderState = true;
- }
- /// <summary>
- /// Create the plane as a GameObject, and fills the internal PlaneData structure for future access.
- /// </summary>
- /// <param name="holder">Scene's holder object to which all planes are parented.</param>
- /// <param name="plane">PlaneData filled by the ZED SDK.</param>
- /// <param name="vertices">Vertices of the mesh.</param>
- /// <param name="triangles">Triangles of the mesh.</param>
- /// <param name="opt_count">If a hit plane, the total number of hit planes detected prior to and including this one.</param>
- public void Create(Camera cam, PlaneData plane, Vector3[] vertices, int[] triangles, int opt_count)
- {
- Create(cam, plane, vertices, triangles, opt_count, GetDefaultMaterial(plane.Type));
- }
- /// <summary>
- /// Create the plane as a GameObject with a custom material, and fills the internal PlaneData structure for future access.
- /// </summary>
- /// <param name="holder">Scene's holder object to which all planes are parented.</param>
- /// <param name="plane">PlaneData filled by the ZED SDK.</param>
- /// <param name="vertices">Vertices of the mesh.</param>
- /// <param name="triangles">Triangles of the mesh.</param>
- /// <param name="opt_count">If a hit plane, the total number of hit planes detected prior to and including this one.</param>
- /// <param name="rendermaterial">Material to replace the default wireframe plane material.</param>
- public void Create(Camera cam, PlaneData plane, Vector3[] vertices, int[] triangles, int opt_count, Material rendermaterial)
- {
- //Copy the supplied PlaneData into this component's own PlaneData, for accessing later.
- planeData.ErrorCode = plane.ErrorCode;
- planeData.Type = plane.Type;
- planeData.PlaneNormal = plane.PlaneNormal;
- planeData.PlaneCenter = plane.PlaneCenter;
- planeData.PlaneTransformPosition = plane.PlaneTransformPosition;
- planeData.PlaneTransformOrientation = plane.PlaneTransformOrientation;
- planeData.PlaneEquation = plane.PlaneEquation;
- planeData.Extents = plane.Extents;
- planeData.BoundsSize = plane.BoundsSize;
- planeData.Bounds = new Vector3[plane.BoundsSize];
- System.Array.Copy(plane.Bounds, planeData.Bounds, plane.BoundsSize);
- //Calculate the world space normal.
- Camera leftCamera = cam;
- worldNormal = cam.transform.TransformDirection(planeData.PlaneNormal);
- //Create the MeshCollider.
- gameObject.AddComponent<MeshCollider>().sharedMesh = null;
- if (plane.Type != PLANE_TYPE.FLOOR) //Give it a name.
- gameObject.name = "Hit Plane " + opt_count;
- else
- gameObject.name = "Floor Plane";
- gameObject.layer = 12;//sl.ZEDCamera.TagOneObject;
- SetComponents(plane, vertices, triangles, rendermaterial);
- isCreated = true;
- //Subscribe to events that let you govern visibility in the scene and game.
- #if !ZED_URP && !ZED_HDRP
- Camera.onPreCull += PreCull;
- Camera.onPostRender += PostRender;
- #else
- RenderPipelineManager.beginFrameRendering += SRPFrameBegin;
- #endif
- }
- /// <summary>
- /// Updates the floor plane with new plane data, if
- /// </summary>
- /// <returns><c>true</c>, if floor plane was updated, <c>false</c> otherwise.</returns>
- /// <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>
- /// <param name="plane">PlaneData returned from the ZED SDK.</param>
- /// <param name="vertices">Vertices of the new plane mesh.</param>
- /// <param name="triangles">Triangles of the new plane mesh.</param>
- public bool UpdateFloorPlane(bool force, ZEDPlaneGameObject.PlaneData plane, Vector3[] vertices, int[] triangles, Material rendermaterial = null)
- {
- bool need_update = false;
- if (!force) //Not force mode. Check if the new mesh contains or is larger than the old one.
- {
- if (!gameObject.GetComponent<MeshRenderer>().isVisible)
- need_update = true;
- else
- {
- Mesh tmp = new Mesh();
- tmp.vertices = vertices;
- tmp.SetTriangles(triangles, 0, false);
- tmp.RecalculateBounds();
- Bounds tmpNBnds = tmp.bounds;
- MeshFilter mf = gameObject.GetComponent<MeshFilter>();
- 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))
- need_update = true;
- tmp.Clear();
- }
- }
- else //Force mode. Update the mesh regardless of the existing mesh.
- need_update = true;
- if (need_update)
- SetComponents(plane, vertices, triangles, gameObject.GetComponent<MeshRenderer>().material);
- MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
- if (mr != null)
- {
- if (rendermaterial != null)
- {
- mr.material = rendermaterial;
- }
- else
- {
- mr.material = GetDefaultMaterial(plane.Type);
- }
- }
- return need_update;
- }
- /// <summary>
- /// Enable/disable the MeshCollider component to turn on/off collisions.
- /// </summary>
- /// <param name="c">If set to <c>true</c>, collisions will be enabled.</param>
- public void SetPhysics(bool c)
- {
- MeshCollider mc = gameObject.GetComponent<MeshCollider>();
- if (mc != null)
- mc.enabled = c;
- }
- /// <summary>
- /// Enable/disable the MeshRenderer component to turn on/off visibility.
- /// </summary>
- /// <param name="c">If set to <c>true</c> c.</param>
- public void SetVisible(bool c)
- {
- MeshRenderer mr = gameObject.GetComponent<MeshRenderer>();
- if (mr != null)
- mr.enabled = c;
- }
- /// <summary>
- /// Gets the size of the bounding rect that fits the plane (aka 'extents').
- /// </summary>
- /// <returns>The scale.</returns>
- public Vector2 GetScale()
- {
- return planeData.Extents;
- }
- /// <summary>
- /// Returns the bounds of the plane from the MeshFilter.
- /// </summary>
- /// <returns></returns>
- public Bounds GetBounds()
- {
- MeshFilter mf = gameObject.GetComponent<MeshFilter>();
- if (mf != null)
- return mf.mesh.bounds;
- else
- return new Bounds(gameObject.transform.position, Vector3.zero);
- }
- /// <summary>
- /// Gets the minimum distance to plane boundaries of a given 3D point (in world space)
- /// </summary>
- /// <returns>The minimum distance to boundaries.</returns>
- /// <param name="worldPosition">World position.</param>
- public float getMinimumDistanceToBoundaries(Camera cam, Vector3 worldPosition, out Vector3 minimumBoundsPosition)
- {
- Camera leftCamera = cam;
- float minimal_distance = ZEDSupportFunctions.DistancePointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[0]), leftCamera.transform.TransformPoint(planeData.Bounds[1]));
- Vector3 BestFoundPoint = new Vector3(0.0f, 0.0f, 0.0f);
- if (planeData.BoundsSize > 2)
- {
- for (int i = 1; i < planeData.BoundsSize - 1; i++)
- {
- float currentDistance = ZEDSupportFunctions.DistancePointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[i]), leftCamera.transform.TransformPoint(planeData.Bounds[i + 1]));
- if (currentDistance < minimal_distance)
- {
- minimal_distance = currentDistance;
- BestFoundPoint = ZEDSupportFunctions.ProjectPointLine(worldPosition, leftCamera.transform.TransformPoint(planeData.Bounds[i]), leftCamera.transform.TransformPoint(planeData.Bounds[i + 1]));
- }
- }
- }
- minimumBoundsPosition = BestFoundPoint;
- return minimal_distance;
- }
- /// <summary>
- /// Determines whether this floor plane is visible by any camera.
- /// </summary>
- /// <returns><c>true</c> if this instance is floor plane visible; otherwise, <c>false</c>.</returns>
- public bool IsFloorPlaneVisible()
- {
- return gameObject.GetComponent<MeshRenderer>().isVisible;
- }
- /// <summary>
- /// Loads the default material for a plane, given its plane type.
- /// Blue wireframe for floor planes and pink wireframe for hit planes.
- /// </summary>
- /// <param name="type">Type of plane based on its orientation and if it's the scene's floor plane.</param>
- /// <returns></returns>
- private Material GetDefaultMaterial(PLANE_TYPE type)
- {
- //Find the default material for the plane type
- Material defaultmaterial = new Material(Resources.Load("Materials/PlaneDetection/Mat_ZED_Geometry_WirePlane") as Material);
- switch (type)
- {
- case PLANE_TYPE.FLOOR:
- //Floor planes are blue
- defaultmaterial.SetColor("_WireColor", new Color(44.0f / 255.0f, 157.0f / 255.0f, 222.0f / 255.0f, 174.0f / 255.0f));
- break;
- case PLANE_TYPE.HIT_HORIZONTAL:
- case PLANE_TYPE.HIT_VERTICAL:
- case PLANE_TYPE.HIT_UNKNOWN:
- // Hit planes are pink
- defaultmaterial.SetColor("_WireColor", new Color(221.0f / 255.0f, 20.0f / 255.0f, 149.0f / 255.0f, 174.0f / 255.0f));
- break;
- default:
- //Misc. planes are white
- defaultmaterial.SetColor("_WireColor", new Color(1, 1, 1, 174.0f / 255.0f));
- break;
- }
- return defaultmaterial;
- }
- #if! ZED_URP && !ZED_HDRP
- /// <summary>
- /// Disables the MeshRenderer object for rendering a single camera, depending on display settings in ZEDPlaneDetectionManager.
- /// </summary>
- /// <param name="currentcam"></param>
- private void PreCull(Camera currentcam)
- {
- lastRenderState = rend.enabled;
- if (!rend.enabled) return; //We weren't going to render this object anyway, so skip the rest of the logic.
- if (currentcam.name.ToLower().Contains("scenecamera"))
- {
- rend.enabled = ZEDPlaneDetectionManager.isSceneDisplay;
- }
- else
- {
- rend.enabled = ZEDPlaneDetectionManager.isGameDisplay;
- }
- }
- /// <summary>
- /// Re-enables the MeshRenderer after PreCull may disable it each time a camera renders.
- /// </summary>
- /// <param name="currentcam"></param>
- private void PostRender(Camera currentcam)
- {
- rend.enabled = lastRenderState;
- }
- #else
- private void SRPFrameBegin(ScriptableRenderContext context, Camera[] rendercams)
- {
- rend.enabled = false; //We'll only draw for certain cameras.
- foreach (Camera rendcam in rendercams)
- {
- if (rendcam.name.ToLower().Contains("scenecamera"))
- {
- if (ZEDPlaneDetectionManager.isSceneDisplay) DrawPlane(rendcam);
- }
- else if (ZEDPlaneDetectionManager.isGameDisplay) DrawPlane(rendcam);
- }
- }
- private void DrawPlane(Camera drawcam)
- {
- Matrix4x4 canvastrs = Matrix4x4.TRS(transform.position, transform.rotation, transform.localScale);
- Graphics.DrawMesh(mfilter.mesh, canvastrs, rend.material, 0, drawcam);
- }
- #endif
- private void OnDestroy()
- {
- #if !ZED_URP && !ZED_HDRP
- Camera.onPreCull -= PreCull;
- Camera.onPostRender -= PostRender;
- #else
- RenderPipelineManager.beginFrameRendering -= SRPFrameBegin;
- #endif
- }
- }
|