-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create ScreenRayTracker.cs #129
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
/// <param name="session"></param> | ||
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); | ||
} | ||
} | ||
|
||
} | ||
Comment on lines
+7
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same with this, it should be generic with specified delimiter |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
using UnityEngine; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using UXF; | ||
using System.Linq; | ||
|
||
/// <summary> | ||
/// 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. | ||
/// </summary> | ||
public class ScreenRayTracker : Tracker { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be in UXF namespace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should use consistent brace style (opening brace |
||
// Public vars | ||
[Header("Necessary Input")] | ||
public Camera cam; | ||
public Session session; | ||
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems problematic, other trackers do not need reference to the 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."; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should not be here, better in the wiki |
||
|
||
[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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can use a LayerMask type |
||
|
||
// Private vars | ||
private List<string> objectDetected = new List<string>(); | ||
private UXFDataRow currentRow; | ||
private bool recording = false; | ||
private int layerMask; | ||
private string noObjectString = "NA"; | ||
private int numRays; | ||
private List<float> x = new List<float>(); | ||
private List<float> y = new List<float>(); | ||
|
||
// 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()); | ||
} | ||
|
||
/// <summary> | ||
/// Gets coordinates for the rays in screen space from .json file and prints screen resolution | ||
/// </summary> | ||
public void GetRayCoordinates(){ | ||
// Coordinates | ||
x = session.settings.GetFloatList("ray_x"); | ||
y = session.settings.GetFloatList("ray_y"); | ||
Comment on lines
+61
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these would be better as serialized/public fields, UXF components should just "work" without some special items in your settings |
||
|
||
// Screen resolution | ||
Debug.Log(Screen.currentResolution); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove or put in debugMode |
||
} | ||
|
||
/// <summary> | ||
/// Starts the recording. This method needs to be added to [UXF_Rig] events called On Trial Begin | ||
/// </summary> | ||
public void StartRecording(){ | ||
recording = true; | ||
} | ||
|
||
/// <summary> | ||
/// Stops the recording. This method needs to be added to [UXF_Rig] events called On Trial End | ||
JAQuent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// </summary> | ||
public void StopRecording(){ | ||
recording = false; | ||
} | ||
|
||
IEnumerator RecordRoutine(){ | ||
while (true){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not use LateUpdate, FixedUpdate, etc? |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means it ignores the |
||
} | ||
} | ||
|
||
/// <summary> | ||
/// Set headers and measurment descriptor | ||
/// </summary> | ||
public override string MeasurementDescriptor => "ObjectsOnScreenTracker"; | ||
public override IEnumerable<string> CustomHeader => new string[] { "rayIndex", "x", "y", "objectDetected"}; | ||
|
||
// Get values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment is not useful |
||
protected override UXFDataRow GetCurrentValues(){ | ||
return currentRow; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means we will end up recording the values twice? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mhmm, strange in the result files each row is unique as far as I can see. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if you use |
||
|
||
/// <summary> | ||
/// Function to detect objects on screen by rays | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment is not useful |
||
/// </summary> | ||
List<string> ray2detectObjects(List<float> x, List<float> y, Camera cam){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Methods should be PascalCase |
||
// Get number of rays | ||
numRays = y.Count; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if x & y are different lengths? |
||
|
||
// Create var to reset the variable | ||
List<string> nameOfObjects = new List<string>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better to create a field, and clear it, rather than make a new list each time |
||
|
||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably be a separate Pull Request, and made generic with the CSV version to accept a delimiter, rather than copying the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely agree :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, just changing the experimenter builder is not enough because then what might happen is that a variable such as a string that is to be displayed to the participant contains and a comma, which UXF then tries to save, which doesn't work if the the output file is an .csv. I currently have work-around where I just replace a comma with an underscore before it is saved but tihis prevents me to copy the data from the input file to the result file.