diff options
Diffstat (limited to 'assets/js/qw.js')
-rw-r--r-- | assets/js/qw.js | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/assets/js/qw.js b/assets/js/qw.js new file mode 100644 index 0000000..7708718 --- /dev/null +++ b/assets/js/qw.js @@ -0,0 +1,335 @@ +$(function() { + + // Return a string safe to display in HTML + var safe = function(html) { + return String(html) + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"'); + }; + + // Return a uniquer identifier + var uniqueId = function() { + return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r, v; + r = Math.random() * 16 | 0; + v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + + // MessageBus ENTER callback + var enter = function(name, opts) { + if (opts && opts.check) { + var found = false; + $('#walkers li').each(function() { + found = found || ($(this).text().trim() === name.trim()); + if (found && opts.me) { + $(this).remove(); + found = false; + } + return !found; + }); + if (found) { + return; + } + } + + var li = $('<li></li>'); + li.text(name); + if (opts && opts.me) { + li.addClass("me"); + $('#walkers').prepend(li); + } else { + $('#walkers').append(li); + } + }; + + // MessageBus LEAVE callback + var leave = function(name) { + $.post('/leave', { name: name }); + MessageBus.stop(); + $('#bus-status').text('stopped'); + }; + + // Assign player to a team + var assign_random_team = function() { + var teams = ['cavity', 'musclel', 'muscler', 'stringl', 'stringr', 'wingl', 'wingr']; + var team = teams[Math.floor(Math.random()*teams.length)]; + $('#team').text(team); + return team; + }; + + + // When the DOM is fully loaded, start playing + $(document).ready(function() { + + var name = uniqueId(), // Uniquely identify client + n_walkers = 0, // Number of clients + n_steps = 0, // Number of steps for this client + last_step = $.now(), // Timestamp for last step + has_accel = false, // Whether this device has an accelerometer + accel_n = 0, // Acceleration (vector norm) + accel = { x: 0, y: 0, z: 0, n: 0 }, // Acceleration with Kalman filter + MIN_N = 10.3, // Minimum threshold for step detection + MAX_N = 20; // Maximum threshold + + // SVG animation + var map, cavity, cavity_b, musclel, musclel_b, muscler, muscler_b, + stringl, stringl_b, stringr, stringr_b, wingl, wingl_b, wingr, wingr_b, flight_path, flight_path_l, last_point; + + var team = assign_random_team(); + var svgp, bbox; + + map = Snap('#left-bits'); +/* + cavity = map.select('#cavity'); + cavity_b = cavity.getBBox(); + musclel = map.select('#musclel'); + musclel_b = musclel.getBBox(); + muscler = map.select('#muscler'); + musclel_b = muscler.getBBox(); + stringl = map.select('#stringl'); + stringl_b = stringl.getBBox(); + stringr = map.select('#stringr'); + stringl_b = stringr.getBBox(); + wingl = map.select('#wingl'); + wingl_b = wingl.getBBox(); + wingr = map.select('#wingr'); + wingr_b = wingr.getBBox(); +*/ flight_path = map.select('#path'); + flight_path_l = Snap.path.getTotalLength(flight_path); + +// console.log('flight_path_length ' + flight_path_l); + last_point = flight_path.getPointAtLength(flight_path_l); + + // Start/pause MessageBus when clicking on status + $('#bus-status').click(function() { + switch($('#bus-status').text()) { + case 'started': + MessageBus.pause(); + break; + case 'paused': + MessageBus.resume(); + break; + case 'stopped': + MessageBus.start(); + break; + default: + break; + } + }); + + var play_move = function(jump, team=false) { + if (svgp = map.select('#' + (team || $('#team').text()))) { + bbox = svgp.getBBox(); + + if (jump > flight_path_l) { +// console.log('GAME OVER'); + return; + } + + Snap.animate(jump, flight_path_l, function() { + var p = Snap.path.getPointAtLength( flight_path, jump ); + x = p.x; + y = p.y; + svgp.transform('translate(' + x + ',' + y + ') rotate(' + (p.alpha - 90) + ', ' + bbox.cx + ', ' + bbox.cy + ')'); + },1000, mina.easeout); + } + } + + // Update MessageBus status (debug) + var statusInfo = setInterval(function() { + $('#bus-status').text(MessageBus.status()); + $('#name').text(name); // should be static + }, 2000); + + // Configuration + MessageBus.headers["X-NAME"] = name; + + // Who's connected +/* MessageBus.subscribe("/presence", function(msg) { + if (msg.enter) { + enter(msg.enter, { check: true }); + } + if (msg.leave) { + $('#walkers li').each(function() { + if ($(this).text() === msg.leave) { + $(this).remove(); + leave(msg.leave); + } + }); + } + }); +*/ + // Count clients +/* MessageBus.subscribe('/n-walkers', function(data) { + if (data && data.count) { + n_walkers = data.count; + $('#n-walkers').text(n_walkers); + } + }, n_walkers); +*/ + // Update client steps + MessageBus.subscribe("/walk", function(data) { + if (data.name) { + jump = data.n * data.step; + if (data.name == name) { + $('#accel-n').text(data.n); + $('#n-steps').text(data.step); + $('#current-team').text(data.team); + } + if (jump > flight_path_l) { + $.post('/walk', { n: data.n, name: data.name, step: 0, team: data.team }); + } else { + play_move(jump, data.team); + } + } + }); // last id is zero, so getting backlog + + // If we have accelerometer, play the game + if (window.DeviceMotionEvent != undefined) { + + // Enter the game +/* $.post("/enter", { name: name }, null, "json" ).success(function(data) { + $.each(data.walkers, function(idx, name) { + enter(name); + }); + name = data.name; + enter(name, { check: true, me: true }); + }); + + // Leave the game + window.onbeforeunload = function() { + $.post("/leave", { name: name }); + }; +*/ + // Compute acceleration or simulate it + var accel_norm = function(e) { + + // has_accel = (window.DeviceMotionEvent != undefined && e != undefined && e.type == 'devicemotion' && e.acceleration.x != null); + var norm; + + if (!has_accel) { +// console.log("This device does not support DeviceMotionEvent"); + var min = 5, max = 15; + norm = Math.floor(Math.random()*(max-min+1)+min); + } else { + var ax, ay, az; + ax = e.accelerationIncludingGravity.x; + ay = e.accelerationIncludingGravity.y; + az = e.accelerationIncludingGravity.z; + + accel = detect_footstep(ax, ay, az); + + norm = Math.sqrt(ax * ax + ay * ay + az * az); + } + $('#accel-n').text(safe(norm)); +// console.log("accel-n " + norm); + return norm; + } + + // Detect footsteps with a Kalman filter + var detect_footstep = function(ax, ay, az) { + + var acc = { + x: ax, + y: ay, + z: az + }; + var cnt = 0; + + var x_0 = $V([acc.x, acc.y, acc.z]); // vector. Initial accelerometer values + + // P prior knowledge of state + var P_0 = $M([ + [1,0,0], + [0,1,0], + [0,0,1] + ]); // identity matrix. Initial covariance. Set to 1 + var F_k = $M([ + [1,0,0], + [0,1,0], + [0,0,1] + ]); // identity matrix. How change to model is applied. Set to 1 + var Q_k = $M([ + [0,0,0], + [0,0,0], + [0,0,0] + ]); // empty matrix. Noise in system is zero + + var KM = new KalmanModel(x_0,P_0,F_k,Q_k); + + var z_k = $V([acc.x, acc.y, acc.z]); // Updated accelerometer values + var H_k = $M([ + [1,0,0], + [0,1,0], + [0,0,1] + ]); // identity matrix. Describes relationship between model and observation + var R_k = $M([ + [2,0,0], + [0,2,0], + [0,0,2] + ]); // 2x Scalar matrix. Describes noise from sensor. Set to 2 to begin + + var KO = new KalmanObservation(z_k,H_k,R_k); + + // each 1/10th second take new reading from accelerometer to update + var getNewPos = window.setInterval(function() { + KO.z_k = $V([acc.x, acc.y, acc.z]); //vector to be new reading from x, y, z + KM.update(KO); + + accel = { x: KM.x_k.elements[0], y: KM.x_k.elements[1], z: KM.x_k.elements[2], n: accel.n }; + accel.n = Math.sqrt(accel.x * accel.x + accel.y * accel.y + accel.z * accel.z); + $('#accel-n-kalman').text(accel.n); + }, 100); + + return accel; + }; + + // If we can use the accelerometer, use it... + window.ondevicemotion = function(e) { +// console.log('ondevicemotion'); + has_accel = (window.DeviceMotionEvent != undefined && e != undefined && e.type == 'devicemotion' && e.acceleration.x != null); + if (has_accel) { +// console.log('has_accel'); + $('#accel-support').text("with accelerometer support"); + walk(e); + } else { + $('#accel-support').text('with simulated accelerometer'); + setInterval(walk, 100); + } + } + // ... otherwise simulate it +// if (!has_accel) { +// $('#accel-support').text('with simulated accelerometer'); +// setInterval(walk, 100); +// } + + // Count steps (ballpark figure) + var walk = function(e) { + var val = accel_norm(e); +// console.log('walk setting val to ' + safe(val)); + if (has_accel) { + // Use Kalman filter + val = accel.n; +// console.log('walk setting val to ' + safe(val) + ' (K)'); + } +// console.log("walk " + (has_accel?'ACCEL ':'NOCEL ') + val); + // Avoid noise + if (val < MIN_N || val > MAX_N) { +// console.log('walk NOISE'); + return; + } + // Take step frequency into account + if (($.now() - last_step) > 400) { + n_steps += 1; + $.post('/walk', { n: val, name: name, step: n_steps, team: $('#team').text() }); + last_step = $.now(); + } + }; + } // DeviceMotionEvent + }); // DOM Ready +}); // Anonymous function + |