// Defines a series of line segments in gameboard space (-) public void ResetGrid(GlassesSettings newGlassesSettings = null) { if (newGlassesSettings != null) { this.glassesSettings = newGlassesSettings; } if (xGridLines == null) { xGridLines = new List <LineSegment>(); } else { xGridLines.Clear(); } if (zGridLines == null) { zGridLines = new List <LineSegment>(); } else { zGridLines.Clear(); } // Starting from the center outward, define x-axis grid lines in 1-unit increments. for (int i = 0; i *glassesSettings.oneUnitLengthInMeters < usableGameBoardWidthInMeters / 2; i++) { float distanceFromCenter = i * (glassesSettings.oneUnitLengthInMeters / usableGameBoardWidthInMeters); int lod = 1; // TODO: Change this later xGridLines.Add(new LineSegment(new Vector3(distanceFromCenter, 0, 0.5f), new Vector3(distanceFromCenter, 0, -0.5f), lod)); // No need to draw a second overlapping pair of lines along the origin when i == 0. if (i < 1) { continue; } xGridLines.Add(new LineSegment(new Vector3(-distanceFromCenter, 0, 0.5f), new Vector3(-distanceFromCenter, 0, -0.5f), lod)); } // Starting from the center outward, define z-axis grid lines in 1-unit increments. for (int i = 0; i *glassesSettings.oneUnitLengthInMeters < usableGameBoardLengthInMeters / 2; i++) { float distanceFromCenter = i * (glassesSettings.oneUnitLengthInMeters / usableGameBoardLengthInMeters); int lod = 1; // TODO: Change this later zGridLines.Add(new LineSegment(new Vector3(0.5f, 0, distanceFromCenter), new Vector3(-0.5f, 0, distanceFromCenter), lod)); // No need to draw a second overlapping pair of lines along the origin when i == 0. if (i < 1) { continue; } zGridLines.Add(new LineSegment(new Vector3(0.5f, 0, -distanceFromCenter), new Vector3(-0.5f, 0, -distanceFromCenter), lod)); } }
/// <summary> /// Tests this <see cref="T:TiltFive.Glasses.GlassesCore"/> for validity /// with the paramterized <see cref="T:TiltFive.Glasses.GlassesSettings"/> /// </summary> /// <returns><c>true</c>, if valid, <c>false</c> otherwise.</returns> /// <param name="glassesSettings">Glasses settings.</param> public bool Validate(GlassesSettings glassesSettings) { bool valid = true; valid &= (glassesSettings.headPoseCamera == splitStereoCamera.headPoseCamera); valid &= (glassesSettings.headPoseCamera.fieldOfView == glassesSettings.defaultFOV); return(valid); }
/// <summary> /// Update the pose of the game board. /// </summary> /// <param name="glassesSettings">Glasses settings.</param> private void UpdateGameBoardPose(GlassesSettings glassesSettings) { Vector3 posUGBL_TGT = glassesSettings.gameBoardCenter; Quaternion rotToUGBL_TGT = Quaternion.Euler(glassesSettings.gameBoardRotation); gameBoardPosition_UnityWorldSpace = posUGBL_TGT; gameBoardRotation_UnityWorldSpace = rotToUGBL_TGT; }
/// <summary> /// Draws the game board gizmo in the Editor Scene view. /// </summary> public void DrawGizmo(GlassesSettings glassesSettings) { UnifyScale(); if (ShowGizmo) { boardGizmo.Draw(glassesSettings, GizmoOpacity); } }
public void Draw(GlassesSettings glassesSettings, float alpha, bool showGrid, float gameBoardWidthInMeters = 0.8f, float gameboardLengthInMeters = 0.8f, float usableGameBoardWidthInMeters = 0.7f, float usableGameboardLengthInMeters = 0.7f, float gridOffsetY = 0f) { Configure(glassesSettings, alpha, gridOffsetY, gameBoardWidthInMeters, gameboardLengthInMeters, usableGameBoardWidthInMeters, usableGameboardLengthInMeters); if (null == glassesSettings || null == glassesSettings.headPoseCamera) { return; } Matrix4x4 mtxOrigin = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, Vector3.one); Matrix4x4 mtxWorld = Matrix4x4.TRS(glassesSettings.gameBoardCenter, Quaternion.Euler(glassesSettings.gameBoardRotation), Vector3.one / contentScaleFactor); Matrix4x4 mtxPreTransform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90.0f, 0.0f, 0.0f), Vector3.one); Matrix4x4 mtxGizmo = mtxOrigin * mtxWorld * mtxPreTransform; Color oldColor = Gizmos.color; Matrix4x4 oldMatrix = Gizmos.matrix; Gizmos.matrix = mtxGizmo; if (meshBorder != null) { Gizmos.color = new Color(0.0f, 0.0f, 0.0f, alpha); Gizmos.DrawMesh(meshBorder, 0); } if (meshSurface != null) { Gizmos.color = new Color(0.5f, 0.5f, 0.5f, alpha); Gizmos.DrawMesh(meshSurface, 0); } if (meshLogoBorder != null && meshLogoLeftCharacter != null && meshLogoRightCharacter != null) { DrawLogo(); } if (showGrid) { DrawGrid(); DrawRulers(); } Gizmos.matrix = oldMatrix; Gizmos.color = oldColor; }
///<summary> /// Tells the Scene view in the editor to zoom in/out as the content scale is modified. ///</summary> private bool ContentScaleCompensate(GlassesSettings glassesSettings) { if (currentContentScaleRatio == glassesSettings.contentScaleRatio && currentContentScaleUnit == glassesSettings.contentScaleUnit) { return(false); } var sceneView = UnityEditor.SceneView.lastActiveSceneView; currentContentScaleUnit = glassesSettings.contentScaleUnit; currentContentScaleRatio = glassesSettings.contentScaleRatio; sceneView.Frame(new Bounds(transform.position, (1 / 5f) * Vector3.one * glassesSettings.worldSpaceUnitsPerPhysicalMeter / localScale), true); return(true); }
public void Draw(GlassesSettings glassesSettings, float alpha) { Configure(); if (null == glassesSettings || null == glassesSettings.headPoseCamera) { return; } Vector3 vOriginTranslation = Vector3.zero; Quaternion qOriginOrientation = Quaternion.identity; Matrix4x4 mtxOrigin = Matrix4x4.TRS(vOriginTranslation, qOriginOrientation, Vector3.one); Matrix4x4 mtxWorld = Matrix4x4.TRS(glassesSettings.gameBoardCenter, Quaternion.Euler(glassesSettings.gameBoardRotation), Vector3.one / (glassesSettings.physicalMetersPerWorldSpaceUnit * glassesSettings.gameBoardScale)); Matrix4x4 mtxPreTransform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90.0f, 0.0f, 0.0f), Vector3.one); Matrix4x4 mtxGizmo = mtxOrigin * mtxWorld * mtxPreTransform; Color oldColor = Gizmos.color; Matrix4x4 oldMatrix = Gizmos.matrix; Gizmos.matrix = mtxGizmo; if (meshBorder != null) { Gizmos.color = new Color(0.0f, 0.0f, 0.0f, alpha); Gizmos.DrawMesh(meshBorder, 0); } if (meshSurface != null) { Gizmos.color = new Color(0.5f, 0.5f, 0.5f, alpha); Gizmos.DrawMesh(meshSurface, 0); } Gizmos.matrix = oldMatrix; Gizmos.color = oldColor; }
///<summary> /// Tells the Scene view in the editor to zoom in/out as the game board is scaled. ///</summary> ///<remarks> /// This function enforces a minumum scale value for the attached GameObject transform. ///</remarks> private bool ScaleCompensate(GlassesSettings glassesSettings) { if (currentScale == transform.localScale) { return(false); } // Prevent negative scale values for the game board. if (transform.localScale.x < MIN_SCALE) { transform.localScale = Vector3.one * MIN_SCALE; } var sceneView = UnityEditor.SceneView.lastActiveSceneView; sceneView.Frame(new Bounds(transform.position, (1 / 5f) * Vector3.one * glassesSettings.worldSpaceUnitsPerPhysicalMeter / localScale), true); currentScale = transform.localScale; return(true); }
/// <summary> /// Draws the game board gizmo in the Editor Scene view. /// </summary> public void DrawGizmo(GlassesSettings glassesSettings) { UnifyScale(); if (ShowGizmo) { boardGizmo.Draw(glassesSettings, GizmoOpacity, ShowGrid, totalGameBoardWidthInMeters, totalGameBoardLengthInMeters, usableGameBoardWidthInMeters, usableGameBoardLengthInMeters, gridOffsetY); } var sceneViewRepaintNecessary = ScaleCompensate(glassesSettings); sceneViewRepaintNecessary |= ContentScaleCompensate(glassesSettings); if (sceneViewRepaintNecessary) { boardGizmo.ResetGrid(glassesSettings); // This may need to change once separate game board configs are in. UnityEditor.SceneView.lastActiveSceneView.Repaint(); } }
/// <summary> /// Reset this <see cref="T:TiltFive.Glasses.GlassesCore"/> /// </summary> /// <param name="glassesSettings">Glasses settings for configuring the instance.</param> public void Reset(GlassesSettings glassesSettings) { configured = false; if (null == glassesSettings.headPoseCamera) { Log.Error($"Required Camera assignment missing from { GetType() }."); return; } if (glassesSettings.headPoseCamera.fieldOfView != glassesSettings.defaultFOV) { glassesSettings.headPoseCamera.fieldOfView = glassesSettings.defaultFOV; } #if UNITY_EDITOR if (glassesSettings.tiltFiveXR) { #endif //if the splitScreenCamera does not exist already. if (null == splitStereoCamera) { //get the head pose camera's GameObject GameObject cameraObject = glassesSettings.headPoseCamera.gameObject; //Check whether it is set up as a SplitScreenCamera, and if not: if (!cameraObject.TryGetComponent <SplitStereoCamera>(out splitStereoCamera)) { // Add it ourselves. The OnAwake call will create & configure // the eye cameras to render with. it will also use theCamera // as the source. splitStereoCamera = cameraObject.AddComponent <SplitStereoCamera>(); } } #if UNITY_EDITOR } #endif //UNITY_EDITOR configured = true; }
/// <summary> /// Updates this <see cref="T:TiltFive.Glasses.GlassesCore"/> /// </summary> /// <param name="glassesSettings">Glasses settings for the update.</param> public virtual void Update(GlassesSettings glassesSettings) { TrackingUpdated = false; #if UNITY_EDITOR if (null == glassesSettings) { Log.Error("GlassesSettings configuration required for Glasses tracking Update."); return; } #endif if (null == splitStereoCamera) { Log.Error("Stereo camera(s) missing from Glasses - aborting Update."); return; } if (glassesSettings.headPoseCamera != splitStereoCamera.headPoseCamera) { Log.Warn("Found mismatched Cameras in GlassesCore Update - should call Reset."); return; } // Check whether the glasses are plugged in and available. glassesAvailable = Plugin.GetGlassesAvailability(); splitStereoCamera.enabled = glassesAvailable; // Latch the latest user-provided transforms game board transforms. UpdateGameBoardPose(glassesSettings); // Get the latest glasses pose w.r.t. the game board. glassesPosition_GameBoardSpace = DEFAULT_GLASSES_POSITION_GAME_BOARD_SPACE; glassesRotation_GameBoardSpace = DEFAULT_GLASSES_ROTATION_GAME_BOARD_SPACE; if (glassesAvailable) { Plugin.GetHeadPose(ref glassesPosition_GameBoardSpace, ref glassesRotation_GameBoardSpace); } // Get the glasses pose in Unity world-space. float scaleToUGBL_UWRLD = glassesSettings.physicalMetersPerWorldSpaceUnit * glassesSettings.gameBoardScale; if (scaleToUGBL_UWRLD <= 0) { Log.Error("Division by zero error: Content Scale and Game Board scale must be positive non-zero values."); scaleToUGBL_UWRLD = Mathf.Max(scaleToUGBL_UWRLD, float.Epsilon); } float scaleToUWRLD_UGBL = 1.0f / scaleToUGBL_UWRLD; Vector3 posUGLS_UWRLD = Quaternion.Inverse(gameBoardRotation_UnityWorldSpace) * (scaleToUWRLD_UGBL * glassesPosition_GameBoardSpace) + gameBoardPosition_UnityWorldSpace; Quaternion rotToUGLS_UWRLD = glassesRotation_GameBoardSpace * gameBoardRotation_UnityWorldSpace; // Set the head pose (main) camera. headPosition = posUGLS_UWRLD; headRotation = Quaternion.Inverse(rotToUGLS_UWRLD); // Set the game board transform on the SplitStereoCamera. splitStereoCamera.posUGBL_UWRLD = gameBoardPosition_UnityWorldSpace; splitStereoCamera.rotToUGBL_UWRLD = gameBoardRotation_UnityWorldSpace; splitStereoCamera.scaleToUGBL_UWRLD = scaleToUGBL_UWRLD; // TODO: Revisit native XR support. // NOTE: We do this because "Mock HMD" in UNITY_2017_0_2_OR_NEWER // the fieldOfView is locked to 111.96 degrees (Vive emulation), // so setting custom projection matrices is broken. If Unity // opens the API to custom settings, we can go back to native XR // support. // Manual split screen 'new glasses' until the day Unity lets // me override their Mock HMD settings. Camera theCamera = glassesSettings.headPoseCamera; if (null == theCamera) { Log.Error("Main Camera missing from GlassesSettings - aborting Update."); return; } Transform headPose = theCamera.transform; headPose.transform.position = headPosition; headPose.transform.rotation = headRotation; // compute half ipd translation float ipd_UGBL = 0.063f; // float ipd_GameBoardSpace float ipd_UWRLD = scaleToUWRLD_UGBL * ipd_UGBL; // float ipd_UnityWorldSpace Vector3 eyeOffset = (headPose.right.normalized * (ipd_UWRLD * 0.5f)); // set the left eye camera offset from the head by the half ipd amount (-) eyePosition[(int)AREyes.EYE_LEFT] = headPose.position - eyeOffset; eyeRotation[(int)AREyes.EYE_LEFT] = headRotation; // set the right eye camera offset from the head by the half ipd amount (+) eyePosition[(int)AREyes.EYE_RIGHT] = headPose.position + eyeOffset; eyeRotation[(int)AREyes.EYE_RIGHT] = headRotation; Camera leftEyeCamera = splitStereoCamera.leftEyeCamera; if (null != leftEyeCamera) { GameObject leftEye = leftEyeCamera.gameObject; leftEye.transform.position = eyePosition[(int)AREyes.EYE_LEFT]; leftEye.transform.rotation = eyeRotation[(int)AREyes.EYE_LEFT]; //make sure projection fields are synchronized to the head camera. leftEyeCamera.nearClipPlane = glassesSettings.headPoseCamera.nearClipPlane; leftEyeCamera.farClipPlane = glassesSettings.headPoseCamera.farClipPlane; leftEyeCamera.fieldOfView = glassesSettings.headPoseCamera.fieldOfView; } Camera rightEyeCamera = splitStereoCamera.rightEyeCamera; if (null != rightEyeCamera) { GameObject rightEye = rightEyeCamera.gameObject; rightEye.transform.position = eyePosition[(int)AREyes.EYE_RIGHT]; rightEye.transform.rotation = eyeRotation[(int)AREyes.EYE_RIGHT]; //make sure projection fields are synchronized to the head camera. rightEyeCamera.nearClipPlane = glassesSettings.headPoseCamera.nearClipPlane; rightEyeCamera.farClipPlane = glassesSettings.headPoseCamera.farClipPlane; rightEyeCamera.fieldOfView = glassesSettings.headPoseCamera.fieldOfView; } }
/// <summary> /// Updates this <see cref="T:TiltFive.Glasses"/>. /// </summary> /// <param name="glassesSettings">Glasses settings for the update.</param> public static void Update(GlassesSettings glassesSettings) { Instance.glassesCore.Update(glassesSettings); }
/// <summary> /// Validates the specified glassesSettings with the current instance. /// </summary> /// <returns> /// <c>true</c>, if the glasses core is valid with the given settings, /// <c>false</c> otherwise. /// </returns> /// <param name="glassesSettings">Glasses settings.</param> public static bool Validate(GlassesSettings glassesSettings) { return(Instance.glassesCore.Validate(glassesSettings)); }
/// <summary> /// Reset this <see cref="T:TiltFive.Glasses"/>. /// </summary> /// <param name="glassesSettings">Glasses settings for configuring the instance.</param> public static void Reset(GlassesSettings glassesSettings) { Instance.glassesCore.Reset(glassesSettings); }
private void Configure(GlassesSettings glassesSettings, float alpha, float gridOffsetY, float gameBoardWidthInMeters, float gameBoardLengthInMeters, float usableGameBoardWidthInMeters, float usableGameBoardLengthInMeters) { this.glassesSettings = glassesSettings; this.gizmoAlpha = alpha; this.yGridOffset = gridOffsetY; this.totalGameBoardWidthInMeters = gameBoardWidthInMeters; this.totalGameBoardLengthInMeters = gameBoardLengthInMeters; this.usableGameBoardWidthInMeters = usableGameBoardWidthInMeters; this.usableGameBoardLengthInMeters = usableGameBoardLengthInMeters; if (null == meshObj) { meshObj = AssetDatabase.LoadAssetAtPath <GameObject>(boardMeshAssetPath); } if (null == meshBorder) { var transform = meshObj.transform.Find(borderMeshChildPath); if (transform != null) { if (transform.TryGetComponent <MeshFilter>(out var meshFilter)) { meshBorder = meshFilter.sharedMesh; } } } if (null == meshSurface) { var transform = meshObj.transform.Find(surfaceMeshChildPath); if (transform != null) { if (transform.TryGetComponent <MeshFilter>(out var meshFilter)) { meshSurface = meshFilter.sharedMesh; } } } if (null == logoObj) { logoObj = AssetDatabase.LoadAssetAtPath <GameObject>(logoMeshAssetPath); } if (null == meshLogoBorder) { var transform = logoObj.transform.Find(logoBorderChildPath); if (transform != null) { if (transform.TryGetComponent <MeshFilter>(out var meshFilter)) { meshLogoBorder = meshFilter.sharedMesh; } } } if (null == meshLogoLeftCharacter) { var transform = logoObj.transform.Find(logoLeftCharacterChildPath); if (transform != null) { if (transform.TryGetComponent <MeshFilter>(out var meshFilter)) { meshLogoLeftCharacter = meshFilter.sharedMesh; } } } if (null == meshLogoRightCharacter) { var transform = logoObj.transform.Find(logoRightCharacterChildPath); if (transform != null) { if (transform.TryGetComponent <MeshFilter>(out var meshFilter)) { meshLogoRightCharacter = meshFilter.sharedMesh; } } } if (null == xGridLines || null == zGridLines) { ResetGrid(); } if (null == rulerData) { rulerData = new List <LineSegment>(); float oneMillimeterLengthInMeters = new Length(1, LengthUnit.Millimeters).ToMeters; // For the centimeter ruler, we're going to draw regular marks for centimeters, and smaller ones for millimeters. for (int i = 0; i *oneMillimeterLengthInMeters < usableGameBoardWidthInMeters; i++) { float currentDistance = i * (oneMillimeterLengthInMeters / usableGameBoardWidthInMeters); float smallestFractionOfBoardWidth = 1f / 150f; float tickMarkLength = smallestFractionOfBoardWidth; int lod = 3; lod -= i % 5 == 0 ? 1 : 0; lod -= i % 10 == 0 ? 1 : 0; tickMarkLength += (3 - lod) * smallestFractionOfBoardWidth; rulerData.Add(new LineSegment(new Vector3(currentDistance, 0f, 0f), new Vector3(currentDistance, 0f, tickMarkLength), lod)); } float oneSixteenthInchLengthInMeters = new Length(1 / 16f, LengthUnit.Inches).ToMeters; // For the inch ruler, we're going to draw regular marks for inches, and smaller ones for half/quarter/eighth/sixteenth inches. for (int i = 0; i *oneSixteenthInchLengthInMeters < usableGameBoardWidthInMeters; i++) { float currentDistance = i * (oneSixteenthInchLengthInMeters / usableGameBoardWidthInMeters); float smallestFractionOfBoardWidth = 1f / 300f; float tickMarkLength = smallestFractionOfBoardWidth; int lod = 5; lod -= i % 2 == 0 ? 1 : 0; lod -= i % 4 == 0 ? 1 : 0; lod -= i % 8 == 0 ? 1 : 0; lod -= i % 16 == 0 ? 1 : 0; tickMarkLength += (5 - lod) * smallestFractionOfBoardWidth; float offsetFromCentimeterRuler = 1 / 16f; rulerData.Add(new LineSegment(new Vector3(currentDistance, 0f, offsetFromCentimeterRuler - tickMarkLength), new Vector3(currentDistance, 0f, offsetFromCentimeterRuler), lod)); } } if (null == meshRuler) { meshRuler = new Mesh(); meshRuler.name = "meshRuler"; int vertArraySizeRatio = 4; // There are 4 vertices for every line in rulerData. Vector3[] verts = new Vector3[rulerData.Count * vertArraySizeRatio]; int triArraySizeRatio = 6; // There are 6 triangle vertex indices for every line in rulerData. int[] triangles = new int[rulerData.Count * triArraySizeRatio]; float lineThickness = 1 / 2800f; // We want to offset the x vector component to achieve line thickness. var lineThicknessOffset = Vector3.right * (lineThickness / 2f); for (int i = 0; i < rulerData.Count; i++) { var line = rulerData[i]; var bottomLeft = line.Start - lineThicknessOffset + Vector3.left / 2 + Vector3.back / 32f; var topLeft = line.End - lineThicknessOffset + Vector3.left / 2 + Vector3.back / 32f; var bottomRight = line.Start + lineThicknessOffset + Vector3.left / 2 + Vector3.back / 32f; var topRight = line.End + lineThicknessOffset + Vector3.left / 2 + Vector3.back / 32f; var vertIndex = i * vertArraySizeRatio; verts[vertIndex] = bottomLeft; verts[vertIndex + 1] = topLeft; verts[vertIndex + 2] = bottomRight; verts[vertIndex + 3] = topRight; var triIndex = i * triArraySizeRatio; triangles[triIndex] = vertIndex; // bottomLeft triangles[triIndex + 1] = vertIndex + 1; // topLeft triangles[triIndex + 2] = vertIndex + 2; // bottomRight triangles[triIndex + 3] = vertIndex + 2; // bottomRight triangles[triIndex + 4] = vertIndex + 1; // topLeft triangles[triIndex + 5] = vertIndex + 3; // topRight } meshRuler.vertices = verts; meshRuler.triangles = triangles; meshRuler.RecalculateNormals(); } }