From 4c44cb342e1443875245625f6475131496ae6a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Alexander=20Quent?= Date: Mon, 9 May 2022 19:16:24 +0800 Subject: [PATCH 1/3] Create ScreenRayTracker.cs --- .../UXF/Scripts/Trackers/ScreenRayTracker.cs | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs diff --git a/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs b/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs new file mode 100644 index 00000000..5633e244 --- /dev/null +++ b/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs @@ -0,0 +1,150 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using UXF; +using System.Linq; + +/// +/// Attach this component to any gameobject (e.g. an empty one) and assign it in the trackedObjects field in an ExperimentSession to record +/// if ray casted from camera is hitting anything. NOTE: Update Type must be set to MANUAL. +/// +public class ScreenRayTracker : Tracker { + // Public vars + [Header("Necessary Input")] + public Camera cam; + public Session session; + + [Header("Ray coordinates")] + [TextArea(10, 10)] + public string HowToUse = "Please provide coordinates in form of lists named ray_x & ray_y in your .json file. For example \n\"ray_x\": [0.5],\n\"ray_y\": [0.5]\nif you want to have one ray in the middle of the screen. Multiple rays can be provide with this method."; + + [Header("Optional Input")] + [Tooltip("Enable if you want to visualise the rays in the scene view and the console output.")] + public bool debugMode = true; + [Tooltip("The max distance the ray should check for collisions. For further information see manual of Physics.Raycast.")] + public float distance = Mathf.Infinity; + + [Tooltip("Set to true if you want to use a LayerMask for the rays (see maunual of LayerMask.GetMask). Note you also need to set Layer Mask Names in this case with one or more layers that you want to use.")] + public bool useLayerMask = false; + [Tooltip("Provide the names of the layers for the mask.")] + public string[] layerMaskNames; + + // Private vars + private List objectDetected = new List(); + private UXFDataRow currentRow; + private bool recording = false; + private int layerMask; + private string noObjectString = "NA"; + private int numRays; + private List x = new List(); + private List y = new List(); + + // Start calc + void Start(){ + // Create layer mask + if(useLayerMask){ + layerMask = LayerMask.GetMask(layerMaskNames); + } else { + layerMask = ~0; // Set to everything as no mask is wanted. + } + + // Start the recoding + StartCoroutine(RecordRoutine()); + } + + /// + /// Gets coordinates for the rays in screen space from .json file and prints screen resolution + /// + public void GetRayCoordinates(){ + // Coordinates + x = session.settings.GetFloatList("ray_x"); + y = session.settings.GetFloatList("ray_y"); + + // Screen resolution + Debug.Log(Screen.currentResolution); + } + + /// + /// Starts the recording. This method needs to be added to [UXF_Rig] events called On Trial Begin + /// + public void StartRecording(){ + recording = true; + } + + /// + /// Stops the recording. This method needs to be added to [UXF_Rig] events called On Trial End + /// + public void StopRecording(){ + recording = false; + } + + IEnumerator RecordRoutine(){ + while (true){ + if (recording){ + objectDetected = ray2detectObjects(x, y, cam); + for(int i = 0; i < numRays; i++){ + // When no object was detected save only if saveNoObject is true + if(objectDetected[i] != noObjectString){ + var values = new UXFDataRow(); + values.Add(("rayIndex", i)); + values.Add(("x", x[i])); + values.Add(("y", y[i])); + values.Add(("objectDetected", objectDetected[i])); + currentRow = values; + RecordRow(); // record for each ray + currentRow = null; + + } + } + } + yield return null; // wait until next frame + } + } + + /// + /// Set headers and measurment descriptor + /// + public override string MeasurementDescriptor => "ObjectsOnScreenTracker"; + public override IEnumerable CustomHeader => new string[] { "rayIndex", "x", "y", "objectDetected"}; + + // Get values + protected override UXFDataRow GetCurrentValues(){ + return currentRow; + } + + /// + /// Function to detect objects on screen by rays + /// + List ray2detectObjects(List x, List y, Camera cam){ + // Get number of rays + numRays = y.Count; + + // Create var to reset the variable + List nameOfObjects = new List(); + + for (int i = 0; i < numRays; i++){ + // Cast the ray and add to list + Ray ray = cam.ViewportPointToRay(new Vector3(x[i], y[i], 0)); + + // Display ray for debugging + if(debugMode){ + Debug.DrawRay(ray.origin, ray.direction * 50, Color.red); + } + + // Raycast and check if something is hit + RaycastHit hit1; + if (Physics.Raycast(ray, out hit1, distance, layerMask)){ + if(debugMode){ + Debug.DrawRay(ray.origin, ray.direction * 50, Color.green); + Debug.Log("I'm looking at " + hit1.transform.name + " with ray " + i); + } + // Add name of GameObject that was hit + nameOfObjects.Add(hit1.transform.name); + } else { + // Add noObjectString becuase no object was hit by ray + nameOfObjects.Add(noObjectString); + } + } + return nameOfObjects; + } +} From 914c7cc354420c14a3d1d31ee6dd4dccf9e6df61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Alexander=20Quent?= Date: Tue, 10 May 2022 12:36:12 +0800 Subject: [PATCH 2/3] Add a TSV builder I've added the possibility to provide trials using tab-separated-values (tsv) so people can use "," in strings. --- Assets/UXF/Scripts/Etc/UXFDataTable.cs | 32 +++++++++++ .../SessionBuilders/TSVExperimentBuilder.cs | 54 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 Assets/UXF/Scripts/SessionBuilders/TSVExperimentBuilder.cs diff --git a/Assets/UXF/Scripts/Etc/UXFDataTable.cs b/Assets/UXF/Scripts/Etc/UXFDataTable.cs index 0eaf51e0..fbdf7dbc 100644 --- a/Assets/UXF/Scripts/Etc/UXFDataTable.cs +++ b/Assets/UXF/Scripts/Etc/UXFDataTable.cs @@ -73,6 +73,38 @@ public static UXFDataTable FromCSV(string[] csvLines) return table; } + /// + /// Build a table from lines of TSV text. + /// + /// + /// + public static UXFDataTable FromTSV(string[] tsvLines) + { + string[] headers = tsvLines[0].Split('\t'); + var table = new UXFDataTable(tsvLines.Length - 1, headers); + + // traverse down rows + for (int i = 1; i < tsvLines.Length; i++) + { + string[] values = tsvLines[i].Split('\t'); + + // if last line, just 1 item in the row, and it is blank, then ignore it + if (i == tsvLines.Length - 1 && values.Length == 1 && values[0].Trim() == string.Empty ) break; + + // check if number of columns is correct + if (values.Length != headers.Length) throw new Exception($"TSV line {i} has {values.Length} columns, but expected {headers.Length}"); + + // build across the row + var row = new UXFDataRow(); + for (int j = 0; j < values.Length; j++) + row.Add((headers[j], values[j].Trim('\"'))); + + table.AddCompleteRow(row); + } + + return table; + } + /// /// Add a complete row to the table /// diff --git a/Assets/UXF/Scripts/SessionBuilders/TSVExperimentBuilder.cs b/Assets/UXF/Scripts/SessionBuilders/TSVExperimentBuilder.cs new file mode 100644 index 00000000..875d7ab3 --- /dev/null +++ b/Assets/UXF/Scripts/SessionBuilders/TSVExperimentBuilder.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.IO; + +namespace UXF +{ + public class TSVExperimentBuilder : MonoBehaviour, IExperimentBuilder + { + + [Tooltip("The name key in the settings that contains the name of the trial specification file.")] + [SerializeField] private string tsvFileKey = "trial_specification_name"; + [Tooltip("Enable to copy all settings from each trial in the TSV file to the the trial results output.")] + [SerializeField] private bool copyToResults = true; + + /// + /// Reads a TSV from filepath as specified in tsvFileKey in the settings. + /// The TSV file is used to generate trials row-by-row, assigning a setting per column. + /// + /// + public void BuildExperiment(Session session) + { + // check if settings contains the tsv file name + if (!session.settings.ContainsKey(tsvFileKey)) + { + throw new Exception($"TSV file name not specified in settings. Please specify a TSV file name in the settings with key \"{tsvFileKey}\"."); + } + + // get the tsv file name + string tsvName = session.settings.GetString(tsvFileKey); + + // check if the file exists + string tsvPath = Path.GetFullPath(Path.Combine(Application.streamingAssetsPath, tsvName)); + if (!File.Exists(tsvPath)) + { + throw new Exception($"TSV file at \"{tsvPath}\" does not exist!"); + } + + // read the tsv file + string[] tsvLines = File.ReadAllLines(tsvPath); + + // parse as table + var table = UXFDataTable.FromTSV(tsvLines); + + // build the experiment. + // this adds a new trial to the session for each row in the table + // the trial will be created with the settings from the values from the table + // if "block_num" is specified in the table, the trial will be added to the block with that number + session.BuildFromTable(table, copyToResults); + } + } + +} From 6b79e8837844694ca951c20d4bcc73a264c36022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Alexander=20Quent?= Date: Wed, 7 Sep 2022 13:03:13 +0800 Subject: [PATCH 3/3] Update ScreenRayTracker.cs --- .../UXF/Scripts/Trackers/ScreenRayTracker.cs | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs b/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs index 5633e244..0c8a6df6 100644 --- a/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs +++ b/Assets/UXF/Scripts/Trackers/ScreenRayTracker.cs @@ -7,17 +7,15 @@ /// /// Attach this component to any gameobject (e.g. an empty one) and assign it in the trackedObjects field in an ExperimentSession to record /// if ray casted from camera is hitting anything. NOTE: Update Type must be set to MANUAL. +/// Please provide coordinates in form of lists named ray_x & ray_y in your .json file. For example \n\"ray_x\": [0.5],\n\"ray_y\": [0.5]\nif you want to have one ray in the middle of the screen. Multiple rays can be provide with this method." /// -public class ScreenRayTracker : Tracker { +public class ScreenRayTracker : Tracker +{ // Public vars [Header("Necessary Input")] public Camera cam; public Session session; - [Header("Ray coordinates")] - [TextArea(10, 10)] - public string HowToUse = "Please provide coordinates in form of lists named ray_x & ray_y in your .json file. For example \n\"ray_x\": [0.5],\n\"ray_y\": [0.5]\nif you want to have one ray in the middle of the screen. Multiple rays can be provide with this method."; - [Header("Optional Input")] [Tooltip("Enable if you want to visualise the rays in the scene view and the console output.")] public bool debugMode = true; @@ -42,9 +40,11 @@ public class ScreenRayTracker : Tracker { // Start calc void Start(){ // Create layer mask - if(useLayerMask){ + if(useLayerMask) + { layerMask = LayerMask.GetMask(layerMaskNames); - } else { + } else + { layerMask = ~0; // Set to everything as no mask is wanted. } @@ -55,7 +55,8 @@ void Start(){ /// /// Gets coordinates for the rays in screen space from .json file and prints screen resolution /// - public void GetRayCoordinates(){ + public void GetRayCoordinates() + { // Coordinates x = session.settings.GetFloatList("ray_x"); y = session.settings.GetFloatList("ray_y"); @@ -67,24 +68,28 @@ public void GetRayCoordinates(){ /// /// Starts the recording. This method needs to be added to [UXF_Rig] events called On Trial Begin /// - public void StartRecording(){ + public void StartRecording() + { recording = true; } /// /// Stops the recording. This method needs to be added to [UXF_Rig] events called On Trial End /// - public void StopRecording(){ + public void StopRecording() + { recording = false; } - IEnumerator RecordRoutine(){ + IEnumerator RecordRoutine() + { while (true){ if (recording){ - objectDetected = ray2detectObjects(x, y, cam); + objectDetected = Ray2DetectObjects(x, y, cam); for(int i = 0; i < numRays; i++){ // When no object was detected save only if saveNoObject is true - if(objectDetected[i] != noObjectString){ + if(objectDetected[i] != noObjectString) + { var values = new UXFDataRow(); values.Add(("rayIndex", i)); values.Add(("x", x[i])); @@ -105,17 +110,18 @@ IEnumerator RecordRoutine(){ /// Set headers and measurment descriptor /// public override string MeasurementDescriptor => "ObjectsOnScreenTracker"; - public override IEnumerable CustomHeader => new string[] { "rayIndex", "x", "y", "objectDetected"}; + public override IEnumerable CustomHeader => new string[] {"rayIndex", "x", "y", "objectDetected"}; - // Get values - protected override UXFDataRow GetCurrentValues(){ + protected override UXFDataRow GetCurrentValues() + { return currentRow; } /// - /// Function to detect objects on screen by rays + /// Function that casts the rays and detects the hits. /// - List ray2detectObjects(List x, List y, Camera cam){ + List Ray2DetectObjects(List x, List y, Camera cam) + { // Get number of rays numRays = y.Count;