/// <summary>
        /// Moves a vertex by index in a direction (-1, +1 or 0) by a factor delta.
        /// A direction can be Vector2(-1,0); move the display in a negative X direction.
        /// The factor delta sets the "speed/distance" of the movement.
        /// </summary>
        /// <param name="direction">the direction to move in</param>
        /// <param name="delta">movement factor</param>
        /// <param name="selectedIndex">the display to move vertex on</param>
        /// <param name="vertexIndex">the vertex to move</param>
        private void LocalShift(Vector2 direction, float delta, int selectedIndex, int vertexIndex)
        {
            PhysicalDisplayCalibration lastCalibration = allOptions[lastSelectedIndex].calibration;

            lastCalibration.HideVisualMarker();
            PhysicalDisplayCalibration calibration = allOptions[selectedIndex].calibration;

            calibration.SetVisualMarkerVertextPoint(vertexIndex);

            Debug.Log("RealtimeCalibration: LocalShift called " + delta + ", " + selectedIndex + ", " + vertexIndex);

            MeshFilter lastWarpedFilter = null;

            foreach (Dewarp dewarp in calibration.GetDisplayWarpsValues())
            {
                MeshFilter meshFilter = dewarp.GetDewarpMeshFilter();
                lastWarpedFilter = meshFilter;
                Vector3[] verts = meshFilter.sharedMesh.vertices;
                // verts[vertexIndex] = new Vector3(verts[vertexIndex].x + direction.x * delta, verts[vertexIndex].y + direction.y * delta, verts[vertexIndex].z);

                Dictionary <int, float> vertsToShift = this.getIndexesSurrounding(dewarp.xSize, vertexIndex);
                foreach (var ind in vertsToShift)
                {
                    verts[ind.Key] = new Vector3(verts[ind.Key].x + (direction.x * delta * ind.Value), verts[ind.Key].y + (direction.y * delta * ind.Value), verts[ind.Key].z);
                }

                meshFilter.sharedMesh.vertices = verts;
                meshFilter.sharedMesh.UploadMeshData(false);
                meshFilter.mesh.RecalculateBounds();
                meshFilter.mesh.RecalculateTangents();
            }

            calibration.UpdateMeshPositions(lastWarpedFilter?.sharedMesh.vertices);
        }
        /// <summary>
        /// Rotates a display in a around a given axis (-1, +1 or 0) by a factor delta.
        /// A roation can be Vector3(-1,0,0) ; roate the display around X axis in negative direction.
        /// The factor delta sets the "speed" of the rotation.
        /// </summary>
        /// <param name="direction">the axis to rotate around</param>
        /// <param name="delta">rotation factor</param>
        /// <param name="selectedIndex">display index of the display to rotate</param>
        private void LocalRotationShift(Vector3 direction, float delta, int selectedIndex)
        {
            PhysicalDisplayCalibration lastCalibration = allOptions[lastSelectedIndex].calibration;

            lastCalibration.HideVisualMarker();
            PhysicalDisplayCalibration calibration = allOptions[selectedIndex].calibration;

            calibration.SetVisualMarkerVertextPoint(vertexIndex);
            calibration.RotateDisplay(new Vector3(direction.x * delta, direction.y * delta, direction.z * delta));
        }
        /// <summary>
        /// Shifts the info window to the display on the given index
        /// </summary>
        /// <param name="selectedIndex">index of the display to set display on</param>
        private void InfoDisplayShift(int selectedIndex)
        {
            PhysicalDisplayCalibration currentDisplay = this.allOptions[selectedIndex].calibration;

            if (currentDisplay == null || this.infoDisplayInstance == null)
            {
                return;
            }

            if (currentDisplay.GetDisplayWarpsValues().Count() > 0)
            {
                this.SetInfoDisplay(infoDisplayInstance.gameObject, currentDisplay.GetDisplayWarpsValues().First().GetDewarpGameObject().transform);
                this.infoDisplayInstance.SetText(this.calibrationType.ToString());
            }
        }
        /// <summary>
        /// Assign all point positions based on positions of the four corner points;
        /// positions are linearly interpolated
        /// </summary>
        public void LocalInterpolateFromCorners(int selectedIndex)
        {
            PhysicalDisplayCalibration lastCalibration = allOptions[lastSelectedIndex].calibration;

            lastCalibration.HideVisualMarker();
            PhysicalDisplayCalibration calibration = allOptions[selectedIndex].calibration;

            calibration.SetVisualMarkerVertextPoint(vertexIndex);

            Debug.Log("RealtimeCalibration: InterpolateFromCorners called, selectedIndex " + selectedIndex);

            MeshFilter lastWarpedFilter = null;

            foreach (Dewarp dewarp in calibration.GetDisplayWarpsValues())
            {
                MeshFilter meshFilter = dewarp.GetDewarpMeshFilter();
                lastWarpedFilter = meshFilter;
                Vector3[] verts = meshFilter.sharedMesh.vertices;
                // verts[vertexIndex] = new Vector3(verts[vertexIndex].x + direction.x * delta, verts[vertexIndex].y + direction.y * delta, verts[vertexIndex].z);

                Vector3 corner_0 = verts[0];
                Vector3 corner_1 = verts[7];
                Vector3 corner_2 = verts[63];
                Vector3 corner_3 = verts[56];
                for (int x = 0; x < 8; x++)
                {
                    float   xfac   = x / 7.0f;
                    Vector3 xmix_0 = corner_0 * (1.0f - xfac) + corner_1 * xfac;
                    Vector3 xmix_1 = corner_3 * (1.0f - xfac) + corner_2 * xfac;
                    for (int y = 0; y < 8; y++)
                    {
                        float yfac = y / 7.0f;
                        verts[y * 8 + x] = xmix_0 * (1.0f - yfac) + xmix_1 * yfac;
                    }
                }

                meshFilter.sharedMesh.vertices = verts;
                meshFilter.sharedMesh.UploadMeshData(false);
                meshFilter.mesh.RecalculateBounds();
                meshFilter.mesh.RecalculateTangents();
            }

            calibration.UpdateMeshPositions(lastWarpedFilter?.sharedMesh.vertices);
        }
        void Start()
        {
            allOptions = new List <CalibrationSelection>();
            //generate list of options
            List <PhysicalDisplay> displays = gameObject.GetComponent <UCNetwork>().GetAllDisplays();

            foreach (PhysicalDisplay disp in displays)
            {
                PhysicalDisplayCalibration cali = disp.gameObject.GetComponent <PhysicalDisplayCalibration>();
                if (cali != null)
                {
                    CalibrationSelection selection;
                    selection.machineName = (disp.manager == null) ? disp.machineName : disp.manager.machineName;
                    selection.calibration = cali;
                    allOptions.Add(selection);
                }
            }

            Debug.Log("RealtimeCalibration: Found " + allOptions.Count + " calibration objects");
            StartCoroutine(InitiateInfoScreen());
        }
        /// <summary>
        /// Waits for all displays to be initialized, then repositions camera viewports and/or sets up post processing
        /// </summary>
        void Update()
        {
            if (!_initialized)
            {
                bool displaysInitialized = true;
                for (int i = 0; i < displays.Count; i++)
                {
                    if (!displays[i].gameObject.activeSelf)
                    {
                        continue;
                    }

                    if (displays[i].enabled && !displays[i].Initialized())
                    {
                        displaysInitialized = false;
                        break;
                    }
                    PhysicalDisplayCalibration cali = displays[i].gameObject.GetComponent <PhysicalDisplayCalibration>();
                    if (cali != null && !cali.Initialized())
                    {
                        displaysInitialized = false;
                        break;
                    }
                }
                //wait until every other display is initialized

                if (displaysInitialized)
                {
                    for (int i = 0; i < displays.Count; i++)
                    {
                        PhysicalDisplay display = displays[i];

                        PhysicalDisplayCalibration cali = display.gameObject.GetComponent <PhysicalDisplayCalibration>();
                        if (cali == null)
                        {
                            if (!display.useRenderTextures)
                            {
                                if (display.dualPipe)
                                {
                                    Vector2Int windowSpaceOffset = display.dualInstance ? new Vector2Int(0, 0) : new Vector2Int(display.windowBounds.x, display.windowBounds.y);
                                    display.leftCam.pixelRect = new Rect(
                                        windowSpaceOffset.x + display.leftViewport.x,
                                        windowSpaceOffset.y + display.leftViewport.y,
                                        display.leftViewport.width,
                                        display.leftViewport.height);
                                    display.rightCam.pixelRect = new Rect(
                                        windowSpaceOffset.x + display.rightViewport.x,
                                        windowSpaceOffset.y + display.rightViewport.y,
                                        display.rightViewport.width,
                                        display.rightViewport.height);
                                }
                                else
                                {
                                    //if stereo blit is enabled, only update the viewport of the center cam (in the future perhaps consolidate this logic with useRenderTextures)
                                    if (display.centerCam != null && display.centerCam.GetComponent <StereoBlit>() != null)
                                    {
                                        display.centerCam.pixelRect = new Rect(display.windowBounds.x, display.windowBounds.y, display.windowBounds.width, display.windowBounds.height);
                                    }
                                    else
                                    {
                                        foreach (Camera cam in display.GetAllCameras())
                                        {
                                            Debug.Log("Manager [" + name + "] set Camera [" + cam.name + "] viewport to <"
                                                      + display.windowBounds.x + ", " + display.windowBounds.y + ", " + display.windowBounds.width + ", " + display.windowBounds.height + ">");
                                            cam.pixelRect = new Rect(display.windowBounds.x, display.windowBounds.y, display.windowBounds.width, display.windowBounds.height);
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            //special case for PhysicalDisplayCalibration
                            //Debug.Log("Display:");
                            foreach (Camera cam in cali.postCams)
                            {
                                Rect r = new Rect(display.windowBounds.x, display.windowBounds.y, display.windowBounds.width, display.windowBounds.height);
                                //Debug.Log("Set cam " + cam.name + " to " + r);
                                cam.pixelRect = r;
                            }
                        }
                    }

                    _initialized = true;
                }
            }
        }