//======= 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 /// /// 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. /// public class ZEDPlaneGameObject : MonoBehaviour { /// /// Type of the plane, determined by its orientation and whether detected by ZEDPlaneDetectionManager's /// DetectFloorPlane() or DetectPlaneAtHit(). /// public enum PLANE_TYPE { /// /// Floor plane of a scene. Retrieved by ZEDPlaneDetectionManager.DetectFloorPlane(). /// FLOOR, /// /// Horizontal plane, such as a tabletop, floor, etc. Detected with DetectPlaneAtHit() using screen-space coordinates. /// HIT_HORIZONTAL, /// /// Vertical plane, such as a wall. Detected with DetectPlaneAtHit() using screen-space coordinates. /// HIT_VERTICAL, /// /// Plane at an angle neither parallel nor perpendicular to the floor. Detected with DetectPlaneAtHit() using screen-space coordinates. /// HIT_UNKNOWN }; /// /// 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. /// [StructLayout(LayoutKind.Sequential)] public struct PlaneData { /// /// Error code returned by the ZED SDK when the plane detection was attempted. /// public sl.ERROR_CODE ErrorCode; /// /// Type of the plane (floor, hit_vertical, etc.) /// public ZEDPlaneGameObject.PLANE_TYPE Type; /// /// Normalized vector of the direction the plane is facing. /// public Vector3 PlaneNormal; /// /// Camera-space position of the center of the plane. /// public Vector3 PlaneCenter; /// /// Camera-space position of the center of the plane. /// public Vector3 PlaneTransformPosition; /// /// Camera-space rotation/orientation of the plane. /// public Quaternion PlaneTransformOrientation; /// /// The mathematical Vector4 equation of the plane. /// public Vector4 PlaneEquation; /// /// How wide and long/tall the plane is in meters. /// public Vector2 Extents; /// /// How many points make up the plane's bounds, eg. the array length of Bounds. /// 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 } /// /// 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. /// public ZEDPlaneGameObject.PlaneData planeData; /// /// Whether the plane has had Create() called yet to build its mesh. /// private bool isCreated = false; /// /// Public accessor for isCreated, which is hether the plane has had Create() called yet to build its mesh. /// public bool IsCreated { get { return isCreated; } } /// /// Normalized vector representing the direction the plane is facing in world space. /// public Vector3 worldNormal { get; private set; } /// /// Position of the plane's center in world space. /// public Vector3 worldCenter { get { return gameObject.transform.position; } } /// /// The MeshFilter used to display the plane. /// MeshFilter mfilter; /// /// The MeshRenderer attached to this object. /// MeshRenderer rend; /// /// Enabled state of the attached Renderer prior to Unity's rendering stage. /// Used so that manually disabling the object's MeshRenderer won't be undone by this script. /// private bool lastRenderState = true; /// /// Creates a mesh from given plane data and assigns it to new MeshFilter, MeshRenderer and MeshCollider components. /// /// /// /// /// private void SetComponents(PlaneData plane, Vector3[] vertices, int[] triangles, Material rendermaterial) { //Create the MeshFilter to render the mesh mfilter = gameObject.GetComponent(); if (mfilter == null) mfilter = gameObject.AddComponent(); //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(); if (rend == null) rend = gameObject.AddComponent(); 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(); if (mc == null) mc = gameObject.AddComponent(); // Set the mesh for the collider. mc.sharedMesh = mfilter.mesh; lastRenderState = true; } /// /// Create the plane as a GameObject, and fills the internal PlaneData structure for future access. /// /// Scene's holder object to which all planes are parented. /// PlaneData filled by the ZED SDK. /// Vertices of the mesh. /// Triangles of the mesh. /// If a hit plane, the total number of hit planes detected prior to and including this one. public void Create(Camera cam, PlaneData plane, Vector3[] vertices, int[] triangles, int opt_count) { Create(cam, plane, vertices, triangles, opt_count, GetDefaultMaterial(plane.Type)); } /// /// Create the plane as a GameObject with a custom material, and fills the internal PlaneData structure for future access. /// /// Scene's holder object to which all planes are parented. /// PlaneData filled by the ZED SDK. /// Vertices of the mesh. /// Triangles of the mesh. /// If a hit plane, the total number of hit planes detected prior to and including this one. /// Material to replace the default wireframe plane material. 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().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 } /// /// Updates the floor plane with new plane data, if /// /// true, if floor plane was updated, false otherwise. /// If set to true force the update. Is set to false, update only if new plane/mesh is bigger or contains the old one /// PlaneData returned from the ZED SDK. /// Vertices of the new plane mesh. /// Triangles of the new plane mesh. 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().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(); 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().material); MeshRenderer mr = gameObject.GetComponent(); if (mr != null) { if (rendermaterial != null) { mr.material = rendermaterial; } else { mr.material = GetDefaultMaterial(plane.Type); } } return need_update; } /// /// Enable/disable the MeshCollider component to turn on/off collisions. /// /// If set to true, collisions will be enabled. public void SetPhysics(bool c) { MeshCollider mc = gameObject.GetComponent(); if (mc != null) mc.enabled = c; } /// /// Enable/disable the MeshRenderer component to turn on/off visibility. /// /// If set to true c. public void SetVisible(bool c) { MeshRenderer mr = gameObject.GetComponent(); if (mr != null) mr.enabled = c; } /// /// Gets the size of the bounding rect that fits the plane (aka 'extents'). /// /// The scale. public Vector2 GetScale() { return planeData.Extents; } /// /// Returns the bounds of the plane from the MeshFilter. /// /// public Bounds GetBounds() { MeshFilter mf = gameObject.GetComponent(); if (mf != null) return mf.mesh.bounds; else return new Bounds(gameObject.transform.position, Vector3.zero); } /// /// Gets the minimum distance to plane boundaries of a given 3D point (in world space) /// /// The minimum distance to boundaries. /// World position. 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; } /// /// Determines whether this floor plane is visible by any camera. /// /// true if this instance is floor plane visible; otherwise, false. public bool IsFloorPlaneVisible() { return gameObject.GetComponent().isVisible; } /// /// Loads the default material for a plane, given its plane type. /// Blue wireframe for floor planes and pink wireframe for hit planes. /// /// Type of plane based on its orientation and if it's the scene's floor plane. /// 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 /// /// Disables the MeshRenderer object for rendering a single camera, depending on display settings in ZEDPlaneDetectionManager. /// /// 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; } } /// /// Re-enables the MeshRenderer after PreCull may disable it each time a camera renders. /// /// 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 } }