using System.Collections; using System.Collections.Generic; using UnityEngine; /// /// Handles automatic calibration mode in the ZED MR calibration scene. /// When set up, it places clickable balls in front of the ZED in positions listed in spherePositions. /// When you click on the balls and re-align the controllers as prompted, it'll call a sl.ZEDCamera function /// to calculate a new camera position. /// public class AutoCalibrationManager : MonoBehaviour { /// /// Prefab of the balls you click on to add a reference point. Should be Prefabs/AutoCalibration Ball. /// [Tooltip("Prefab of the balls you click on to add a reference point. Should be Prefabs/AutoCalibration Ball.")] public GameObject autoCalibBallPrefab; /// /// CameraAnchor in the scene that's holding the ZED camera. /// [Tooltip("CameraAnchor in the scene that's holding the ZED camera. ")] public CameraAnchor camAnchor; private List balls = new List(); /// /// The positions used to calibrate the ZED. Should all be visible from the ZED, /// and vary on all three axes to make it easy to calibrate. /// [Tooltip("The positions used to calibrate the ZED. Should all be visible from the ZED, " + "and vary on all three axes to make it easy to calibrate. ")] public Vector3[] spherePositions = { new Vector3(0.0f, -0.4f, -0.5f), new Vector3(0.4f, -0.1f, -0.5f), new Vector3(-0.4f, -0.4f, -0.3f), new Vector3(-0.4f, -0.1f, -0.1f), new Vector3(0.4f, -0.2f, -0.1f)}; /// /// The scene's ZEDManager. Will be assigned to ZEDManager of camera index 1 if not set. /// [Tooltip("The scene's ZEDManager. Will be assigned to ZEDManager of camera index 1 if not set. ")] [Space(5)] public ZEDManager zedManager; private Vector3[] virtualPositions; private Vector3[] realPositions; private const float angleMAX = 35; private bool isSetup = false; private void Awake() { if (!zedManager) { zedManager = ZEDManager.GetInstance(sl.ZED_CAMERA_ID.CAMERA_ID_01); } if(!camAnchor) { camAnchor = FindObjectOfType(); } SetUpBalls(false); } /// /// Creates the ball objects and other minor setup. "ForceReset" will clear existing balls if /// they were aleady set up, otherwise nothing will happen. /// public void SetUpBalls(bool forcereset = true) { if(isSetup) { if(forcereset) //If we're resetting, clear all existing balls and positions. { CleanUpBalls(); } else //Already set up but we don't want to reset. This is likely unintentional. { Debug.LogError("Called AutomatedCalibration.Setup when it was already set up, but without requesting a reset."); return; } } Transform zedtrans = zedManager.transform; //Shorthand //Create the balls. for (int i = 0; i < spherePositions.Length; i++) { virtualPositions = new Vector3[spherePositions.Length]; realPositions = new Vector3[spherePositions.Length]; GameObject newballgo = Instantiate(autoCalibBallPrefab, zedtrans, false); newballgo.transform.localPosition = spherePositions[i]; AutoCalibBall newball = newballgo.GetComponentInChildren(); if (!newball) { throw new System.Exception("No AutoCalibBall script on autoCalibBallPrefab."); } newball.Setup(this, i); balls.Add(newball); } isSetup = true; //Update all message displays with instructions. MessageDisplay.DisplayMessageAll("AUTOMATIC MODE\r\nPut your controller inside a ball and click.\r\n" + "If the virtual ZED isn't facing you, use Manual Mode to get the image roughly aligned."); } /// /// Destroys all existing balls. Use when switching out of Automatic mode. /// public void CleanUpBalls() { for (int i = 0; i < balls.Count; i++) { Destroy(balls[i].gameObject); } balls.Clear(); } /// /// Registers a new combination of real and virtual positions to be used to calculate the camera position. /// This is called after a single ball has been used and set. /// /// Index of the ball within this class, used in both virtualPositions and realPositions. /// Position of the controller when the ball was first activated. /// Position of the controller after the user has aligned it with the real world. public void AddNewPositions(int index, Vector3 virtualpos, Vector3 realpos) { if (virtualPositions == null || virtualPositions.Length == 0) virtualPositions = new Vector3[spherePositions.Length]; if (realPositions == null || realPositions.Length == 0) realPositions = new Vector3[spherePositions.Length]; if (index >= spherePositions.Length) { throw new System.Exception("Invalid index passed to AutomatedCalibration.AddNewPositions. Passed " + index + ", max is " + (spherePositions.Length - 1).ToString() + "."); } virtualPositions[index] = virtualpos; realPositions[index] = realpos; UpdateZEDPosition(); } /// /// Prepare the current virtual and real positions (provided by AddNewPositions after using the balls) into /// data to be used by ZEDCamera.ComputeOffset, and call it. This will update the camera's position based on /// the inputs the user provided. /// private void UpdateZEDPosition() { List validvirtposes = new List(); List validrealposes = new List(); for (int i = 0; i < virtualPositions.Length; i++) { if (virtualPositions[i] != null && realPositions[i] != null) //Don't add either unless both are valid. { validvirtposes.Add(virtualPositions[i]); validrealposes.Add(realPositions[i]); } } if (validvirtposes.Count == 0) return; int posecount = validvirtposes.Count; //Shorthand. //Make array of floats used to call ZEDCamera.ComputeOffset. We use arrays because it's turned into a matrix internally. float[] inputA = new float[posecount * 4]; float[] inputB = new float[posecount * 4]; for (int i = 0; i < posecount; i++) { inputA[i * 4 + 0] = validvirtposes[i].x; inputA[i * 4 + 1] = validvirtposes[i].y; inputA[i * 4 + 2] = validvirtposes[i].z; inputA[i * 4 + 3] = 1; //Will be W in the matrix. inputB[i * 4 + 0] = validrealposes[i].x; inputB[i * 4 + 1] = validrealposes[i].y; inputB[i * 4 + 2] = validrealposes[i].z; inputB[i * 4 + 3] = 1; //Will be W in the matrix. } Vector3 newtranslation = new Vector3(); Quaternion newrotation = Quaternion.identity; sl.ZEDCamera.ComputeOffset(inputA, inputB, posecount, ref newrotation, ref newtranslation); if (IsInsideRangeAngle(newrotation)) { /* //This was in original calibration app but I suspect it caused issues. for (int j = 0; j < (calibrationStage + 1) * 0.5; j++) { realPositions[j] = Quaternion.Inverse(rotation) * (realPositions[j] - translation); }*/ //Okay nevermind let's try it. for(int i = 0; i < realPositions.Length; i++) { if(realPositions[i] != null && realPositions[i] != Vector3.zero) //May not need that second half. We'll see. { realPositions[i] = Quaternion.Inverse(newrotation) * (realPositions[i] - newtranslation); } } //zedManager.transform.position -= Quaternion.Inverse(newrotation) * newtranslation; //zedManager.transform.rotation = Quaternion.Inverse(newrotation) * zedManager.transform.rotation; camAnchor.MoveZEDPose(-(Quaternion.Inverse(newrotation) * newtranslation), Quaternion.Inverse(newrotation), false); } else { print("Calibration is not yet precise. Continue adding points and consider redoing existing points."); } } private void OnDisable() { CleanUpBalls(); } /// /// Checks whether or not the current alignment is reasonably accurate. /// /// /// private bool IsInsideRangeAngle(Quaternion rotation) { Vector3 angle = rotation.eulerAngles; return (angle.x < angleMAX || angle.x > 360 - angleMAX) && (angle.y < angleMAX || angle.y > 360 - angleMAX) && (angle.z < angleMAX || angle.z > 360 - angleMAX); } }