aboutsummaryrefslogtreecommitdiff
path: root/assets/js/qw.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/qw.js')
-rw-r--r--assets/js/qw.js335
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, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;');
+ };
+
+ // 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
+