content/hifi-content/angus/gesture/gesture4_withrays.js
2022-02-13 21:18:16 +01:00

1179 lines
No EOL
48 KiB
JavaScript

"use strict";
/*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Script, Users, console */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// gestures.js
//
// Created by Zach Fox on 2018-07-24
// Copyright 2018 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
//
(function () { // BEGIN LOCAL_SCOPE
var AppUi = Script.require('appUi');
/********************************
// START Debug Functions
********************************/
var DEBUG_UNIMPORTANT = 0;
var DEBUG_IMPORTANT = 1;
var DEBUG_URGENT = 2;
var DEBUG_ONLY_PRINT_URGENT = 0;
var DEBUG_PRINT_URGENT_AND_IMPORTANT = 1;
var DEBUG_PRINT_EVERYTHING = 2;
var debugLevel = DEBUG_ONLY_PRINT_URGENT;
var avatarIgnoreID = null;
var borderEntityID = null;
var loadingBarEntityID = null;
var isBlockFocus = true;
var blockFurtherDetection = false;
function maybePrint(string, importance) {
if (importance >= (DEBUG_URGENT - debugLevel)) {
console.log(string);
}
}
/********************************
// END Debug Functions
********************************/
/********************************
//overlay code
*******************************/
var ROOT = "http://mpassets.highfidelity.com/62f401b8-cc95-4e18-8723-12f076e6e5fd-v1/";
var startFrame = 1;
var inFrontOfMyAvatar = getPositionForIcon();
inFrontOfMyAvatar.x = inFrontOfMyAvatar.x - 0.51;
var animation = {
setupAnimation: function (url, width, height, maxFrames, fps, text) {
maxFrames = maxFrames || 1;
var anim = {
timer: null,
url: url,
frames: {},
running: false,
fps: 1000 / fps,
currentFrame: startFrame,
maxFrames: maxFrames,
width: width,
height: height,
emissive: true,
text: text,
blockedYet: false
};
for (var i = startFrame; i <= maxFrames; i++) {
anim.frames[i] = TextureCache.prefetch(url.slice(0, -4) + i + ".png");
}
anim.border = Overlays.addOverlay("image3d", {
position: getPositionForIcon(),
drawInFront: true,
isFacingAvatar: true,
url: "http://hifi-content.s3.amazonaws.com/angus/gesture/outline.png",
alpha: 0.0,
ignorePickIntersection: true,
parentID: MyAvatar.sessionUUID
});
anim.loadBar = Overlays.addOverlay("image3d", {
parentID: anim.border,
localPosition: {x: -0.48, y: 0, z: 0}, // getPositionForIcon(),
dimensions: {x: 0.0, y: 0.5},
drawInFront: false,
isFacingAvatar: true,
url: "http://hifi-content.s3.amazonaws.com/angus/gesture/loadingBar.png",
alpha: 0.0,
ignorePickIntersection: true
});
anim.uuid = Overlays.addOverlay("image3d", {
position: getPositionForIcon(),
drawInFront: true,
dimensions: {x: 0.5, y: 0.5},
isFacingAvatar: true,
url: anim.frames[startFrame].url,
alpha: 0,
ignorePickIntersection: true,
parentID: MyAvatar.sessionUUID
});
anim.textUUID = Overlays.addOverlay("text3d", {
localPosition: {x: -(anim.text.length * 0.00875), y: 0.0, z: 0},
parentID: anim.border,
drawInFront: true,
dimensions: {x: 0.0, y: 0.0},
leftMargin: -0.04,
topMargin: -0.01,
isFacingAvatar: true,
text: anim.text,
lineHeight: 0.07,
textAlpha: 0,
alpha: 0,
ignorePickIntersection: true,
fontsize: 36
});
anim.end = function (stopped) {
Script.clearInterval(anim.timer);
anim.running = false;
anim.currentFrame = startFrame;
Overlays.editOverlay(anim.uuid, {alpha: 0});
Overlays.editOverlay(anim.textUUID, {textAlpha: 0});
Overlays.editOverlay(anim.border, {alpha: 0});
Overlays.editOverlay(anim.loadBar, {alpha: 0});
//if (!stopped) {
// anim.callback(anim.callbackData);
//}
anim.blockedYet = false;
};
anim.stop = function () {
anim.end(true);
};
anim.nextFrame = function () {
anim.currentFrame++;
if (anim.currentFrame >= anim.maxFrames) {
if (anim.running) {
anim.end(false);
}
}
if (anim.currentFrame < (anim.maxFrames - 20)) {
Overlays.editOverlay(anim.loadBar, {
localPosition: { x: - (0.47 - (anim.currentFrame/(anim.maxFrames-21))*0.47), y: 0, z: 0 },
dimensions: {x: (anim.currentFrame/(anim.maxFrames-21))*0.97, y: 0.48}
});
Overlays.editOverlay(anim.border, {
position: getPositionForIcon()
});
} else {
Overlays.editOverlay(anim.textUUID, {
text: "USER BLOCKED"
});
if (!anim.blockedYet) {
anim.blockedYet = true;
anim.callback(anim.callbackData);
}
}
var propertiesToGet = {};
propertiesToGet[anim.uuid] = ["rotation","parentID"];
var props = Overlays.getOverlaysProperties(propertiesToGet);
};
anim.start = function (position, _callback, _callbackData) {
if (!anim.running) {
anim.running = true;
anim.callback = _callback;
anim.callbackData = _callbackData;
Overlays.editOverlay(anim.uuid, {
url: anim.frames[startFrame].url,
position: position,
alpha: 0
});
Overlays.editOverlay(anim.textUUID, {
textAlpha: 1,
text: anim.text,
});
Overlays.editOverlay(anim.border, {
position: position,
alpha: 1
});
Overlays.editOverlay(anim.loadBar, {
localPosition: {x: -0.47, y: 0, z: 0},
alpha: 1
});
if (anim.timer) {
Script.clearInterval(anim.timer);
}
anim.timer = Script.setInterval(anim.nextFrame, anim.fps);
}
};
return anim;
}
};
var animShush = animation.setupAnimation(ROOT + "loading/loading.png", 200, 200, 60, 30, "Hold to Block");
function getPositionForIcon() {
var camPos = Camera.position;
var camRot = Camera.orientation;
return Vec3.sum(camPos, Vec3.multiplyQbyV(camRot, {x: 0, y: 0, z: -1}));
}
function shushPerson(hand) {
if ((avatarIgnoreID !== null) && !isBlockFocus) {
Users.ignore(avatarIgnoreID);
avatarIgnoreID = null;
isBlockFocus = true;
}
blockFurtherDetection = false;
print("sush timer expired");
}
/********************************
// START GestureRecorder
********************************/
var LEFT_HAND_JOINT_NAME = "leftHand";
var RIGHT_HAND_JOINT_NAME = "rightHand";
var HEAD_JOINT_NAME = "head";
function GestureRecorder(jointName) {
this.jointName = jointName;
this.currentPoseFrameData = [];
this.previouslyRecordedFrameData = [];
this.recordingStartTimeMS;
this.recordingLengthSEC;
this.isRecordingGesture = false;
}
GestureRecorder.prototype.initializeDataForRecording = function () {
this.currentPoseFrameData = [];
this.recordingStartTimeMS = Date.now();
maybePrint("ZRF: Starting gesture recording for joint '" + this.jointName + "'.", DEBUG_IMPORTANT);
};
var Y_AXIS = { x: 0, y: 1, z: 0 };
GestureRecorder.prototype.sampleFrame = function (timeSinceStartSEC, previousSample) {
var pose;
var headPose = Controller.getPoseValue(Controller.Standard.Head);
if (this.jointName === LEFT_HAND_JOINT_NAME) {
pose = Controller.getPoseValue(Controller.Standard.LeftHand);
} else if (this.jointName === RIGHT_HAND_JOINT_NAME) {
pose = Controller.getPoseValue(Controller.Standard.RightHand);
pose.rotation = Quat.multiply(Quat.inverse(headPose.rotation), pose.rotation);
var handOffset = Vec3.subtract(pose.translation, headPose.translation);
pose.translation = Vec3.multiplyQbyV(Quat.inverse(headPose.rotation), handOffset);
var headDotHand = Vec3.dot({x: 0.0,y: 0.0,z: 1.0}, Vec3.normalize(pose.translation));
if (headDotHand < 0.866) {
isBlockFocus = true;
if (animShush.running) {
animShush.stop();
}
blockFurtherDetection = false;
}
// print("isBlockFocus " + isBlockFocus);
} else if (this.jointName === HEAD_JOINT_NAME) {
pose = Controller.getPoseValue(Controller.Standard.Head);
// pose.rotation = Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0 });
// pose.translation = { x: 0.0, y: 0.0, z: 0.0 };
}
// make things relative to the head
// pose.rotation = Quat.multiply(Quat.inverse(headPose.rotation), pose.rotation);
// pose.translation = Vec3.subtract(pose.translation, headPose.translation);
var frameData = {
timeSinceStartSEC: timeSinceStartSEC,
x: pose.translation.x,
y: pose.translation.y,
z: pose.translation.z,
rotation: pose.rotation
};
return frameData;
};
var MS_PER_SEC = 1000;
GestureRecorder.prototype.captureDataNow = function () {
var now = Date.now();
var timeSinceStartSEC = (now - this.recordingStartTimeMS) / MS_PER_SEC;
this.currentPoseFrameData.push(this.sampleFrame(timeSinceStartSEC));
};
GestureRecorder.prototype.stopRecording = function () {
this.recordingLengthSEC = (Date.now() - this.recordingStartTimeMS) / MS_PER_SEC;
calculateDerivatives(this.currentPoseFrameData);
maybePrint("ZRF: Finished gesture recording for joint '" + this.jointName + "'.", DEBUG_IMPORTANT);
JSON.stringify(this.currentPoseFrameData, null, 4).split("\n").forEach(function (str) {
maybePrint(str, DEBUG_UNIMPORTANT);
});
};
GestureRecorder.prototype.gestureDetectCheck = function () {
maybePrint("gestureDetect() currentPoseFrameData.length = " + this.currentPoseFrameData.length +
", previouslyRecordedFrameData.data.length = " + this.previouslyRecordedFrameData[gestureToDetect_index].data.length +
", recordingLength = " + this.recordingLengthSEC + " (sec)", DEBUG_UNIMPORTANT);
// not enough frames to test
if (this.currentPoseFrameData.length < this.previouslyRecordedFrameData[gestureToDetect_index].data.length / 2) {
return false;
}
var i = 0, j = 0;
var framesTested = 0;
var framesPassed = 0;
while (i < this.previouslyRecordedFrameData[gestureToDetect_index].data.length && j < this.currentPoseFrameData.length) {
var it = this.previouslyRecordedFrameData[gestureToDetect_index].data[i].timeSinceStartSEC;
var jt = this.currentPoseFrameData[j].timeSinceStartSEC - this.currentPoseFrameData[0].timeSinceStartSEC;
if (jt < it) {
var iPrev = i > 0 ? (i - 1) : 0;
var a = this.previouslyRecordedFrameData[gestureToDetect_index].data[iPrev];
var b = this.previouslyRecordedFrameData[gestureToDetect_index].data[i];
var alpha = i > 0 ? (jt - a.timeSinceStartSEC) / (b.timeSinceStartSEC - a.timeSinceStartSEC) : 0;
var frame = lerpFrame(a, b, alpha);
framesTested++;
if (testFrames(this.currentPoseFrameData[j], frame)) {
framesPassed++;
}
j++;
} else {
i++;
}
}
// console.log("frames tested and passed " + framesTested + " " + framesPassed );
if (framesPassed / framesTested > 0.4) {
maybePrint(~~((framesPassed / framesTested) * 100) + "% match", DEBUG_UNIMPORTANT);
}
return framesPassed / framesTested > 0.75;
};
GestureRecorder.prototype.gestureDetect = function () {
var t = Date.now() / MS_PER_SEC;
this.currentPoseFrameData.push(this.sampleFrame(t));
calculateDerivatives(this.currentPoseFrameData);
var dataLength = this.previouslyRecordedFrameData[gestureToDetect_index].data.length;
var i;
for (i = 0; i < this.currentPoseFrameData.length; i++) {
if (i > 0 && this.currentPoseFrameData[i].timeSinceStartSEC > t - this.previouslyRecordedFrameData[gestureToDetect_index].data[dataLength-1].timeSinceStartSEC) {
this.currentPoseFrameData = this.currentPoseFrameData.slice(i - 1);
break;
}
}
// console.log("length of recording " + this.previouslyRecordedFrameData[gestureToDetect_index].data[dataLength-1].timeSinceStartSEC + " length of currentData " + this.currentPoseFrameData.length);
if (this.currentPoseFrameData.length > 0 && this.gestureDetectCheck()) {
maybePrint("ZRF: Gesture with index " + gestureToDetect_index + " detected on joint " + this.jointName + "!", DEBUG_UNIMPORTANT);
return true;
}
return false;
};
/********************************
// END GestureRecorder
********************************/
/********************************
// START Shared Math Utility Functions
********************************/
// Function Name: inFrontOf()
//
// Description:
// -Returns the position in front of the given "position" argument, where the forward vector is based off
// the "orientation" argument and the amount in front is based off the "distance" argument.
function inFrontOf(distance, position, orientation) {
return Vec3.sum(position || MyAvatar.position,
Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
}
function calculateDerivatives(frames) {
var i, length = frames.length;
var keys = ["x", "y", "z"];
var dKeys = ["dx", "dy", "dz"];
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;
}
}
}
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;
}
var RAD_TO_DEG = 180 / Math.PI;
var DEG_TO_RAD = Math.PI / 180;
var ROTATION_THRESHOLD = 30.0 * DEG_TO_RAD; // radians
var X_THRESHOLD = 0.2; // meters
var Y_THRESHOLD = 0.2; // meters
var Z_THRESHOLD = 0.2; // meters
var DX_THRESHOLD = 0.02; // meters / sec
var DY_THRESHOLD = 0.02; // meters / sec
var DZ_THRESHOLD = 0.02; // meters / sec
function testFrames(a, b) {
//console.log("gesture test frames");
if (Math.abs(Quat.dot(a.rotation, b.rotation)) < Math.cos(ROTATION_THRESHOLD * gestureDetectionSensitivityMultiplier)) {
return false;
}
if (Math.abs(a.x - b.x) > X_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
if (Math.abs(a.y - b.y) > Y_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
if (Math.abs(a.z - b.z) > Z_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
if (Math.abs(a.dx - b.dx) > DX_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
if (Math.abs(a.dy - b.dy) > DY_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
if (Math.abs(a.dz - b.dz) > DZ_THRESHOLD * gestureDetectionSensitivityMultiplier) {
return false;
}
return true;
}
/********************************
// END Shared Math Utility Functions
********************************/
/********************************
// START Global Detection Start/Stop/Update functions
********************************/
var isDetectingGesture = false;
var gestureToDetect_index = -1;
var gestureDetectionSensitivityMultiplier = Settings.getValue('gestures/sensitivity', 1.0);
function updateGestureDetectionSystem() {
if (gestureToDetect_index > -1 && !isRecordingSelectedGestures) {
if (isDetectingGesture) {
Script.update.disconnect(gestureDetectionUpdateLoop);
isDetectingGesture = false;
}
isDetectingGesture = true;
Script.update.connect(gestureDetectionUpdateLoop);
} else {
if (isDetectingGesture) {
Script.update.disconnect(gestureDetectionUpdateLoop);
isDetectingGesture = false;
leftHandRecorder.currentPoseFrameData = [];
rightHandRecorder.currentPoseFrameData = [];
headRecorder.currentPoseFrameData = [];
}
}
maybePrint("ZRF: The gesture to detect has index: " + gestureToDetect_index + ". Currently detecting gestures: " + isDetectingGesture, DEBUG_IMPORTANT);
}
var detectedEntity = false;
var deleteDetectedEntityTimeout = false;
function handleDetectedEntity() {
if (!detectedEntity) {
detectedEntity = Entities.addEntity({
"collidesWith": "",
"collisionMask": 0,
"collisionless": true,
"color": {
"blue": 20,
"green": 200,
"red": 20
},
"dimensions": {
"blue": 0.05000000074505806,
"green": 0.4000000059604645,
"red": 0.4000000059604645,
"x": 0.4000000059604645,
"y": 0.4000000059604645,
"z": 0.05000000074505806
},
"ignoreForCollisions": true,
"shape": "Cube",
"type": "Box",
"userData": "{\"grabbableKey\":{\"grabbable\":false}}",
"position": inFrontOf(0.8, Camera.position, Camera.orientation),
"rotation": Camera.orientation
}, true);
}
if (deleteDetectedEntityTimeout) {
Script.clearTimeout(deleteDetectedEntityTimeout);
}
deleteDetectedEntityTimeout = Script.setTimeout(function () {
if (detectedEntity) {
Entities.deleteEntity(detectedEntity);
detectedEntity = false;
}
deleteDetectedEntityTimeout = false;
}, 1000);
}
function handleEntityPickRay() {
var pickRay = {
origin: Camera.position,
direction: Quat.getFront(Camera.orientation),
length: 100
};
var entityIntersection = Entities.findRayIntersection(pickRay, true);
if (entityIntersection.intersects) {
var intersectEntityID = entityIntersection.entityID;
Entities.editEntity(intersectEntityID, {
color: {
red: Math.random() * 255,
green: Math.random() * 255,
blue: Math.random() * 255
}
});
}
}
function handleAvatarPickRay() {
var myPos = Camera.position;
var myPos1 = {x: Camera.position.x, y: (Camera.position.y - 1.3), z: Camera.position.z};
var myPos2 = {x: Camera.position.x, y: (Camera.position.y - 1.1), z: Camera.position.z};
var myPos3 = {x: Camera.position.x, y: (Camera.position.y - 0.9), z: Camera.position.z};
var myPos4 = {x: Camera.position.x, y: (Camera.position.y - 0.7), z: Camera.position.z};
var myPos5 = {x: Camera.position.x, y: (Camera.position.y - 0.5), z: Camera.position.z};
var myPos6 = {x: Camera.position.x, y: (Camera.position.y - 0.4), z: Camera.position.z};
var forwardDirection = Quat.getFront(Camera.orientation);
forwardDirection.y = 0.0;
var flatForward = Vec3.normalize(forwardDirection);
var offsetUp = Vec3.sum(Vec3.multiply(0.1, Quat.getUp(Camera.orientation)),myPos);
var forwardEnd = Vec3.multiply(5.0,Quat.getFront(Camera.orientation));
var pickRay = {
origin: Camera.position,
direction: Quat.getFront(Camera.orientation),
length: avatarIgnoreMaxDistance
};
var pickRay1 = {
origin: myPos1,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var pickRay2 = {
origin: myPos2,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var pickRay3 = {
origin: myPos3,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var pickRay4 = {
origin: myPos4,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var pickRay5 = {
origin: myPos5,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var pickRay6 = {
origin: myPos2,
direction: flatForward,
length: avatarIgnoreMaxDistance
};
var avatarIntersection = AvatarList.findRayIntersection(pickRay, [], [MyAvatar.sessionUUID]);
var avatarIntersection1 = AvatarList.findRayIntersection(pickRay1, [], [MyAvatar.sessionUUID]);
var avatarIntersection2 = AvatarList.findRayIntersection(pickRay2, [], [MyAvatar.sessionUUID]);
var avatarIntersection3 = AvatarList.findRayIntersection(pickRay3, [], [MyAvatar.sessionUUID]);
var avatarIntersection4 = AvatarList.findRayIntersection(pickRay4, [], [MyAvatar.sessionUUID]);
var avatarIntersection5 = AvatarList.findRayIntersection(pickRay5, [], [MyAvatar.sessionUUID]);
var avatarIntersection6 = AvatarList.findRayIntersection(pickRay6, [], [MyAvatar.sessionUUID]);
var ret = false;
// console.log("camera position" + JSON.stringify(Camera.position) );
if (avatarIntersection.intersects) {
avatarIgnoreID = avatarIntersection.avatarID;
ret = true;
console.log("we have a hit");
} else if (avatarIntersection1.intersects) {
avatarIgnoreID = avatarIntersection1.avatarID;
ret = true;
console.log("we have a hit--1");
} else if (avatarIntersection2.intersects) {
avatarIgnoreID = avatarIntersection2.avatarID;
ret = true;
console.log("we have a hit----2");
} else if (avatarIntersection3.intersects) {
avatarIgnoreID = avatarIntersection3.avatarID;
ret = true;
console.log("we have a hit------3");
} else if (avatarIntersection4.intersects) {
avatarIgnoreID = avatarIntersection4.avatarID;
ret = true;
console.log("we have a hit--------4");
} else if (avatarIntersection5.intersects) {
avatarIgnoreID = avatarIntersection5.avatarID;
ret = true;
console.log("we have a hit----------5");
} else if (avatarIntersection6.intersects) {
avatarIgnoreID = avatarIntersection6.avatarID;
ret = true;
console.log("we have a hit------------6");
}
return ret;
}
function performGestureDetectedAction() {
//maybePrint("ZRF: GESTURE WITH INDEX " + gestureToDetect_index + " DETECTED!", DEBUG_URGENT);
sendToQml({ method: 'gestureDetected' });
//handleDetectedEntity();
handleEntityPickRay();
var hitAvatar = handleAvatarPickRay();
//
if (hitAvatar) {
console.log("did we pick an avatar? " + hitAvatar);
//Audio.playSound(SOUND_GESTURE_DETECTED, {
// position: MyAvatar.position,
// localOnly: true,
// volume: 0.3
//});
isBlockFocus = false;
animShush.start(getPositionForIcon(), shushPerson, 0);
} else {
blockFurtherDetection = false;
}
// print("avatar is in my view " + hitAvatar);
}
function gestureDetectionUpdateLoop() {
var detected = [false, false, false];
var mustBeDetected = [
leftHandRecorder.previouslyRecordedFrameData[gestureToDetect_index] !== "NODATA",
rightHandRecorder.previouslyRecordedFrameData[gestureToDetect_index] !== "NODATA",
headRecorder.previouslyRecordedFrameData[gestureToDetect_index] !== "NODATA"
];
if (mustBeDetected[0]) {
detected[0] = leftHandRecorder.gestureDetect();
}
if (mustBeDetected[1]) {
detected[1] = rightHandRecorder.gestureDetect();
}
if (mustBeDetected[2]) {
detected[2] = headRecorder.gestureDetect();
}
var allNecessaryGesturesDetected = false;
for (var i = 0; i < 3; i++) {
if (detected[i] !== mustBeDetected[i]) {
allNecessaryGesturesDetected = false;
break;
} else {
allNecessaryGesturesDetected = true;
}
}
//var GREEN = { r: 0, g: 1, b: 0, a: 1 };
//var lookingRay = Vec3.multiply(5.0, Quat.getForward(Camera.orientation));
//DebugDraw.drawRay(Camera.position, Vec3.sum(Camera.position,lookingRay), GREEN);
// console.log(" block further detection " + allNecessaryGesturesDetected + " " + blockFurtherDetection);
if (allNecessaryGesturesDetected && !blockFurtherDetection) {
blockFurtherDetection = true;
performGestureDetectedAction();
leftHandRecorder.currentPoseFrameData = [];
rightHandRecorder.currentPoseFrameData = [];
headRecorder.currentPoseFrameData = [];
}
var myPos = Camera.position;
var myPos1 = {x: Camera.position.x, y: (Camera.position.y - 1.3), z: Camera.position.z};
var myPos2 = {x: Camera.position.x, y: (Camera.position.y - 1.1), z: Camera.position.z};
var myPos3 = {x: Camera.position.x, y: (Camera.position.y - 0.9), z: Camera.position.z};
var myPos4 = {x: Camera.position.x, y: (Camera.position.y - 0.7), z: Camera.position.z};
var myPos5 = {x: Camera.position.x, y: (Camera.position.y - 0.5), z: Camera.position.z};
var myPos6 = {x: Camera.position.x, y: (Camera.position.y - 0.4), z: Camera.position.z};
var forwardDirection = Quat.getFront(Camera.orientation);
forwardDirection.y = 0.0;
var flatForward = Vec3.normalize(forwardDirection);
//var up = Vec3.multiply(0.1,Quat.getUp(Camera.orientation));
//var above = Vec3.sum(up,Camera.position);
var offsetUp = Vec3.sum(Vec3.multiply(0.1, Quat.getUp(Camera.orientation)),myPos);
var forwardEnd = Vec3.sum(myPos,Vec3.multiply(avatarIgnoreMaxDistance,Quat.getFront(Camera.orientation)));
DebugDraw.drawRay(offsetUp, forwardEnd,{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.drawRay(myPos1, Vec3.sum(myPos1,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.drawRay(myPos2, Vec3.sum(myPos2,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.drawRay(myPos3, Vec3.sum(myPos3,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.drawRay(myPos4, Vec3.sum(myPos4,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.drawRay(myPos5, Vec3.sum(myPos5,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.drawRay(myPos6, Vec3.sum(myPos6,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:0, blue:1, green:0, alpha:1 });
DebugDraw.addMarker("head", Camera.orientation, forwardEnd,{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos1", Camera.orientation, Vec3.sum(myPos1,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos2", Camera.orientation, Vec3.sum(myPos2,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos3", Camera.orientation, Vec3.sum(myPos3,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos4", Camera.orientation, Vec3.sum(myPos4,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos5", Camera.orientation, Vec3.sum(myPos5,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
DebugDraw.addMarker("pos6", Camera.orientation, Vec3.sum(myPos6,Vec3.multiply(avatarIgnoreMaxDistance,flatForward)),{ red:1, blue:0, green:0, alpha:1 });
}
/********************************
// END Global Detection Start/Stop/Update functions
********************************/
/********************************
// START Global Capture Start/Stop/Update functions
********************************/
var dataCaptureStartTimeMS;
var DATA_CAPTURE_TIMEOUT_MS = 3000;
function dataCaptureUpdateLoop() {
if (Date.now() - dataCaptureStartTimeMS > DATA_CAPTURE_TIMEOUT_MS) {
stopRecordingSelectedGestures();
return;
}
if (jointDataToRecord[0]) {
leftHandRecorder.captureDataNow();
}
if (jointDataToRecord[1]) {
rightHandRecorder.captureDataNow();
}
if (jointDataToRecord[2]) {
headRecorder.captureDataNow();
}
}
// [0] is "left hand"
// [1] is "right hand"
// [2] is "head"
var jointDataToRecord = [
Settings.getValue('gestures/captureLeftHandData', false),
Settings.getValue('gestures/captureRightHandData', false),
Settings.getValue('gestures/captureHeadData', false)
];
var updateConnected = false;
var leftHandRecorder = new GestureRecorder(LEFT_HAND_JOINT_NAME);
var rightHandRecorder = new GestureRecorder(RIGHT_HAND_JOINT_NAME);
var headRecorder = new GestureRecorder(HEAD_JOINT_NAME);
function startRecordingSelectedGestures() {
if (!(jointDataToRecord[0] || jointDataToRecord[1] || jointDataToRecord[2])) {
return;
}
maybePrint("ZRF startRecordingSelectedGestures()", DEBUG_UNIMPORTANT);
isRecordingSelectedGestures = true;
sendToQml({ method: 'updateIsRecordingSelectedGestures', isRecordingSelectedGestures: isRecordingSelectedGestures });
Audio.playSound(SOUND_GESTURE_RECORDING_START, {
position: MyAvatar.position,
localOnly: true,
volume: 0.8
});
if (updateConnected) {
Script.update.disconnect(dataCaptureUpdateLoop);
updateConnected = false;
}
if (jointDataToRecord[0]) {
leftHandRecorder.initializeDataForRecording();
}
if (jointDataToRecord[1]) {
rightHandRecorder.initializeDataForRecording();
}
if (jointDataToRecord[2]) {
headRecorder.initializeDataForRecording();
}
dataCaptureStartTimeMS = Date.now();
Script.update.connect(dataCaptureUpdateLoop);
updateConnected = true;
updateGestureDetectionSystem();
}
function stopRecordingSelectedGestures() {
maybePrint("ZRF stopRecordingSelectedGestures()", DEBUG_UNIMPORTANT);
isRecordingSelectedGestures = false;
if (updateConnected) {
Script.update.disconnect(dataCaptureUpdateLoop);
updateConnected = false;
}
sendToQml({ method: 'updateIsRecordingSelectedGestures', isRecordingSelectedGestures: isRecordingSelectedGestures });
Audio.playSound(SOUND_GESTURE_RECORDING_STOP, {
position: MyAvatar.position,
localOnly: true,
volume: 0.8
});
var timestamp;
var index = -1;
if (jointDataToRecord[0]) {
var dataToSave = {
timestamp: false,
data: []
};
leftHandRecorder.stopRecording();
timestamp = leftHandRecorder.recordingStartTimeMS;
dataToSave.timestamp = timestamp;
dataToSave.data = leftHandRecorder.currentPoseFrameData;
leftHandRecorder.previouslyRecordedFrameData.push(dataToSave);
leftHandRecorder.currentPoseFrameData = [];
index = leftHandRecorder.previouslyRecordedFrameData.length - 1;
} else {
leftHandRecorder.previouslyRecordedFrameData.push("NODATA");
}
if (jointDataToRecord[1]) {
var dataToSave2 = {
timestamp: false,
data: []
};
rightHandRecorder.stopRecording();
timestamp = rightHandRecorder.recordingStartTimeMS;
dataToSave2.timestamp = timestamp;
dataToSave2.data = rightHandRecorder.currentPoseFrameData;
rightHandRecorder.previouslyRecordedFrameData.push(dataToSave2);
rightHandRecorder.currentPoseFrameData = [];
index = rightHandRecorder.previouslyRecordedFrameData.length - 1;
} else {
rightHandRecorder.previouslyRecordedFrameData.push("NODATA");
}
if (jointDataToRecord[2]) {
var dataToSave3 = {
timestamp: false,
data: []
};
headRecorder.stopRecording();
timestamp = headRecorder.recordingStartTimeMS;
dataToSave3.timestamp = timestamp;
dataToSave3.data = headRecorder.currentPoseFrameData;
headRecorder.previouslyRecordedFrameData.push(dataToSave3);
headRecorder.currentPoseFrameData = [];
index = headRecorder.previouslyRecordedFrameData.length - 1;
} else {
headRecorder.previouslyRecordedFrameData.push("NODATA");
}
timestamp = new Date(timestamp);
sendToQml({
method: 'appendRecordedGesture',
index: index,
recordedTime: timestamp.getHours() + ":" + timestamp.getMinutes() + ":" + timestamp.getSeconds(),
recordedJoints: [jointDataToRecord[0], jointDataToRecord[1], jointDataToRecord[2]]
});
updateGestureDetectionSystem();
}
var isRecordingSelectedGestures = false;
function toggleRecordingGesture() {
if (!isRecordingSelectedGestures) {
startRecordingSelectedGestures();
} else {
stopRecordingSelectedGestures();
}
}
/********************************
// END Global Capture Start/Stop/Update functions
********************************/
/********************************
// START Controller Mapping
********************************/
var gesturesControllerMapping = false;
var gesturesControllerMappingName = 'Hifi-Gestures-Mapping';
function maybeRegisterButtonMappings() {
// Don't re-register
if (gesturesControllerMapping) {
return;
}
gesturesControllerMapping = Controller.newMapping(gesturesControllerMappingName);
if (controllerType === "OculusTouch") {
gesturesControllerMapping.from(Controller.Standard.RS).to(function (value) {
if (value === 1.0) {
toggleRecordingGesture();
}
return;
});
} else if (controllerType === "Vive") {
gesturesControllerMapping.from(Controller.Standard.RightPrimaryThumb).to(function (value) {
if (value === 1.0) {
toggleRecordingGesture();
}
return;
});
}
gesturesControllerMapping.enable();
}
function disableButtonMappings() {
if (gesturesControllerMapping) {
gesturesControllerMapping.disable();
gesturesControllerMapping = false;
}
}
/********************************
// END Controller Mapping
********************************/
/********************************
// START App-Related Functions
********************************/
// Function Name: sendToQml()
//
// Description:
// -Use this function to send a message to the app's QML (i.e. to change appearances). The "message" argument is what is sent to
// the app's QML in the format "{method, params}", like json-rpc. See also fromQml().
function sendToQml(message) {
ui.sendMessage(message);
}
// Function Name: fromQml()
//
// Description:
// -Called when a message is received from the app QML. The "message" argument is what is sent from the app QML
// in the format "{method, params}", like json-rpc. See also sendToQml().
function fromQml(message) {
switch (message.method) {
case 'enableRecordingSwitchChanged':
if (message.status) {
maybeRegisterButtonMappings();
} else {
disableButtonMappings();
}
Settings.setValue('gestures/enableControllerMapping', message.status);
break;
case 'updateJointSelections':
jointDataToRecord = message.selections;
Settings.setValue('gestures/captureLeftHandData', jointDataToRecord[0]);
Settings.setValue('gestures/captureRightHandData', jointDataToRecord[1]);
Settings.setValue('gestures/captureHeadData', jointDataToRecord[2]);
break;
case 'toggleManualDataCapture':
toggleRecordingGesture();
break;
case 'modifyListeningForGestureIndex':
// console.log("the gesture index is set to " + message.listeningForGestureIndex);
gestureToDetect_index = message.listeningForGestureIndex;
updateGestureDetectionSystem();
break;
case 'clearRecordedGestures':
gestureToDetect_index = -1;
leftHandRecorder.previouslyRecordedFrameData = [];
rightHandRecorder.previouslyRecordedFrameData = [];
headRecorder.previouslyRecordedFrameData = [];
break;
case 'updateSensitivity':
gestureDetectionSensitivityMultiplier = message.sensitivity;
Settings.setValue('gestures/sensitivity', gestureDetectionSensitivityMultiplier);
break;
case 'updateMaxIgnoreDistance':
avatarIgnoreMaxDistance = message.distance;
Settings.setValue('gestures/maxIgnoreDistance', avatarIgnoreMaxDistance);
maybePrint("ZRF: Updating max avatar ignore distance to '" + avatarIgnoreMaxDistance, DEBUG_IMPORTANT);
break;
case 'copyDataToClipboard':
var dataToCopy = [];
var leftHandData = "NODATA";
var rightHandData = "NODATA";
var headData = "NODATA";
if (leftHandRecorder.previouslyRecordedFrameData[message.index] !== "NODATA") {
leftHandData = leftHandRecorder.previouslyRecordedFrameData[message.index];
}
if (rightHandRecorder.previouslyRecordedFrameData[message.index] !== "NODATA") {
rightHandData = rightHandRecorder.previouslyRecordedFrameData[message.index];
}
if (headRecorder.previouslyRecordedFrameData[message.index] !== "NODATA") {
headData = headRecorder.previouslyRecordedFrameData[message.index];
}
dataToCopy.push(leftHandData);
dataToCopy.push(rightHandData);
dataToCopy.push(headData);
Window.copyToClipboard(JSON.stringify(dataToCopy));
break;
case 'importGestureData':
var data = JSON.parse(message.data);
if (data.length !== 3) {
maybePrint('Unrecognized import data format, bailing!', DEBUG_URGENT);
return;
}
leftHandRecorder.previouslyRecordedFrameData.push(data[0]);
rightHandRecorder.previouslyRecordedFrameData.push(data[1]);
headRecorder.previouslyRecordedFrameData.push(data[2]);
appendGestureToQMLWithIndex(rightHandRecorder.previouslyRecordedFrameData.length - 1);
break;
default:
maybePrint('Unrecognized message from Gestures.qml: ' + JSON.stringify(message), DEBUG_URGENT);
}
}
function appendGestureToQMLWithIndex(index) {
var timestamp;
var recordedJoints = [false, false, false];
if (leftHandRecorder.previouslyRecordedFrameData[index] !== "NODATA") {
recordedJoints[0] = true;
timestamp = leftHandRecorder.previouslyRecordedFrameData[index].timestamp;
}
if (rightHandRecorder.previouslyRecordedFrameData[index] !== "NODATA") {
recordedJoints[1] = true;
timestamp = rightHandRecorder.previouslyRecordedFrameData[index].timestamp;
}
if (headRecorder.previouslyRecordedFrameData[index] !== "NODATA") {
recordedJoints[2] = true;
timestamp = headRecorder.previouslyRecordedFrameData[index].timestamp;
}
timestamp = new Date(timestamp);
sendToQml({
method: 'appendRecordedGesture',
index: index,
recordedTime: timestamp.getHours() + ":" + timestamp.getMinutes() + ":" + timestamp.getSeconds(),
recordedJoints: recordedJoints
});
}
function appendAllGesturesToQML() {
for (var i = 0; i < leftHandRecorder.previouslyRecordedFrameData.length; i++) {
appendGestureToQMLWithIndex(i);
}
}
// Function Name: appUiOpened()
//
// Description:
// - Called when the app's UI is opened
//
var APP_INITIALIZE_UI_DELAY = 500; // MS
function appUiOpened() {
// In the case of a remote QML app, it takes a bit of time
// for the event bridge to actually connect, so we have to wait...
Script.setTimeout(function () {
sendToQml({
method: 'initializeUI',
masterSwitchOn: !!gesturesControllerMapping,
jointDataToRecord: jointDataToRecord,
currentlyDetectingGesture: gestureToDetect_index,
gestureDetectionSensitivityMultiplier: gestureDetectionSensitivityMultiplier,
maxIgnoreDistance: avatarIgnoreMaxDistance
});
appendAllGesturesToQML();
}, APP_INITIALIZE_UI_DELAY);
}
// Function Name: appUiClosed()
//
// Description:
// - Called when the app's UI is closed
//
function appUiClosed() {
}
// Function Name: startup()
//
// Description:
// -startup() will be called when the script is loaded.
//
var ui;
var controllerType = "Other";
var avatarIgnoreMaxDistance = 5.0;
function startup() {
ui = new AppUi({
buttonName: "GESTURES",
home: Script.resolvePath('https://hifi-content.s3.amazonaws.com/zfox/gestureApp/Gestures.qml'),
onOpened: appUiOpened,
onClosed: appUiClosed,
onMessage: fromQml,
sortOrder: 15,
normalButton: Script.resourcesPath() + "icons/tablet-icons/avatar-record-i.svg",
activeButton: Script.resourcesPath() + "icons/tablet-icons/avatar-record-a.svg"
});
// Controller type detection
var VRDevices = Controller.getDeviceNames().toString();
if (VRDevices) {
if (VRDevices.indexOf("Vive") !== -1) {
controllerType = "Vive";
} else if (VRDevices.indexOf("OculusTouch") !== -1) {
controllerType = "OculusTouch";
}
}
if (Settings.getValue('gestures/enableControllerMapping', false)) {
maybeRegisterButtonMappings();
}
avatarIgnoreMaxDistance = Settings.getValue('gestures/maxIgnoreDistance', 5.0);
var borderModelProperties = {
position: getPositionForIcon(),
lifetime: 100,
modelURL: "file:///c:/angus/javascript_bak/gesture/outline.fbx",
name: " border",
type : "Model"
};
var loadingBarModelProperties = {
position: getPositionForIcon(),
lifetime: 100,
dimensions: {
x: 0.001,
y: 0.1,
z: 0.1
},
modelURL: "file:///c:/angus/javascript_bak/gesture/loadingBar.fbx",
name: " border",
type : "Model"
};
//loadingBarEntityID = Entities.addEntity(loadingBarModelProperties);
//borderEntityID = Entities.addEntity(borderModelProperties);
//Entities.editEntity(borderEntityID, {dimensions: { x: 0.001, y: 0.1, z: 0.5 }});
}
// Function Name: shutdown()
//
// Description:
// - Called when the script ends (i.e. is stopped).
//
function shutdown() {
appUiClosed();
//Entities.deleteEntity(borderEntityID);
//Entities.deleteEntity(loadingBarEntityID);
Overlays.deleteOverlay(animShush.uuid);
Overlays.deleteOverlay(animShush.border);
Overlays.deleteOverlay(animShush.loadBar);
Overlays.deleteOverlay(animShush.textUUID);
DebugDraw.removeMarker("head");
DebugDraw.removeMarker("pos1");
DebugDraw.removeMarker("pos2");
DebugDraw.removeMarker("pos3");
DebugDraw.removeMarker("pos4");
DebugDraw.removeMarker("pos5");
DebugDraw.removeMarker("pos6");
}
var SOUND_GESTURE_RECORDING_START = SoundCache.getSound(Script.resolvePath("https://hifi-content.s3.amazonaws.com/zfox/gestureApp/startRecording.wav"));
var SOUND_GESTURE_RECORDING_STOP = SoundCache.getSound(Script.resolvePath("https://hifi-content.s3.amazonaws.com/zfox/gestureApp/stopRecording.wav"));
var SOUND_GESTURE_DETECTED = SoundCache.getSound(Script.resolvePath("https://hifi-content.s3.amazonaws.com/zfox/gestureApp/gestureDetected.wav"));
startup();
Script.scriptEnding.connect(shutdown);
/********************************
// END App-Related Functions
********************************/
}()); // END LOCAL_SCOPE