/// <summary>
    /// Updates which faces are active.
    /// </summary>
    /// <remarks>
    /// This works by destroying faces that are no longer needed and creating faces which are new.
    /// </remarks>
    private void UpdateActiveFaces()
    {
        // If were not connected we can't create or update any cut planes
        if (!RemoteManagerUnity.IsConnected)
        {
            return;
        }

        // Which faces were added and removed?
        ClippingBoxFaces removed = (lastFaces & ~faces);
        ClippingBoxFaces added   = (faces & ~lastFaces);

        // Create and destroy as necessary
        DestroyFaces(removed);
        CreateFaces(added);

        // Sync
        lastFaces = faces;

        // Did we exceed the maximum number of cut planes?
        if (faceObjects.Count > MAX_CUTPLANES)
        {
            Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, "{0}", $"There are {faceObjects.Count} cut planes in use but some servers may only support {MAX_CUTPLANES}. Some cut planes may be ignored.");
        }
    }
    /// <summary>
    /// Converts a <see cref="ClippingBoxFaces"/> enum value to child position.
    /// </summary>
    /// <param name="face">
    /// The face to convert.
    /// </param>
    /// <param name="parent">
    /// The parent transform.
    /// </param>
    /// <returns>
    /// The corresponding position.
    /// </returns>
    static private Vector3 FaceToPosition(ClippingBoxFaces face, Transform parent)
    {
        switch (face)
        {
        case ClippingBoxFaces.PositiveX:
            return(new Vector3(parent.localScale.x / 2, 0, 0));

        case ClippingBoxFaces.NegativeX:
            return(new Vector3(-parent.localScale.x / 2, 0, 0));

        case ClippingBoxFaces.PositiveY:
            return(new Vector3(0, parent.localScale.y / 2, 0));

        case ClippingBoxFaces.NegativeY:
            return(new Vector3(0, -parent.localScale.y / 2, 0));

        case ClippingBoxFaces.PositiveZ:
            return(new Vector3(0, 0, parent.localScale.z / 2));

        case ClippingBoxFaces.NegativeZ:
            return(new Vector3(0, 0, -parent.localScale.z / 2));

        default:
            throw new InvalidOperationException("Unknown face");
        }
    }
    /// <summary>
    /// Converts a <see cref="ClippingBoxFaces"/> enum value to an ARR direction.
    /// </summary>
    /// <param name="face">
    /// The face to convert.
    /// </param>
    /// <returns>
    /// The corresponding axis.
    /// </returns>
    static private Axis FaceToNormal(ClippingBoxFaces face)
    {
        switch (face)
        {
        case ClippingBoxFaces.PositiveX:
            return(Axis.X);

        case ClippingBoxFaces.NegativeX:
            return(Axis.X_Neg);

        case ClippingBoxFaces.PositiveY:
            return(Axis.Y);

        case ClippingBoxFaces.NegativeY:
            return(Axis.Y_Neg);

        case ClippingBoxFaces.PositiveZ:
            return(Axis.Z);

        case ClippingBoxFaces.NegativeZ:
            return(Axis.Z_Neg);

        default:
            // Catch all
            throw new InvalidOperationException("Unknown face");
        }
    }
    /// <summary>
    /// Destroys the specified faces.
    /// </summary>
    /// <param name="faces">
    /// The faces to destroy.
    /// </param>
    private void DestroyFaces(ClippingBoxFaces faces)
    {
        // Loop through specified faces
        ForSpecificFaces(faces, (face, faceObject) =>
        {
            // Get the cut plane component
            ARRCutPlaneComponent cutPlane = faceObject.GetComponent <ARRCutPlaneComponent>();

            // If connected, disable the cut plane component
            if (RemoteManagerUnity.IsConnected)
            {
                try
                {
                    cutPlane.RemoteComponent.Enabled = false;
                }
                catch (Exception ex)
                {
                    Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, "{0}", $"Could not disable cut plane: {ex.Message}");
                }
            }

            // Remove the face object from the active list
            faceObjects.Remove(face);

            // Mark it for destruction
            GameObject.Destroy(faceObject);
        });
    }
    private void RemoteManager_ConnectionStatusChanged(ConnectionStatus status, Result error)
    {
        // Were we just disconnected?
        if (status == ConnectionStatus.Disconnected)
        {
            // Destroy all faces
            DestroyFaces((ClippingBoxFaces)ClippingBoxFaceGroups.All);

            // Set last faces to none, so on the next connect the right ones will be created
            lastFaces = (ClippingBoxFaces)ClippingBoxFaceGroups.None;
        }

        // Note: Connect and creation is handled as part of the update loop
    }
    private void RemoteManagerUnity_OnSessionUpdate(RemoteManagerUnity.SessionUpdate update)
    {
        // Were we just disconnected?
        if (update == RemoteManagerUnity.SessionUpdate.SessionDisconnected)
        {
            // Destroy all faces
            DestroyFaces((ClippingBoxFaces)ClippingBoxFaceGroups.All);

            // Set last faces to none, so on the next connect the right ones will be created
            lastFaces = (ClippingBoxFaces)ClippingBoxFaceGroups.None;
        }

        // Note: Connect and creation is handled as part of the update loop
    }
 /// <summary>
 /// Performs the specified action for the specific faces.
 /// </summary>
 /// <param name="faces">
 /// The faces to match.
 /// </param>
 /// <param name="action">
 /// The action to perform.
 /// </param>
 private void ForSpecificFaces(ClippingBoxFaces faces, Action <ClippingBoxFaces, GameObject> action)
 {
     // Loop through all enum values
     foreach (ClippingBoxFaces face in Enum.GetValues(typeof(ClippingBoxFaces)))
     {
         // But only if this flag was passed in
         if (faces.HasFlag(face))
         {
             // And only if it's alive
             if (faceObjects.ContainsKey(face))
             {
                 // Perform the action
                 action(face, faceObjects[face]);
             }
         }
     }
 }
    /// <summary>
    /// Creates the specified faces.
    /// </summary>
    /// <param name="faces">
    /// The faces to create.
    /// </param>
    private void CreateFaces(ClippingBoxFaces faces)
    {
        // Loop through all values
        foreach (ClippingBoxFaces face in Enum.GetValues(typeof(ClippingBoxFaces)))
        {
            // Is this face being passed in?
            if (faces.HasFlag(face))
            {
                // Only create if not already alive
                if (!faceObjects.ContainsKey(face))
                {
                    // Create the object
                    GameObject faceObject = new GameObject(Enum.GetName(typeof(ClippingBoxFaces), face));

                    // Parent it
                    faceObject.transform.SetParent(transform, worldPositionStays: false);

                    // Create the component
                    ARRCutPlaneComponent cutPlane = faceObject.CreateArrComponent <ARRCutPlaneComponent>(RemoteManagerUnity.CurrentSession);

                    // Configure it
                    if (cutPlane.RemoteComponent != null)
                    {
                        cutPlane.RemoteComponent.Normal     = FaceToNormal(face);
                        cutPlane.RemoteComponent.FadeLength = .0000025f;
                        cutPlane.RemoteComponent.FadeColor  = new Color4Ub(0, 0, 75, 255);
                    }

                    RemoteEntitySyncObject syncObject = faceObject.GetComponent <RemoteEntitySyncObject>();
                    if (syncObject != null)
                    {
                        syncObject.SyncEveryFrame = true;
                    }

                    // Add it to the active list
                    faceObjects[face] = faceObject;
                }
            }
        }
    }