/* * plugin for jsPsych based in Qisheng Li 11/2019. /// https://github.com/QishengLi/virtual_chinrest Modified by Gustavo Juantorena 08/2020 // https://github.com/GEJ1 Contributions from Peter J. Kohler: https://github.com/pjkohler */ jsPsych.plugins['virtual-chinrest'] = (function() { var plugin = {}; plugin.info = { name: "virtual-chinrest", parameters: { resize_units: { type: jsPsych.plugins.parameterType.STRING, default: "none", description: 'What units to resize to? ["none"/"cm"/"inch"/"deg"]. If "none", no resize will be done.' }, pixels_per_unit: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Pixels per unit', default: 100, description: 'After the scaling factor is applied, this many pixels will equal one unit of measurement.' }, blindspot_reps: { type: jsPsych.plugins.parameterType.INT, pretty_name: 'Blindspot measurement repetitions', default: 5, description: 'How many times to measure the blindspot location? If 0, blindspot will not detected and viewing distance not computed.' }, prompt_card: { type: jsPsych.plugins.parameterType.STRING, default: ' Let’s find out how big your monitor is! '+ '

Please use any credit card that you have available.
' + 'It can also be a grocery store membership card,
'+ 'your drivers license or anything else of the same format.
'+ 'Place your card flat onto the screen, and adjust the slider below to match its size.

'+ '

If you do not have access to a real card
'+ 'you can use a ruler to measure the image width to 3.37 inches or 85.6 mm.
' }, prompt_blindspot: { type: jsPsych.plugins.parameterType.STRING, default: 'Now, let’s quickly test how far away you are sitting.'+ '

You might know that vision tests at a doctor’s practice often involve chinrests.
'+ 'The doctor basically asks you to sit away from a screen in a specific distance.
'+ 'We do this here with a “virtual chinrest”.


'+ 'Instructions'+ '
'+ '
  1. Put your finger on space bar on the keyboard.
  2. '+ '
  3. Close your right eye. (Tips: it might be easier to cover your right eye by hand!)
  4. '+ '
  5. Using your left eye, focus on the black square.
  6. '+ '
  7. Click the button below to start the animation of the red ball. The red ball '+ 'will disappear as it moves from right to left. Press the “Space” key as soon as the ball disappears from your eye sight.
  8. '+ '

' }, card_path: { type: jsPsych.plugins.parameterType.STRING, default: "img/card.png" } } } // Get screen size var w = window.innerWidth; var h = window.innerHeight; const screen_size_px = [] screen_size_px.push(w) screen_size_px.push('x') screen_size_px.push(h) let trial_data = { "card_width_mm": 85.60, //card dimension: 85.60 × 53.98 mm (3.370 × 2.125 in) }; let config_data = { "ball_pos": [], "slider_clck": false } plugin.trial = function(display_element, trial) { try { if ( !( trial.blindspot_reps > 0 ) && ( (trial.resize_units == "deg" ) || (trial.resize_units == "degrees" ) ) ) { throw Error("Blindspot repetitions set to 0, so resizing to degrees of visual angle is not possible!") } else { const start_time = performance.now(); if ( trial.blindspot_reps > 0 ) { button_str = ''+ ''+ '' blindspot_content = '' display_element.innerHTML = '
'+ pagesize_content + blindspot_content + '
' //Event listeners for buttons if ( trial.blindspot_reps > 0 ) { display_element.querySelector('#blind_spot').addEventListener('click', function(){ configureBlindSpot() }) display_element.querySelector('#start_ball').addEventListener('click', function(){ animateBall() }) } else { // run the two relevant functions to get card_width_mm and px2mm distanceSetup.px2mm(getCardWidth()) } display_element.querySelector('#proceed').addEventListener('click', function(){ // finish trial trial_data.rt = performance.now() - start_time; display_element.innerHTML = ''; trial_data.card_width_deg = 2*(Math.atan((trial_data["card_width_mm"]/2)/trial_data["view_dist_mm"])) * 180/Math.PI trial_data.px2deg = trial_data["card_width_px"] / trial_data.card_width_deg // size of card in pixels divided by size of card in degrees of visual angle let px2unit_scr = 0 switch (trial.resize_units) { case "cm": case "centimeters": px2unit_scr = trial_data["px2mm"]*10 // pixels per centimeter break; case "inch": case "inches": px2unit_scr = trial_data["px2mm"]*25.4 // pixels per inch break; case "deg": case "degrees": px2unit_scr = trial_data["px2deg"] // pixels per degree of visual angle break; } if (px2unit_scr > 0) { // scale the window scale_factor = px2unit_scr / trial.pixels_per_unit; document.getElementById("jspsych-content").style.transform = "scale(" + scale_factor + ")"; // pixels have been scaled, so pixels per degree, pixels per mm and pixels per card_width needs to be updated trial_data.px2deg = trial_data.px2deg / scale_factor trial_data.px2mm = trial_data.px2mm / scale_factor trial_data.card_width_px = trial_data.card_width_px / scale_factor trial_data.scale_factor = scale_factor } if ( trial.blindspot_reps > 0 ) { trial_data.win_width_deg = window.innerWidth/trial_data.px2deg trial_data.win_height_deg = window.innerHeight/trial_data.px2deg } else { // delete degree related properties delete trial_data.px2deg delete trial_data.card_width_deg } jsPsych.finishTrial(trial_data); jsPsych.pluginAPI.cancelAllKeyboardResponses(); }) } } catch (e) { console.error(e) } }; (function ( distanceSetup, $ ) { // jQuery short-hand for $(document).ready(function() { ... }); distanceSetup.round = function(value, decimals) { return Number(Math.round(value+'e'+decimals)+'e-'+decimals); }; distanceSetup.px2mm = function(cardImageWidth) { const cardWidth = 85.6; //card dimension: 85.60 × 53.98 mm (3.370 × 2.125 in) var px2mm = cardImageWidth/cardWidth; trial_data["px2mm"] = distanceSetup.round(px2mm, 2); return px2mm; }; }( window.distanceSetup = window.distanceSetup || {}, jQuery)) function getCardWidth() { var card_width_px = $('#card').width(); trial_data["card_width_px"] = distanceSetup.round(card_width_px,2); return card_width_px } function configureBlindSpot() { drawBall(); $('#page-size').remove(); $('#blind-spot').css({'visibility':'visible'}); // $(document).on('keydown', recordPosition); $(document).on('keydown', recordPosition); } $( function() { $( "#slider" ).slider({value:"50"}); } ); $(document).ready(function() { $( "#slider" ).on("slide", function (event, ui) { var cardWidth = ui.value + "%"; $("#card").css({"width":cardWidth}); }); $('#slider').on('slidechange', function(event, ui){ config_data["slider_clck"] = true; }); }); //============================= //Ball Animation function drawBall(pos=180){ // pos: define where the fixation square should be. var mySVG = SVG("svgDiv"); const cardWidthPx = getCardWidth() const rectX = distanceSetup.px2mm(cardWidthPx)*pos; const ballX = rectX*0.6 // define where the ball is var ball = mySVG.circle(30).move(ballX, 50).fill("#f00"); window.ball = ball; var square = mySVG.rect(30, 30).move(Math.min(rectX - 50, 950), 50); //square position config_data["square_pos"] = distanceSetup.round(square.cx(),2); config_data['rectX'] = rectX config_data['ballX'] = ballX }; function animateBall(){ ball.animate(7000).during( function(pos){ moveX = - pos*config_data['ballX']; window.moveX = moveX; moveY = 0; ball.attr({transform:"translate("+moveX+","+moveY+")"}); } ).loop(true, false). after(function(){ animateBall(); }); //disable the button after clicked once. $("#start_ball").attr("disabled", true); $('#start_ball').css("display", "none"); }; function recordPosition(event, angle=13.5) { // angle: define horizontal blind spot entry point position in degrees. if (event.keyCode == '32') { //Press "Space" config_data["ball_pos"].push(distanceSetup.round((ball.cx() + moveX),2)); var sum = config_data["ball_pos"].reduce((a, b) => a + b, 0); var ballPosLen = config_data["ball_pos"].length; config_data["avg_ball_pos"] = distanceSetup.round(sum/ballPosLen, 2); var ball_sqr_distance = (config_data["square_pos"]-config_data["avg_ball_pos"])/trial_data["px2mm"]; var viewDistance = ball_sqr_distance/Math.radians(angle) trial_data["view_dist_mm"] = distanceSetup.round(viewDistance, 2); //counter and stop var counter = Number($('#click').text()); counter = counter - 1; $('#click').text(Math.max(counter, 0)); if (counter <= 0) { ball.stop(); // Disable space key $('html').bind('keydown', function(e) { if (e.keyCode == 32) {return false;} }); // Display data $('#info').css("visibility", "visible"); $('#info-h').append(trial_data["view_dist_mm"]/10) $('#proceed').css("display", "inline"); return //trial_data.viewing_distance_cm; } ball.stop(); animateBall(); } }; //helper function for radians // Converts from degrees to radians. Math.radians = function(degrees) { return degrees * Math.PI / 180; }; return plugin; })();