Skip to content

Instantly share code, notes, and snippets.

@hyperlogic
Created July 6, 2016 00:40
Show Gist options
  • Select an option

  • Save hyperlogic/b1fb82c3ddf357e202d1736070912752 to your computer and use it in GitHub Desktop.

Select an option

Save hyperlogic/b1fb82c3ddf357e202d1736070912752 to your computer and use it in GitHub Desktop.
"use strict";
/* globals Xform */
//
// gestureRecorder.js
// examples
//
// Created by Anthony Thibault on 2016/06/30
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("/~/system/libraries/Xform.js");
var isRecording = false;
var LEFT_HAND = 0;
var RIGHT_HAND = 1;
var hand = LEFT_HAND;
var TRIGGER_PRESS_VALUE = 0.75;
var TRIGGER_PRESS_HYSTERESIS = 0.1;
var MS_PER_SEC = 1000.0;
var RAD_TO_DEG = 180 / Math.PI;
var DEG_TO_RAD = Math.PI / 180;
// ctor
function HandControllerGestureRecorder(hand) {
this.hand = hand;
this.frames = [];
this.isRecording = false;
this.triggerDown = false;
this.triggerPress = false;
this.triggerRelease = false;
this.recordingFrames = [];
this.recordingLength = 0;
}
function calculateDerivatives(frames) {
var i, length = frames.length;
var keys = ["y", "twist"];
var dKeys = ["dy", "dTwist"];
for (i = 0; i < length; i++) {
var prevIndex = (i === 0) ? 0 : i - 1;
var nextIndex = (i === length - 1) ? i : i + 1;
var j = 0, numKeys = keys.length;
for (j = 0; j < numKeys; j++) {
var d1 = frames[i][keys[j]] - frames[prevIndex][keys[j]];
var d2 = frames[nextIndex][keys[j]] - frames[i][keys[j]];
frames[i][dKeys[j]] = (d1 + d2) / 2;
}
}
}
HandControllerGestureRecorder.prototype.update = function (deltaTime) {
if (this.triggerPress) {
if (this.isRecording) {
// stop recording
this.isRecording = false;
this.endRecording();
this.frames = [];
} else {
this.isRecording = true;
this.beginRecording();
this.frames = [];
}
}
var now = Date.now();
var t;
if (this.isRecording) {
t = (now - this.recordingStartTime) / MS_PER_SEC;
this.recordingFrames.push(this.sampleFrame(t));
} else {
t = now / MS_PER_SEC;
this.frames.push(this.sampleFrame(t));
calculateDerivatives(this.frames);
// prune old frames.
var i;
for (i = 0; i < this.frames.length; i++) {
if (i > 0 && this.frames[i].t > t - this.recordingLength) {
this.frames = this.frames.slice(i - 1);
break;
}
}
if (this.recordingFrames.length > 0 && this.gestureDetect()) {
Controller.triggerShortHapticPulse(1.0, this.hand);
print("gestureDetected!");
}
}
this.triggerPress = false;
this.triggerRelease = false;
};
HandControllerGestureRecorder.prototype.triggerValue = function (value) {
if (!this.triggerDown && value > TRIGGER_PRESS_VALUE + TRIGGER_PRESS_HYSTERESIS) {
this.triggerDown = true;
this.triggerPress = true;
} else if (this.triggerDown && value < TRIGGER_PRESS_VALUE - TRIGGER_PRESS_HYSTERESIS) {
this.triggerDown = false;
this.triggerRelease = true;
}
};
HandControllerGestureRecorder.prototype.beginRecording = function () {
this.recordingStartTime = Date.now();
this.recordingFrames = [];
print("begin recording for " + (this.hand ? "Right" : "Left") + " hand");
};
HandControllerGestureRecorder.prototype.endRecording = function () {
this.recordingLength = (Date.now() - this.recordingStartTime) / MS_PER_SEC;
calculateDerivatives(this.recordingFrames);
print("end recording for " + (this.hand ? "Right" : "Left") + " hand");
JSON.stringify(this.recordingFrames, null, 4).split("\n").forEach(function (str) {
print(str);
});
};
HandControllerGestureRecorder.prototype.sampleFrame = function (t, prevSample) {
var pose = Controller.getPoseValue(this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
var localOffsetRot = this.hand === RIGHT_HAND ? {x: 0.5, y: 0.5, z: -0.5, w: 0.5} : {x: -0.5, y: 0.5, z: -0.5, w: -0.5};
var rot = Quat.multiply(pose.rotation, localOffsetRot);
var Y_AXIS = {x: 0, y: 1, z: 0};
var twist = Math.acos(Vec3.dot(Vec3.multiplyQbyV(rot, Y_AXIS), Y_AXIS));
var frame = {
t: t,
y: pose.translation.y,
twist: twist
};
return frame;
};
function lerp(a, b, alpha) {
return a * (1 - alpha) + b * alpha;
}
function lerpFrame(a, b, alpha) {
var keys = Object.keys(a);
var result = {};
keys.forEach(function (key) {
result[key] = lerp(a[key], b[key], alpha);
});
return result;
}
function testFrames(a, b) {
var TWIST_THRESHOLD = 20 * DEG_TO_RAD; // radians
var DTWIST_THRESHOLD = 3; // radians / sec
var DY_THRESHOLD = 0.3; // meters / sec
if (Math.abs(a.twist - b.twist) > TWIST_THRESHOLD) {
print("FAIL TWIST_THRESHOLD");
print(" a = " + JSON.stringify(a));
print(" b = " + JSON.stringify(b));
return false;
}
if (Math.abs(a.dTwist - b.dTwist) > DTWIST_THRESHOLD) {
print("FAIL DTWIST_THRESHOLD");
print(" a = " + JSON.stringify(a));
print(" b = " + JSON.stringify(b));
return false;
}
if (Math.abs(a.dy - b.dy) > DY_THRESHOLD) {
print("FAIL DY_THRESHOLD");
print(" a = " + JSON.stringify(a));
print(" b = " + JSON.stringify(b));
return false;
}
return true;
}
HandControllerGestureRecorder.prototype.gestureDetect = function () {
print("gestureDetect() frames.length = " + this.frames.length + ", recordingFrames.length = " + this.recordingFrames.length + ", recordingLength = " + this.recordingLength + " (msec)");
// not enough frames to test
if (this.frames.length < this.recordingFrames.length / 2) {
return false;
}
var i = 0, j = 0;
var framesTested = 0;
var framesPassed = 0;
while (i < this.recordingFrames.length && j < this.frames.length) {
var it = this.recordingFrames[i].t;
var jt = this.frames[j].t - this.frames[0].t;
if (jt < it) {
var iPrev = i > 0 ? (i - 1) : 0;
var a = this.recordingFrames[iPrev];
var b = this.recordingFrames[i];
var alpha = i > 0 ? (jt - a.t) / (b.t - a.t) : 0;
var frame = lerpFrame(a, b, alpha);
framesTested++;
if (testFrames(this.frames[j], frame)) {
framesPassed++;
}
j++;
} else {
i++;
}
}
if (framesPassed / framesTested > 0.4) {
print(~~((framesPassed / framesTested) * 100) + "% match");
}
return framesPassed / framesTested > 0.75;
};
var rightRecorder = new HandControllerGestureRecorder(RIGHT_HAND);
//var leftRecorder = new HandControllerGestureRecorder(LEFT_HAND);
// set up controller trigger mapping
var mapping = Controller.newMapping("gestureRecorder");
mapping.from([Controller.Standard.RT]).peek().to(function (value) {
rightRecorder.triggerValue(value);
});
mapping.from([Controller.Standard.LT]).peek().to(function (value) {
//leftRecorder.triggerValue(value);
});
Controller.enableMapping("gestureRecorder");
// set up update callbacks
function update(deltaTime) {
rightRecorder.update(deltaTime);
//leftRecorder.update(deltaTime);
}
Script.update.connect(update);
Script.scriptEnding.connect(function () {
Script.update.disconnect(update);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment