Created
July 6, 2016 00:40
-
-
Save hyperlogic/b1fb82c3ddf357e202d1736070912752 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "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