Пример #1
0
        // 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));
            }
        }
Пример #2
0
            /// <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);
            }
Пример #3
0
            /// <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;
            }
Пример #4
0
        /// <summary>
        /// Draws the game board gizmo in the Editor Scene view.
        /// </summary>
        public void DrawGizmo(GlassesSettings glassesSettings)
        {
            UnifyScale();

            if (ShowGizmo)
            {
                boardGizmo.Draw(glassesSettings, GizmoOpacity);
            }
        }
Пример #5
0
        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;
        }
Пример #6
0
        ///<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);
        }
Пример #7
0
        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;
        }
Пример #8
0
        ///<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);
        }
Пример #9
0
        /// <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();
            }
        }
Пример #10
0
            /// <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;
            }
Пример #11
0
            /// <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;
                }
            }
Пример #12
0
 /// <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);
 }
Пример #13
0
 /// <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));
 }
Пример #14
0
 /// <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);
 }
Пример #15
0
        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();
            }
        }