Пример #1
0
        /// <summary>
        /// On Update check if we need to invoke an action, process waiting data and if existing update the data access layer
        /// </summary>
        private void Update()
        {
            // Call the Unity update for the data access layer if it exists
            dataAccessLayer?.UnityUpdate();

            // Check if there are new actions to be executed and if yes execute them
            if (actionQueue.Count > 0)
            {
                // Process all actions which are waiting to be processed
                // Note: This isn't 100% thread save as we could end in a loop when there are new actions coming in faster than we are processing them.
                //       However, actions are added that rarely that we shouldn't run into issues.
                while (actionQueue.TryDequeue(out Action action))
                {
                    // Invoke the action from the queue
                    action.Invoke();
                }
            }

            // Check if there is new data to process and if yes process it
            if (dataQueue.Count > 0)
            {
                // Process all data which is waiting to be processed
                // Note: This isn't 100% thread save as we could end in a loop when there is still new data coming in faster than we are processing it.
                //       However, data is added slowly enough that we shouldn't run into issues.
                while (dataQueue.TryDequeue(out GazeAPIData gazeAPIData))
                {
                    // Initialize the resulting data object with the API data
                    GazeData gazeData = new GazeData(gazeAPIData);

                    // Add the current frame time
                    gazeData.FrameTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

                    // If we have valid gaze data process it
                    if (gazeAPIData.GazeHasValue)
                    {
                        // Crate a gaze ray based on the gaze data
                        Ray gazeRay = new Ray(gazeAPIData.GazeOrigin, gazeAPIData.GazeDirection);

                        ////
                        // The 3D gaze point is the actual position the wearer is looking at.
                        // As everything apart from the eye tracking layers is visible, we have to collide the gaze with every layer except the eye tracking layers

                        // Check if the gaze hits anything that isn't an AOI
                        gazeData.GazePointHit = Physics.Raycast(gazeRay, out RaycastHit hitInfo, Mathf.Infinity, notEyeTrackingLayerMask);

                        // If we hit something, write the hit info to the data
                        if (gazeData.GazePointHit)
                        {
                            // Write all info from the hit to the data object
                            gazeData.GazePoint     = hitInfo.point;
                            gazeData.GazePointName = hitInfo.collider.name;

                            // Cache the transform of the game object which was hit
                            Transform hitTransform = hitInfo.collider.transform;

                            // Get the position of the hit in the local coordinates of the game object which was hit
                            gazeData.GazePointOnHit = hitTransform.InverseTransformPoint(hitInfo.point);

                            // Get the info about the object which was hit
                            gazeData.GazePointHitPosition = hitTransform.position;
                            gazeData.GazePointHitRotation = hitTransform.rotation.eulerAngles;
                            gazeData.GazePointHitScale    = hitTransform.lossyScale;

                            // Update the position of the GazePoint visualization (only visible in the MRC view)
                            GazePointVis.transform.position = hitInfo.point;

                            // Get the position of the gaze point in the right and left eye if we have stereo rendering
                            if (mainCamera.stereoActiveEye != Camera.MonoOrStereoscopicEye.Mono)
                            {
                                gazeData.GazePointLeftDisplay  = mainCamera.WorldToScreenPoint(hitInfo.point, Camera.MonoOrStereoscopicEye.Left);
                                gazeData.GazePointRightDisplay = mainCamera.WorldToScreenPoint(hitInfo.point, Camera.MonoOrStereoscopicEye.Right);
                            }
                            else
                            {
                                gazeData.GazePointLeftDisplay  = null;
                                gazeData.GazePointRightDisplay = null;
                            }

                            // Also get the mono position (and always do this)
                            gazeData.GazePointMonoDisplay = mainCamera.WorldToScreenPoint(hitInfo.point, Camera.MonoOrStereoscopicEye.Mono);

                            // Get the position of the gaze point on the webcam image
                            gazeData.GazePointWebcam = webcamCamera.WorldToScreenPoint(hitInfo.point, Camera.MonoOrStereoscopicEye.Mono);
                        }
                        else
                        {
                            // Update the position of the GazePoint visualization (only visible in the MRC view)
                            GazePointVis.transform.position = Vector3.zero;
                        }

                        ////
                        // To check for AOIs we do a separate ray cast on the AOI layer

                        // Check if the gaze hits a AOI
                        gazeData.GazePointAOIHit = Physics.Raycast(gazeRay, out hitInfo, Mathf.Infinity, eyeTrackingAOILayerMask);

                        // If we hit an AOI, write the hit info to data, otherwise simply leave it empty
                        if (gazeData.GazePointAOIHit)
                        {
                            // Write all info from the hit to the data object
                            gazeData.GazePointAOI     = hitInfo.point;
                            gazeData.GazePointAOIName = hitInfo.collider.name;

                            // Cache the transform of the game object which was hit
                            Transform hitTransform = hitInfo.collider.transform;

                            // Get the position of the hit in the local coordinates of the game object which was hit
                            gazeData.GazePointAOIOnHit = hitTransform.InverseTransformPoint(hitInfo.point);

                            // Get the info about the object which was hit
                            gazeData.GazePointAOIHitPosition = hitTransform.position;
                            gazeData.GazePointAOIHitRotation = hitTransform.rotation.eulerAngles;
                            gazeData.GazePointAOIHitScale    = hitTransform.lossyScale;

                            // Get the position of the gaze point on the web cam image
                            gazeData.GazePointAOIWebcam = webcamCamera.WorldToScreenPoint(hitInfo.point, Camera.MonoOrStereoscopicEye.Mono);
                        }
                    }

                    // Get the position of the game objects we want to log

                    // Create new data array
                    gazeData.positionInfos = new PositionInfo[PositionLoggedGameObjects.Count];
                    // Go through every game object and log its position
                    for (int i = 0; i < PositionLoggedGameObjects.Count; i++)
                    {
                        // Check if the game object still exists
                        gazeData.positionInfos[i].positionValid = PositionLoggedGameObjects[i] != null;

                        // If it still exists log its position
                        if (gazeData.positionInfos[i].positionValid)
                        {
                            // Name
                            gazeData.positionInfos[i].gameObjectName = PositionLoggedGameObjects[i].name;

                            // Position
                            Vector3 position = PositionLoggedGameObjects[i].transform.position;
                            gazeData.positionInfos[i].xPosition = position.x;
                            gazeData.positionInfos[i].yPosition = position.y;
                            gazeData.positionInfos[i].zPosition = position.z;

                            // Rotation
                            Vector3 rotation = PositionLoggedGameObjects[i].transform.rotation.eulerAngles;
                            gazeData.positionInfos[i].xRotation = rotation.x;
                            gazeData.positionInfos[i].yRotation = rotation.y;
                            gazeData.positionInfos[i].zRotation = rotation.z;

                            // Scale
                            Vector3 scale = PositionLoggedGameObjects[i].transform.lossyScale;
                            gazeData.positionInfos[i].xScale = scale.x;
                            gazeData.positionInfos[i].yScale = scale.y;
                            gazeData.positionInfos[i].zScale = scale.z;
                        }
                    }

                    // Invoke new data event
                    NewDataEvent?.Invoke(gazeData);
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Handle new eye tracking data by logging it
        /// Note: We should be in the main Unity thread when receiving the event from the data provider, however logging should also work outside the main thread
        /// </summary>
        /// <param name="gazeData"></param>
        private void NewDataHandler(GazeData gazeData)
        {
            // Start the resulting data string
            StringBuilder logStringBuilder = new StringBuilder();

            logStringBuilder.Append(gazeData.EyeDataTimestamp.ToString(ci));
            logStringBuilder.Append(",");
            // Note: Highest accuracy for the EyeDataRelativeTimestamp is 100ns so we don't loose information by outputting a fixed number of decimal places
            logStringBuilder.Append(gazeData.EyeDataRelativeTimestamp.ToString("F4", ci));
            logStringBuilder.Append(",");
            logStringBuilder.Append(gazeData.FrameTimestamp.ToString(ci));
            logStringBuilder.Append(",");
            logStringBuilder.Append(gazeData.IsCalibrationValid.ToString(ci));
            logStringBuilder.Append(",");
            logStringBuilder.Append(gazeData.GazeHasValue.ToString(ci));
            logStringBuilder.Append(",");

            // If we have valid gaze data process it
            if (gazeData.GazeHasValue)
            {
                // Append the info about the gaze to our log
                logStringBuilder.Append(gazeData.GazeOrigin.x.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.GazeOrigin.y.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.GazeOrigin.z.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.GazeDirection.x.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.GazeDirection.y.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.GazeDirection.z.ToString("F5", ci));
                logStringBuilder.Append(",");

                // Did we hit any GameObject?
                logStringBuilder.Append(gazeData.GazePointHit);
                logStringBuilder.Append(",");

                // If we did hit something on the gaze ray, write the hit info to the log, otherwise simply write NA
                if (gazeData.GazePointHit)
                {
                    logStringBuilder.Append(gazeData.GazePoint.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePoint.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePoint.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointName);
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointOnHit.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointOnHit.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointOnHit.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitPosition.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitPosition.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitPosition.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitRotation.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitRotation.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitRotation.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitScale.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitScale.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointHitScale.z.ToString("F5", ci));
                    logStringBuilder.Append(",");

                    if (gazeData.GazePointLeftDisplay.HasValue)
                    {
                        logStringBuilder.Append(gazeData.GazePointLeftDisplay.Value.x.ToString("F5", ci));
                        logStringBuilder.Append(",");
                        logStringBuilder.Append(gazeData.GazePointLeftDisplay.Value.y.ToString("F5", ci));
                        logStringBuilder.Append(",");
                        logStringBuilder.Append(gazeData.GazePointLeftDisplay.Value.z.ToString("F5", ci));
                        logStringBuilder.Append(",");
                        logStringBuilder.Append(gazeData.GazePointRightDisplay.Value.x.ToString("F5", ci));
                        logStringBuilder.Append(",");
                        logStringBuilder.Append(gazeData.GazePointRightDisplay.Value.y.ToString("F5", ci));
                        logStringBuilder.Append(",");
                        logStringBuilder.Append(gazeData.GazePointRightDisplay.Value.z.ToString("F5", ci));
                        logStringBuilder.Append(",");
                    }
                    else
                    {
                        logStringBuilder.Append("NA,NA,NA,NA,NA,NA,");
                    }

                    logStringBuilder.Append(gazeData.GazePointMonoDisplay.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointMonoDisplay.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointMonoDisplay.z.ToString("F5", ci));
                    logStringBuilder.Append(",");

                    logStringBuilder.Append(gazeData.GazePointWebcam.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointWebcam.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointWebcam.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                }
                else
                {
                    logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,");
                    logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,");
                }

                // Did we hit an AOI?
                logStringBuilder.Append(gazeData.GazePointAOIHit);
                logStringBuilder.Append(",");

                // If we hit an AOI, write the hit info to the log, otherwise simply write NA
                if (gazeData.GazePointAOIHit)
                {
                    logStringBuilder.Append(gazeData.GazePointAOI.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOI.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOI.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIName);
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIOnHit.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIOnHit.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIOnHit.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitPosition.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitPosition.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitPosition.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitRotation.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitRotation.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitRotation.z.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitScale.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitScale.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIHitScale.z.ToString("F5", ci));
                    logStringBuilder.Append(",");

                    logStringBuilder.Append(gazeData.GazePointAOIWebcam.x.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIWebcam.y.ToString("F5", ci));
                    logStringBuilder.Append(",");
                    logStringBuilder.Append(gazeData.GazePointAOIWebcam.z.ToString("F5", ci));
                }
                else
                {
                    logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,");
                    logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA");
                }
            }
            else
            {
                // No gaze data
                logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,");
                // No gaze hit
                logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,");
                logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,");
                // No AOI hit
                logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,");
                logStringBuilder.Append("NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA");
            }

            // If we are supposed to log the position of game objects, log them
            // Note: We log the position even when we have no gaze data!
            for (int i = 0; i < gazeData.positionInfos.Length; i++)
            {
                // Make sure the object does have a valid position
                if (!gazeData.positionInfos[i].positionValid)
                {
                    logStringBuilder.Append(",NA,NA,NA,NA,NA,NA,NA,NA,NA");
                    continue;
                }

                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].xPosition.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].yPosition.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].zPosition.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].xRotation.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].yRotation.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].zRotation.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].xScale.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].yScale.ToString("F5", ci));
                logStringBuilder.Append(",");
                logStringBuilder.Append(gazeData.positionInfos[i].zScale.ToString("F5", ci));
            }

            // Append the separator for the info to the output string
            logStringBuilder.Append(",");

            // If there is info we should log, log it
            if (infoToLog.Count > 0)
            {
                // Add the missing infos to the string
                while (infoToLog.TryDequeue(out string info))
                {
                    // Append the string
                    logStringBuilder.Append(info);

                    // If there are more strings to append, add a separator between them
                    if (infoToLog.Count > 0)
                    {
                        logStringBuilder.Append(";");
                    }
                }
            }
            // If there is nothing to log simply leave this column empty.
            // This saves space and as we are at the end of the columns it doesn't mess up the other columns

            // Write info to file
            FileHandler.WriteData(logStringBuilder.ToString());
        }