From f0c9f1d120522f18c232a55d06520f6b81508b1c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Jul 2017 15:32:13 -0700 Subject: [PATCH 01/78] starting on controller-dispatcher experiment --- scripts/defaultScripts.js | 6 +- .../controllers/controllerDispatcher.js | 122 ++++++++++ .../system/controllers/controllerScripts.js | 11 +- scripts/system/controllers/nearGrab.js | 208 ++++++++++++++++++ 4 files changed, 340 insertions(+), 7 deletions(-) create mode 100644 scripts/system/controllers/controllerDispatcher.js create mode 100644 scripts/system/controllers/nearGrab.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2270118861..f4c7b42ee2 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -20,15 +20,15 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/bubble.js", "system/snapshot.js", "system/help.js", - "system/pal.js", // "system/mod.js", // older UX, if you prefer + // "system/pal.js", // "system/mod.js", // older UX, if you prefer "system/makeUserConnection.js", "system/tablet-goto.js", "system/marketplaces/marketplaces.js", - "system/edit.js", + // "system/edit.js", "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/tablet-ui/tabletUI.js" + // "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js new file mode 100644 index 0000000000..5470c64438 --- /dev/null +++ b/scripts/system/controllers/controllerDispatcher.js @@ -0,0 +1,122 @@ +"use strict"; + +// controllerDispatcher.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation */ + +Script.include("/~/system/libraries/utils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var _this = this; + + // var LEFT_HAND = 0; + // var RIGHT_HAND = 1; + + var NEAR_GRAB_RADIUS = 0.1; + var DISPATCHER_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + + this.runningPluginName = null; + + this.leftTriggerPress = function(value) { + _this.leftTriggerValue = value; + }; + + this.leftTriggerClick = function(value) { + _this.leftTriggerClicked = value; + }; + + this.rightTriggerPress = function(value) { + _this.rightTriggerValue = value; + }; + + this.rightTriggerClick = function(value) { + _this.rightTriggerClicked = value; + }; + + this.update = function () { + + var leftControllerLocation = getControllerWorldLocation(Controller.Standard.LeftHand, true); + var rightControllerLocation = getControllerWorldLocation(Controller.Standard.RightHand, true); + + var leftNearbyEntityIDs = Entities.findEntities(leftControllerLocation, NEAR_GRAB_RADIUS); + var rightNearbyEntityIDs = Entities.findEntities(rightControllerLocation, NEAR_GRAB_RADIUS); + + var leftNearbyEntityProperties = {}; + leftNearbyEntityIDs.forEach(function (entityID) { + var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + props.id = entityID; + props.distanceFromController = Vec3.length(Vec3.subtract(leftControllerLocation, props.position)); + leftNearbyEntityProperties.push(props); + }); + + var rightNearbyEntityProperties = {}; + rightNearbyEntityIDs.forEach(function (entityID) { + var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + props.id = entityID; + props.distanceFromController = Vec3.length(Vec3.subtract(rightControllerLocation, props.position)); + rightNearbyEntityProperties.push(props); + }); + + + var controllerData = { + triggerValues: [this.leftTriggerValue, this.rightTriggerValue], + triggerPresses: [this.leftTriggerPress, this.rightTriggerPress], + controllerLocations: [ leftControllerLocation, rightControllerLocation ], + nearbyEntityProperties: [ leftNearbyEntityProperties, rightNearbyEntityProperties ], + }; + + if (this.runningPluginName) { + var plugin = controllerDispatcherPlugins[this.runningPluginName]; + if (!plugin || !plugin.run(controllerData)) { + this.runningPluginName = null; + } + } else if (controllerDispatcherPlugins) { + for (var pluginName in controllerDispatcherPlugins) { + // TODO sort names by plugin.priority + if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { + var candidatePlugin = controllerDispatcherPlugins[pluginName]; + if (candidatePlugin.isReady(controllerData)) { + this.runningPluginName = candidatePlugin; + break; + } + } + } + } + }; + + var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; + var mapping = Controller.newMapping(MAPPING_NAME); + mapping.from([Controller.Standard.RT]).peek().to(this.rightTriggerPress); + mapping.from([Controller.Standard.RTClick]).peek().to(this.rightTriggerClicked); + mapping.from([Controller.Standard.LT]).peek().to(this.leftTriggerPress); + mapping.from([Controller.Standard.LTClick]).peek().to(this.leftTriggerClicked); + Controller.enableMapping(MAPPING_NAME); + + this.cleanup = function () { + Script.update.disconnect(this.update); + Controller.disableMapping(MAPPING_NAME); + }; + + Script.scriptEnding.connect(this.cleanup); + Script.update.connect(this.update); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index df11a1e5be..5adfd93745 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -12,11 +12,14 @@ var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", - "handControllerGrab.js", - "handControllerPointer.js", - "grab.js", - "teleport.js", + // "handControllerGrab.js", + // "handControllerPointer.js", + // "grab.js", + // "teleport.js", "toggleAdvancedMovementForHandControllers.js", + + "ControllerDispatcher.js", + "nearGrab.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/nearGrab.js b/scripts/system/controllers/nearGrab.js new file mode 100644 index 0000000000..691f66c4d3 --- /dev/null +++ b/scripts/system/controllers/nearGrab.js @@ -0,0 +1,208 @@ +"use strict"; + +// nearGrab.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, HMD, Camera, MyAvatar, Controller, controllerDispatcherPlugins */ + + +(function() { + + var LEFT_HAND = 0; + var RIGHT_HAND = 1; + + var HAPTIC_PULSE_STRENGTH = 1.0; + var HAPTIC_PULSE_DURATION = 13.0; + + var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; + var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; + + var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + + function getControllerJointIndex(hand) { + if (HMD.isHandControllerAvailable()) { + var controllerJointIndex = -1; + if (Camera.mode === "first person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else if (Camera.mode === "third person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + + return controllerJointIndex; + } + + return MyAvatar.getJointIndex("Head"); + } + + function propsArePhysical(props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; + } + + function entityIsGrabbable(props) { + var grabbableProps = {}; + var userDataParsed = JSON.parse(props.userData); + if (userDataParsed && userDataParsed.grabbable) { + grabbableProps = userDataParsed.grabbable; + } + var grabbable = propsArePhysical(props); + if (grabbableProps.hasOwnProperty("grabbable")) { + grabbable = grabbableProps.grabbable; + } + if (!grabbable) { + return false; + } + if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + return false; + } + if (props.locked) { + return false; + } + if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { + return false; + } + return true; + } + + + function NearGrab(hand) { + this.priority = 5; + + this.hand = hand; + this.grabbedThingID = null; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.previouslyUnhooked = {}; + + + this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + + this.thisHandIsParent = function(props) { + if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { + return false; + } + + var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + if (props.parentJointIndex == handJointIndex) { + return true; + } + + var controllerJointIndex = this.controllerJointIndex; + if (props.parentJointIndex == controllerJointIndex) { + return true; + } + + var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + if (props.parentJointIndex == controllerCRJointIndex) { + return true; + } + + return false; + }; + + this.startNearGrab = function (controllerData) { + var grabbedProperties = controllerData.nearbyEntityProperties[this.hand][this.grabbedThingID]; + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + + var reparentProps = { + parentID: AVATAR_SELF_ID, + parentJointIndex: getControllerJointIndex(this.hand), + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }; + + if (this.thisHandIsParent(grabbedProperties)) { + // this should never happen, but if it does, don't set previous parent to be this hand. + // this.previousParentID[this.grabbedThingID] = NULL; + // this.previousParentJointIndex[this.grabbedThingID] = -1; + } else { + this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; + this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; + } + Entities.editEntity(this.grabbedThingID, reparentProps); + }; + + this.endNearGrab = function (controllerData) { + if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] + }); + } else { + // we're putting this back as a child of some other parent, so zero its velocity + Entities.editEntity(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }); + } + this.grabbedThingID = null; + }; + + this.isReady = function (controllerData) { + if (!controllerData.triggerPresses[this.hand]) { + return false; + } + + var grabbable = null; + var bestDistance = 1000; + controllerData.nearbyEntityProperties[this.hand].forEach(function(nearbyEntityProperties) { + if (entityIsGrabbable(nearbyEntityProperties)) { + if (nearbyEntityProperties.distanceFromController < bestDistance) { + bestDistance = nearbyEntityProperties.distanceFromController; + grabbable = nearbyEntityProperties; + } + } + }); + + if (grabbable) { + this.grabbedThingID = grabbable.id; + this.startNearGrab(); + return true; + } else { + return false; + } + }; + + this.run = function (controllerData) { + if (!controllerData.triggerPresses[this.hand]) { + this.endNearGrab(controllerData); + return false; + } + return true; + }; + } + + var leftNearGrab = new NearGrab(LEFT_HAND); + leftNearGrab.name = "leftNearGrab"; + + var rightNearGrab = new NearGrab(RIGHT_HAND); + rightNearGrab.name = "rightNearGrab"; + + if (!controllerDispatcherPlugins) { + controllerDispatcherPlugins = {}; + } + controllerDispatcherPlugins.leftNearGrab = leftNearGrab; + controllerDispatcherPlugins.rightNearGrab = rightNearGrab; + + + this.cleanup = function () { + delete controllerDispatcherPlugins.leftNearGrab; + delete controllerDispatcherPlugins.rightNearGrab; + }; + Script.scriptEnding.connect(this.cleanup); +}()); From fcf1dc839a8bb81ab3e321d0e18d33b4c6306875 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Jul 2017 17:52:23 -0700 Subject: [PATCH 02/78] parenting near grab works --- .../controllers/controllerDispatcher.js | 72 ++++++++++--------- scripts/system/controllers/nearGrab.js | 40 ++++++----- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 5470c64438..8ffa0f0ca3 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -7,16 +7,21 @@ /* global Script, Entities, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation */ +controllerDispatcherPlugins = {}; + Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { var _this = this; - // var LEFT_HAND = 0; - // var RIGHT_HAND = 1; + var LEFT_HAND = 0; + var RIGHT_HAND = 1; var NEAR_GRAB_RADIUS = 0.1; + var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand + + var DISPATCHER_PROPERTIES = [ "position", "registrationPoint", @@ -36,6 +41,11 @@ Script.include("/~/system/libraries/controllers.js"); ]; this.runningPluginName = null; + this.leftTriggerValue = 0; + this.leftTriggerClicked = 0; + this.rightTriggerValue = 0; + this.rightTriggerClicked = 0; + this.leftTriggerPress = function(value) { _this.leftTriggerValue = value; @@ -55,40 +65,36 @@ Script.include("/~/system/libraries/controllers.js"); this.update = function () { - var leftControllerLocation = getControllerWorldLocation(Controller.Standard.LeftHand, true); - var rightControllerLocation = getControllerWorldLocation(Controller.Standard.RightHand, true); - - var leftNearbyEntityIDs = Entities.findEntities(leftControllerLocation, NEAR_GRAB_RADIUS); - var rightNearbyEntityIDs = Entities.findEntities(rightControllerLocation, NEAR_GRAB_RADIUS); - - var leftNearbyEntityProperties = {}; - leftNearbyEntityIDs.forEach(function (entityID) { - var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - props.id = entityID; - props.distanceFromController = Vec3.length(Vec3.subtract(leftControllerLocation, props.position)); - leftNearbyEntityProperties.push(props); - }); - - var rightNearbyEntityProperties = {}; - rightNearbyEntityIDs.forEach(function (entityID) { - var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - props.id = entityID; - props.distanceFromController = Vec3.length(Vec3.subtract(rightControllerLocation, props.position)); - rightNearbyEntityProperties.push(props); - }); + var controllerLocations = [getControllerWorldLocation(Controller.Standard.LeftHand, true), + getControllerWorldLocation(Controller.Standard.RightHand, true)]; + var nearbyEntityProperties = [[], []]; + for (var i = LEFT_HAND; i <= RIGHT_HAND; i ++) { + // todo: check controllerLocations[i].valid + var controllerPosition = controllerLocations[i].position; + var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_GRAB_RADIUS); + for (var j = 0; j < nearbyEntityIDs.length; j++) { + var entityID = nearbyEntityIDs[j]; + var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + props.id = entityID; + props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position)); + if (props.distanceFromController < NEAR_GRAB_MAX_DISTANCE) { + nearbyEntityProperties[i].push(props); + } + } + } var controllerData = { - triggerValues: [this.leftTriggerValue, this.rightTriggerValue], - triggerPresses: [this.leftTriggerPress, this.rightTriggerPress], - controllerLocations: [ leftControllerLocation, rightControllerLocation ], - nearbyEntityProperties: [ leftNearbyEntityProperties, rightNearbyEntityProperties ], + triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], + triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], + controllerLocations: controllerLocations, + nearbyEntityProperties: nearbyEntityProperties, }; - if (this.runningPluginName) { - var plugin = controllerDispatcherPlugins[this.runningPluginName]; + if (_this.runningPluginName) { + var plugin = controllerDispatcherPlugins[_this.runningPluginName]; if (!plugin || !plugin.run(controllerData)) { - this.runningPluginName = null; + _this.runningPluginName = null; } } else if (controllerDispatcherPlugins) { for (var pluginName in controllerDispatcherPlugins) { @@ -96,7 +102,7 @@ Script.include("/~/system/libraries/controllers.js"); if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { var candidatePlugin = controllerDispatcherPlugins[pluginName]; if (candidatePlugin.isReady(controllerData)) { - this.runningPluginName = candidatePlugin; + _this.runningPluginName = pluginName; break; } } @@ -107,9 +113,9 @@ Script.include("/~/system/libraries/controllers.js"); var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(this.rightTriggerPress); - mapping.from([Controller.Standard.RTClick]).peek().to(this.rightTriggerClicked); + mapping.from([Controller.Standard.RTClick]).peek().to(this.rightTriggerClick); mapping.from([Controller.Standard.LT]).peek().to(this.leftTriggerPress); - mapping.from([Controller.Standard.LTClick]).peek().to(this.leftTriggerClicked); + mapping.from([Controller.Standard.LTClick]).peek().to(this.leftTriggerClick); Controller.enableMapping(MAPPING_NAME); this.cleanup = function () { diff --git a/scripts/system/controllers/nearGrab.js b/scripts/system/controllers/nearGrab.js index 691f66c4d3..fc805114ee 100644 --- a/scripts/system/controllers/nearGrab.js +++ b/scripts/system/controllers/nearGrab.js @@ -35,7 +35,7 @@ "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } - + return controllerJointIndex; } @@ -52,7 +52,11 @@ function entityIsGrabbable(props) { var grabbableProps = {}; - var userDataParsed = JSON.parse(props.userData); + var userDataParsed = null; + try { + userDataParsed = JSON.parse(props.userData); + } catch (err) { + } if (userDataParsed && userDataParsed.grabbable) { grabbableProps = userDataParsed.grabbable; } @@ -60,6 +64,7 @@ if (grabbableProps.hasOwnProperty("grabbable")) { grabbable = grabbableProps.grabbable; } + if (!grabbable) { return false; } @@ -86,6 +91,7 @@ this.previouslyUnhooked = {}; + // todo: does this change if the avatar changes? this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); this.thisHandIsParent = function(props) { @@ -113,8 +119,7 @@ return false; }; - this.startNearGrab = function (controllerData) { - var grabbedProperties = controllerData.nearbyEntityProperties[this.hand][this.grabbedThingID]; + this.startNearGrab = function (controllerData, grabbedProperties) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); var reparentProps = { @@ -154,24 +159,27 @@ }; this.isReady = function (controllerData) { - if (!controllerData.triggerPresses[this.hand]) { + if (controllerData.triggerClicks[this.hand] == 0) { return false; } - var grabbable = null; + var grabbedProperties = null; var bestDistance = 1000; - controllerData.nearbyEntityProperties[this.hand].forEach(function(nearbyEntityProperties) { - if (entityIsGrabbable(nearbyEntityProperties)) { - if (nearbyEntityProperties.distanceFromController < bestDistance) { - bestDistance = nearbyEntityProperties.distanceFromController; - grabbable = nearbyEntityProperties; + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + + for (var i = 0; i < nearbyEntityProperties.length; i ++) { + var props = nearbyEntityProperties[i]; + if (entityIsGrabbable(props)) { + if (props.distanceFromController < bestDistance) { + bestDistance = props.distanceFromController; + grabbedProperties = props; } } - }); + } - if (grabbable) { - this.grabbedThingID = grabbable.id; - this.startNearGrab(); + if (grabbedProperties) { + this.grabbedThingID = grabbedProperties.id; + this.startNearGrab(controllerData, grabbedProperties); return true; } else { return false; @@ -179,7 +187,7 @@ }; this.run = function (controllerData) { - if (!controllerData.triggerPresses[this.hand]) { + if (controllerData.triggerClicks[this.hand] == 0) { this.endNearGrab(controllerData); return false; } From cfe3981bc2a5c0192fba6b340cdd6b2efb2a38b0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Jul 2017 18:22:17 -0700 Subject: [PATCH 03/78] cleanups --- .../controllers/controllerDispatcher.js | 15 ++- .../controllers/controllerDispatcherUtils.js | 54 +++++++++++ scripts/system/controllers/nearGrab.js | 97 +++++-------------- 3 files changed, 84 insertions(+), 82 deletions(-) create mode 100644 scripts/system/controllers/controllerDispatcherUtils.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 8ffa0f0ca3..820bfe942c 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -5,21 +5,20 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation */ +/* global Script, Entities, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation, + LEFT_HAND, RIGHT_HAND */ controllerDispatcherPlugins = {}; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { var _this = this; - var LEFT_HAND = 0; - var RIGHT_HAND = 1; - - var NEAR_GRAB_RADIUS = 0.1; - var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand + var NEAR_MIN_RADIUS = 0.1; + var NEAR_MAX_RADIUS = 1.0; var DISPATCHER_PROPERTIES = [ @@ -72,13 +71,13 @@ Script.include("/~/system/libraries/controllers.js"); for (var i = LEFT_HAND; i <= RIGHT_HAND; i ++) { // todo: check controllerLocations[i].valid var controllerPosition = controllerLocations[i].position; - var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_GRAB_RADIUS); + var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); for (var j = 0; j < nearbyEntityIDs.length; j++) { var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position)); - if (props.distanceFromController < NEAR_GRAB_MAX_DISTANCE) { + if (props.distanceFromController < NEAR_MAX_RADIUS) { nearbyEntityProperties[i].push(props); } } diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js new file mode 100644 index 0000000000..d1c8143f98 --- /dev/null +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -0,0 +1,54 @@ +"use strict"; + +// controllerDispatcherUtils.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Camera, HMD, MyAvatar, getControllerJointIndex, + LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, getGrabbableData */ + +LEFT_HAND = 0; +RIGHT_HAND = 1; + +NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + +getGrabbableData = function (props) { + // look in userData for a "grabbable" key, return the value or some defaults + var grabbableData = {}; + var userDataParsed = null; + try { + userDataParsed = JSON.parse(props.userData); + } catch (err) { + } + if (userDataParsed && userDataParsed.grabbable) { + grabbableData = userDataParsed.grabbable; + } + if (!grabbableData.hasOwnProperty("grabbable")) { + grabbableData.grabbable = true; + } + + return grabbableData; +}; + + +getControllerJointIndex = function (hand) { + if (HMD.isHandControllerAvailable()) { + var controllerJointIndex = -1; + if (Camera.mode === "first person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else if (Camera.mode === "third person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + + return controllerJointIndex; + } + + return MyAvatar.getJointIndex("Head"); +}; diff --git a/scripts/system/controllers/nearGrab.js b/scripts/system/controllers/nearGrab.js index fc805114ee..bf02858ada 100644 --- a/scripts/system/controllers/nearGrab.js +++ b/scripts/system/controllers/nearGrab.js @@ -6,82 +6,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, HMD, Camera, MyAvatar, Controller, controllerDispatcherPlugins */ +/* global Script, Entities, MyAvatar, Controller, controllerDispatcherPlugins, + RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, getGrabbableData, NULL_UUID */ +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { - var LEFT_HAND = 0; - var RIGHT_HAND = 1; - var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; - var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; - var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; - - function getControllerJointIndex(hand) { - if (HMD.isHandControllerAvailable()) { - var controllerJointIndex = -1; - if (Camera.mode === "first person") { - controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); - } else if (Camera.mode === "third person") { - controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - } - - return controllerJointIndex; - } - - return MyAvatar.getJointIndex("Head"); - } - - function propsArePhysical(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; - } - - function entityIsGrabbable(props) { - var grabbableProps = {}; - var userDataParsed = null; - try { - userDataParsed = JSON.parse(props.userData); - } catch (err) { - } - if (userDataParsed && userDataParsed.grabbable) { - grabbableProps = userDataParsed.grabbable; - } - var grabbable = propsArePhysical(props); - if (grabbableProps.hasOwnProperty("grabbable")) { - grabbable = grabbableProps.grabbable; - } - - if (!grabbable) { - return false; - } - if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { - return false; - } - if (props.locked) { - return false; - } - if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { + function entityIsParentingGrabbable(props) { + var grabbable = getGrabbableData(props).grabbable; + if (!grabbable || + props.locked || + FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0 || + FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { return false; } return true; } - - function NearGrab(hand) { + function NearGrabParenting(hand) { this.priority = 5; this.hand = hand; @@ -119,7 +68,7 @@ return false; }; - this.startNearGrab = function (controllerData, grabbedProperties) { + this.startNearGrabParenting = function (controllerData, grabbedProperties) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); var reparentProps = { @@ -140,7 +89,7 @@ Entities.editEntity(this.grabbedThingID, reparentProps); }; - this.endNearGrab = function (controllerData) { + this.endNearGrabParenting = function (controllerData) { if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { Entities.editEntity(this.grabbedThingID, { parentID: this.previousParentID[this.grabbedThingID], @@ -169,7 +118,7 @@ for (var i = 0; i < nearbyEntityProperties.length; i ++) { var props = nearbyEntityProperties[i]; - if (entityIsGrabbable(props)) { + if (entityIsParentingGrabbable(props)) { if (props.distanceFromController < bestDistance) { bestDistance = props.distanceFromController; grabbedProperties = props; @@ -179,7 +128,7 @@ if (grabbedProperties) { this.grabbedThingID = grabbedProperties.id; - this.startNearGrab(controllerData, grabbedProperties); + this.startNearGrabParenting(controllerData, grabbedProperties); return true; } else { return false; @@ -188,29 +137,29 @@ this.run = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { - this.endNearGrab(controllerData); + this.endNearGrabParenting(controllerData); return false; } return true; }; } - var leftNearGrab = new NearGrab(LEFT_HAND); - leftNearGrab.name = "leftNearGrab"; + var leftNearGrabParenting = new NearGrabParenting(LEFT_HAND); + leftNearGrabParenting.name = "leftNearGrabParenting"; - var rightNearGrab = new NearGrab(RIGHT_HAND); - rightNearGrab.name = "rightNearGrab"; + var rightNearGrabParenting = new NearGrabParenting(RIGHT_HAND); + rightNearGrabParenting.name = "rightNearGrabParenting"; if (!controllerDispatcherPlugins) { controllerDispatcherPlugins = {}; } - controllerDispatcherPlugins.leftNearGrab = leftNearGrab; - controllerDispatcherPlugins.rightNearGrab = rightNearGrab; + controllerDispatcherPlugins.leftNearGrabParenting = leftNearGrabParenting; + controllerDispatcherPlugins.rightNearGrabParenting = rightNearGrabParenting; this.cleanup = function () { - delete controllerDispatcherPlugins.leftNearGrab; - delete controllerDispatcherPlugins.rightNearGrab; + delete controllerDispatcherPlugins.leftNearGrabParenting; + delete controllerDispatcherPlugins.rightNearGrabParenting; }; Script.scriptEnding.connect(this.cleanup); }()); From 0d0f147056b734804a246af7506e23009b0c712e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 9 Aug 2017 17:48:35 -0700 Subject: [PATCH 04/78] port action-near-grab to dispatcher --- scripts/defaultScripts.js | 4 +- .../controllers/controllerDispatcher.js | 14 +- .../controllers/controllerDispatcherUtils.js | 54 ++++- .../controllerModules/nearActionGrabEntity.js | 203 ++++++++++++++++++ .../nearParentGrabEntity.js} | 102 ++++----- .../system/controllers/controllerScripts.js | 3 +- 6 files changed, 321 insertions(+), 59 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/nearActionGrabEntity.js rename scripts/system/controllers/{nearGrab.js => controllerModules/nearParentGrabEntity.js} (59%) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index f4c7b42ee2..63f37f3199 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -20,11 +20,11 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/bubble.js", "system/snapshot.js", "system/help.js", - // "system/pal.js", // "system/mod.js", // older UX, if you prefer + "system/pal.js", // "system/mod.js", // older UX, if you prefer "system/makeUserConnection.js", "system/tablet-goto.js", "system/marketplaces/marketplaces.js", - // "system/edit.js", + "system/edit.js", "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 820bfe942c..1560ac5b44 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -68,9 +68,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); getControllerWorldLocation(Controller.Standard.RightHand, true)]; var nearbyEntityProperties = [[], []]; - for (var i = LEFT_HAND; i <= RIGHT_HAND; i ++) { - // todo: check controllerLocations[i].valid - var controllerPosition = controllerLocations[i].position; + for (var h = LEFT_HAND; h <= RIGHT_HAND; h ++) { + // todo: check controllerLocations[h].valid + var controllerPosition = controllerLocations[h].position; var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); for (var j = 0; j < nearbyEntityIDs.length; j++) { var entityID = nearbyEntityIDs[j]; @@ -78,9 +78,15 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); props.id = entityID; props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position)); if (props.distanceFromController < NEAR_MAX_RADIUS) { - nearbyEntityProperties[i].push(props); + nearbyEntityProperties[h].push(props); } } + // sort by distance from each hand + nearbyEntityProperties[h].sort(function (a, b) { + var aDistance = Vec3.distance(a.position, controllerLocations[h]); + var bDistance = Vec3.distance(b.position, controllerLocations[h]); + return aDistance - bDistance; + }); } var controllerData = { diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index d1c8143f98..8717be34d5 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -6,8 +6,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Camera, HMD, MyAvatar, getControllerJointIndex, - LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, getGrabbableData */ +/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, + MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + enableDispatcherModule, + disableDispatcherModule, + getGrabbableData, + entityIsGrabbable, + getControllerJointIndex, + propsArePhysical +*/ + +MSECS_PER_SEC = 1000.0; LEFT_HAND = 0; RIGHT_HAND = 1; @@ -15,6 +25,23 @@ RIGHT_HAND = 1; NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; +FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; + +HAPTIC_PULSE_STRENGTH = 1.0; +HAPTIC_PULSE_DURATION = 13.0; + + +enableDispatcherModule = function (moduleName, module, priority) { + if (!controllerDispatcherPlugins) { + controllerDispatcherPlugins = {}; + } + controllerDispatcherPlugins[moduleName] = module; +}; + +disableDispatcherModule = function (moduleName) { + delete controllerDispatcherPlugins[moduleName]; +}; + getGrabbableData = function (props) { // look in userData for a "grabbable" key, return the value or some defaults var grabbableData = {}; @@ -29,10 +56,25 @@ getGrabbableData = function (props) { if (!grabbableData.hasOwnProperty("grabbable")) { grabbableData.grabbable = true; } + if (!grabbableData.hasOwnProperty("ignoreIK")) { + grabbableData.ignoreIK = true; + } + if (!grabbableData.hasOwnProperty("kinematicGrab")) { + grabbableData.kinematicGrab = false; + } return grabbableData; }; +entityIsGrabbable = function (props) { + var grabbable = getGrabbableData(props).grabbable; + if (!grabbable || + props.locked || + FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + return false; + } + return true; +}; getControllerJointIndex = function (hand) { if (HMD.isHandControllerAvailable()) { @@ -52,3 +94,11 @@ getControllerJointIndex = function (hand) { return MyAvatar.getJointIndex("Head"); }; + +propsArePhysical = function (props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; +}; diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js new file mode 100644 index 0000000000..57df123c1d --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -0,0 +1,203 @@ +"use strict"; + +// nearActionGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, + Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + + function NearActionGrabEntity(hand) { + this.hand = hand; + this.grabbedThingID = null; + this.actionID = null; // action this script created... + + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position + var ACTION_TTL = 15; // seconds + var ACTION_TTL_REFRESH = 5; + + // XXX does handJointIndex change if the avatar changes? + this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + this.controllerJointIndex = getControllerJointIndex(this.hand); + + + // handPosition is where the avatar's hand appears to be, in-world. + this.getHandPosition = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmPosition(); + } else { + return MyAvatar.getLeftPalmPosition(); + } + }; + + this.getHandRotation = function () { + if (this.hand === RIGHT_HAND) { + return MyAvatar.getRightPalmRotation(); + } else { + return MyAvatar.getLeftPalmRotation(); + } + }; + + + this.startNearGrabAction = function (controllerData, grabbedProperties) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + + var grabbableData = getGrabbableData(grabbedProperties); + this.ignoreIK = grabbableData.ignoreIK; + this.kinematicGrab = grabbableData.kinematicGrab; + + var handRotation; + var handPosition; + if (this.ignoreIK) { + var controllerID = + (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var controllerLocation = getControllerWorldLocation(controllerID, false); + handRotation = controllerLocation.orientation; + handPosition = controllerLocation.position; + } else { + handRotation = this.getHandRotation(); + handPosition = this.getHandPosition(); + } + + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + + var now = Date.now(); + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); + + if (this.actionID) { + Entities.deleteAction(this.grabbedThingID, this.actionID); + } + this.actionID = Entities.addAction("hold", this.grabbedThingID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: this.kinematicGrab, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_UUID) { + this.actionID = null; + return; + } + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: this.grabbedThingID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + }; + + // this is for when the action creation failed, before + this.restartNearGrabAction = function (controllerData) { + var props = Entities.getEntityProperties(this.grabbedThingID, ["position", "rotation", "userData"]); + if (props && entityIsGrabbable(props)) { + this.startNearGrabAction(controllerData, props); + } + }; + + // this is for when the action is going to time-out + this.refreshNearGrabAction = function (controllerData) { + var now = Date.now(); + if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { + // if less than a 5 seconds left, refresh the actions ttl + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: this.kinematicGrab, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (success) { + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); + } else { + print("continueNearGrabbing -- updateAction failed"); + this.restartNearGrabAction(controllerData); + } + } + }; + + this.endNearGrabAction = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + + Entities.deleteAction(this.grabbedThingID, this.actionID); + this.actionID = null; + + this.grabbedThingID = null; + }; + + this.isReady = function (controllerData) { + if (controllerData.triggerClicks[this.hand] == 0) { + return false; + } + + var grabbedProperties = null; + // nearbyEntityProperties is already sorted by length from controller + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + if (entityIsGrabbable(props)) { + grabbedProperties = props; + break; + } + } + + if (grabbedProperties) { + if (!propsArePhysical(grabbedProperties)) { + return false; // let nearParentGrabEntity handle it + } + this.grabbedThingID = grabbedProperties.id; + this.startNearGrabAction(controllerData, grabbedProperties); + return true; + } else { + return false; + } + }; + + this.run = function (controllerData) { + if (controllerData.triggerClicks[this.hand] == 0) { + this.endNearGrabAction(controllerData); + return false; + } + + if (!this.actionID) { + this.restartNearGrabAction(controllerData); + } + + this.refreshNearGrabAction(controllerData); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args); + + return true; + }; + } + + enableDispatcherModule("LeftNearActionGrabEntity", new NearActionGrabEntity(LEFT_HAND), 500); + enableDispatcherModule("RightNearActionGrabEntity", new NearActionGrabEntity(RIGHT_HAND), 500); + + this.cleanup = function () { + disableDispatcherModule("LeftNearActionGrabEntity"); + disableDispatcherModule("RightNearActionGrabEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/nearGrab.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js similarity index 59% rename from scripts/system/controllers/nearGrab.js rename to scripts/system/controllers/controllerModules/nearParentGrabEntity.js index bf02858ada..70e8bb363a 100644 --- a/scripts/system/controllers/nearGrab.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -1,38 +1,24 @@ "use strict"; -// nearGrab.js +// nearParentGrabEntity.js // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, MyAvatar, Controller, controllerDispatcherPlugins, - RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, getGrabbableData, NULL_UUID */ +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, + getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + FORBIDDEN_GRAB_TYPES, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION +*/ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { - var HAPTIC_PULSE_STRENGTH = 1.0; - var HAPTIC_PULSE_DURATION = 13.0; - - var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; - var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; - - function entityIsParentingGrabbable(props) { - var grabbable = getGrabbableData(props).grabbable; - if (!grabbable || - props.locked || - FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0 || - FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { - return false; - } - return true; - } - - function NearGrabParenting(hand) { - this.priority = 5; + // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + function NearParentingGrabEntity(hand) { this.hand = hand; this.grabbedThingID = null; this.previousParentID = {}; @@ -40,8 +26,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.previouslyUnhooked = {}; - // todo: does this change if the avatar changes? + // XXX does handJointIndex change if the avatar changes? this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + this.controllerJointIndex = getControllerJointIndex(this.hand); this.thisHandIsParent = function(props) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { @@ -68,12 +55,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return false; }; - this.startNearGrabParenting = function (controllerData, grabbedProperties) { + this.startNearParentingGrabEntity = function (controllerData, grabbedProperties) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + var handJointIndex; + // if (this.ignoreIK) { + // handJointIndex = this.controllerJointIndex; + // } else { + // handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + // } + handJointIndex = this.controllerJointIndex; + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "startNearGrab", args); + var reparentProps = { parentID: AVATAR_SELF_ID, - parentJointIndex: getControllerJointIndex(this.hand), + parentJointIndex: handJointIndex, velocity: {x: 0, y: 0, z: 0}, angularVelocity: {x: 0, y: 0, z: 0} }; @@ -87,9 +85,15 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; } Entities.editEntity(this.grabbedThingID, reparentProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: this.grabbedThingID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); }; - this.endNearGrabParenting = function (controllerData) { + this.endNearParentingGrabEntity = function (controllerData) { if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { Entities.editEntity(this.grabbedThingID, { parentID: this.previousParentID[this.grabbedThingID], @@ -104,6 +108,10 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); angularVelocity: {x: 0, y: 0, z: 0} }); } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + this.grabbedThingID = null; }; @@ -113,22 +121,22 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } var grabbedProperties = null; - var bestDistance = 1000; + // nearbyEntityProperties is already sorted by length from controller var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; - - for (var i = 0; i < nearbyEntityProperties.length; i ++) { + for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; - if (entityIsParentingGrabbable(props)) { - if (props.distanceFromController < bestDistance) { - bestDistance = props.distanceFromController; - grabbedProperties = props; - } + if (entityIsGrabbable(props)) { + grabbedProperties = props; + break; } } if (grabbedProperties) { + if (propsArePhysical(grabbedProperties)) { + return false; // let nearActionGrabEntity handle it + } this.grabbedThingID = grabbedProperties.id; - this.startNearGrabParenting(controllerData, grabbedProperties); + this.startNearParentingGrabEntity(controllerData, grabbedProperties); return true; } else { return false; @@ -137,29 +145,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.run = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { - this.endNearGrabParenting(controllerData); + this.endNearParentingGrabEntity(controllerData); return false; } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args); + return true; }; } - var leftNearGrabParenting = new NearGrabParenting(LEFT_HAND); - leftNearGrabParenting.name = "leftNearGrabParenting"; - - var rightNearGrabParenting = new NearGrabParenting(RIGHT_HAND); - rightNearGrabParenting.name = "rightNearGrabParenting"; - - if (!controllerDispatcherPlugins) { - controllerDispatcherPlugins = {}; - } - controllerDispatcherPlugins.leftNearGrabParenting = leftNearGrabParenting; - controllerDispatcherPlugins.rightNearGrabParenting = rightNearGrabParenting; - + enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND), 500); + enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND), 500); this.cleanup = function () { - delete controllerDispatcherPlugins.leftNearGrabParenting; - delete controllerDispatcherPlugins.rightNearGrabParenting; + disableDispatcherModule("LeftNearParentingGrabEntity"); + disableDispatcherModule("RightNearParentingGrabEntity"); }; Script.scriptEnding.connect(this.cleanup); }()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 5adfd93745..efc187dd86 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -19,7 +19,8 @@ var CONTOLLER_SCRIPTS = [ "toggleAdvancedMovementForHandControllers.js", "ControllerDispatcher.js", - "nearGrab.js" + "controllerModules/nearParentGrabEntity.js", + "controllerModules/nearActionGrabEntity.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From b4a54b017ac173be7c6184117b4fbb7d567b4d17 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 11 Aug 2017 12:15:41 -0700 Subject: [PATCH 05/78] plugins occupy activity-slots and more than one plugin may run at a time, as long as their slots aren't in conflict --- .../controllers/controllerDispatcher.js | 93 ++++++++++++++----- .../controllers/controllerDispatcherUtils.js | 15 +++ .../controllerModules/nearActionGrabEntity.js | 12 ++- .../controllerModules/nearParentGrabEntity.js | 15 ++- 4 files changed, 105 insertions(+), 30 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 1560ac5b44..9e454eafc0 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -20,7 +20,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var NEAR_MIN_RADIUS = 0.1; var NEAR_MAX_RADIUS = 1.0; - var DISPATCHER_PROPERTIES = [ "position", "registrationPoint", @@ -39,7 +38,39 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); "userData" ]; - this.runningPluginName = null; + // a module can occupy one or more slots while it's running. If all the required slots for a module are + // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name + // is stored as the value, rather than false. + this.activitySlots = { + leftHand: false, + rightHand: false + }; + + this.slotsAreAvailable = function (plugin) { + for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { + if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { + return false; // something is already using a slot which _this plugin requires + } + } + return true; + }; + + this.markSlots = function (plugin, used) { + for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { + _this.activitySlots[plugin.parameters.activitySlots[i]] = used; + } + }; + + this.unmarkSlotsForPluginName = function (runningPluginName) { + // this is used to free activity-slots when a plugin is deactivated while it's running. + for (var activitySlot in _this.activitySlots) { + if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] == runningPluginName) { + _this.activitySlots[activitySlot] = false; + } + } + }; + + this.runningPluginNames = {}; this.leftTriggerValue = 0; this.leftTriggerClicked = 0; this.rightTriggerValue = 0; @@ -68,7 +99,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); getControllerWorldLocation(Controller.Standard.RightHand, true)]; var nearbyEntityProperties = [[], []]; - for (var h = LEFT_HAND; h <= RIGHT_HAND; h ++) { + for (var h = LEFT_HAND; h <= RIGHT_HAND; h++) { // todo: check controllerLocations[h].valid var controllerPosition = controllerLocations[h].position; var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); @@ -82,11 +113,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } } // sort by distance from each hand - nearbyEntityProperties[h].sort(function (a, b) { + var sorter = function (a, b) { var aDistance = Vec3.distance(a.position, controllerLocations[h]); var bDistance = Vec3.distance(b.position, controllerLocations[h]); return aDistance - bDistance; - }); + }; + nearbyEntityProperties[h].sort(sorter); } var controllerData = { @@ -96,20 +128,35 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); nearbyEntityProperties: nearbyEntityProperties, }; - if (_this.runningPluginName) { - var plugin = controllerDispatcherPlugins[_this.runningPluginName]; - if (!plugin || !plugin.run(controllerData)) { - _this.runningPluginName = null; + + // check for plugins that would like to start + for (var pluginName in controllerDispatcherPlugins) { + // TODO sort names by plugin.priority + if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { + var candidatePlugin = controllerDispatcherPlugins[pluginName]; + if (_this.slotsAreAvailable(candidatePlugin) && candidatePlugin.isReady(controllerData)) { + // this plugin will start. add it to the list of running plugins and mark the + // activity-slots which this plugin consumes as "in use" + _this.runningPluginNames[pluginName] = true; + _this.markSlots(candidatePlugin, pluginName); + } } - } else if (controllerDispatcherPlugins) { - for (var pluginName in controllerDispatcherPlugins) { - // TODO sort names by plugin.priority - if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { - var candidatePlugin = controllerDispatcherPlugins[pluginName]; - if (candidatePlugin.isReady(controllerData)) { - _this.runningPluginName = pluginName; - break; - } + } + + // give time to running plugins + for (var runningPluginName in _this.runningPluginNames) { + if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) { + var plugin = controllerDispatcherPlugins[runningPluginName]; + if (!plugin) { + // plugin was deactivated while running. find the activity-slots it was using and make + // them available. + delete _this.runningPluginNames[runningPluginName]; + _this.unmarkSlotsForPluginName(runningPluginName); + } else if (!plugin.run(controllerData)) { + // plugin is finished running, for now. remove it from the list + // of running plugins and mark its activity-slots as "not in use" + delete _this.runningPluginNames[runningPluginName]; + _this.markSlots(plugin, false); } } } @@ -117,14 +164,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; var mapping = Controller.newMapping(MAPPING_NAME); - mapping.from([Controller.Standard.RT]).peek().to(this.rightTriggerPress); - mapping.from([Controller.Standard.RTClick]).peek().to(this.rightTriggerClick); - mapping.from([Controller.Standard.LT]).peek().to(this.leftTriggerPress); - mapping.from([Controller.Standard.LTClick]).peek().to(this.leftTriggerClick); + mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress); + mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick); + mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress); + mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick); Controller.enableMapping(MAPPING_NAME); this.cleanup = function () { - Script.update.disconnect(this.update); + Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); }; diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 8717be34d5..7f494761f5 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -9,6 +9,7 @@ /* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + makeDispatcherModuleParameters, enableDispatcherModule, disableDispatcherModule, getGrabbableData, @@ -31,6 +32,20 @@ HAPTIC_PULSE_STRENGTH = 1.0; HAPTIC_PULSE_DURATION = 13.0; +// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step +// activitySlots -- indicates which "slots" must not yet be in use for this module to start +// requiredDataForStart -- which "situation" parts this module looks at to decide if it will start +// sleepMSBetweenRuns -- how long to wait between calls to this module's "run" method +makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForStart, sleepMSBetweenRuns) { + return { + priority: priority, + activitySlots: activitySlots, + requiredDataForStart: requiredDataForStart, + sleepMSBetweenRuns: sleepMSBetweenRuns + }; +}; + + enableDispatcherModule = function (moduleName, module, priority) { if (!controllerDispatcherPlugins) { controllerDispatcherPlugins = {}; diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 57df123c1d..d6b5ffe4f7 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -8,7 +8,7 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, - Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation + Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -21,6 +21,12 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; this.actionID = null; // action this script created... + this.parameters = makeDispatcherModuleParameters( + 500, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var ACTION_TTL = 15; // seconds var ACTION_TTL_REFRESH = 5; @@ -192,8 +198,8 @@ Script.include("/~/system/libraries/controllers.js"); }; } - enableDispatcherModule("LeftNearActionGrabEntity", new NearActionGrabEntity(LEFT_HAND), 500); - enableDispatcherModule("RightNearActionGrabEntity", new NearActionGrabEntity(RIGHT_HAND), 500); + enableDispatcherModule("LeftNearActionGrabEntity", new NearActionGrabEntity(LEFT_HAND)); + enableDispatcherModule("RightNearActionGrabEntity", new NearActionGrabEntity(RIGHT_HAND)); this.cleanup = function () { disableDispatcherModule("LeftNearActionGrabEntity"); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 70e8bb363a..58bd3d2dab 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -7,8 +7,9 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, - getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, - FORBIDDEN_GRAB_TYPES, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION + getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + makeDispatcherModuleParameters, entityIsGrabbable */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -25,6 +26,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.previousParentJointIndex = {}; this.previouslyUnhooked = {}; + this.parameters = makeDispatcherModuleParameters( + 500, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + // XXX does handJointIndex change if the avatar changes? this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); @@ -156,8 +163,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; } - enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND), 500); - enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND), 500); + enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND)); + enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND)); this.cleanup = function () { disableDispatcherModule("LeftNearParentingGrabEntity"); From 7ae41064db48b596161c73e1fa31700c88be6e09 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Aug 2017 11:19:39 -0700 Subject: [PATCH 06/78] tablet stylus works again --- interface/src/Application.cpp | 1 + scripts/defaultScripts.js | 2 +- .../controllers/controllerDispatcher.js | 73 +- .../controllers/controllerDispatcherUtils.js | 8 +- .../controllerModules/tabletStylusInput.js | 685 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 5 +- 6 files changed, 756 insertions(+), 18 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/tabletStylusInput.js diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 67bb96fa9f..d34f08d052 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1795,6 +1795,7 @@ QString Application::getUserAgent() { void Application::toggleTabletUI(bool shouldOpen) const { auto tabletScriptingInterface = DependencyManager::get(); auto hmd = DependencyManager::get(); + qDebug() << "Application::toggleTabletUI" << shouldOpen << hmd->getShouldShowTablet(); if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 63f37f3199..2270118861 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -28,7 +28,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - // "system/tablet-ui/tabletUI.js" + "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 9e454eafc0..6283b3c583 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -38,6 +38,16 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); "userData" ]; + + var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update + var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var lastInterval = Date.now(); + var intervalCount = 0; + var totalDelta = 0; + var totalVariance = 0; + var highVarianceCount = 0; + var veryhighVarianceCount = 0; + // a module can occupy one or more slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. @@ -77,26 +87,58 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.rightTriggerClicked = 0; - this.leftTriggerPress = function(value) { + this.leftTriggerPress = function (value) { _this.leftTriggerValue = value; }; - this.leftTriggerClick = function(value) { + this.leftTriggerClick = function (value) { _this.leftTriggerClicked = value; }; - this.rightTriggerPress = function(value) { + this.rightTriggerPress = function (value) { _this.rightTriggerValue = value; }; - this.rightTriggerClick = function(value) { + this.rightTriggerClick = function (value) { _this.rightTriggerClicked = value; }; - this.update = function () { + this.dataGatherers = {}; + this.dataGatherers.leftControllerLocation = function () { + return getControllerWorldLocation(Controller.Standard.LeftHand, true); + }; + this.dataGatherers.rightControllerLocation = function () { + return getControllerWorldLocation(Controller.Standard.RightHand, true); + }; - var controllerLocations = [getControllerWorldLocation(Controller.Standard.LeftHand, true), - getControllerWorldLocation(Controller.Standard.RightHand, true)]; + this.updateTimings = function () { + intervalCount++; + var thisInterval = Date.now(); + var deltaTimeMsec = thisInterval - lastInterval; + var deltaTime = deltaTimeMsec / 1000; + lastInterval = thisInterval; + + totalDelta += deltaTimeMsec; + + var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); + totalVariance += variance; + + if (variance > 1) { + highVarianceCount++; + } + + if (variance > 5) { + veryhighVarianceCount++; + } + + return deltaTime; + }; + + this.update = function () { + var deltaTime = this.updateTimings(); + + var controllerLocations = [_this.dataGatherers.leftControllerLocation(), + _this.dataGatherers.rightControllerLocation()]; var nearbyEntityProperties = [[], []]; for (var h = LEFT_HAND; h <= RIGHT_HAND; h++) { @@ -113,12 +155,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } } // sort by distance from each hand - var sorter = function (a, b) { - var aDistance = Vec3.distance(a.position, controllerLocations[h]); - var bDistance = Vec3.distance(b.position, controllerLocations[h]); - return aDistance - bDistance; + var makeSorter = function (handIndex) { + return function (a, b) { + var aDistance = Vec3.distance(a.position, controllerLocations[handIndex]); + var bDistance = Vec3.distance(b.position, controllerLocations[handIndex]); + return aDistance - bDistance; + }; }; - nearbyEntityProperties[h].sort(sorter); + nearbyEntityProperties[h].sort(makeSorter(h)); } var controllerData = { @@ -128,13 +172,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); nearbyEntityProperties: nearbyEntityProperties, }; + // print("QQQ dispatcher " + JSON.stringify(_this.runningPluginNames) + " : " + JSON.stringify(_this.activitySlots)); // check for plugins that would like to start for (var pluginName in controllerDispatcherPlugins) { // TODO sort names by plugin.priority if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { var candidatePlugin = controllerDispatcherPlugins[pluginName]; - if (_this.slotsAreAvailable(candidatePlugin) && candidatePlugin.isReady(controllerData)) { + if (_this.slotsAreAvailable(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { // this plugin will start. add it to the list of running plugins and mark the // activity-slots which this plugin consumes as "in use" _this.runningPluginNames[pluginName] = true; @@ -152,7 +197,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // them available. delete _this.runningPluginNames[runningPluginName]; _this.unmarkSlotsForPluginName(runningPluginName); - } else if (!plugin.run(controllerData)) { + } else if (!plugin.run(controllerData, deltaTime)) { // plugin is finished running, for now. remove it from the list // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 7f494761f5..39fcf1ae66 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -8,7 +8,7 @@ /* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, - HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, ZERO_VEC, ONE_VEC, DEFAULT_REGISTRATION_POINT, INCHES_TO_METERS, makeDispatcherModuleParameters, enableDispatcherModule, disableDispatcherModule, @@ -19,6 +19,10 @@ */ MSECS_PER_SEC = 1000.0; +INCHES_TO_METERS = 1.0 / 39.3701; + +ZERO_VEC = { x: 0, y: 0, z: 0 }; +ONE_VEC = { x: 1, y: 1, z: 1 }; LEFT_HAND = 0; RIGHT_HAND = 1; @@ -31,6 +35,8 @@ FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; HAPTIC_PULSE_STRENGTH = 1.0; HAPTIC_PULSE_DURATION = 13.0; +DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; + // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js new file mode 100644 index 0000000000..7e9f69959f --- /dev/null +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -0,0 +1,685 @@ +"use strict"; + +// tabletStylusInput.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + NULL_UUID, enableDispatcherModule, disableDispatcherModule, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + + // triggered when stylus presses a web overlay/entity + var HAPTIC_STYLUS_STRENGTH = 1.0; + var HAPTIC_STYLUS_DURATION = 20.0; + + var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; + var WEB_STYLUS_LENGTH = 0.2; + var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand + + + function stylusTargetHasKeyboardFocus(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + return Entities.keyboardFocusEntity === stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + return Overlays.keyboardFocusOverlay === stylusTarget.overlayID; + } + } + + function setKeyboardFocusOnStylusTarget(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID && + Entities.wantsHandControllerPointerEvents(stylusTarget.entityID)) { + Overlays.keyboardFocusOverlay = NULL_UUID; + Entities.keyboardFocusEntity = stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.keyboardFocusOverlay = stylusTarget.overlayID; + Entities.keyboardFocusEntity = NULL_UUID; + } + } + + function sendHoverEnterEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendHoverEnterEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendHoverEnterOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendHoverOverEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchStartEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMousePressOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMousePressOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchEndEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + // will return undefined if overlayID does not exist. + function calculateStylusTargetFromOverlay(stylusTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + if (overlayPosition === undefined) { + return; + } + + // project stylusTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + if (overlayRotation === undefined) { + return; + } + var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, overlayPosition), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // calclulate normalized position + var invRot = Quat.inverse(overlayRotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var dpi = Overlays.getProperty(overlayID, "dpi"); + + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property + // is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + if (resolution === undefined) { + return; + } + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + if (scale === undefined) { + return; + } + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; + } + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y }; // flip y-axis + + return { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: dimensions, + valid: true + }; + } + + // will return undefined if entity does not exist. + function calculateStylusTargetFromEntity(stylusTip, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return; + } + + // project stylus tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, props.position), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + + return { + entityID: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; + } + + function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + // check to see if the projected stylusTip is within within the 2d border + var borderMin = {x: -edgeBorder, y: -edgeBorder}; + var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; + if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && + stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && + stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { + return true; + } + } + return false; + } + + function calculateNearestStylusTarget(stylusTargets) { + var nearestStylusTarget; + + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && + stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && + stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { + nearestStylusTarget = stylusTarget; + } + } + + return nearestStylusTarget; + } + + function getFingerWorldLocation(hand) { + var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; + + var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); + var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); + var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); + + return { + position: worldFingerPosition, + orientation: worldFingerRotation, + rotation: worldFingerRotation, + valid: true + }; + } + + function distance2D(a, b) { + var dx = (a.x - b.x); + var dy = (a.y - b.y); + return Math.sqrt(dx * dx + dy * dy); + } + + function TabletStylusInput(hand) { + this.hand = hand; + this.previousStylusTouchingTarget = false; + this.stylusTouchingTarget = false; + + this.useFingerInsteadOfStylus = false; + this.fingerPointing = false; + + // initialize stylus tip + var DEFAULT_STYLUS_TIP = { + position: {x: 0, y: 0, z: 0}, + orientation: {x: 0, y: 0, z: 0, w: 0}, + rotation: {x: 0, y: 0, z: 0, w: 0}, + velocity: {x: 0, y: 0, z: 0}, + valid: false + }; + this.stylusTip = DEFAULT_STYLUS_TIP; + + + this.parameters = makeDispatcherModuleParameters( + 400, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.getOtherHandController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + }; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.updateFingerAsStylusSetting = function () { + var DEFAULT_USE_FINGER_AS_STYLUS = false; + var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); + if (USE_FINGER_AS_STYLUS === "") { + USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS; + } + if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) { + this.useFingerInsteadOfStylus = true; + } else { + this.useFingerInsteadOfStylus = false; + } + }; + + this.updateStylusTip = function() { + if (this.useFingerInsteadOfStylus) { + this.stylusTip = getFingerWorldLocation(this.hand); + } else { + this.stylusTip = getControllerWorldLocation(this.handToController(), true); + + // translate tip forward according to constant. + var TIP_OFFSET = {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0}; + this.stylusTip.position = Vec3.sum(this.stylusTip.position, + Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); + } + + // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. + var pose = Controller.getPoseValue(this.handToController()); + if (pose.valid) { + var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation)); + var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); + var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity); + var tipVelocity = Vec3.sum(worldControllerLinearVel, + Vec3.cross(worldControllerAngularVel, + Vec3.subtract(this.stylusTip.position, worldControllerPos))); + this.stylusTip.velocity = tipVelocity; + } else { + this.stylusTip.velocity = {x: 0, y: 0, z: 0}; + } + }; + + this.showStylus = function() { + if (this.stylus) { + return; + } + + var stylusProperties = { + name: "stylus", + url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", + loadPriority: 10.0, + localPosition: Vec3.sum({ x: 0.0, + y: WEB_TOUCH_Y_OFFSET, + z: 0.0 }, + getGrabPointSphereOffset(this.handToController())), + localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + dimensions: { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false, + parentID: AVATAR_SELF_ID, + parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND") + }; + this.stylus = Overlays.addOverlay("model", stylusProperties); + }; + + this.hideStylus = function() { + if (!this.stylus) { + return; + } + Overlays.deleteOverlay(this.stylus); + this.stylus = null; + }; + + this.stealTouchFocus = function(stylusTarget) { + // send hover events to target + // record the entity or overlay we are hovering over. + if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || + (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { + this.getOtherHandController().relinquishTouchFocus(); + } + this.requestTouchFocus(stylusTarget); + }; + + this.requestTouchFocus = function(stylusTarget) { + + // send hover events to target if we can. + // record the entity or overlay we are hovering over. + if (stylusTarget.entityID && + stylusTarget.entityID !== this.hoverEntity && + stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { + this.hoverEntity = stylusTarget.entityID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } else if (stylusTarget.overlayID && + stylusTarget.overlayID !== this.hoverOverlay && + stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { + this.hoverOverlay = stylusTarget.overlayID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } + }; + + this.hasTouchFocus = function(stylusTarget) { + return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || + (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); + }; + + this.relinquishTouchFocus = function() { + // send hover leave event. + var pointerEvent = { type: "Move", id: this.hand + 1 }; + if (this.hoverEntity) { + Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); + this.hoverEntity = null; + } else if (this.hoverOverlay) { + Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); + this.hoverOverlay = null; + } + }; + + this.pointFinger = function(value) { + var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; + if (this.fingerPointing !== value) { + var message; + if (this.hand === RIGHT_HAND) { + message = { pointRightIndex: value }; + } else { + message = { pointLeftIndex: value }; + } + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); + this.fingerPointing = value; + } + }; + + this.processStylus = function(controllerData) { + this.updateStylusTip(); + + if (!this.stylusTip.valid) { + this.pointFinger(false); + this.hideStylus(); + return false; + } + + if (this.useFingerInsteadOfStylus) { + this.hideStylus(); + } + + // build list of stylus targets, near the stylusTip + var stylusTargets = []; + var candidateEntities = controllerData.nearbyEntityProperties; + var i, props, stylusTarget; + for (i = 0; i < candidateEntities.length; i++) { + props = candidateEntities[i]; + if (props && props.type === "Web") { + stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, candidateEntities[i]); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } + } + + // add the tabletScreen, if it is valid + if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID && + Overlays.getProperty(HMD.tabletScreenID, "visible")) { + stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.tabletScreenID); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } + + // add the tablet home button. + if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID && + Overlays.getProperty(HMD.homeButtonID, "visible")) { + stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, HMD.homeButtonID); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } + + var TABLET_MIN_HOVER_DISTANCE = 0.01; + var TABLET_MAX_HOVER_DISTANCE = 0.1; + var TABLET_MIN_TOUCH_DISTANCE = -0.05; + var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; + var EDGE_BORDER = 0.075; + + var hysteresisOffset = 0.0; + if (this.isNearStylusTarget) { + hysteresisOffset = 0.05; + } + + this.isNearStylusTarget = isNearStylusTarget(stylusTargets, EDGE_BORDER + hysteresisOffset, + TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset, + WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset); + + if (this.isNearStylusTarget) { + if (!this.useFingerInsteadOfStylus) { + this.showStylus(); + } else { + this.pointFinger(true); + } + } else { + this.hideStylus(); + this.pointFinger(false); + } + + var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets); + + if (nearestStylusTarget && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + nearestStylusTarget.distance < TABLET_MAX_HOVER_DISTANCE) { + + this.requestTouchFocus(nearestStylusTarget); + + if (!stylusTargetHasKeyboardFocus(nearestStylusTarget)) { + setKeyboardFocusOnStylusTarget(nearestStylusTarget); + } + + if (this.hasTouchFocus(nearestStylusTarget)) { + sendHoverOverEventToStylusTarget(this.hand, nearestStylusTarget); + } + + // filter out presses when tip is moving away from tablet. + // ensure that stylus is within bounding box by checking normalizedPosition + if (nearestStylusTarget.valid && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + nearestStylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE && + Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 && + nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 && + nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) { + + this.stylusTarget = nearestStylusTarget; + this.stylusTouchingTarget = true; + } + } else { + this.relinquishTouchFocus(); + } + + this.homeButtonTouched = false; + + if (this.isNearStylusTarget) { + return true; + } else { + this.pointFinger(false); + this.hideStylus(); + return false; + } + }; + + this.stylusTouchingEnter = function () { + this.stealTouchFocus(this.stylusTarget); + sendTouchStartEventToStylusTarget(this.hand, this.stylusTarget); + Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); + + this.touchingEnterTimer = 0; + this.touchingEnterStylusTarget = this.stylusTarget; + this.deadspotExpired = false; + + var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; + this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; + }; + + this.stylusTouchingExit = function () { + + if (this.stylusTarget === undefined) { + return; + } + + // special case to handle home button. + if (this.stylusTarget.overlayID === HMD.homeButtonID) { + Messages.sendLocalMessage("home", this.stylusTarget.overlayID); + } + + // send press event + if (this.deadspotExpired) { + sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget); + } else { + sendTouchEndEventToStylusTarget(this.hand, this.touchingEnterStylusTarget); + } + }; + + this.stylusTouching = function (controllerData, dt) { + + this.touchingEnterTimer += dt; + + if (this.stylusTarget.entityID) { + this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); + } else if (this.stylusTarget.overlayID) { + this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); + } + + var TABLET_MIN_TOUCH_DISTANCE = -0.1; + var TABLET_MAX_TOUCH_DISTANCE = 0.01; + + if (this.stylusTarget) { + if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.stylusTarget.position2D, + this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { + sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + this.deadspotExpired = true; + } + } else { + this.stylusTouchingTarget = false; + } + } else { + this.stylusTouchingTarget = false; + } + }; + + this.isReady = function (controllerData) { + return this.processStylus(controllerData); + }; + + this.run = function (controllerData, deltaTime) { + this.updateFingerAsStylusSetting(); + + if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { + this.stylusTouchingEnter(); + } + if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { + this.stylusTouchingExit(); + } + this.previousStylusTouchingTarget = this.stylusTouchingTarget; + + if (this.stylusTouchingTarget) { + this.stylusTouching(controllerData, deltaTime); + } + return this.processStylus(controllerData); + }; + + this.cleanup = function () { + this.hideStylus(); + }; + } + + var leftTabletStylusInput = new TabletStylusInput(LEFT_HAND); + var rightTabletStylusInput = new TabletStylusInput(RIGHT_HAND); + + enableDispatcherModule("LeftTabletStylusInput", leftTabletStylusInput); + enableDispatcherModule("RightTabletStylusInput", rightTabletStylusInput); + + + this.cleanup = function () { + disableDispatcherModule("LeftTabletStylusInput"); + disableDispatcherModule("RightTabletStylusInput"); + leftTabletStylusInput.cleanup(); + rightTabletStylusInput.cleanup(); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index efc187dd86..31c464ee49 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -13,14 +13,15 @@ var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", // "handControllerGrab.js", - // "handControllerPointer.js", + "handControllerPointer.js", // "grab.js", // "teleport.js", "toggleAdvancedMovementForHandControllers.js", "ControllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", - "controllerModules/nearActionGrabEntity.js" + "controllerModules/nearActionGrabEntity.js", + "controllerModules/tabletStylusInput.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 9fb1835a26433817fef0958903575f3a6297f572 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Aug 2017 12:38:36 -0700 Subject: [PATCH 07/78] grabbing overlays works, again --- .../controllers/controllerDispatcher.js | 32 +++- .../nearParentGrabOverlay.js | 167 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 1 + 3 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/nearParentGrabOverlay.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 6283b3c583..d2b6418ee9 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation, +/* global Script, Entities, Overlays, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation, LEFT_HAND, RIGHT_HAND */ controllerDispatcherPlugins = {}; @@ -48,7 +48,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var highVarianceCount = 0; var veryhighVarianceCount = 0; - // a module can occupy one or more slots while it's running. If all the required slots for a module are + // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { @@ -56,7 +56,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); rightHand: false }; - this.slotsAreAvailable = function (plugin) { + this.slotsAreAvailableForPlugin = function (plugin) { for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { return false; // something is already using a slot which _this plugin requires @@ -140,8 +140,28 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var controllerLocations = [_this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation()]; + // find 3d overlays near each hand + var nearbyOverlayIDs = []; + var h; + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + // todo: check controllerLocations[h].valid + var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MIN_RADIUS); + var makeOverlaySorter = function (handIndex) { + return function (a, b) { + var aPosition = Overlays.getProperty(a, "position"); + var aDistance = Vec3.distance(aPosition, controllerLocations[handIndex]); + var bPosition = Overlays.getProperty(b, "position"); + var bDistance = Vec3.distance(bPosition, controllerLocations[handIndex]); + return aDistance - bDistance; + }; + }; + nearbyOverlays.sort(makeOverlaySorter(h)); + nearbyOverlayIDs.push(nearbyOverlays); + } + + // find entities near each hand var nearbyEntityProperties = [[], []]; - for (var h = LEFT_HAND; h <= RIGHT_HAND; h++) { + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // todo: check controllerLocations[h].valid var controllerPosition = controllerLocations[h].position; var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); @@ -165,11 +185,13 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); nearbyEntityProperties[h].sort(makeSorter(h)); } + // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, + nearbyOverlayIDs: nearbyOverlayIDs }; // print("QQQ dispatcher " + JSON.stringify(_this.runningPluginNames) + " : " + JSON.stringify(_this.activitySlots)); @@ -179,7 +201,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // TODO sort names by plugin.priority if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { var candidatePlugin = controllerDispatcherPlugins[pluginName]; - if (_this.slotsAreAvailable(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { + if (_this.slotsAreAvailableForPlugin(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { // this plugin will start. add it to the list of running plugins and mark the // activity-slots which this plugin consumes as "in use" _this.runningPluginNames[pluginName] = true; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js new file mode 100644 index 0000000000..4a70b31820 --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -0,0 +1,167 @@ +"use strict"; + +// nearParentGrabOverlay.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, + getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + makeDispatcherModuleParameters, Overlays +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); + +(function() { + + // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; + // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; + + function NearParentingGrabOverlay(hand) { + this.hand = hand; + this.grabbedThingID = null; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.previouslyUnhooked = {}; + + this.parameters = makeDispatcherModuleParameters( + 500, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + + // XXX does handJointIndex change if the avatar changes? + this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + this.controllerJointIndex = getControllerJointIndex(this.hand); + + this.thisHandIsParent = function(props) { + if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { + return false; + } + + var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + if (props.parentJointIndex == handJointIndex) { + return true; + } + + var controllerJointIndex = this.controllerJointIndex; + if (props.parentJointIndex == controllerJointIndex) { + return true; + } + + var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + if (props.parentJointIndex == controllerCRJointIndex) { + return true; + } + + return false; + }; + + this.startNearParentingGrabOverlay = function (controllerData) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + + var handJointIndex; + // if (this.ignoreIK) { + // handJointIndex = this.controllerJointIndex; + // } else { + // handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + // } + handJointIndex = this.controllerJointIndex; + + var grabbedProperties = { + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + dynamic: false, + shapeType: "none" + }; + + var reparentProps = { + parentID: AVATAR_SELF_ID, + parentJointIndex: handJointIndex, + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0} + }; + + if (this.thisHandIsParent(grabbedProperties)) { + // this should never happen, but if it does, don't set previous parent to be this hand. + // this.previousParentID[this.grabbedThingID] = NULL; + // this.previousParentJointIndex[this.grabbedThingID] = -1; + } else { + this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; + this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; + } + Overlays.editOverlay(this.grabbedThingID, reparentProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: this.grabbedThingID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + }; + + this.endNearParentingGrabOverlay = function (controllerData) { + if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { + Overlays.editOverlay(this.grabbedThingID, { + parentID: NULL_UUID, + parentJointIndex: -1 + }); + } else { + // before we grabbed it, overlay was a child of something; put it back. + Overlays.editOverlay(this.grabbedThingID, { + parentID: this.previousParentID[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + }); + } + + this.grabbedThingID = null; + }; + + this.isReady = function (controllerData) { + if (controllerData.triggerClicks[this.hand] == 0) { + return false; + } + + this.grabbedThingID = null; + + var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; + print("candidateOverlays.length = " + candidateOverlays.length); + var grabbableOverlays = candidateOverlays.filter(function(overlayID) { + return Overlays.getProperty(overlayID, "grabbable"); + }); + print("grabbableOverlays.length = " + grabbableOverlays.length); + + if (grabbableOverlays.length > 0) { + this.grabbedThingID = grabbableOverlays[0]; + this.startNearParentingGrabOverlay(controllerData); + return true; + } else { + return false; + } + }; + + this.run = function (controllerData) { + if (controllerData.triggerClicks[this.hand] == 0) { + this.endNearParentingGrabOverlay(controllerData); + return false; + } else { + return true; + } + }; + } + + enableDispatcherModule("LeftNearParentingGrabOverlay", new NearParentingGrabOverlay(LEFT_HAND)); + enableDispatcherModule("RightNearParentingGrabOverlay", new NearParentingGrabOverlay(RIGHT_HAND)); + + this.cleanup = function () { + disableDispatcherModule("LeftNearParentingGrabOverlay"); + disableDispatcherModule("RightNearParentingGrabOverlay"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 31c464ee49..5a067e44c6 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -20,6 +20,7 @@ var CONTOLLER_SCRIPTS = [ "ControllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", + "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", "controllerModules/tabletStylusInput.js" ]; From 505b564c62122f81dda9c50d7b977cc02b7cd01d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Aug 2017 13:14:00 -0700 Subject: [PATCH 08/78] sort plugins isReady calls by plugin priority. hook up pickrays in data-gathering phase --- .../controllers/controllerDispatcher.js | 82 +++++++++++++++---- .../controllers/controllerDispatcherUtils.js | 2 + 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index d2b6418ee9..e666cd8c5b 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -5,10 +5,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, Overlays, controllerDispatcherPlugins, Controller, Vec3, getControllerWorldLocation, +/*jslint bitwise: true */ + +/* global Script, Entities, Overlays, Controller, Vec3, getControllerWorldLocation, RayPick, + controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, LEFT_HAND, RIGHT_HAND */ controllerDispatcherPlugins = {}; +controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); @@ -137,9 +141,38 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.update = function () { var deltaTime = this.updateTimings(); + if (controllerDispatcherPluginsNeedSort) { + this.orderedPluginNames = []; + for (var pluginName in controllerDispatcherPlugins) { + if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { + this.orderedPluginNames.push(pluginName); + } + } + this.orderedPluginNames.sort(function (a, b) { + return controllerDispatcherPlugins[a].priority < controllerDispatcherPlugins[b].priority; + }); + controllerDispatcherPluginsNeedSort = false; + } + var controllerLocations = [_this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation()]; + + // interface/src/raypick/LaserPointerManager.cpp | 62 +++++++++++++-------------- + // interface/src/raypick/LaserPointerManager.h | 13 +++--- + // interface/src/raypick/RayPickManager.cpp | 56 ++++++++++++------------ + // interface/src/raypick/RayPickManager.h | 13 +++--- + + + // raypick for each controller + var rayPicks = [ + RayPick.getPrevRayPickResult(_this.leftControllerRayPick), + RayPick.getPrevRayPickResult(_this.rightControllerRayPick) + ]; + // result.intersects + // result.distance + + // find 3d overlays near each hand var nearbyOverlayIDs = []; var h; @@ -191,22 +224,19 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, - nearbyOverlayIDs: nearbyOverlayIDs + nearbyOverlayIDs: nearbyOverlayIDs, + rayPicks: rayPicks }; - // print("QQQ dispatcher " + JSON.stringify(_this.runningPluginNames) + " : " + JSON.stringify(_this.activitySlots)); - - // check for plugins that would like to start - for (var pluginName in controllerDispatcherPlugins) { - // TODO sort names by plugin.priority - if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { - var candidatePlugin = controllerDispatcherPlugins[pluginName]; - if (_this.slotsAreAvailableForPlugin(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { - // this plugin will start. add it to the list of running plugins and mark the - // activity-slots which this plugin consumes as "in use" - _this.runningPluginNames[pluginName] = true; - _this.markSlots(candidatePlugin, pluginName); - } + // check for plugins that would like to start. ask in order of increasing priority value + for (var pluginIndex = 0; pluginIndex < this.orderedPluginNames.length; pluginIndex++) { + var orderedPluginName = this.orderedPluginNames[pluginIndex]; + var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; + if (_this.slotsAreAvailableForPlugin(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { + // this plugin will start. add it to the list of running plugins and mark the + // activity-slots which this plugin consumes as "in use" + _this.runningPluginNames[orderedPluginName] = true; + _this.markSlots(candidatePlugin, orderedPluginName); } } @@ -237,9 +267,31 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick); Controller.enableMapping(MAPPING_NAME); + + this.mouseRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true + }); + + this.leftControllerRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true + }); + this.rightControllerRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true + }); + + this.cleanup = function () { Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); + // RayPick.removeRayPick(_this.mouseRayPick); + RayPick.removeRayPick(_this.leftControllerRayPick); + RayPick.removeRayPick(_this.rightControllerRayPick); }; Script.scriptEnding.connect(this.cleanup); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 39fcf1ae66..d1ac6a842a 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -57,10 +57,12 @@ enableDispatcherModule = function (moduleName, module, priority) { controllerDispatcherPlugins = {}; } controllerDispatcherPlugins[moduleName] = module; + controllerDispatcherPluginsNeedSort = true; }; disableDispatcherModule = function (moduleName) { delete controllerDispatcherPlugins[moduleName]; + controllerDispatcherPluginsNeedSort = true; }; getGrabbableData = function (props) { From ec93172676e051b76531dedc39737217af06706b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Aug 2017 17:27:48 -0700 Subject: [PATCH 09/78] far-search rays work, but nothing else about far-grab. --- .../controllers/controllerDispatcher.js | 4 + .../controllers/controllerDispatcherUtils.js | 18 ++ .../controllerModules/farActionGrabEntity.js | 213 ++++++++++++++++++ .../nearParentGrabOverlay.js | 2 - .../system/controllers/controllerScripts.js | 1 + 5 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/farActionGrabEntity.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index e666cd8c5b..28252a0888 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -151,6 +151,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.orderedPluginNames.sort(function (a, b) { return controllerDispatcherPlugins[a].priority < controllerDispatcherPlugins[b].priority; }); + + print("controllerDispatcher: new plugin order: " + JSON.stringify(this.orderedPluginNames)); + controllerDispatcherPluginsNeedSort = false; } @@ -232,6 +235,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); for (var pluginIndex = 0; pluginIndex < this.orderedPluginNames.length; pluginIndex++) { var orderedPluginName = this.orderedPluginNames[pluginIndex]; var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; + if (_this.slotsAreAvailableForPlugin(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { // this plugin will start. add it to the list of running plugins and mark the // activity-slots which this plugin consumes as "in use" diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index d1ac6a842a..94d58c1c4d 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -9,6 +9,13 @@ /* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, ZERO_VEC, ONE_VEC, DEFAULT_REGISTRATION_POINT, INCHES_TO_METERS, + TRIGGER_OFF_VALUE, + TRIGGER_ON_VALUE, + PICK_MAX_DISTANCE, + DEFAULT_SEARCH_SPHERE_DISTANCE, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + COLORS_GRAB_DISTANCE_HOLD, makeDispatcherModuleParameters, enableDispatcherModule, disableDispatcherModule, @@ -37,6 +44,17 @@ HAPTIC_PULSE_DURATION = 13.0; DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; +TRIGGER_OFF_VALUE = 0.1; +TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab + +PICK_MAX_DISTANCE = 500; // max length of pick-ray +DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? + +COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; +COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; +COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; + + // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js new file mode 100644 index 0000000000..3d1964d639 --- /dev/null +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -0,0 +1,213 @@ +"use strict"; + +// farActionGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/*jslint bitwise: true */ + +/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, + getGrabPointSphereOffset, entityIsGrabbable, + enableDispatcherModule, disableDispatcherModule, + makeDispatcherModuleParameters, + PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, + +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + + var PICK_WITH_HAND_RAY = true; + + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; + + var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + + + function FarActionGrabEntity(hand) { + this.hand = hand; + this.grabbedThingID = null; + this.actionID = null; // action this script created... + + this.parameters = makeDispatcherModuleParameters( + 550, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.updateLaserPointer = function(controllerData, distanceHolding, distanceRotating) { + var SEARCH_SPHERE_SIZE = 0.011; + var MIN_SPHERE_SIZE = 0.0005; + var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE); + var dim = {x: radius, y: radius, z: radius}; + var mode = "hold"; + if (!distanceHolding && !distanceRotating) { + // mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "half"; + if (controllerData.triggerClicks[this.hand] + // || this.secondarySqueezed() // XXX + ) { + mode = "full"; + } else { + mode = "half"; + } + } + + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + if (mode === "full") { + var fullEndToEdit = PICK_WITH_HAND_RAY ? this.fullEnd : fullEnd; + fullEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: fullPath, end: fullEndToEdit}); + } else if (mode === "half") { + var halfEndToEdit = PICK_WITH_HAND_RAY ? this.halfEnd : halfEnd; + halfEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit}); + } + LaserPointers.enableLaserPointer(laserPointerID); + LaserPointers.setRenderState(laserPointerID, mode); + if (distanceHolding || distanceRotating) { + LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay); + } else { + LaserPointers.setLockEndUUID(laserPointerID, null, false); + } + }; + + this.laserPointerOff = function() { + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + LaserPointers.disableLaserPointer(laserPointerID); + }; + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.isReady = function (controllerData) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.updateLaserPointer(controllerData, false, false); + return true; + } else { + return false; + } + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + this.laserPointerOff(); + return false; + } + + + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + if (entityIsGrabbable(props)) { + this.laserPointerOff(); + return false; + } + } + + // this.updateLaserPointer(controllerData, false, false); + + // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); + + return true; + }; + + this.cleanup = function () { + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + }; + + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + } + + var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND); + var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity); + enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity); + + this.cleanup = function () { + leftFarActionGrabEntity.cleanup(); + rightFarActionGrabEntity.cleanup(); + disableDispatcherModule("LeftFarActionGrabEntity"); + disableDispatcherModule("RightFarActionGrabEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 4a70b31820..9ce0b95abb 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -131,11 +131,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.grabbedThingID = null; var candidateOverlays = controllerData.nearbyOverlayIDs[this.hand]; - print("candidateOverlays.length = " + candidateOverlays.length); var grabbableOverlays = candidateOverlays.filter(function(overlayID) { return Overlays.getProperty(overlayID, "grabbable"); }); - print("grabbableOverlays.length = " + grabbableOverlays.length); if (grabbableOverlays.length > 0) { this.grabbedThingID = grabbableOverlays[0]; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 5a067e44c6..552aec20a4 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -22,6 +22,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", + "controllerModules/farActionGrabEntity.js", "controllerModules/tabletStylusInput.js" ]; From e7437d66efdcb05cea4d1eb4e9026eb5b8a71f34 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 12 Aug 2017 17:49:02 -0700 Subject: [PATCH 10/78] if a raypick hits something that's very close, put it into the nearby-entities list --- .../controllers/controllerDispatcher.js | 40 ++++++++++--------- .../controllers/controllerDispatcherUtils.js | 5 ++- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28252a0888..4fc8c84d5f 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -8,8 +8,8 @@ /*jslint bitwise: true */ /* global Script, Entities, Overlays, Controller, Vec3, getControllerWorldLocation, RayPick, - controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, - LEFT_HAND, RIGHT_HAND */ + controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable, + LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS */ controllerDispatcherPlugins = {}; controllerDispatcherPluginsNeedSort = false; @@ -160,22 +160,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var controllerLocations = [_this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation()]; - - // interface/src/raypick/LaserPointerManager.cpp | 62 +++++++++++++-------------- - // interface/src/raypick/LaserPointerManager.h | 13 +++--- - // interface/src/raypick/RayPickManager.cpp | 56 ++++++++++++------------ - // interface/src/raypick/RayPickManager.h | 13 +++--- - - - // raypick for each controller - var rayPicks = [ - RayPick.getPrevRayPickResult(_this.leftControllerRayPick), - RayPick.getPrevRayPickResult(_this.rightControllerRayPick) - ]; - // result.intersects - // result.distance - - // find 3d overlays near each hand var nearbyOverlayIDs = []; var h; @@ -221,6 +205,26 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); nearbyEntityProperties[h].sort(makeSorter(h)); } + // raypick for each controller + var rayPicks = [ + RayPick.getPrevRayPickResult(_this.leftControllerRayPick), + RayPick.getPrevRayPickResult(_this.rightControllerRayPick) + ]; + // if the pickray hit something very nearby, put it into the nearby entities list + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + var nearEntityID = rayPicks[h].entityID; + if (nearEntityID) { + // XXX check to make sure this one isn't already in nearbyEntityProperties? + if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) { + var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); + nearbyProps.id = nearEntityID; + if (entityIsGrabbable(nearbyProps)) { + nearbyEntityProperties[h].push(nearbyProps); + } + } + } + } + // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 94d58c1c4d..e2cea96a42 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -13,6 +13,7 @@ TRIGGER_ON_VALUE, PICK_MAX_DISTANCE, DEFAULT_SEARCH_SPHERE_DISTANCE, + NEAR_GRAB_PICK_RADIUS, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, @@ -22,7 +23,8 @@ getGrabbableData, entityIsGrabbable, getControllerJointIndex, - propsArePhysical + propsArePhysical, + controllerDispatcherPluginsNeedSort */ MSECS_PER_SEC = 1000.0; @@ -49,6 +51,7 @@ TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate PICK_MAX_DISTANCE = 500; // max length of pick-ray DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? +NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; From 327bc23b5c728ebb32deb403cd38d8d039e2d4ce Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 11:41:12 -0700 Subject: [PATCH 11/78] get ready for highlighting --- .../controllers/controllerDispatcher.js | 26 ++-- .../controllers/controllerDispatcherUtils.js | 22 ++- .../controllerModules/farActionGrabEntity.js | 37 +++-- .../controllerModules/nearActionGrabEntity.js | 130 ++++++++++-------- .../controllerModules/nearParentGrabEntity.js | 128 +++++++++++------ .../nearParentGrabOverlay.js | 31 +++-- .../controllerModules/tabletStylusInput.js | 19 ++- 7 files changed, 251 insertions(+), 142 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 4fc8c84d5f..d79c2bd236 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -240,11 +240,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var orderedPluginName = this.orderedPluginNames[pluginIndex]; var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; - if (_this.slotsAreAvailableForPlugin(candidatePlugin) && candidatePlugin.isReady(controllerData, deltaTime)) { - // this plugin will start. add it to the list of running plugins and mark the - // activity-slots which this plugin consumes as "in use" - _this.runningPluginNames[orderedPluginName] = true; - _this.markSlots(candidatePlugin, orderedPluginName); + if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { + var readiness = candidatePlugin.isReady(controllerData, deltaTime); + if (readiness.active) { + // this plugin will start. add it to the list of running plugins and mark the + // activity-slots which this plugin consumes as "in use" + _this.runningPluginNames[orderedPluginName] = true; + _this.markSlots(candidatePlugin, orderedPluginName); + } } } @@ -257,11 +260,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // them available. delete _this.runningPluginNames[runningPluginName]; _this.unmarkSlotsForPluginName(runningPluginName); - } else if (!plugin.run(controllerData, deltaTime)) { - // plugin is finished running, for now. remove it from the list - // of running plugins and mark its activity-slots as "not in use" - delete _this.runningPluginNames[runningPluginName]; - _this.markSlots(plugin, false); + } else { + var runningness = plugin.run(controllerData, deltaTime); + if (!runningness.active) { + // plugin is finished running, for now. remove it from the list + // of running plugins and mark its activity-slots as "not in use" + delete _this.runningPluginNames[runningPluginName]; + _this.markSlots(plugin, false); + } } } } diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index e2cea96a42..e52c158219 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -18,8 +18,10 @@ COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, makeDispatcherModuleParameters, + makeRunningValues, enableDispatcherModule, disableDispatcherModule, + getEnabledModuleByName, getGrabbableData, entityIsGrabbable, getControllerJointIndex, @@ -61,17 +63,24 @@ COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start -// requiredDataForStart -- which "situation" parts this module looks at to decide if it will start +// requiredDataForReady -- which "situation" parts this module looks at to decide if it will start // sleepMSBetweenRuns -- how long to wait between calls to this module's "run" method -makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForStart, sleepMSBetweenRuns) { +makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForReady, sleepMSBetweenRuns) { return { priority: priority, activitySlots: activitySlots, - requiredDataForStart: requiredDataForStart, + requiredDataForReady: requiredDataForReady, sleepMSBetweenRuns: sleepMSBetweenRuns }; }; +makeRunningValues = function (active, targets, requiredDataForRun) { + return { + active: active, + targets: targets, + requiredDataForRun: requiredDataForRun + }; +}; enableDispatcherModule = function (moduleName, module, priority) { if (!controllerDispatcherPlugins) { @@ -86,6 +95,13 @@ disableDispatcherModule = function (moduleName) { controllerDispatcherPluginsNeedSort = true; }; +getEnabledModuleByName = function (moduleName) { + if (controllerDispatcherPlugins.hasOwnProperty(moduleName)) { + return controllerDispatcherPlugins[moduleName]; + } + return null; +}; + getGrabbableData = function (props) { // look in userData for a "grabbable" key, return the value or some defaults var grabbableData = {}; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 3d1964d639..6b54b0fe25 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -8,7 +8,7 @@ /*jslint bitwise: true */ /* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, - getGrabPointSphereOffset, entityIsGrabbable, + getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, enableDispatcherModule, disableDispatcherModule, makeDispatcherModuleParameters, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, @@ -135,8 +135,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.laserPointerOff = function() { - var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; - LaserPointers.disableLaserPointer(laserPointerID); + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.disableLaserPointer(this.headLaserPointer); }; @@ -145,38 +145,49 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { this.updateLaserPointer(controllerData, false, false); - return true; + return makeRunningValues(true, [], []); } else { - return false; + return makeRunningValues(false, [], []); } }; this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { this.laserPointerOff(); - return false; + return makeRunningValues(false, [], []); } + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + ]; + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } // if we are doing a distance search and this controller moves into a position // where it could near-grab something, stop searching. - var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; - for (var i = 0; i < nearbyEntityProperties.length; i++) { - var props = nearbyEntityProperties[i]; - if (entityIsGrabbable(props)) { + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { this.laserPointerOff(); - return false; + return makeRunningValues(false, [], []); } } - + // this.updateLaserPointer(controllerData, false, false); // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); - return true; + return makeRunningValues(true, [], []); }; this.cleanup = function () { diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index d6b5ffe4f7..8017f457fe 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -8,7 +8,8 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, - Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters + Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, + TRIGGER_OFF_VALUE */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -18,7 +19,7 @@ Script.include("/~/system/libraries/controllers.js"); function NearActionGrabEntity(hand) { this.hand = hand; - this.grabbedThingID = null; + this.targetEntityID = null; this.actionID = null; // action this script created... this.parameters = makeDispatcherModuleParameters( @@ -54,10 +55,10 @@ Script.include("/~/system/libraries/controllers.js"); }; - this.startNearGrabAction = function (controllerData, grabbedProperties) { + this.startNearGrabAction = function (controllerData, targetProps) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - var grabbableData = getGrabbableData(grabbedProperties); + var grabbableData = getGrabbableData(targetProps); this.ignoreIK = grabbableData.ignoreIK; this.kinematicGrab = grabbableData.kinematicGrab; @@ -74,10 +75,10 @@ Script.include("/~/system/libraries/controllers.js"); handPosition = this.getHandPosition(); } - var objectRotation = grabbedProperties.rotation; + var objectRotation = targetProps.rotation; this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - var currentObjectPosition = grabbedProperties.position; + var currentObjectPosition = targetProps.position; var offset = Vec3.subtract(currentObjectPosition, handPosition); this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); @@ -85,9 +86,9 @@ Script.include("/~/system/libraries/controllers.js"); this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID) { - Entities.deleteAction(this.grabbedThingID, this.actionID); + Entities.deleteAction(this.targetEntityID, this.actionID); } - this.actionID = Entities.addAction("hold", this.grabbedThingID, { + this.actionID = Entities.addAction("hold", this.targetEntityID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -104,25 +105,17 @@ Script.include("/~/system/libraries/controllers.js"); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedThingID, + grabbedEntity: this.targetEntityID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); }; - // this is for when the action creation failed, before - this.restartNearGrabAction = function (controllerData) { - var props = Entities.getEntityProperties(this.grabbedThingID, ["position", "rotation", "userData"]); - if (props && entityIsGrabbable(props)) { - this.startNearGrabAction(controllerData, props); - } - }; - // this is for when the action is going to time-out this.refreshNearGrabAction = function (controllerData) { var now = Date.now(); if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl - var success = Entities.updateAction(this.grabbedThingID, this.actionID, { + var success = Entities.updateAction(this.targetEntityID, this.actionID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -134,74 +127,101 @@ Script.include("/~/system/libraries/controllers.js"); }); if (success) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - } else { - print("continueNearGrabbing -- updateAction failed"); - this.restartNearGrabAction(controllerData); } } }; - this.endNearGrabAction = function (controllerData) { + this.endNearGrabAction = function () { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - Entities.deleteAction(this.grabbedThingID, this.actionID); + Entities.deleteAction(this.targetEntityID, this.actionID); this.actionID = null; - this.grabbedThingID = null; + this.targetEntityID = null; }; - this.isReady = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { - return false; - } - - var grabbedProperties = null; - // nearbyEntityProperties is already sorted by length from controller + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by distance from controller var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; if (entityIsGrabbable(props)) { - grabbedProperties = props; - break; + return props; } } + return null; + }; - if (grabbedProperties) { - if (!propsArePhysical(grabbedProperties)) { - return false; // let nearParentGrabEntity handle it + this.isReady = function (controllerData) { + this.targetEntityID = null; + + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + if (!propsArePhysical(targetProps)) { + // XXX make sure no highlights are enabled from this module + return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it + } else { + this.targetEntityID = targetProps.id; + // XXX highlight this.targetEntityID here + return makeRunningValues(true, [this.targetEntityID], []); } - this.grabbedThingID = grabbedProperties.id; - this.startNearGrabAction(controllerData, grabbedProperties); - return true; } else { - return false; + // XXX make sure no highlights are enabled from this module + return makeRunningValues(false, [], []); } }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { - this.endNearGrabAction(controllerData); - return false; + if (this.actionID) { + if (controllerData.triggerClicks[this.hand] == 0) { + this.endNearGrabAction(); + return makeRunningValues(false, [], []); + } + + this.refreshNearGrabAction(controllerData); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); + } else { + // still searching / highlighting + var readiness = this.isReady (controllerData); + if (!readiness.active) { + return readiness; + } + if (controllerData.triggerClicks[this.hand] == 1) { + // stop highlighting, switch to grabbing + // XXX stop highlight here + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.startNearGrabAction(controllerData, targetProps); + } + } } - if (!this.actionID) { - this.restartNearGrabAction(controllerData); + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.cleanup = function () { + if (this.targetEntityID) { + this.endNearGrabAction(); } - - this.refreshNearGrabAction(controllerData); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args); - - return true; }; } - enableDispatcherModule("LeftNearActionGrabEntity", new NearActionGrabEntity(LEFT_HAND)); - enableDispatcherModule("RightNearActionGrabEntity", new NearActionGrabEntity(RIGHT_HAND)); + var leftNearActionGrabEntity = new NearActionGrabEntity(LEFT_HAND); + var rightNearActionGrabEntity = new NearActionGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearActionGrabEntity", leftNearActionGrabEntity); + enableDispatcherModule("RightNearActionGrabEntity", rightNearActionGrabEntity); this.cleanup = function () { + leftNearActionGrabEntity.cleanup(); + rightNearActionGrabEntity.cleanup(); disableDispatcherModule("LeftNearActionGrabEntity"); disableDispatcherModule("RightNearActionGrabEntity"); }; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 58bd3d2dab..1de2fee5ea 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -8,8 +8,8 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, - propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, - makeDispatcherModuleParameters, entityIsGrabbable + propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, + makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -21,7 +21,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); function NearParentingGrabEntity(hand) { this.hand = hand; - this.grabbedThingID = null; + this.targetEntityID = null; + this.grabbing = false; this.previousParentID = {}; this.previousParentJointIndex = {}; this.previouslyUnhooked = {}; @@ -62,7 +63,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return false; }; - this.startNearParentingGrabEntity = function (controllerData, grabbedProperties) { + this.startNearParentingGrabEntity = function (controllerData, targetProps) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); var handJointIndex; @@ -74,7 +75,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); handJointIndex = this.controllerJointIndex; var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "startNearGrab", args); + Entities.callEntityMethod(targetProps.id, "startNearGrab", args); var reparentProps = { parentID: AVATAR_SELF_ID, @@ -83,90 +84,127 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); angularVelocity: {x: 0, y: 0, z: 0} }; - if (this.thisHandIsParent(grabbedProperties)) { + if (this.thisHandIsParent(targetProps)) { // this should never happen, but if it does, don't set previous parent to be this hand. - // this.previousParentID[this.grabbedThingID] = NULL; - // this.previousParentJointIndex[this.grabbedThingID] = -1; + // this.previousParentID[targetProps.id] = NULL; + // this.previousParentJointIndex[targetProps.id] = -1; } else { - this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; - this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; + this.previousParentID[targetProps.id] = targetProps.parentID; + this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex; } - Entities.editEntity(this.grabbedThingID, reparentProps); + Entities.editEntity(targetProps.id, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', - grabbedEntity: this.grabbedThingID, + grabbedEntity: targetProps.id, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); }; - this.endNearParentingGrabEntity = function (controllerData) { - if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { - Entities.editEntity(this.grabbedThingID, { - parentID: this.previousParentID[this.grabbedThingID], - parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] + this.endNearParentingGrabEntity = function () { + if (this.previousParentID[this.targetEntityID] === NULL_UUID) { + Entities.editEntity(this.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID] }); } else { // we're putting this back as a child of some other parent, so zero its velocity - Entities.editEntity(this.grabbedThingID, { - parentID: this.previousParentID[this.grabbedThingID], - parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + Entities.editEntity(this.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID], velocity: {x: 0, y: 0, z: 0}, angularVelocity: {x: 0, y: 0, z: 0} }); } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - this.grabbedThingID = null; + this.grabbing = false; + this.targetEntityID = null; }; - this.isReady = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { - return false; - } - - var grabbedProperties = null; + this.getTargetProps = function (controllerData) { // nearbyEntityProperties is already sorted by length from controller var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; if (entityIsGrabbable(props)) { - grabbedProperties = props; - break; + return props; } } + return null; + }; - if (grabbedProperties) { - if (propsArePhysical(grabbedProperties)) { - return false; // let nearActionGrabEntity handle it + this.isReady = function (controllerData) { + this.targetEntityID = null; + this.grabbing = false; + + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + if (propsArePhysical(targetProps)) { + // XXX make sure no highlights are enabled from this module + return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it + } else { + this.targetEntityID = targetProps.id; + // XXX highlight this.targetEntityID here + return makeRunningValues(true, [this.targetEntityID], []); } - this.grabbedThingID = grabbedProperties.id; - this.startNearParentingGrabEntity(controllerData, grabbedProperties); - return true; } else { - return false; + // XXX make sure no highlights are enabled from this module + return makeRunningValues(false, [], []); } }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { - this.endNearParentingGrabEntity(controllerData); - return false; + if (this.grabbing) { + if (controllerData.triggerClicks[this.hand] == 0) { + this.endNearParentingGrabEntity(); + return makeRunningValues(false, [], []); + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); + } else { + // still searching / highlighting + var readiness = this.isReady (controllerData); + if (!readiness.active) { + return readiness; + } + if (controllerData.triggerClicks[this.hand] == 1) { + // stop highlighting, switch to grabbing + // XXX stop highlight here + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.grabbing = true; + this.startNearParentingGrabEntity(controllerData, targetProps); + } + } } - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args); + return makeRunningValues(true, [this.targetEntityID], []); + }; - return true; + this.cleanup = function () { + if (this.targetEntityID) { + this.endNearParentingGrabEntity(); + } }; } - enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND)); - enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND)); + var leftNearParentingGrabEntity = new NearParentingGrabEntity(LEFT_HAND); + var rightNearParentingGrabEntity = new NearParentingGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); + enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); this.cleanup = function () { + leftNearParentingGrabEntity.cleanup(); + rightNearParentingGrabEntity.cleanup(); disableDispatcherModule("LeftNearParentingGrabEntity"); disableDispatcherModule("RightNearParentingGrabEntity"); }; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 9ce0b95abb..58b6a12090 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -9,7 +9,7 @@ /* global Script, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, - makeDispatcherModuleParameters, Overlays + makeDispatcherModuleParameters, Overlays, makeRunningValues */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -106,7 +106,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); })); }; - this.endNearParentingGrabOverlay = function (controllerData) { + this.endNearParentingGrabOverlay = function () { if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { Overlays.editOverlay(this.grabbedThingID, { parentID: NULL_UUID, @@ -125,7 +125,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.isReady = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { - return false; + return makeRunningValues(false, [], []); } this.grabbedThingID = null; @@ -138,26 +138,37 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); if (grabbableOverlays.length > 0) { this.grabbedThingID = grabbableOverlays[0]; this.startNearParentingGrabOverlay(controllerData); - return true; + return makeRunningValues(true, [this.grabbedThingID], []); } else { - return false; + return makeRunningValues(false, [], []); } }; this.run = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { - this.endNearParentingGrabOverlay(controllerData); - return false; + this.endNearParentingGrabOverlay(); + return makeRunningValues(false, [], []); } else { - return true; + return makeRunningValues(true, [this.grabbedThingID], []); + } + }; + + this.cleanup = function () { + if (this.grabbedThingID) { + this.endNearParentingGrabOverlay(); } }; } - enableDispatcherModule("LeftNearParentingGrabOverlay", new NearParentingGrabOverlay(LEFT_HAND)); - enableDispatcherModule("RightNearParentingGrabOverlay", new NearParentingGrabOverlay(RIGHT_HAND)); + var leftNearParentingGrabOverlay = new NearParentingGrabOverlay(LEFT_HAND); + var rightNearParentingGrabOverlay = new NearParentingGrabOverlay(RIGHT_HAND); + + enableDispatcherModule("LeftNearParentingGrabOverlay", leftNearParentingGrabOverlay); + enableDispatcherModule("RightNearParentingGrabOverlay", rightNearParentingGrabOverlay); this.cleanup = function () { + leftNearParentingGrabOverlay.cleanup(); + rightNearParentingGrabOverlay.cleanup(); disableDispatcherModule("LeftNearParentingGrabOverlay"); disableDispatcherModule("RightNearParentingGrabOverlay"); }; diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 7e9f69959f..8ada1b31d7 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, - NULL_UUID, enableDispatcherModule, disableDispatcherModule, + NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset */ @@ -643,7 +643,11 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - return this.processStylus(controllerData); + if (this.processStylus(controllerData)) { + return makeRunningValues(true, [], []); + } else { + return makeRunningValues(false, [], []); + } }; this.run = function (controllerData, deltaTime) { @@ -660,7 +664,11 @@ Script.include("/~/system/libraries/controllers.js"); if (this.stylusTouchingTarget) { this.stylusTouching(controllerData, deltaTime); } - return this.processStylus(controllerData); + if (this.processStylus(controllerData)) { + return makeRunningValues(true, [], []); + } else { + return makeRunningValues(false, [], []); + } }; this.cleanup = function () { @@ -674,12 +682,11 @@ Script.include("/~/system/libraries/controllers.js"); enableDispatcherModule("LeftTabletStylusInput", leftTabletStylusInput); enableDispatcherModule("RightTabletStylusInput", rightTabletStylusInput); - this.cleanup = function () { - disableDispatcherModule("LeftTabletStylusInput"); - disableDispatcherModule("RightTabletStylusInput"); leftTabletStylusInput.cleanup(); rightTabletStylusInput.cleanup(); + disableDispatcherModule("LeftTabletStylusInput"); + disableDispatcherModule("RightTabletStylusInput"); }; Script.scriptEnding.connect(this.cleanup); }()); From c2861d29d2227a7775311ad21c4ae30eec808a26 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 13:49:17 -0700 Subject: [PATCH 12/78] trying to get highlighting to work --- .../controllers/controllerDispatcher.js | 10 ++++- .../controllers/controllerDispatcherUtils.js | 43 ++++++++++++++++++- .../controllerModules/nearActionGrabEntity.js | 33 +++++++++++--- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index d79c2bd236..06853fa4ba 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -7,7 +7,7 @@ /*jslint bitwise: true */ -/* global Script, Entities, Overlays, Controller, Vec3, getControllerWorldLocation, RayPick, +/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS */ @@ -212,6 +212,14 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); ]; // if the pickray hit something very nearby, put it into the nearby entities list for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + + // XXX find a way to extract searchRay from samuel's stuff + rayPicks[h].searchRay = { + origin: controllerLocations[h].position, + direction: Quat.getUp(controllerLocations[h].orientation), + length: 1000 + }; + var nearEntityID = rayPicks[h].entityID; if (nearEntityID) { // XXX check to make sure this one isn't already in nearbyEntityProperties? diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index e52c158219..cdf8729d9c 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -26,7 +26,10 @@ entityIsGrabbable, getControllerJointIndex, propsArePhysical, - controllerDispatcherPluginsNeedSort + controllerDispatcherPluginsNeedSort, + projectOntoXYPlane, + projectOntoEntityXYPlane, + projectOntoOverlayXYPlane */ MSECS_PER_SEC = 1000.0; @@ -162,3 +165,41 @@ propsArePhysical = function (props) { var isPhysical = (props.shapeType && props.shapeType != 'none'); return isPhysical; }; + +projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) { + var invRot = Quat.inverse(rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position)); + var invDimensions = { x: 1 / dimensions.x, + y: 1 / dimensions.y, + z: 1 / dimensions.z }; + var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint); + return { x: normalizedPos.x * dimensions.x, + y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis +} + +projectOntoEntityXYPlane = function (entityID, worldPos, props) { + return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); +} + +projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { + var position = Overlays.getProperty(overlayID, "position"); + var rotation = Overlays.getProperty(overlayID, "rotation"); + var dimensions; + + var dpi = Overlays.getProperty(overlayID, "dpi"); + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions.z) { + dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. + } + } + + return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); +} diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 8017f457fe..42859f3e19 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -167,6 +167,8 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it } else { this.targetEntityID = targetProps.id; + ContextOverlay.entityWithContextOverlay = this.targetEntityID; + ContextOverlay.enabled = true; // XXX highlight this.targetEntityID here return makeRunningValues(true, [this.targetEntityID], []); } @@ -188,16 +190,35 @@ Script.include("/~/system/libraries/controllers.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); } else { - // still searching / highlighting + + // still searching / highlighting var readiness = this.isReady (controllerData); if (!readiness.active) { return readiness; } - if (controllerData.triggerClicks[this.hand] == 1) { - // stop highlighting, switch to grabbing - // XXX stop highlight here - var targetProps = this.getTargetProps(controllerData); - if (targetProps) { + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + + // XXX + var rayPickInfo = controllerData.rayPicks[this.hand]; + var pointerEvent = { + type: "Move", + id: this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection, targetProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.normal, + direction: rayPickInfo.searchRay.direction, + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.entityID, pointerEvent)) { + } + // XXX + + + if (controllerData.triggerClicks[this.hand] == 1) { + // stop highlighting, switch to grabbing + // XXX stop highlight here this.startNearGrabAction(controllerData, targetProps); } } From 58b199e9f9f06a9cb52b67fb0a788eadc11d3fe4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 15:54:03 -0700 Subject: [PATCH 13/78] far-grab sort-of works --- .../controllers/controllerDispatcher.js | 13 +- .../controllers/controllerDispatcherUtils.js | 35 ++- .../controllerModules/farActionGrabEntity.js | 261 ++++++++++++++++-- 3 files changed, 280 insertions(+), 29 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 06853fa4ba..3398d535f4 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -9,7 +9,8 @@ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable, - LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS */ + LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE +*/ controllerDispatcherPlugins = {}; controllerDispatcherPluginsNeedSort = false; @@ -297,14 +298,16 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }); this.leftControllerRayPick = RayPick.createRayPick({ - joint: "Mouse", + joint: "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, - enabled: true + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE }); this.rightControllerRayPick = RayPick.createRayPick({ - joint: "Mouse", + joint: "_CONTROLLER_RIGHTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, - enabled: true + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE }); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index cdf8729d9c..1c9edd6002 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, +/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, Quat, Vec3, Overlays, MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, ZERO_VEC, ONE_VEC, DEFAULT_REGISTRATION_POINT, INCHES_TO_METERS, TRIGGER_OFF_VALUE, @@ -24,6 +24,7 @@ getEnabledModuleByName, getGrabbableData, entityIsGrabbable, + entityIsDistanceGrabbable, getControllerJointIndex, propsArePhysical, controllerDispatcherPluginsNeedSort, @@ -139,6 +140,32 @@ entityIsGrabbable = function (props) { return true; }; +entityIsDistanceGrabbable = function(props) { + if (!entityIsGrabbable(props)) { + return false; + } + + // we can't distance-grab non-physical + var isPhysical = propsArePhysical(props); + if (!isPhysical) { + return false; + } + + // XXX + // var distance = Vec3.distance(props.position, handPosition); + // this.otherGrabbingUUID = entityIsGrabbedByOther(entityID); + // if (this.otherGrabbingUUID !== null) { + // // don't distance grab something that is already grabbed. + // if (debug) { + // print("distance grab is skipping '" + props.name + "': already grabbed by another."); + // } + // return false; + // } + + return true; +}; + + getControllerJointIndex = function (hand) { if (HMD.isHandControllerAvailable()) { var controllerJointIndex = -1; @@ -175,11 +202,11 @@ projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registr var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint); return { x: normalizedPos.x * dimensions.x, y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis -} +}; projectOntoEntityXYPlane = function (entityID, worldPos, props) { return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); -} +}; projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { var position = Overlays.getProperty(overlayID, "position"); @@ -202,4 +229,4 @@ projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldP } return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); -} +}; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 6b54b0fe25..0664c8eee3 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -7,10 +7,10 @@ /*jslint bitwise: true */ -/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, - getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, - enableDispatcherModule, disableDispatcherModule, - makeDispatcherModuleParameters, +/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, + getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID, + enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, + makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, @@ -92,19 +92,26 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; this.actionID = null; // action this script created... + var ACTION_TTL = 15; // seconds + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + this.parameters = makeDispatcherModuleParameters( 550, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); - this.updateLaserPointer = function(controllerData, distanceHolding, distanceRotating) { + this.updateLaserPointer = function(controllerData) { var SEARCH_SPHERE_SIZE = 0.011; var MIN_SPHERE_SIZE = 0.0005; var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE); var dim = {x: radius, y: radius, z: radius}; var mode = "hold"; - if (!distanceHolding && !distanceRotating) { + if (!this.distanceHolding && !this.distanceRotating) { // mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "half"; if (controllerData.triggerClicks[this.hand] // || this.secondarySqueezed() // XXX @@ -127,7 +134,7 @@ Script.include("/~/system/libraries/controllers.js"); } LaserPointers.enableLaserPointer(laserPointerID); LaserPointers.setRenderState(laserPointerID, mode); - if (distanceHolding || distanceRotating) { + if (this.distanceHolding || this.distanceRotating) { LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay); } else { LaserPointers.setLockEndUUID(laserPointerID, null, false); @@ -144,10 +151,194 @@ Script.include("/~/system/libraries/controllers.js"); return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.startFarGrabAction = function (controllerData, grabbedProperties) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = grabbedProperties.position; + this.currentObjectRotation = grabbedProperties.rotation; + this.currentObjectTime = now; + this.currentCameraOrientation = Camera.orientation; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); + var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); + this.linearTimeScale = timeScale; + this.actionID = NULL_UUID; + this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { + targetPosition: this.currentObjectPosition, + linearTimeScale: timeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: timeScale, + tag: "far-grab-" + MyAvatar.sessionUUID, + ttl: ACTION_TTL + }); + if (this.actionID === NULL_UUID) { + this.actionID = null; + } + + // XXX + // if (this.actionID !== null) { + // this.callEntityMethodOnGrabbed("startDistanceGrab"); + // } + + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.turnOffVisualizations(); + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, ["position"]); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + // XXX + // this.callEntityMethodOnGrabbed("continueDistantGrab"); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // XXX + // this.maybeScale(grabbedProperties); + + // visualizations + this.updateLaserPointer(controllerData); + + var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); + + this.linearTimeScale = (this.linearTimeScale / 2); + if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) { + this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + var success = Entities.updateAction(this.grabbedThingID, this.actionID, { + targetPosition: newTargetPosition, + linearTimeScale: this.linearTimeScale, + targetRotation: this.currentObjectRotation, + angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), + ttl: ACTION_TTL + }); + if (!success) { + print("continueDistanceHolding -- updateAction failed: " + this.actionID); + this.actionID = null; + } + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.ensureDynamic = function () { + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); + if (props.dynamic && props.parentID == NULL_UUID) { + var velocity = props.velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z: 0.0 }; + Entities.editEntity(this.grabbedThingID, { velocity: velocity }); + } + } + }; + + this.endNearGrabAction = function () { + this.ensureDynamic(); + this.distanceHolding = false; + this.distanceRotating = false; + Entities.deleteAction(this.grabbedThingID, this.actionID); + this.actionID = null; + this.grabbedThingID = null; + }; + this.isReady = function (controllerData) { + this.distanceHolding = false; + this.distanceRotating = false; if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.updateLaserPointer(controllerData, false, false); + this.updateLaserPointer(controllerData); return makeRunningValues(true, [], []); } else { return makeRunningValues(false, [], []); @@ -156,6 +347,7 @@ Script.include("/~/system/libraries/controllers.js"); this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); } @@ -173,20 +365,49 @@ Script.include("/~/system/libraries/controllers.js"); nearGrabReadiness.push(ready); } - // if we are doing a distance search and this controller moves into a position - // where it could near-grab something, stop searching. - for (var j = 0; j < nearGrabReadiness.length; j++) { - if (nearGrabReadiness[j].active) { - this.laserPointerOff(); - return makeRunningValues(false, [], []); + if (this.actionID) { + this.continueDistanceHolding(controllerData); + // this.updateLaserPointer(controllerData, false, false); + + // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); + } else { + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type == RayPick.INTERSECTED_ENTITY) { + var entityID = rayPickInfo.objectID; + print("QQQ rayPickInfo.entityID = " + entityID); + var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type"]); + if (entityIsDistanceGrabbable(targetProps)) { + print("QQQ is distance grabbable"); + this.grabbedThingID = entityID; + this.grabbedDistance = rayPickInfo.distance; + var otherModuleName = this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + if (otherFarGrabModule.grabbedThingID == this.grabbedThingID) { + this.distanceRotating = true; + this.distanceHolding = false; + // XXX rotate + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarGrabAction(controllerData, targetProps); + } + } else { + print("QQQ is NOT distance grabbable"); + } + } + + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.laserPointerOff(); + return makeRunningValues(false, [], []); + } } } - - // this.updateLaserPointer(controllerData, false, false); - - // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); - return makeRunningValues(true, [], []); }; From f10dc7cbd592e061a66be33a2dab5d22ee210545 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 16:54:37 -0700 Subject: [PATCH 14/78] allow far to near-grab transition --- .../controllerModules/farActionGrabEntity.js | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0664c8eee3..448f21a592 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -366,21 +366,38 @@ Script.include("/~/system/libraries/controllers.js"); } if (this.actionID) { + // if we are doing a distance grab and the object gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] == this.grabbedThingID) { + this.laserPointerOff(); + this.endNearGrabAction(); + return makeRunningValues(false, [], []); + } + } + this.continueDistanceHolding(controllerData); // this.updateLaserPointer(controllerData, false, false); // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.laserPointerOff(); + return makeRunningValues(false, [], []); + } + } + var rayPickInfo = controllerData.rayPicks[this.hand]; if (rayPickInfo.type == RayPick.INTERSECTED_ENTITY) { var entityID = rayPickInfo.objectID; - print("QQQ rayPickInfo.entityID = " + entityID); var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", "rotation", "dimensions", "density", "userData", "locked", "type"]); if (entityIsDistanceGrabbable(targetProps)) { - print("QQQ is distance grabbable"); this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; var otherModuleName = this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; @@ -394,17 +411,6 @@ Script.include("/~/system/libraries/controllers.js"); this.distanceRotating = false; this.startFarGrabAction(controllerData, targetProps); } - } else { - print("QQQ is NOT distance grabbable"); - } - } - - // if we are doing a distance search and this controller moves into a position - // where it could near-grab something, stop searching. - for (var j = 0; j < nearGrabReadiness.length; j++) { - if (nearGrabReadiness[j].active) { - this.laserPointerOff(); - return makeRunningValues(false, [], []); } } } From 9015d69c71dc9c1fb5925ba53163243a96049e85 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 17:21:55 -0700 Subject: [PATCH 15/78] fix pop at start of far-grab --- .../controllers/controllerModules/farActionGrabEntity.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 448f21a592..f16b6c52c2 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -202,7 +202,6 @@ Script.include("/~/system/libraries/controllers.js"); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); this.linearTimeScale = timeScale; - this.actionID = NULL_UUID; this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { targetPosition: this.currentObjectPosition, linearTimeScale: timeScale, @@ -221,12 +220,10 @@ Script.include("/~/system/libraries/controllers.js"); // } Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.turnOffVisualizations(); this.previousRoomControllerPosition = roomControllerPosition; }; this.continueDistanceHolding = function(controllerData) { - var controllerLocation = controllerData.controllerLocations[this.hand]; var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; From 13424eb792b387a6c1398b0d388ccd998a20a45f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 13 Aug 2017 17:42:08 -0700 Subject: [PATCH 16/78] kinematic-grab defaults to true --- scripts/system/controllers/controllerDispatcherUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 1c9edd6002..72cf9362d1 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -124,7 +124,7 @@ getGrabbableData = function (props) { grabbableData.ignoreIK = true; } if (!grabbableData.hasOwnProperty("kinematicGrab")) { - grabbableData.kinematicGrab = false; + grabbableData.kinematicGrab = true; } return grabbableData; From b52a406ff18547f6be42e8156e0cc2c2c1a81c55 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 14 Aug 2017 18:25:57 -0700 Subject: [PATCH 17/78] trying to get equipping working --- .../controllers/controllerDispatcher.js | 89 ++- .../controllers/controllerDispatcherUtils.js | 16 +- .../controllerModules/equipEntity.js | 634 ++++++++++++++++++ .../controllerModules/farActionGrabEntity.js | 3 +- .../system/controllers/controllerScripts.js | 3 +- 5 files changed, 704 insertions(+), 41 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/equipEntity.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 3398d535f4..b35891e71e 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -90,23 +90,28 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.leftTriggerClicked = 0; this.rightTriggerValue = 0; this.rightTriggerClicked = 0; - + this.leftSecondaryValue = 0; + this.rightSecondaryValue = 0; this.leftTriggerPress = function (value) { _this.leftTriggerValue = value; }; - this.leftTriggerClick = function (value) { _this.leftTriggerClicked = value; }; - this.rightTriggerPress = function (value) { _this.rightTriggerValue = value; }; - this.rightTriggerClick = function (value) { _this.rightTriggerClicked = value; }; + this.leftSecondaryPress = function (value) { + _this.leftSecondaryValue = value; + }; + this.rightSecondaryPress = function (value) { + _this.rightSecondaryValue = value; + }; + this.dataGatherers = {}; this.dataGatherers.leftControllerLocation = function () { @@ -150,10 +155,21 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } } this.orderedPluginNames.sort(function (a, b) { - return controllerDispatcherPlugins[a].priority < controllerDispatcherPlugins[b].priority; + return controllerDispatcherPlugins[a].parameters.priority - + controllerDispatcherPlugins[b].parameters.priority; }); - print("controllerDispatcher: new plugin order: " + JSON.stringify(this.orderedPluginNames)); + // print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames)); + var output = "controllerDispatcher -- new plugin order: "; + for (var k = 0; k < this.orderedPluginNames.length; k++) { + var dbgPluginName = this.orderedPluginNames[k]; + var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority; + output += dbgPluginName + ":" + priority; + if (k + 1 < this.orderedPluginNames.length) { + output += ", "; + } + } + print(output); controllerDispatcherPluginsNeedSort = false; } @@ -166,44 +182,31 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var h; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // todo: check controllerLocations[h].valid - var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MIN_RADIUS); - var makeOverlaySorter = function (handIndex) { - return function (a, b) { - var aPosition = Overlays.getProperty(a, "position"); - var aDistance = Vec3.distance(aPosition, controllerLocations[handIndex]); - var bPosition = Overlays.getProperty(b, "position"); - var bDistance = Vec3.distance(bPosition, controllerLocations[handIndex]); - return aDistance - bDistance; - }; - }; - nearbyOverlays.sort(makeOverlaySorter(h)); + var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS); + nearbyOverlays.sort(function (a, b) { + var aPosition = Overlays.getProperty(a, "position"); + var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); + var bPosition = Overlays.getProperty(b, "position"); + var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); + return aDistance - bDistance; + }); nearbyOverlayIDs.push(nearbyOverlays); } // find entities near each hand var nearbyEntityProperties = [[], []]; + var nearbyEntityPropertiesByID = {}; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // todo: check controllerLocations[h].valid var controllerPosition = controllerLocations[h].position; - var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); + var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MAX_RADIUS); for (var j = 0; j < nearbyEntityIDs.length; j++) { var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; - props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position)); - if (props.distanceFromController < NEAR_MAX_RADIUS) { - nearbyEntityProperties[h].push(props); - } + nearbyEntityPropertiesByID[entityID] = props; + nearbyEntityProperties[h].push(props); } - // sort by distance from each hand - var makeSorter = function (handIndex) { - return function (a, b) { - var aDistance = Vec3.distance(a.position, controllerLocations[handIndex]); - var bDistance = Vec3.distance(b.position, controllerLocations[handIndex]); - return aDistance - bDistance; - }; - }; - nearbyEntityProperties[h].sort(makeSorter(h)); } // raypick for each controller @@ -221,25 +224,33 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); length: 1000 }; - var nearEntityID = rayPicks[h].entityID; - if (nearEntityID) { + if (rayPicks[h].type == RayPick.INTERSECTED_ENTITY) { // XXX check to make sure this one isn't already in nearbyEntityProperties? if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) { + var nearEntityID = rayPicks[h].objectID; var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); nearbyProps.id = nearEntityID; - if (entityIsGrabbable(nearbyProps)) { - nearbyEntityProperties[h].push(nearbyProps); - } + nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; + nearbyEntityProperties[h].push(nearbyProps); } } + + // sort by distance from each hand + nearbyEntityProperties[h].sort(function (a, b) { + var aDistance = Vec3.distance(a.position, controllerLocations[h].position); + var bDistance = Vec3.distance(b.position, controllerLocations[h].position); + return aDistance - bDistance; + }); } // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], + secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, + nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, nearbyOverlayIDs: nearbyOverlayIDs, rayPicks: rayPicks }; @@ -288,6 +299,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick); mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress); mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick); + + mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress); + mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress); + mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress); + mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress); + Controller.enableMapping(MAPPING_NAME); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 72cf9362d1..daf6b667ed 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -17,6 +17,7 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + Entities, makeDispatcherModuleParameters, makeRunningValues, enableDispatcherModule, @@ -30,7 +31,8 @@ controllerDispatcherPluginsNeedSort, projectOntoXYPlane, projectOntoEntityXYPlane, - projectOntoOverlayXYPlane + projectOntoOverlayXYPlane, + entityHasActions */ MSECS_PER_SEC = 1000.0; @@ -111,10 +113,14 @@ getGrabbableData = function (props) { var grabbableData = {}; var userDataParsed = null; try { - userDataParsed = JSON.parse(props.userData); + if (!props.userDataParsed) { + props.userDataParsed = JSON.parse(props.userData); + } + userDataParsed = props.userDataParsed; } catch (err) { + userDataParsed = {}; } - if (userDataParsed && userDataParsed.grabbable) { + if (userDataParsed.grabbable) { grabbableData = userDataParsed.grabbable; } if (!grabbableData.hasOwnProperty("grabbable")) { @@ -230,3 +236,7 @@ projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldP return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); }; + +entityHasActions = function (entityID) { + return Entities.getActionIDs(entityID).length > 0; +}; diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js new file mode 100644 index 0000000000..65668f0d23 --- /dev/null +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -0,0 +1,634 @@ +"use strict"; + +// equipEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, + getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, + Vec3, Overlays, flatten, Xform, getControllerWorldLocation +*/ + +Script.include("/~/system/libraries/Xform.js"); +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + + +var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; +var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + + +// Each overlayInfoSet describes a single equip hotspot. +// It is an object with the following keys: +// timestamp - last time this object was updated, used to delete stale hotspot overlays. +// entityID - entity assosicated with this hotspot +// localPosition - position relative to the entity +// hotspot - hotspot object +// overlays - array of overlay objects created by Overlay.addOverlay() +// currentSize - current animated scale value +// targetSize - the target of our scale animations +// type - "sphere" or "model". +function EquipHotspotBuddy() { + // holds map from {string} hotspot.key to {object} overlayInfoSet. + this.map = {}; + + // array of all hotspots that are highlighed. + this.highlightedHotspots = []; +} +EquipHotspotBuddy.prototype.clear = function() { + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + this.deleteOverlayInfoSet(overlayInfoSet); + } + this.map = {}; + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.highlightHotspot = function(hotspot) { + this.highlightedHotspots.push(hotspot.key); +}; +EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { + var overlayInfoSet = this.map[hotspot.key]; + if (!overlayInfoSet) { + // create a new overlayInfoSet + overlayInfoSet = { + timestamp: timestamp, + entityID: hotspot.entityID, + localPosition: hotspot.localPosition, + hotspot: hotspot, + currentSize: 0, + targetSize: 1, + overlays: [] + }; + + var diameter = hotspot.radius * 2; + + // override default sphere with a user specified model, if it exists. + overlayInfoSet.overlays.push(Overlays.addOverlay("model", { + name: "hotspot overlay", + url: hotspot.modelURL ? hotspot.modelURL : DEFAULT_SPHERE_MODEL_URL, + position: hotspot.worldPosition, + rotation: { + x: 0, + y: 0, + z: 0, + w: 1 + }, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + scale: hotspot.modelScale, + ignoreRayIntersection: true + })); + overlayInfoSet.type = "model"; + print("QQQ adding hopspot: " + hotspot.key); + this.map[hotspot.key] = overlayInfoSet; + } else { + print("QQQ updating hopspot: " + hotspot.key); + overlayInfoSet.timestamp = timestamp; + } +}; +EquipHotspotBuddy.prototype.updateHotspots = function(hotspots, timestamp) { + var _this = this; + hotspots.forEach(function(hotspot) { + _this.updateHotspot(hotspot, timestamp); + }); + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerData) { + + var HIGHLIGHT_SIZE = 1.1; + var NORMAL_SIZE = 1.0; + + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + + // this overlayInfo is highlighted. + if (this.highlightedHotspots.indexOf(keys[i]) != -1) { + overlayInfoSet.targetSize = HIGHLIGHT_SIZE; + } else { + overlayInfoSet.targetSize = NORMAL_SIZE; + } + + // start to fade out this hotspot. + if (overlayInfoSet.timestamp != timestamp) { + print("QQQ fading " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); + overlayInfoSet.targetSize = 0; + } + + // animate the size. + var SIZE_TIMESCALE = 0.1; + var tau = deltaTime / SIZE_TIMESCALE; + if (tau > 1.0) { + tau = 1.0; + } + overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau; + + if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { + print("QQQ deleting " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); + + // this is an old overlay, that has finished fading out, delete it! + overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); + delete this.map[keys[i]]; + } else { + // update overlay position, rotation to follow the object it's attached to. + + print("QQQ grew " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); + + var props = controllerData.nearbyEntityPropertiesByID[overlayInfoSet.entityID]; + if (props) { + var entityXform = new Xform(props.rotation, props.position); + var position = entityXform.xformPoint(overlayInfoSet.localPosition); + + var dimensions; + if (overlayInfoSet.type == "sphere") { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; + } else { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; + } + + overlayInfoSet.overlays.forEach(function(overlay) { + Overlays.editOverlay(overlay, { + position: position, + rotation: props.rotation, + dimensions: dimensions + }); + }); + } else { + print("QQQ but no props for " + overlayInfoSet.entityID); + overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); + delete this.map[keys[i]]; + } + } + } +}; + + + +(function() { + + var debug = true; + var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; + + var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. + + var HAPTIC_PULSE_STRENGTH = 1.0; + var HAPTIC_PULSE_DURATION = 13.0; + var HAPTIC_TEXTURE_STRENGTH = 0.1; + var HAPTIC_TEXTURE_DURATION = 3.0; + var HAPTIC_TEXTURE_DISTANCE = 0.002; + var HAPTIC_DEQUIP_STRENGTH = 0.75; + var HAPTIC_DEQUIP_DURATION = 50.0; + + var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing + var TRIGGER_OFF_VALUE = 0.1; + var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab + var BUMPER_ON_VALUE = 0.5; + + + function getWearableData(props) { + var wearable = {}; + try { + if (!props.userDataParsed) { + props.userDataParsed = JSON.parse(props.userData); + } + wearable = props.userDataParsed.wearable ? props.userDataParsed.wearable : {}; + } catch (err) { + } + return wearable; + } + function getEquipHotspotsData(props) { + var equipHotspots = []; + try { + if (!props.userDataParsed) { + props.userDataParsed = JSON.parse(props.userData); + } + equipHotspots = props.userDataParsed.equipHotspots ? props.userDataParsed.equipHotspots : []; + } catch (err) { + } + return equipHotspots; + } + + function getAttachPointSettings() { + try { + var str = Settings.getValue(ATTACH_POINT_SETTINGS); + if (str === "false") { + return {}; + } else { + return JSON.parse(str); + } + } catch (err) { + print("Error parsing attachPointSettings: " + err); + return {}; + } + } + + function setAttachPointSettings(attachPointSettings) { + var str = JSON.stringify(attachPointSettings); + Settings.setValue(ATTACH_POINT_SETTINGS, str); + } + + function getAttachPointForHotspotFromSettings(hotspot, hand) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (joints) { + return joints[jointName]; + } else { + return undefined; + } + } + + function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (!joints) { + joints = {}; + attachPointSettings[hotspot.key] = joints; + } + joints[jointName] = [offsetPosition, offsetRotation]; + setAttachPointSettings(attachPointSettings); + } + + function EquipEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.prevHandIsUpsideDown = false; + + this.parameters = makeDispatcherModuleParameters( + 300, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + var equipHotspotBuddy = new EquipHotspotBuddy(); + + // returns a list of all equip-hotspots assosiated with this entity. + // @param {UUID} entityID + // @returns {Object[]} array of objects with the following fields. + // * key {string} a string that can be used to uniquely identify this hotspot + // * entityID {UUID} + // * localPosition {Vec3} position of the hotspot in object space. + // * worldPosition {vec3} position of the hotspot in world space. + // * radius {number} radius of equip hotspot + // * joints {Object} keys are joint names values are arrays of two elements: + // offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint. + // * modelURL {string} url for model to use instead of default sphere. + // * modelScale {Vec3} scale factor for model + this.collectEquipHotspots = function(props) { + var result = []; + var entityID = props.id; + var entityXform = new Xform(props.rotation, props.position); + + var equipHotspotsProps = getEquipHotspotsData(props); + if (equipHotspotsProps && equipHotspotsProps.length > 0) { + var i, length = equipHotspotsProps.length; + for (i = 0; i < length; i++) { + var hotspot = equipHotspotsProps[i]; + if (hotspot.position && hotspot.radius && hotspot.joints) { + result.push({ + key: entityID.toString() + i.toString(), + entityID: entityID, + localPosition: hotspot.position, + worldPosition: entityXform.xformPoint(hotspot.position), + radius: hotspot.radius, + joints: hotspot.joints, + modelURL: hotspot.modelURL, + modelScale: hotspot.modelScale + }); + } + } + } else { + var wearableProps = getWearableData(props); + if (wearableProps && wearableProps.joints) { + result.push({ + key: entityID.toString() + "0", + entityID: entityID, + localPosition: { + x: 0, + y: 0, + z: 0 + }, + worldPosition: entityXform.pos, + radius: EQUIP_RADIUS, + joints: wearableProps.joints, + modelURL: null, + modelScale: null + }); + } + } + return result; + }; + + this.hotspotIsEquippable = function(hotspot, controllerData) { + var props = controllerData.nearbyEntityPropertiesByID[hotspot.entityID]; + + var hasParent = true; + if (props.parentID === NULL_UUID) { + hasParent = false; + } + if (hasParent || entityHasActions(hotspot.entityID)) { + if (debug) { + print("equip is skipping '" + props.name + "': grabbed by someone else: " + + hasParent + " : " + entityHasActions(hotspot.entityID)); + } + return false; + } + + return true; + }; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + + + this.triggerPress = function(value) { + this.rawTriggerValue = value; + }; + + this.triggerClick = function(value) { + this.triggerClicked = value; + }; + + this.secondaryPress = function(value) { + this.rawSecondaryValue = value; + }; + + this.updateSmoothedTrigger = function(controllerData) { + var triggerValue = controllerData.triggerValues[this.hand]; + // smooth out trigger value + this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + }; + + this.triggerSmoothedGrab = function() { + return this.triggerClicked; + }; + + this.triggerSmoothedSqueezed = function() { + return this.triggerValue > TRIGGER_ON_VALUE; + }; + + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + + this.secondarySqueezed = function() { + return this.rawSecondaryValue > BUMPER_ON_VALUE; + }; + + this.secondaryReleased = function() { + return this.rawSecondaryValue < BUMPER_ON_VALUE; + }; + + + + this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) { + var _this = this; + var collectedHotspots = flatten(candidateEntityProps.map(function(props) { + return _this.collectEquipHotspots(props); + })); + var controllerLocation = controllerData.controllerLocations[_this.hand]; + var worldControllerPosition = controllerLocation.position; + var equippableHotspots = collectedHotspots.filter(function(hotspot) { + var hotspotDistance = Vec3.distance(hotspot.worldPosition, worldControllerPosition); + return _this.hotspotIsEquippable(hotspot, controllerData) && + hotspotDistance < hotspot.radius; + }); + return equippableHotspots; + }; + + this.chooseBestEquipHotspot = function(candidateEntityProps, controllerData) { + var equippableHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData); + if (equippableHotspots.length > 0) { + // sort by distance; + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + equippableHotspots.sort(function(a, b) { + var aDistance = Vec3.distance(a.worldPosition, worldControllerPosition); + var bDistance = Vec3.distance(b.worldPosition, worldControllerPosition); + return aDistance - bDistance; + }); + return equippableHotspots[0]; + } else { + return null; + } + }; + + this.dropGestureReset = function() { + this.prevHandIsUpsideDown = false; + }; + + this.dropGestureProcess = function (deltaTime) { + var worldHandRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var localHandUpAxis = this.hand === RIGHT_HAND ? { x: 1, y: 0, z: 0 } : { x: -1, y: 0, z: 0 }; + var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis); + var DOWN = { x: 0, y: -1, z: 0 }; + + var DROP_ANGLE = Math.PI / 3; + var HYSTERESIS_FACTOR = 1.1; + var ROTATION_ENTER_THRESHOLD = Math.cos(DROP_ANGLE); + var ROTATION_EXIT_THRESHOLD = Math.cos(DROP_ANGLE * HYSTERESIS_FACTOR); + var rotationThreshold = this.prevHandIsUpsideDown ? ROTATION_EXIT_THRESHOLD : ROTATION_ENTER_THRESHOLD; + + var handIsUpsideDown = false; + if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) { + handIsUpsideDown = true; + } + + if (handIsUpsideDown != this.prevHandIsUpsideDown) { + this.prevHandIsUpsideDown = handIsUpsideDown; + Controller.triggerHapticPulse(HAPTIC_DEQUIP_STRENGTH, HAPTIC_DEQUIP_DURATION, this.hand); + } + + return handIsUpsideDown; + }; + + this.clearEquipHaptics = function() { + this.prevPotentialEquipHotspot = null; + }; + + this.updateEquipHaptics = function(potentialEquipHotspot, currentLocation) { + if (potentialEquipHotspot && !this.prevPotentialEquipHotspot || + !potentialEquipHotspot && this.prevPotentialEquipHotspot) { + Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); + this.lastHapticPulseLocation = currentLocation; + } else if (potentialEquipHotspot && + Vec3.distance(this.lastHapticPulseLocation, currentLocation) > HAPTIC_TEXTURE_DISTANCE) { + Controller.triggerHapticPulse(HAPTIC_TEXTURE_STRENGTH, HAPTIC_TEXTURE_DURATION, this.hand); + this.lastHapticPulseLocation = currentLocation; + } + this.prevPotentialEquipHotspot = potentialEquipHotspot; + }; + + this.startEquipEntity = function (controllerData) { + this.dropGestureReset(); + this.clearEquipHaptics(); + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + + // if an object is "equipped" and has a predefined offset, use it. + var offsets = getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); + if (offsets) { + this.offsetPosition = offsets[0]; + this.offsetRotation = offsets[1]; + } else { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (this.grabbedHotspot.joints[handJointName]) { + this.offsetPosition = this.grabbedHotspot.joints[handJointName][0]; + this.offsetRotation = this.grabbedHotspot.joints[handJointName][1]; + } + } + + var handJointIndex; + if (this.ignoreIK) { + handJointIndex = this.controllerJointIndex; + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + + var reparentProps = { + parentID: AVATAR_SELF_ID, + parentJointIndex: handJointIndex, + velocity: {x: 0, y: 0, z: 0}, + angularVelocity: {x: 0, y: 0, z: 0}, + localPosition: this.offsetPosition, + localRotation: this.offsetRotation + }; + Entities.editEntity(this.grabbedThingID, reparentProps); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'equip', + grabbedEntity: this.grabbedThingID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + }; + + this.endEquipEntity = function () { + Entities.editEntity(this.targetEntityID, { + parentID: NULL_UUID, + parentJointIndex: -1 + }); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args); + + this.targetEntityID = null; + }; + + this.isReady = function (controllerData, deltaTime) { + + this.rawTriggerValue = controllerData.triggerValues[this.hand]; + this.triggerClicked = controllerData.triggerClicks[this.hand]; + this.rawSecondaryValue = controllerData.secondaryValues[this.hand]; + this.updateSmoothedTrigger(controllerData); + + this.controllerJointIndex = getControllerJointIndex(this.hand); + + if (this.triggerSmoothedReleased() && this.secondaryReleased()) { + this.waitForTriggerRelease = false; + } + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + + // var candidateEntities = controllerData.nearbyEntityProperties[this.hand].map(function (props) { + // return props.id; + // }); + + var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand]; + + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData); + if (!this.waitForTriggerRelease) { + this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition); + } + + var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData); + var timestamp = Date.now(); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } + + equipHotspotBuddy.update(deltaTime, timestamp, controllerData); + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + + if (controllerData.secondaryValues[this.hand]) { + // this.secondaryReleased() will always be true when not depressed + // so we cannot simply rely on that for release - ensure that the + // trigger was first "prepared" by being pushed in before the release + this.preparingHoldRelease = true; + } + + if (this.preparingHoldRelease && !controllerData.secondaryValues[this.hand]) { + // we have an equipped object and the secondary trigger was released + // short-circuit the other checks and release it + this.preparingHoldRelease = false; + this.endEquipEntity(); + return makeRunningValues(false, [], []); + } + + var dropDetected = this.dropGestureProcess(deltaTime); + + if (this.triggerSmoothedReleased()) { + this.waitForTriggerRelease = false; + } + + if (dropDetected && this.prevDropDetected != dropDetected) { + this.waitForTriggerRelease = true; + } + + // highlight the grabbed hotspot when the dropGesture is detected. + var timestamp = Date.now(); + if (dropDetected) { + equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp); + equipHotspotBuddy.highlightHotspot(this.grabbedHotspot); + } + + if (dropDetected && !this.waitForTriggerRelease && controllerData.triggerClicks[this.hand]) { + // store the offset attach points into preferences. + if (this.grabbedHotspot && this.grabbedThingID) { + var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]); + if (prefprops && prefprops.localPosition && prefprops.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, + prefprops.localPosition, prefprops.localRotation); + } + } + + this.endEquipEntity(); + return makeRunningValues(false, [], []); + } + this.prevDropDetected = dropDetected; + + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.cleanup = function () { + if (this.targetEntityID) { + this.endEquipEntity(); + } + }; + } + + var leftEquipEntity = new EquipEntity(LEFT_HAND); + var rightEquipEntity = new EquipEntity(RIGHT_HAND); + + enableDispatcherModule("LeftEquipEntity", leftEquipEntity); + enableDispatcherModule("RightEquipEntity", rightEquipEntity); + + this.cleanup = function () { + leftEquipEntity.cleanup(); + rightEquipEntity.cleanup(); + disableDispatcherModule("LeftEquipEntity"); + disableDispatcherModule("RightEquipEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index f16b6c52c2..a2edab4102 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -397,7 +397,8 @@ Script.include("/~/system/libraries/controllers.js"); if (entityIsDistanceGrabbable(targetProps)) { this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; - var otherModuleName = this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherModuleName = + this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; var otherFarGrabModule = getEnabledModuleByName(otherModuleName); if (otherFarGrabModule.grabbedThingID == this.grabbedThingID) { this.distanceRotating = true; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 552aec20a4..ea45f0a2c6 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -23,7 +23,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", "controllerModules/farActionGrabEntity.js", - "controllerModules/tabletStylusInput.js" + "controllerModules/tabletStylusInput.js", + "controllerModules/equipEntity.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From b2d7eefc976f226ea303e9e14bcf44fde3e0044d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 10:30:47 -0700 Subject: [PATCH 18/78] equip highlight works now --- .../controllers/controllerDispatcher.js | 2 ++ .../controllers/controllerDispatcherUtils.js | 4 ++++ .../controllerModules/equipEntity.js | 20 +++++++++++++++---- .../controllerModules/nearActionGrabEntity.js | 7 ++++++- .../controllerModules/nearParentGrabEntity.js | 7 ++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index b35891e71e..959f217eb4 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -271,6 +271,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } } + // print("QQQ running plugins: " + JSON.stringify(_this.runningPluginNames)); + // give time to running plugins for (var runningPluginName in _this.runningPluginNames) { if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) { diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index daf6b667ed..a7bd3339d0 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -17,6 +17,7 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + NEAR_GRAB_RADIUS, Entities, makeDispatcherModuleParameters, makeRunningValues, @@ -66,6 +67,9 @@ COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; +NEAR_GRAB_RADIUS = 0.1; + + // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 65668f0d23..c009d74764 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -82,7 +82,7 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { ignoreRayIntersection: true })); overlayInfoSet.type = "model"; - print("QQQ adding hopspot: " + hotspot.key); + print("QQQ adding hotspot: " + hotspot.key); this.map[hotspot.key] = overlayInfoSet; } else { print("QQQ updating hopspot: " + hotspot.key); @@ -520,7 +520,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; }; - this.isReady = function (controllerData, deltaTime) { + this.checkNearbyHotspots = function (controllerData, deltaTime) { + + var timestamp = Date.now(); this.rawTriggerValue = controllerData.triggerValues[this.hand]; this.triggerClicked = controllerData.triggerClicks[this.hand]; @@ -548,7 +550,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData); - var timestamp = Date.now(); equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); if (potentialEquipHotspot) { equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); @@ -556,11 +557,22 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); - return makeRunningValues(false, [], []); + if (potentialEquipHotspot) { + return makeRunningValues(true, [potentialEquipHotspot.entityID], []); + } else { + return makeRunningValues(false, [], []); + } + + }; + + this.isReady = function (controllerData, deltaTime) { + return this.checkNearbyHotspots(controllerData, deltaTime); }; this.run = function (controllerData, deltaTime) { + return this.checkNearbyHotspots(controllerData, deltaTime); + if (controllerData.secondaryValues[this.hand]) { // this.secondaryReleased() will always be true when not depressed // so we cannot simply rely on that for release - ensure that the diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 42859f3e19..217e90ef88 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -9,7 +9,7 @@ getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, - TRIGGER_OFF_VALUE + TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -146,6 +146,11 @@ Script.include("/~/system/libraries/controllers.js"); var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(props.position, handPosition); + if (distance > NEAR_GRAB_RADIUS) { + break; + } if (entityIsGrabbable(props)) { return props; } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 1de2fee5ea..c5ca95fe3b 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -9,7 +9,7 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, - makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues + makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -129,6 +129,11 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(props.position, handPosition); + if (distance > NEAR_GRAB_RADIUS) { + break; + } if (entityIsGrabbable(props)) { return props; } From e6ac07d43dc375638bdcfa83f16f7e8196c81806 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 11:45:45 -0700 Subject: [PATCH 19/78] equipping mostly works --- .../controllers/controllerDispatcherUtils.js | 16 ++++- .../controllerModules/equipEntity.js | 66 ++++++------------- .../controllerModules/farActionGrabEntity.js | 15 +---- 3 files changed, 35 insertions(+), 62 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index a7bd3339d0..5b6d624892 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -33,7 +33,8 @@ projectOntoXYPlane, projectOntoEntityXYPlane, projectOntoOverlayXYPlane, - entityHasActions + entityHasActions, + ensureDynamic */ MSECS_PER_SEC = 1000.0; @@ -244,3 +245,16 @@ projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldP entityHasActions = function (entityID) { return Entities.getActionIDs(entityID).length > 0; }; + +ensureDynamic = function (entityID) { + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var props = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); + if (props.dynamic && props.parentID == NULL_UUID) { + var velocity = props.velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z: 0.0 }; + Entities.editEntity(entityID, { velocity: velocity }); + } + } +}; diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index c009d74764..f44ce4ff78 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -9,7 +9,7 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, - Vec3, Overlays, flatten, Xform, getControllerWorldLocation + Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic */ Script.include("/~/system/libraries/Xform.js"); @@ -82,10 +82,8 @@ EquipHotspotBuddy.prototype.updateHotspot = function(hotspot, timestamp) { ignoreRayIntersection: true })); overlayInfoSet.type = "model"; - print("QQQ adding hotspot: " + hotspot.key); this.map[hotspot.key] = overlayInfoSet; } else { - print("QQQ updating hopspot: " + hotspot.key); overlayInfoSet.timestamp = timestamp; } }; @@ -114,7 +112,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // start to fade out this hotspot. if (overlayInfoSet.timestamp != timestamp) { - print("QQQ fading " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); overlayInfoSet.targetSize = 0; } @@ -127,16 +124,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau; if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { - print("QQQ deleting " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); - // this is an old overlay, that has finished fading out, delete it! overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; } else { // update overlay position, rotation to follow the object it's attached to. - - print("QQQ grew " + overlayInfoSet.entityID + " " + overlayInfoSet.timestamp + " != " + timestamp); - var props = controllerData.nearbyEntityPropertiesByID[overlayInfoSet.entityID]; if (props) { var entityXform = new Xform(props.rotation, props.position); @@ -157,7 +149,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }); }); } else { - print("QQQ but no props for " + overlayInfoSet.entityID); overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; } @@ -214,7 +205,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa function getAttachPointSettings() { try { var str = Settings.getValue(ATTACH_POINT_SETTINGS); - if (str === "false") { + if (str === "false" || str === "") { return {}; } else { return JSON.parse(str); @@ -257,6 +248,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.hand = hand; this.targetEntityID = null; this.prevHandIsUpsideDown = false; + this.triggerValue = 0; this.parameters = makeDispatcherModuleParameters( 300, @@ -333,7 +325,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (hasParent || entityHasActions(hotspot.entityID)) { if (debug) { print("equip is skipping '" + props.name + "': grabbed by someone else: " + - hasParent + " : " + entityHasActions(hotspot.entityID)); + hasParent + " : " + entityHasActions(hotspot.entityID) + " : " + this.hand); } return false; } @@ -345,20 +337,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; - - - this.triggerPress = function(value) { - this.rawTriggerValue = value; - }; - - this.triggerClick = function(value) { - this.triggerClicked = value; - }; - - this.secondaryPress = function(value) { - this.rawSecondaryValue = value; - }; - this.updateSmoothedTrigger = function(controllerData) { var triggerValue = controllerData.triggerValues[this.hand]; // smooth out trigger value @@ -366,10 +344,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); }; - this.triggerSmoothedGrab = function() { - return this.triggerClicked; - }; - this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; }; @@ -378,16 +352,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa return this.triggerValue < TRIGGER_OFF_VALUE; }; - this.secondarySqueezed = function() { - return this.rawSecondaryValue > BUMPER_ON_VALUE; - }; - this.secondaryReleased = function() { return this.rawSecondaryValue < BUMPER_ON_VALUE; }; - - this.chooseNearEquipHotspots = function(candidateEntityProps, controllerData) { var _this = this; var collectedHotspots = flatten(candidateEntityProps.map(function(props) { @@ -499,11 +467,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa localPosition: this.offsetPosition, localRotation: this.offsetRotation }; - Entities.editEntity(this.grabbedThingID, reparentProps); + Entities.editEntity(this.targetEntityID, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', - grabbedEntity: this.grabbedThingID, + grabbedEntity: this.targetEntityID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); }; @@ -517,6 +485,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "releaseEquip", args); + ensureDynamic(this.targetEntityID); this.targetEntityID = null; }; @@ -537,11 +506,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; - - // var candidateEntities = controllerData.nearbyEntityProperties[this.hand].map(function (props) { - // return props.id; - // }); - var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand]; var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData); @@ -558,11 +522,15 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); if (potentialEquipHotspot) { + if (this.triggerSmoothedSqueezed()) { + this.grabbedHotspot = potentialEquipHotspot; + this.targetEntityID = this.grabbedHotspot.entityID; + this.startEquipEntity(controllerData); + } return makeRunningValues(true, [potentialEquipHotspot.entityID], []); } else { return makeRunningValues(false, [], []); } - }; this.isReady = function (controllerData, deltaTime) { @@ -571,7 +539,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.run = function (controllerData, deltaTime) { - return this.checkNearbyHotspots(controllerData, deltaTime); + if (!this.targetEntityID) { + return this.checkNearbyHotspots(controllerData, deltaTime); + } + + equipHotspotBuddy.update(deltaTime, timestamp, controllerData); if (controllerData.secondaryValues[this.hand]) { // this.secondaryReleased() will always be true when not depressed @@ -607,8 +579,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (dropDetected && !this.waitForTriggerRelease && controllerData.triggerClicks[this.hand]) { // store the offset attach points into preferences. - if (this.grabbedHotspot && this.grabbedThingID) { - var prefprops = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "localRotation"]); + if (this.grabbedHotspot && this.targetEntityID) { + var prefprops = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]); if (prefprops && prefprops.localPosition && prefprops.localRotation) { storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, prefprops.localPosition, prefprops.localRotation); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index a2edab4102..c1b87bc6e8 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -308,21 +308,8 @@ Script.include("/~/system/libraries/controllers.js"); this.previousRoomControllerPosition = roomControllerPosition; }; - this.ensureDynamic = function () { - // if we distance hold something and keep it very still before releasing it, it ends up - // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. - var props = Entities.getEntityProperties(this.grabbedThingID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID == NULL_UUID) { - var velocity = props.velocity; - if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD - velocity = { x: 0.0, y: 0.2, z: 0.0 }; - Entities.editEntity(this.grabbedThingID, { velocity: velocity }); - } - } - }; - this.endNearGrabAction = function () { - this.ensureDynamic(); + ensureDynamic(this.grabbedThingID); this.distanceHolding = false; this.distanceRotating = false; Entities.deleteAction(this.grabbedThingID, this.actionID); From 1d24523be997313a5a326f674a8dd6c1ef02bd42 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 13:09:17 -0700 Subject: [PATCH 20/78] equip drop gesture works --- .../controllerModules/equipEntity.js | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index f44ce4ff78..34db9709b3 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -160,7 +160,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa (function() { - var debug = true; var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. @@ -323,10 +322,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa hasParent = false; } if (hasParent || entityHasActions(hotspot.entityID)) { - if (debug) { - print("equip is skipping '" + props.name + "': grabbed by someone else: " + - hasParent + " : " + entityHasActions(hotspot.entityID) + " : " + this.hand); - } return false; } @@ -344,6 +339,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); }; + this.triggerSmoothedGrab = function() { + return this.triggerClicked; + }; + this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; }; @@ -489,15 +488,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; }; - this.checkNearbyHotspots = function (controllerData, deltaTime) { - - var timestamp = Date.now(); - + this.updateInputs = function (controllerData) { this.rawTriggerValue = controllerData.triggerValues[this.hand]; this.triggerClicked = controllerData.triggerClicks[this.hand]; this.rawSecondaryValue = controllerData.secondaryValues[this.hand]; this.updateSmoothedTrigger(controllerData); + }; + this.checkNearbyHotspots = function (controllerData, deltaTime, timestamp) { this.controllerJointIndex = getControllerJointIndex(this.hand); if (this.triggerSmoothedReleased() && this.secondaryReleased()) { @@ -522,7 +520,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); if (potentialEquipHotspot) { - if (this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) { this.grabbedHotspot = potentialEquipHotspot; this.targetEntityID = this.grabbedHotspot.entityID; this.startEquipEntity(controllerData); @@ -534,17 +532,19 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; this.isReady = function (controllerData, deltaTime) { - return this.checkNearbyHotspots(controllerData, deltaTime); + var timestamp = Date.now(); + this.updateInputs(controllerData); + return this.checkNearbyHotspots(controllerData, deltaTime, timestamp); }; this.run = function (controllerData, deltaTime) { + var timestamp = Date.now(); + this.updateInputs(controllerData); if (!this.targetEntityID) { - return this.checkNearbyHotspots(controllerData, deltaTime); + return this.checkNearbyHotspots(controllerData, deltaTime, timestamp); } - equipHotspotBuddy.update(deltaTime, timestamp, controllerData); - if (controllerData.secondaryValues[this.hand]) { // this.secondaryReleased() will always be true when not depressed // so we cannot simply rely on that for release - ensure that the @@ -571,13 +571,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } // highlight the grabbed hotspot when the dropGesture is detected. - var timestamp = Date.now(); if (dropDetected) { equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp); equipHotspotBuddy.highlightHotspot(this.grabbedHotspot); } - if (dropDetected && !this.waitForTriggerRelease && controllerData.triggerClicks[this.hand]) { + if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { + this.waitForTriggerRelease = true; // store the offset attach points into preferences. if (this.grabbedHotspot && this.targetEntityID) { var prefprops = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]); @@ -592,6 +592,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } this.prevDropDetected = dropDetected; + equipHotspotBuddy.update(deltaTime, timestamp, controllerData); + return makeRunningValues(true, [this.targetEntityID], []); }; From ddca25672fd1e56763eefe159ceb8a3e5069133b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 14:14:28 -0700 Subject: [PATCH 21/78] trying to get near-triggering working --- .../controllers/controllerDispatcher.js | 21 +--- .../controllers/controllerDispatcherUtils.js | 42 ++++++- .../controllerModules/nearActionGrabEntity.js | 34 ++--- .../controllerModules/nearParentGrabEntity.js | 19 +-- .../controllerModules/nearTrigger.js | 119 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 3 +- 6 files changed, 182 insertions(+), 56 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/nearTrigger.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 959f217eb4..94bc9851da 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -9,7 +9,7 @@ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable, - LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE + LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES */ controllerDispatcherPlugins = {}; @@ -25,25 +25,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var NEAR_MIN_RADIUS = 0.1; var NEAR_MAX_RADIUS = 1.0; - var DISPATCHER_PROPERTIES = [ - "position", - "registrationPoint", - "rotation", - "gravity", - "collidesWith", - "dynamic", - "collisionless", - "locked", - "name", - "shapeType", - "parentID", - "parentJointIndex", - "density", - "dimensions", - "userData" - ]; - - var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var lastInterval = Date.now(); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 5b6d624892..913faf60bf 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -18,6 +18,7 @@ COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, NEAR_GRAB_RADIUS, + DISPATCHER_PROPERTIES, Entities, makeDispatcherModuleParameters, makeRunningValues, @@ -34,7 +35,8 @@ projectOntoEntityXYPlane, projectOntoOverlayXYPlane, entityHasActions, - ensureDynamic + ensureDynamic, + findGroupParent */ MSECS_PER_SEC = 1000.0; @@ -72,6 +74,27 @@ NEAR_GRAB_RADIUS = 0.1; +DISPATCHER_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" +]; + + + + // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start // requiredDataForReady -- which "situation" parts this module looks at to decide if it will start @@ -137,6 +160,9 @@ getGrabbableData = function (props) { if (!grabbableData.hasOwnProperty("kinematicGrab")) { grabbableData.kinematicGrab = true; } + if (!grabbableData.hasOwnProperty("wantsTrigger")) { + grabbableData.wantsTrigger = false; + } return grabbableData; }; @@ -258,3 +284,17 @@ ensureDynamic = function (entityID) { } } }; + +findGroupParent = function (controllerData, targetProps) { + while (targetProps.parentID && targetProps.parentID != NULL_UUID) { + var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); + if (!parentProps) { + break; + } + parentProps.id = targetProps.parentID; + targetProps = parentProps; + controllerData.nearbyEntityPropertiesByID[targetProps.id] = targetProps; + } + + return targetProps; +}; diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 217e90ef88..a2673f8bca 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -9,7 +9,7 @@ getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, - TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS + TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -152,6 +152,11 @@ Script.include("/~/system/libraries/controllers.js"); break; } if (entityIsGrabbable(props)) { + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } return props; } } @@ -162,23 +167,18 @@ Script.include("/~/system/libraries/controllers.js"); this.targetEntityID = null; if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { - makeRunningValues(false, [], []); + return makeRunningValues(false, [], []); } var targetProps = this.getTargetProps(controllerData); if (targetProps) { if (!propsArePhysical(targetProps)) { - // XXX make sure no highlights are enabled from this module return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it } else { this.targetEntityID = targetProps.id; - ContextOverlay.entityWithContextOverlay = this.targetEntityID; - ContextOverlay.enabled = true; - // XXX highlight this.targetEntityID here return makeRunningValues(true, [this.targetEntityID], []); } } else { - // XXX make sure no highlights are enabled from this module return makeRunningValues(false, [], []); } }; @@ -204,26 +204,8 @@ Script.include("/~/system/libraries/controllers.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - - // XXX - var rayPickInfo = controllerData.rayPicks[this.hand]; - var pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection, targetProps), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.entityID, pointerEvent)) { - } - // XXX - - if (controllerData.triggerClicks[this.hand] == 1) { - // stop highlighting, switch to grabbing - // XXX stop highlight here + // switch to grabbing this.startNearGrabAction(controllerData, targetProps); } } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index c5ca95fe3b..7d5d1163bf 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -9,7 +9,8 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, - makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS + makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, + findGroupParent, Vec3 */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -29,7 +30,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.parameters = makeDispatcherModuleParameters( 500, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + this.hand === RIGHT_HAND ? ["rightHand", "rightHandTrigger"] : ["leftHand", "leftHandTrigger"], [], 100); @@ -64,6 +65,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.startNearParentingGrabEntity = function (controllerData, targetProps) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); var handJointIndex; @@ -135,6 +137,11 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); break; } if (entityIsGrabbable(props)) { + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } return props; } } @@ -146,21 +153,18 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.grabbing = false; if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { - makeRunningValues(false, [], []); + return makeRunningValues(false, [], []); } var targetProps = this.getTargetProps(controllerData); if (targetProps) { if (propsArePhysical(targetProps)) { - // XXX make sure no highlights are enabled from this module return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it } else { this.targetEntityID = targetProps.id; - // XXX highlight this.targetEntityID here return makeRunningValues(true, [this.targetEntityID], []); } } else { - // XXX make sure no highlights are enabled from this module return makeRunningValues(false, [], []); } }; @@ -181,8 +185,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return readiness; } if (controllerData.triggerClicks[this.hand] == 1) { - // stop highlighting, switch to grabbing - // XXX stop highlight here + // switch to grab var targetProps = this.getTargetProps(controllerData); if (targetProps) { this.grabbing = true; diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js new file mode 100644 index 0000000000..93f509c6b4 --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -0,0 +1,119 @@ +"use strict"; + +// nearParentGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + enableDispatcherModule, disableDispatcherModule, getGrabbableData, Vec3, + TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); + +(function() { + + function entityWantsNearTrigger(props) { + return getGrabbableData(props).triggerable; + } + + function NearTriggerEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.grabbing = false; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.previouslyUnhooked = {}; + + this.parameters = makeDispatcherModuleParameters( + 200, + this.hand === RIGHT_HAND ? ["rightHandTrigger"] : ["leftHandTrigger"], + [], + 100); + + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by length from controller + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(props.position, handPosition); + // if (distance > NEAR_GRAB_RADIUS) { + // print("QQQ nop 0"); + // break; + // } + if (entityWantsNearTrigger(props)) { + return props; + } else { + print("QQQ nop 1"); + } + } + return null; + }; + + this.startNearTrigger = function (controllerData) { + Controller.triggerShortHapticPulse(1.0, this.hand); + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args); + }; + + this.continueNearTrigger = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueNearTrigger", args); + }; + + this.endNearTrigger = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "endNearTrigger", args); + }; + + this.isReady = function (controllerData) { + this.targetEntityID = null; + + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + return makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.targetEntityID = targetProps.id; + this.startNearTrigger(controllerData); + return makeRunningValues(true, [this.targetEntityID], []); + } else { + return makeRunningValues(false, [], []); + } + }; + + this.run = function (controllerData) { + if (controllerData.triggerClicks[this.hand] == 0) { + this.endNearTrigger(controllerData); + return makeRunningValues(false, [], []); + } + + this.continueNearTrigger(controllerData); + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.cleanup = function () { + if (this.targetEntityID) { + this.endNearParentingGrabEntity(); + } + }; + } + + var leftNearParentingGrabEntity = new NearTriggerEntity(LEFT_HAND); + var rightNearParentingGrabEntity = new NearTriggerEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); + enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); + + this.cleanup = function () { + leftNearParentingGrabEntity.cleanup(); + rightNearParentingGrabEntity.cleanup(); + disableDispatcherModule("LeftNearParentingGrabEntity"); + disableDispatcherModule("RightNearParentingGrabEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index ea45f0a2c6..d41dec6de1 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -24,7 +24,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearActionGrabEntity.js", "controllerModules/farActionGrabEntity.js", "controllerModules/tabletStylusInput.js", - "controllerModules/equipEntity.js" + "controllerModules/equipEntity.js", + "controllerModules/nearTrigger.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 3b61e8518ff734dad7f99e7695fef971acc0aebc Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 14:46:48 -0700 Subject: [PATCH 22/78] near-trigger works? equip calls its entity methods now --- .../system/controllers/controllerDispatcherUtils.js | 7 +++++-- .../controllers/controllerModules/equipEntity.js | 10 ++++++++-- .../controllers/controllerModules/nearTrigger.js | 13 +++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 913faf60bf..a1bf2726da 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -148,8 +148,8 @@ getGrabbableData = function (props) { } catch (err) { userDataParsed = {}; } - if (userDataParsed.grabbable) { - grabbableData = userDataParsed.grabbable; + if (userDataParsed.grabbableKey) { + grabbableData = userDataParsed.grabbableKey; } if (!grabbableData.hasOwnProperty("grabbable")) { grabbableData.grabbable = true; @@ -163,6 +163,9 @@ getGrabbableData = function (props) { if (!grabbableData.hasOwnProperty("wantsTrigger")) { grabbableData.wantsTrigger = false; } + if (!grabbableData.hasOwnProperty("triggerable")) { + grabbableData.triggerable = false; + } return grabbableData; }; diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 34db9709b3..823247ea29 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -461,13 +461,16 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var reparentProps = { parentID: AVATAR_SELF_ID, parentJointIndex: handJointIndex, - velocity: {x: 0, y: 0, z: 0}, - angularVelocity: {x: 0, y: 0, z: 0}, + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0}, localPosition: this.offsetPosition, localRotation: this.offsetRotation }; Entities.editEntity(this.targetEntityID, reparentProps); + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "startEquip", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', grabbedEntity: this.targetEntityID, @@ -594,6 +597,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueEquip", args); + return makeRunningValues(true, [this.targetEntityID], []); }; diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 93f509c6b4..fbaa300e82 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -16,7 +16,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { function entityWantsNearTrigger(props) { - return getGrabbableData(props).triggerable; + var grabbableData = getGrabbableData(props); + return grabbableData.triggerable || grabbableData.wantsTrigger; } function NearTriggerEntity(hand) { @@ -40,21 +41,17 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var props = nearbyEntityProperties[i]; var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(props.position, handPosition); - // if (distance > NEAR_GRAB_RADIUS) { - // print("QQQ nop 0"); - // break; - // } + if (distance > NEAR_GRAB_RADIUS) { + break; + } if (entityWantsNearTrigger(props)) { return props; - } else { - print("QQQ nop 1"); } } return null; }; this.startNearTrigger = function (controllerData) { - Controller.triggerShortHapticPulse(1.0, this.hand); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "startNearTrigger", args); }; From 03977334a0c90133832be10786a62c2827426fdb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 14:50:39 -0700 Subject: [PATCH 23/78] near-trigger works? --- scripts/system/controllers/controllerModules/nearTrigger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index fbaa300e82..c2c6e697d6 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -95,7 +95,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.cleanup = function () { if (this.targetEntityID) { - this.endNearParentingGrabEntity(); + this.endNearTrigger(); } }; } From 4bfbab294d3ef19a7dfea4874d43a42e397f5945 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 14:53:58 -0700 Subject: [PATCH 24/78] near-trigger works? --- .../controllerModules/nearTrigger.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index c2c6e697d6..8b7ef65710 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -1,6 +1,6 @@ "use strict"; -// nearParentGrabEntity.js +// nearTrigger.js // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -100,17 +100,17 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; } - var leftNearParentingGrabEntity = new NearTriggerEntity(LEFT_HAND); - var rightNearParentingGrabEntity = new NearTriggerEntity(RIGHT_HAND); + var leftNearTriggerEntity = new NearTriggerEntity(LEFT_HAND); + var rightNearTriggerEntity = new NearTriggerEntity(RIGHT_HAND); - enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); - enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); + enableDispatcherModule("LeftNearTriggerEntity", leftNearTriggerEntity); + enableDispatcherModule("RightNearTriggerEntity", rightNearTriggerEntity); this.cleanup = function () { - leftNearParentingGrabEntity.cleanup(); - rightNearParentingGrabEntity.cleanup(); - disableDispatcherModule("LeftNearParentingGrabEntity"); - disableDispatcherModule("RightNearParentingGrabEntity"); + leftNearTriggerEntity.cleanup(); + rightNearTriggerEntity.cleanup(); + disableDispatcherModule("LeftNearTriggerEntity"); + disableDispatcherModule("RightNearTriggerEntity"); }; Script.scriptEnding.connect(this.cleanup); }()); From 3082c357f9f85b1fd617c2e7012ca589f3be22b0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 17:54:04 -0700 Subject: [PATCH 25/78] cloning sort-of works... cloning an equipable doesn't --- interface/src/avatar/AvatarManager.h | 6 +- .../controllers/controllerDispatcherUtils.js | 1 + .../controllerModules/cloneEntity.js | 160 ++++++++++++++++++ .../controllerModules/equipEntity.js | 2 +- .../controllerModules/nearParentGrabEntity.js | 8 +- .../system/controllers/controllerScripts.js | 3 +- 6 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/cloneEntity.js diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index c21214484b..f595f148a8 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -73,9 +73,9 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); - Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, - const QVector& avatarsToInclude, - const QVector& avatarsToDiscard); + /* Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, */ + /* const QVector& avatarsToInclude, */ + /* const QVector& avatarsToDiscard); */ // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index a1bf2726da..808623fc79 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -290,6 +290,7 @@ ensureDynamic = function (entityID) { findGroupParent = function (controllerData, targetProps) { while (targetProps.parentID && targetProps.parentID != NULL_UUID) { + // XXX use controllerData.nearbyEntityPropertiesByID ? var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); if (!parentProps) { break; diff --git a/scripts/system/controllers/controllerModules/cloneEntity.js b/scripts/system/controllers/controllerModules/cloneEntity.js new file mode 100644 index 0000000000..0539ee983a --- /dev/null +++ b/scripts/system/controllers/controllerModules/cloneEntity.js @@ -0,0 +1,160 @@ +"use strict"; + +// cloneEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, RIGHT_HAND, LEFT_HAND, + enableDispatcherModule, disableDispatcherModule, getGrabbableData, Vec3, + TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); + +// Object assign polyfill +if (typeof Object.assign != 'function') { + Object.assign = function(target, varArgs) { + if (target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource !== null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +(function() { + + function entityIsCloneable(props) { + var grabbableData = getGrabbableData(props); + return grabbableData.cloneable; + } + + function CloneEntity(hand) { + this.hand = hand; + this.grabbing = false; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.previouslyUnhooked = {}; + + this.parameters = makeDispatcherModuleParameters( + 150, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by length from controller + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(props.position, handPosition); + if (distance > NEAR_GRAB_RADIUS) { + break; + } + if (entityIsCloneable(props)) { + return props; + } + } + return null; + }; + + this.isReady = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + this.waiting = false; + return makeRunningValues(false, [], []); + } + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + if (this.waiting) { + return makeRunningValues(false, [], []); + } + this.waiting = true; + } + + var cloneableProps = this.getTargetProps(controllerData); + if (!cloneableProps) { + return makeRunningValues(false, [], []); + } + + // we need all the properties, for this + cloneableProps = Entities.getEntityProperties(cloneableProps.id); + + var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; + var count = 0; + worldEntityProps.forEach(function(itemWE) { + if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) { + count++; + } + }); + + var grabInfo = getGrabbableData(cloneableProps); + + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; + if (count >= limit && limit !== 0) { + return makeRunningValues(false, [], []); + } + + cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var cUserData = Object.assign({}, cloneableProps.userData); + var cProperties = Object.assign({}, cloneableProps); + + try { + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id; + } catch(e) { + } + + cProperties.dynamic = dynamic; + cProperties.locked = false; + if (!cUserData.grabbableKey) { + cUserData.grabbableKey = {}; + } + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + // var cloneID = + Entities.addEntity(cProperties); + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + }; + + this.cleanup = function () { + }; + } + + var leftNearParentingGrabEntity = new CloneEntity(LEFT_HAND); + var rightNearParentingGrabEntity = new CloneEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); + enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); + + this.cleanup = function () { + leftNearParentingGrabEntity.cleanup(); + rightNearParentingGrabEntity.cleanup(); + disableDispatcherModule("LeftNearParentingGrabEntity"); + disableDispatcherModule("RightNearParentingGrabEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 823247ea29..2197fdca28 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -321,7 +321,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (props.parentID === NULL_UUID) { hasParent = false; } - if (hasParent || entityHasActions(hotspot.entityID)) { + if (hasParent || props.locked || entityHasActions(hotspot.entityID)) { return false; } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 7d5d1163bf..2332fdd32c 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -82,8 +82,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var reparentProps = { parentID: AVATAR_SELF_ID, parentJointIndex: handJointIndex, - velocity: {x: 0, y: 0, z: 0}, - angularVelocity: {x: 0, y: 0, z: 0} + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} }; if (this.thisHandIsParent(targetProps)) { @@ -114,8 +114,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Entities.editEntity(this.targetEntityID, { parentID: this.previousParentID[this.targetEntityID], parentJointIndex: this.previousParentJointIndex[this.targetEntityID], - velocity: {x: 0, y: 0, z: 0}, - angularVelocity: {x: 0, y: 0, z: 0} + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} }); } diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index d41dec6de1..01b0d2ef11 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -25,7 +25,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/farActionGrabEntity.js", "controllerModules/tabletStylusInput.js", "controllerModules/equipEntity.js", - "controllerModules/nearTrigger.js" + "controllerModules/nearTrigger.js", + "controllerModules/cloneEntity.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 243cffb98d48eb2c31f460923145bff120c32c37 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 15 Aug 2017 18:05:06 -0700 Subject: [PATCH 26/78] oops --- interface/src/avatar/AvatarManager.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index f595f148a8..c21214484b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -73,9 +73,9 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); - /* Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, */ - /* const QVector& avatarsToInclude, */ - /* const QVector& avatarsToDiscard); */ + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); From 8d04047d8ac2a6f3752e328d35df9c3277a05046 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 18 Aug 2017 13:23:44 -0700 Subject: [PATCH 27/78] starting to integrate teleport.js --- .../system/controllers/controllerScripts.js | 4 +- scripts/system/controllers/teleport.js | 378 ++++++++++-------- 2 files changed, 207 insertions(+), 175 deletions(-) diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 01b0d2ef11..b69b2a6e3f 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -15,7 +15,6 @@ var CONTOLLER_SCRIPTS = [ // "handControllerGrab.js", "handControllerPointer.js", // "grab.js", - // "teleport.js", "toggleAdvancedMovementForHandControllers.js", "ControllerDispatcher.js", @@ -26,7 +25,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/tabletStylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", - "controllerModules/cloneEntity.js" + "controllerModules/cloneEntity.js", + "teleport.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 17ca2f91c5..6d5a129b98 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -8,6 +8,17 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, + getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, + Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, + Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic +*/ + +Script.include("/~/system/libraries/Xform.js"); +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + (function() { // BEGIN LOCAL_SCOPE var inTeleportMode = false; @@ -149,8 +160,9 @@ var TARGET = { SEAT: 'seat', // The current target is a seat }; -function Teleporter() { +function Teleporter(hand) { var _this = this; + this.hand = hand; this.active = false; this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; @@ -276,6 +288,21 @@ function Teleporter() { inTeleportMode = false; }; + this.parameters = makeDispatcherModuleParameters( + 300, + //this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.isReady = function(controllerData, deltaTime) { + print("--------> is teleport plugin ready <--------"); + return false; + }; + + this.run = function(controllerData, deltaTime) { + print("---------> running teleport plugin <--------"); + }; + this.update = function() { if (_this.state === TELEPORTER_STATES.IDLE) { return; @@ -385,191 +412,196 @@ function Teleporter() { }; } -// related to repositioning the avatar after you teleport -var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; -var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; -function getAvatarFootOffset() { + // related to repositioning the avatar after you teleport + var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; + var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; + function getAvatarFootOffset() { - // find a valid foot jointIndex - var footJointIndex = -1; - var i, l = FOOT_JOINT_NAMES.length; - for (i = 0; i < l; i++) { - footJointIndex = MyAvatar.getJointIndex(FOOT_JOINT_NAMES[i]); + // find a valid foot jointIndex + var footJointIndex = -1; + var i, l = FOOT_JOINT_NAMES.length; + for (i = 0; i < l; i++) { + footJointIndex = MyAvatar.getJointIndex(FOOT_JOINT_NAMES[i]); + if (footJointIndex != -1) { + break; + } + } if (footJointIndex != -1) { - break; - } - } - if (footJointIndex != -1) { - // default vertical offset from foot to avatar root. - var footPos = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex); - if (footPos.x === 0 && footPos.y === 0 && footPos.z === 0.0) { - // if footPos is exactly zero, it's probably wrong because avatar is currently loading, fall back to default. + // default vertical offset from foot to avatar root. + var footPos = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex); + if (footPos.x === 0 && footPos.y === 0 && footPos.z === 0.0) { + // if footPos is exactly zero, it's probably wrong because avatar is currently loading, fall back to default. + return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale; + } else { + return -footPos.y; + } + } else { return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale; - } else { - return -footPos.y; - } - } else { - return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale; - } -} - -var leftPad = new ThumbPad('left'); -var rightPad = new ThumbPad('right'); -var leftTrigger = new Trigger('left'); -var rightTrigger = new Trigger('right'); - -var mappingName, teleportMapping; - -var TELEPORT_DELAY = 0; - -function isMoving() { - var LY = Controller.getValue(Controller.Standard.LY); - var LX = Controller.getValue(Controller.Standard.LX); - if (LY !== 0 || LX !== 0) { - return true; - } else { - return false; - } -} - -function parseJSON(json) { - try { - return JSON.parse(json); - } catch (e) { - return undefined; - } -} -// When determininig whether you can teleport to a location, the normal of the -// point that is being intersected with is looked at. If this normal is more -// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then -// you can't teleport there. -var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; -function getTeleportTargetType(result) { - if (result.type == RayPick.INTERSECTED_NONE) { - return TARGET.NONE; - } - - var props = Entities.getEntityProperties(result.objectID, ['userData', 'visible']); - var data = parseJSON(props.userData); - if (data !== undefined && data.seat !== undefined) { - var avatarUuid = Uuid.fromString(data.seat.user); - if (Uuid.isNull(avatarUuid) || !AvatarList.getAvatar(avatarUuid)) { - return TARGET.SEAT; - } else { - return TARGET.INVALID; } } - if (!props.visible) { - return TARGET.INVISIBLE; + var leftPad = new ThumbPad('left'); + var rightPad = new ThumbPad('right'); + + var mappingName, teleportMapping; + + var TELEPORT_DELAY = 0; + + function isMoving() { + var LY = Controller.getValue(Controller.Standard.LY); + var LX = Controller.getValue(Controller.Standard.LX); + if (LY !== 0 || LX !== 0) { + return true; + } else { + return false; + } } - var surfaceNormal = result.surfaceNormal; - var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); - var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + } + // When determininig whether you can teleport to a location, the normal of the + // point that is being intersected with is looked at. If this normal is more + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then + // you can't teleport there. + var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; + function getTeleportTargetType(result) { + if (result.type == RayPick.INTERSECTED_NONE) { + return TARGET.NONE; + } + + var props = Entities.getEntityProperties(result.objectID, ['userData', 'visible']); + var data = parseJSON(props.userData); + if (data !== undefined && data.seat !== undefined) { + var avatarUuid = Uuid.fromString(data.seat.user); + if (Uuid.isNull(avatarUuid) || !AvatarList.getAvatar(avatarUuid)) { + return TARGET.SEAT; + } else { + return TARGET.INVALID; + } + } - if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || + if (!props.visible) { + return TARGET.INVISIBLE; + } + + var surfaceNormal = result.surfaceNormal; + var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); + var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); + + if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE) { - return TARGET.INVALID; - } else { - return TARGET.SURFACE; - } -} - -function registerMappings() { - mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); - teleportMapping = Controller.newMapping(mappingName); - teleportMapping.from(Controller.Standard.RT).peek().to(rightTrigger.buttonPress); - teleportMapping.from(Controller.Standard.LT).peek().to(leftTrigger.buttonPress); - - teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightPad.buttonPress); - teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftPad.buttonPress); - - teleportMapping.from(Controller.Standard.LeftPrimaryThumb) - .to(function(value) { - if (isDisabled === 'left' || isDisabled === 'both') { - return; - } - if (leftTrigger.down()) { - return; - } - if (isMoving() === true) { - return; - } - teleporter.enterTeleportMode('left'); - return; - }); - teleportMapping.from(Controller.Standard.RightPrimaryThumb) - .to(function(value) { - if (isDisabled === 'right' || isDisabled === 'both') { - return; - } - if (rightTrigger.down()) { - return; - } - if (isMoving() === true) { - return; - } - - teleporter.enterTeleportMode('right'); - return; - }); -} - -registerMappings(); - -var teleporter = new Teleporter(); - -Controller.enableMapping(mappingName); - -function cleanup() { - teleportMapping.disable(); - teleporter.cleanup(); -} -Script.scriptEnding.connect(cleanup); - -var setIgnoreEntities = function() { - LaserPointers.setIgnoreEntities(teleporter.teleportRayRightVisible, ignoredEntities); - LaserPointers.setIgnoreEntities(teleporter.teleportRayRightInvisible, ignoredEntities); - LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftVisible, ignoredEntities); - LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftInvisible, ignoredEntities); - LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadVisible, ignoredEntities); - LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadInvisible, ignoredEntities); -} - -var isDisabled = false; -var handleTeleportMessages = function(channel, message, sender) { - if (sender === MyAvatar.sessionUUID) { - if (channel === 'Hifi-Teleport-Disabler') { - if (message === 'both') { - isDisabled = 'both'; - } - if (message === 'left') { - isDisabled = 'left'; - } - if (message === 'right') { - isDisabled = 'right'; - } - if (message === 'none') { - isDisabled = false; - } - } else if (channel === 'Hifi-Teleport-Ignore-Add' && !Uuid.isNull(message) && ignoredEntities.indexOf(message) === -1) { - ignoredEntities.push(message); - setIgnoreEntities(); - } else if (channel === 'Hifi-Teleport-Ignore-Remove' && !Uuid.isNull(message)) { - var removeIndex = ignoredEntities.indexOf(message); - if (removeIndex > -1) { - ignoredEntities.splice(removeIndex, 1); - setIgnoreEntities(); - } + return TARGET.INVALID; + } else { + return TARGET.SURFACE; } } -}; -Messages.subscribe('Hifi-Teleport-Disabler'); -Messages.subscribe('Hifi-Teleport-Ignore-Add'); -Messages.subscribe('Hifi-Teleport-Ignore-Remove'); -Messages.messageReceived.connect(handleTeleportMessages); + function registerMappings() { + mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); + teleportMapping = Controller.newMapping(mappingName); + //teleportMapping.from(Controller.Standard.RT).peek().to(rightTrigger.buttonPress); + //teleportMapping.from(Controller.Standard.LT).peek().to(leftTrigger.buttonPress); + + teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightPad.buttonPress); + teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftPad.buttonPress); + + /*teleportMapping.from(Controller.Standard.LeftPrimaryThumb) + .to(function(value) { + if (isDisabled === 'left' || isDisabled === 'both') { + return; + } + if (leftTrigger.down()) { + return; + } + if (isMoving() === true) { + return; + } + //teleporter.enterTeleportMode('left'); + return; + }); + teleportMapping.from(Controller.Standard.RightPrimaryThumb) + .to(function(value) { + if (isDisabled === 'right' || isDisabled === 'both') { + return; + } + if (rightTrigger.down()) { + return; + } + if (isMoving() === true) { + return; + } + + //teleporter.enterTeleportMode('right'); + return; + });*/ + } + + registerMappings(); + + var teleporter = new Teleporter(LEFT_HAND); + var leftTeleporter = new Teleporter(LEFT_HAND); + var rightTeleporter = new Teleporter(RIGHT_HAND); + + enableDispatcherModule("LeftTeleporter", leftTeleporter); + enableDispatcherModule("RightTeleporter", rightTeleporter); + + Controller.enableMapping(mappingName); + + function cleanup() { + teleportMapping.disable(); + //teleporter.cleanup(); + disableDispatcherModule("LeftTeleporter"); + disableDispatcherModule("RightTeleporter"); + } + Script.scriptEnding.connect(cleanup); + + var setIgnoreEntities = function() { + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadInvisible, ignoredEntities); + } + + var isDisabled = false; + var handleTeleportMessages = function(channel, message, sender) { + if (sender === MyAvatar.sessionUUID) { + if (channel === 'Hifi-Teleport-Disabler') { + if (message === 'both') { + isDisabled = 'both'; + } + if (message === 'left') { + isDisabled = 'left'; + } + if (message === 'right') { + isDisabled = 'right'; + } + if (message === 'none') { + isDisabled = false; + } + } else if (channel === 'Hifi-Teleport-Ignore-Add' && !Uuid.isNull(message) && ignoredEntities.indexOf(message) === -1) { + ignoredEntities.push(message); + setIgnoreEntities(); + } else if (channel === 'Hifi-Teleport-Ignore-Remove' && !Uuid.isNull(message)) { + var removeIndex = ignoredEntities.indexOf(message); + if (removeIndex > -1) { + ignoredEntities.splice(removeIndex, 1); + setIgnoreEntities(); + } + } + } + }; + + Messages.subscribe('Hifi-Teleport-Disabler'); + Messages.subscribe('Hifi-Teleport-Ignore-Add'); + Messages.subscribe('Hifi-Teleport-Ignore-Remove'); + Messages.messageReceived.connect(handleTeleportMessages); }()); // END LOCAL_SCOPE From f6151fae85dc022d6fdf1a556d59362406f7dbb5 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 21 Aug 2017 16:53:28 -0700 Subject: [PATCH 28/78] slowly intergrating teleport.js --- scripts/system/controllers/teleport.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 6d5a129b98..857d88ee9d 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -237,10 +237,6 @@ function Teleporter(hand) { LaserPointers.removeLaserPointer(this.teleportRayRightInvisible); LaserPointers.removeLaserPointer(this.teleportRayHeadVisible); LaserPointers.removeLaserPointer(this.teleportRayHeadInvisible); - - if (this.updateConnected === true) { - Script.update.disconnect(this, this.update); - } }; this.enterTeleportMode = function(hand) { @@ -266,15 +262,9 @@ function Teleporter(hand) { this.activeHand = hand; this.enableMappings(); - Script.update.connect(this, this.update); - this.updateConnected = true; }; this.exitTeleportMode = function(value) { - if (this.updateConnected === true) { - Script.update.disconnect(this, this.update); - } - this.disableMappings(); LaserPointers.disableLaserPointer(this.teleportRayLeftVisible); LaserPointers.disableLaserPointer(this.teleportRayLeftInvisible); @@ -298,12 +288,8 @@ function Teleporter(hand) { print("--------> is teleport plugin ready <--------"); return false; }; - + this.run = function(controllerData, deltaTime) { - print("---------> running teleport plugin <--------"); - }; - - this.update = function() { if (_this.state === TELEPORTER_STATES.IDLE) { return; } From cdd184e32d6c8fae8c6e22ea3281009cced8087b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 22 Aug 2017 11:06:11 -0700 Subject: [PATCH 29/78] intergradted teleport --- .../controllers/controllerDispatcher.js | 3 +- scripts/system/controllers/grab.js | 8 + scripts/system/controllers/teleport.js | 227 +++++------------- 3 files changed, 76 insertions(+), 162 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 94bc9851da..116abe2c9a 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -39,7 +39,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // is stored as the value, rather than false. this.activitySlots = { leftHand: false, - rightHand: false + rightHand: false, + mouse: false }; this.slotsAreAvailableForPlugin = function (plugin) { diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 2844940d2b..50f9a5c5f0 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -21,6 +21,8 @@ (function() { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/utils.js"); +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds @@ -251,6 +253,12 @@ function Grabber() { z: 0 }; + this.paramters = makeDispatcherModuleParameters( + 300, + "mouse", + [], + 100); + this.targetPosition = null; this.targetRotation = null; diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 857d88ee9d..6246c0f03a 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -163,41 +163,27 @@ var TARGET = { function Teleporter(hand) { var _this = this; this.hand = hand; + this.buttonValue = 0; this.active = false; this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; + this.currentResult = null; - this.teleportRayLeftVisible = LaserPointers.createLaserPointer({ - joint: "LeftHand", + this.teleportRayHandVisible = LaserPointers.createLaserPointer({ + joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", filter: RayPick.PICK_ENTITIES, faceAvatar: true, centerEndY: false, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); - this.teleportRayLeftInvisible = LaserPointers.createLaserPointer({ - joint: "LeftHand", + this.teleportRayHandInvisible = LaserPointers.createLaserPointer({ + joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, faceAvatar: true, centerEndY: false, renderStates: teleportRenderStates }); - this.teleportRayRightVisible = LaserPointers.createLaserPointer({ - joint: "RightHand", - filter: RayPick.PICK_ENTITIES, - faceAvatar: true, - centerEndY: false, - renderStates: teleportRenderStates, - defaultRenderStates: teleportDefaultRenderStates - }); - this.teleportRayRightInvisible = LaserPointers.createLaserPointer({ - joint: "RightHand", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, - faceAvatar: true, - centerEndY: false, - renderStates: teleportRenderStates - }); - this.teleportRayHeadVisible = LaserPointers.createLaserPointer({ joint: "Avatar", filter: RayPick.PICK_ENTITIES, @@ -214,9 +200,6 @@ function Teleporter(hand) { renderStates: teleportRenderStates }); - this.updateConnected = null; - this.activeHand = null; - this.teleporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); this.teleportMappingInternal = Controller.newMapping(this.teleporterMappingInternalName); @@ -231,90 +214,43 @@ function Teleporter(hand) { this.cleanup = function() { this.disableMappings(); - LaserPointers.removeLaserPointer(this.teleportRayLeftVisible); - LaserPointers.removeLaserPointer(this.teleportRayLeftInvisible); - LaserPointers.removeLaserPointer(this.teleportRayRightVisible); - LaserPointers.removeLaserPointer(this.teleportRayRightInvisible); + LaserPointers.removeLaserPointer(this.teleportRayHandVisible); + LaserPointers.removeLaserPointer(this.teleportRayHandInvisible); LaserPointers.removeLaserPointer(this.teleportRayHeadVisible); LaserPointers.removeLaserPointer(this.teleportRayHeadInvisible); }; - this.enterTeleportMode = function(hand) { - if (inTeleportMode === true) { - return; - } - if (isDisabled === 'both' || isDisabled === hand) { - return; - } - - inTeleportMode = true; - - if (coolInTimeout !== null) { - Script.clearTimeout(coolInTimeout); - } - - this.state = TELEPORTER_STATES.COOL_IN; - coolInTimeout = Script.setTimeout(function() { - if (_this.state === TELEPORTER_STATES.COOL_IN) { - _this.state = TELEPORTER_STATES.TARGETTING; - } - }, COOL_IN_DURATION); - - this.activeHand = hand; - this.enableMappings(); - }; - - this.exitTeleportMode = function(value) { - this.disableMappings(); - LaserPointers.disableLaserPointer(this.teleportRayLeftVisible); - LaserPointers.disableLaserPointer(this.teleportRayLeftInvisible); - LaserPointers.disableLaserPointer(this.teleportRayRightVisible); - LaserPointers.disableLaserPointer(this.teleportRayRightInvisible); - LaserPointers.disableLaserPointer(this.teleportRayHeadVisible); - LaserPointers.disableLaserPointer(this.teleportRayHeadInvisible); - - this.updateConnected = null; - this.state = TELEPORTER_STATES.IDLE; - inTeleportMode = false; - }; - + this.buttonPress = function(value) { + _this.buttonValue = value; + } + this.parameters = makeDispatcherModuleParameters( 300, - //this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); this.isReady = function(controllerData, deltaTime) { - print("--------> is teleport plugin ready <--------"); - return false; + if (_this.buttonValue !== 0) { + return makeRunningValues(true, [], []); + } + return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { - if (_this.state === TELEPORTER_STATES.IDLE) { - return; - } + _this.state = TELEPORTER_STATES.TARGETTING; // Get current hand pose information to see if the pose is valid - var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput); - var mode = pose.valid ? _this.activeHand : 'head'; + var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); + var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - if (mode === 'right') { - LaserPointers.disableLaserPointer(_this.teleportRayRightVisible); - LaserPointers.disableLaserPointer(_this.teleportRayRightInvisible); - } else { - LaserPointers.disableLaserPointer(_this.teleportRayLeftVisible); - LaserPointers.disableLaserPointer(_this.teleportRayLeftInvisible); - } + LaserPointers.disableLaserPointer(_this.teleportRayHandVisible); + LaserPointers.disableLaserPointer(_this.teleportRayHandInvisible); LaserPointers.enableLaserPointer(_this.teleportRayHeadVisible); LaserPointers.enableLaserPointer(_this.teleportRayHeadInvisible); } else { - if (mode === 'right') { - LaserPointers.enableLaserPointer(_this.teleportRayRightVisible); - LaserPointers.enableLaserPointer(_this.teleportRayRightInvisible); - } else { - LaserPointers.enableLaserPointer(_this.teleportRayLeftVisible); - LaserPointers.enableLaserPointer(_this.teleportRayLeftInvisible); - } + LaserPointers.enableLaserPointer(_this.teleportRayHandVisible); + LaserPointers.enableLaserPointer(_this.teleportRayHandInvisible); LaserPointers.disableLaserPointer(_this.teleportRayHeadVisible); LaserPointers.disableLaserPointer(_this.teleportRayHeadInvisible); } @@ -329,23 +265,20 @@ function Teleporter(hand) { // * In the second pass we pick against visible entities only. // var result; - if (mode === 'right') { - result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightInvisible); - } else if (mode === 'left') { - result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftInvisible); - } else { + if (mode === 'head') { result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadInvisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHandInvisible); } var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { - if (mode === 'right') { - result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightVisible); - } else if (mode === 'left') { - result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftVisible); - } else { + if (mode === 'head') { result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadVisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHandVisible); } + teleportLocationType = getTeleportTargetType(result); } @@ -363,37 +296,45 @@ function Teleporter(hand) { } else if (teleportLocationType === TARGET.SEAT) { this.setTeleportState(mode, "", "seat"); } + return this.teleport(result, teleportLocationType); + }; - - if (((_this.activeHand === 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) { - // remember the state before we exit teleport mode and set it back to IDLE - var previousState = this.state; - this.exitTeleportMode(); - - if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || previousState === TELEPORTER_STATES.COOL_IN) { - // Do nothing - } else if (teleportLocationType === TARGET.SEAT) { - Entities.callEntityMethod(result.objectID, 'sit'); - } else if (teleportLocationType === TARGET.SURFACE) { - var offset = getAvatarFootOffset(); - result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); - HMD.centerUI(); - MyAvatar.centerBody(); - } + this.teleport = function(newResult, target) { + var result = newResult; + if (_this.buttonValue !== 0) { + return makeRunningValues(true, [], []); } + + if (target === TARGET.NONE || target === TARGET.INVALID) { + // Do nothing + } else if (target === TARGET.SEAT) { + Entities.callEntityMethod(result.objectID, 'sit'); + } else if (target === TARGET.SURFACE) { + var offset = getAvatarFootOffset(); + result.intersection.y += offset; + MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + HMD.centerUI(); + MyAvatar.centerBody(); + } + + this.disableLasers(); + return makeRunningValues(false, [], []); + }; + + this.disableLasers = function() { + LaserPointers.disableLaserPointer(_this.teleportRayHandVisible); + LaserPointers.disableLaserPointer(_this.teleportRayHandInvisible); + LaserPointers.disableLaserPointer(_this.teleportRayHeadVisible); + LaserPointers.disableLaserPointer(_this.teleportRayHeadInvisible); }; this.setTeleportState = function(mode, visibleState, invisibleState) { - if (mode === 'right') { - LaserPointers.setRenderState(_this.teleportRayRightVisible, visibleState); - LaserPointers.setRenderState(_this.teleportRayRightInvisible, invisibleState); - } else if (mode === 'left') { - LaserPointers.setRenderState(_this.teleportRayLeftVisible, visibleState); - LaserPointers.setRenderState(_this.teleportRayLeftInvisible, invisibleState); - } else { + if (mode === 'head') { LaserPointers.setRenderState(_this.teleportRayHeadVisible, visibleState); LaserPointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); + } else { + LaserPointers.setRenderState(_this.teleportRayHandVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayHandInvisible, invisibleState); } }; } @@ -491,57 +432,21 @@ function Teleporter(hand) { function registerMappings() { mappingName = 'Hifi-Teleporter-Dev-' + Math.random(); teleportMapping = Controller.newMapping(mappingName); - //teleportMapping.from(Controller.Standard.RT).peek().to(rightTrigger.buttonPress); - //teleportMapping.from(Controller.Standard.LT).peek().to(leftTrigger.buttonPress); - teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightPad.buttonPress); - teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftPad.buttonPress); - - /*teleportMapping.from(Controller.Standard.LeftPrimaryThumb) - .to(function(value) { - if (isDisabled === 'left' || isDisabled === 'both') { - return; - } - if (leftTrigger.down()) { - return; - } - if (isMoving() === true) { - return; - } - //teleporter.enterTeleportMode('left'); - return; - }); - teleportMapping.from(Controller.Standard.RightPrimaryThumb) - .to(function(value) { - if (isDisabled === 'right' || isDisabled === 'both') { - return; - } - if (rightTrigger.down()) { - return; - } - if (isMoving() === true) { - return; - } - - //teleporter.enterTeleportMode('right'); - return; - });*/ + teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); + teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftTeleporter.buttonPress); } - - registerMappings(); - var teleporter = new Teleporter(LEFT_HAND); var leftTeleporter = new Teleporter(LEFT_HAND); var rightTeleporter = new Teleporter(RIGHT_HAND); enableDispatcherModule("LeftTeleporter", leftTeleporter); enableDispatcherModule("RightTeleporter", rightTeleporter); - + registerMappings(); Controller.enableMapping(mappingName); function cleanup() { teleportMapping.disable(); - //teleporter.cleanup(); disableDispatcherModule("LeftTeleporter"); disableDispatcherModule("RightTeleporter"); } From dc8d0c960428bfb3cca36bc3de914464dd204eac Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 22 Aug 2017 16:51:13 -0700 Subject: [PATCH 30/78] disable far grab when on hud --- .../controllers/controllerDispatcher.js | 27 ++++++++++++++++++- .../controllerModules/farActionGrabEntity.js | 6 +++++ .../controllerModules/nearTrigger.js | 1 + .../controllers/handControllerPointer.js | 1 + 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 116abe2c9a..26edc8609c 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -196,6 +196,10 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); RayPick.getPrevRayPickResult(_this.leftControllerRayPick), RayPick.getPrevRayPickResult(_this.rightControllerRayPick) ]; + var hudRayPicks = [ + RayPick.getPrevRayPickResult(_this.leftControllerHudRayPick), + RayPick.getPrevRayPickResult(_this.rightControllerHudRayPick) + ]; // if the pickray hit something very nearby, put it into the nearby entities list for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { @@ -234,7 +238,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); nearbyEntityProperties: nearbyEntityProperties, nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, nearbyOverlayIDs: nearbyOverlayIDs, - rayPicks: rayPicks + rayPicks: rayPicks, + hudRayPicks: hudRayPicks }; // check for plugins that would like to start. ask in order of increasing priority value @@ -298,18 +303,36 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); enabled: true }); + this.mouseHudRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_HUD, + enabled: true + }); + this.leftControllerRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, enabled: true, maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE }); + this.leftControllerHudRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_HUD, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + }); this.rightControllerRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_RIGHTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, enabled: true, maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE }); + this.rightControllerHudRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_RIGHTHAND", + filter: RayPick.PICK_HUD, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + }); this.cleanup = function () { @@ -318,6 +341,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // RayPick.removeRayPick(_this.mouseRayPick); RayPick.removeRayPick(_this.leftControllerRayPick); RayPick.removeRayPick(_this.rightControllerRayPick); + RayPick.removeRayPick(_this.rightControllerHudRayPick); + RayPick.removeRayPick(_this.leftControllerHudRayPick); }; Script.scriptEnding.connect(this.cleanup); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index c1b87bc6e8..a82a97b6eb 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -376,6 +376,8 @@ Script.include("/~/system/libraries/controllers.js"); } var rayPickInfo = controllerData.rayPicks[this.hand]; + var hudRayPickInfo = controllerData.hudRayPicks[this.hand]; + var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection); if (rayPickInfo.type == RayPick.INTERSECTED_ENTITY) { var entityID = rayPickInfo.objectID; var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", @@ -397,6 +399,10 @@ Script.include("/~/system/libraries/controllers.js"); this.startFarGrabAction(controllerData, targetProps); } } + } else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { + this.endNearGrabAction(); + this.laserPointerOff(); + return makeRunningValues(false, [], []); } } return makeRunningValues(true, [], []); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 8b7ef65710..141244b60b 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -57,6 +57,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.continueNearTrigger = function (controllerData) { + print("-------> continue near trigger <-------"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearTrigger", args); }; diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 538fe0b1e4..fbbb708a92 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -639,6 +639,7 @@ function update() { // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. if (isPointingAtOverlay(hudPoint2d) && isPointerEnabled) { + //print("--------> pointing at HUD <--------"); if (HMD.active) { Reticle.depth = hudReticleDistance(); From dbe8aa77d77b50044986fd6288fafe14b3873d41 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 23 Aug 2017 17:32:41 -0700 Subject: [PATCH 31/78] working on laser v tablet --- .../controllers/controllerDispatcher.js | 1 + .../controllerModules/cloneEntity.js | 23 +- .../controllerModules/farActionGrabEntity.js | 15 +- .../controllerModules/nearParentGrabEntity.js | 8 +- .../nearParentGrabOverlay.js | 19 +- .../controllerModules/nearTrigger.js | 5 +- .../controllerModules/tabletLaserInput.js | 626 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 1 + 8 files changed, 676 insertions(+), 22 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/tabletLaserInput.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 26edc8609c..b5681fef3f 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -248,6 +248,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { + //print(orderedPluginName); var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the diff --git a/scripts/system/controllers/controllerModules/cloneEntity.js b/scripts/system/controllers/controllerModules/cloneEntity.js index 0539ee983a..cfe9cb56da 100644 --- a/scripts/system/controllers/controllerModules/cloneEntity.js +++ b/scripts/system/controllers/controllerModules/cloneEntity.js @@ -49,7 +49,7 @@ if (typeof Object.assign != 'function') { this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 150, + 300, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -78,12 +78,15 @@ if (typeof Object.assign != 'function') { } if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - if (this.waiting) { - return makeRunningValues(false, [], []); + if (!this.waiting) { + this.waiting = true; + return makeRunningValues(true, [], []); } - this.waiting = true; } + return makeRunningValues(false, [], []); + }; + this.run = function (controllerData, deltaTime) { var cloneableProps = this.getTargetProps(controllerData); if (!cloneableProps) { return makeRunningValues(false, [], []); @@ -133,22 +136,18 @@ if (typeof Object.assign != 'function') { cProperties.userData = JSON.stringify(cUserData); // var cloneID = Entities.addEntity(cProperties); - return makeRunningValues(false, [], []); }; - this.run = function (controllerData) { - }; - this.cleanup = function () { }; } - var leftNearParentingGrabEntity = new CloneEntity(LEFT_HAND); - var rightNearParentingGrabEntity = new CloneEntity(RIGHT_HAND); + var leftCloneEntity = new CloneEntity(LEFT_HAND); + var rightCloneEntity = new CloneEntity(RIGHT_HAND); - enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); - enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); + enableDispatcherModule("LeftCloneEntity", leftCloneEntity); + enableDispatcherModule("RightCloneEntity", rightCloneEntity); this.cleanup = function () { leftNearParentingGrabEntity.cleanup(); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index a82a97b6eb..3c8fba6c24 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -317,7 +317,20 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; }; + this.pointingOnTablet = function(controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + + if (target === HMD.homeButtonID || target === HMD.tabletScreenID) { + return true; + } + return false + }; + this.isReady = function (controllerData) { + if (this.pointingOnTablet(controllerData)) { + return makeRunningValues(false, [], []); + } + this.distanceHolding = false; this.distanceRotating = false; @@ -330,7 +343,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingOnTablet(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 2332fdd32c..cb004acd03 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -30,7 +30,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.parameters = makeDispatcherModuleParameters( 500, - this.hand === RIGHT_HAND ? ["rightHand", "rightHandTrigger"] : ["leftHand", "leftHandTrigger"], + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -134,7 +134,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(props.position, handPosition); if (distance > NEAR_GRAB_RADIUS) { - break; + continue; } if (entityIsGrabbable(props)) { // if we've attempted to grab a child, roll up to the root of the tree @@ -148,7 +148,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return null; }; - this.isReady = function (controllerData) { + this.isReady = function (controllerData, deltaTime) { this.targetEntityID = null; this.grabbing = false; @@ -169,7 +169,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } }; - this.run = function (controllerData) { + this.run = function (controllerData, deltaTime) { if (this.grabbing) { if (controllerData.triggerClicks[this.hand] == 0) { this.endNearParentingGrabEntity(); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 58b6a12090..399814a0ed 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -13,6 +13,7 @@ */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +var GRAB_RADIUS = 0.35; (function() { @@ -123,6 +124,19 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.grabbedThingID = null; }; + this.getTargetID = function(overlays, controllerData) { + for (var i = 0; i < overlays.length; i++) { + var overlayPosition = Overlays.getProperty(overlays[i], "position"); + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(overlayPosition, handPosition); + if (distance <= GRAB_RADIUS) { + return overlays[i]; + } + } + return null; + }; + + this.isReady = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { return makeRunningValues(false, [], []); @@ -135,8 +149,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return Overlays.getProperty(overlayID, "grabbable"); }); - if (grabbableOverlays.length > 0) { - this.grabbedThingID = grabbableOverlays[0]; + var targetID = this.getTargetID(grabbableOverlays, controllerData); + if (targetID) { + this.grabbedThingID = targetID; this.startNearParentingGrabOverlay(controllerData); return makeRunningValues(true, [this.grabbedThingID], []); } else { diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 141244b60b..fd24aeefa2 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 200, + 520, this.hand === RIGHT_HAND ? ["rightHandTrigger"] : ["leftHandTrigger"], [], 100); @@ -42,7 +42,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(props.position, handPosition); if (distance > NEAR_GRAB_RADIUS) { - break; + continue; } if (entityWantsNearTrigger(props)) { return props; @@ -57,7 +57,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.continueNearTrigger = function (controllerData) { - print("-------> continue near trigger <-------"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearTrigger", args); }; diff --git a/scripts/system/controllers/controllerModules/tabletLaserInput.js b/scripts/system/controllers/controllerModules/tabletLaserInput.js new file mode 100644 index 0000000000..c7098d4e9a --- /dev/null +++ b/scripts/system/controllers/controllerModules/tabletLaserInput.js @@ -0,0 +1,626 @@ +"use strict" + +// tabletLaserInput.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + + + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; + + var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + + + // triggered when stylus presses a web overlay/entity + var HAPTIC_STYLUS_STRENGTH = 1.0; + var HAPTIC_STYLUS_DURATION = 20.0; + + function stylusTargetHasKeyboardFocus(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + return Entities.keyboardFocusEntity === stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + return Overlays.keyboardFocusOverlay === stylusTarget.overlayID; + } + } + + function setKeyboardFocusOnStylusTarget(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID && + Entities.wantsHandControllerPointerEvents(stylusTarget.entityID)) { + Overlays.keyboardFocusOverlay = NULL_UUID; + Entities.keyboardFocusEntity = stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.keyboardFocusOverlay = stylusTarget.overlayID; + Entities.keyboardFocusEntity = NULL_UUID; + } + } + + function sendHoverEnterEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendHoverEnterEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendHoverEnterOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendHoverOverEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchStartEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMousePressOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMousePressOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchEndEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + } + } + + // will return undefined if overlayID does not exist. + function calculateStylusTargetFromOverlay(stylusTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + if (overlayPosition === undefined) { + return; + } + + // project stylusTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + if (overlayRotation === undefined) { + return; + } + var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, overlayPosition), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // calclulate normalized position + var invRot = Quat.inverse(overlayRotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var dpi = Overlays.getProperty(overlayID, "dpi"); + + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property + // is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + if (resolution === undefined) { + return; + } + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + if (scale === undefined) { + return; + } + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; + } + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y }; // flip y-axis + + return { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: dimensions, + valid: true + }; + } + + // will return undefined if entity does not exist. + function calculateStylusTargetFromEntity(stylusTip, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return; + } + + // project stylus tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, props.position), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + + return { + entityID: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; + } + + function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + // check to see if the projected stylusTip is within within the 2d border + var borderMin = {x: -edgeBorder, y: -edgeBorder}; + var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; + if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && + stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && + stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { + return true; + } + } + return false; + } + + function calculateNearestStylusTarget(stylusTargets) { + var nearestStylusTarget; + + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && + stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && + stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { + nearestStylusTarget = stylusTarget; + } + } + + return nearestStylusTarget; + } + + function getFingerWorldLocation(hand) { + var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; + + var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); + var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); + var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); + + return { + position: worldFingerPosition, + orientation: worldFingerRotation, + rotation: worldFingerRotation, + valid: true + }; + } + + function distance2D(a, b) { + var dx = (a.x - b.x); + var dy = (a.y - b.y); + return Math.sqrt(dx * dx + dy * dy); + } + + function TabletLaserInput(hand) { + this.hand = hand; + this.previousStylusTouchingTarget = false; + this.stylusTouchingTarget = false; + this.tabletScreenID = HMD.tabletScreenID; + + this.useFingerInsteadOfStylus = false; + this.fingerPointing = false; + + + this.parameters = makeDispatcherModuleParameters( + 200, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.getOtherHandController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + }; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.stealTouchFocus = function(laserTarget) { + // send hover events to target + // record the entity or overlay we are hovering over. + if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || + (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { + this.getOtherHandController().relinquishTouchFocus(); + } + this.requestTouchFocus(stylusTarget); + }; + + this.requestTouchFocus = function(laserTarget) { + + // send hover events to target if we can. + // record the entity or overlay we are hovering over. + if (stylusTarget.entityID && + stylusTarget.entityID !== this.hoverEntity && + stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { + this.hoverEntity = stylusTarget.entityID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } else if (stylusTarget.overlayID && + stylusTarget.overlayID !== this.hoverOverlay && + stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { + this.hoverOverlay = stylusTarget.overlayID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } + }; + + this.hasTouchFocus = function(laserTarget) { + return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || + (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); + }; + + this.relinquishTouchFocus = function() { + // send hover leave event. + var pointerEvent = { type: "Move", id: this.hand + 1 }; + if (this.hoverEntity) { + Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); + this.hoverEntity = null; + } else if (this.hoverOverlay) { + Overlays.sendMouseMoveOnOverlay(HMD.tabletScreenID, pointerEvent); + Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); + this.hoverOverlay = null; + } + }; + + this.updateLaserPointer = function(controllerData) { + var RADIUS = 0.005; + var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; + + var mode = "none"; + + if (controllerData.triggerClicks[this.hand]) { + mode = "full"; + } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + mode = "half"; + } else { + LaserPointers.disableLaserPointer(this.laserPointer); + return; + } + + if (mode === "full") { + this.fullEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, mode, {path: fullPath, end: this.fullEnd}); + } else if (mode === "half") { + this.halfEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, mode, {path: halfPath, end: this.halfEnd}); + } + + LaserPointers.enableLaserPointer(this.laserPointer); + LaserPointers.setRenderState(this.laserPointer, mode); + }; + + this.pointFinger = function(value) { + var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; + if (this.fingerPointing !== value) { + var message; + if (this.hand === RIGHT_HAND) { + message = { pointRightIndex: value }; + } else { + message = { pointLeftIndex: value }; + } + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); + this.fingerPointing = value; + } + }; + + this.processLaser = function(controllerData) { + if (controllerData.rayPicks[this.hand].objectID === HMD.tabletScreenID) { + + } + this.homeButtonTouched = false; + }; + + this.laserTouchingEnter = function () { + this.stealTouchFocus(this.stylusTarget); + sendTouchStartEventToStylusTarget(this.hand, this.stylusTarget); + Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); + + this.touchingEnterTimer = 0; + this.touchingEnterStylusTarget = this.stylusTarget; + this.deadspotExpired = false; + + var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; + this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; + }; + + this.laserTouchingExit = function () { + + if (this.stylusTarget === undefined) { + return; + } + + // special case to handle home button. + if (this.stylusTarget.overlayID === HMD.homeButtonID) { + Messages.sendLocalMessage("home", this.stylusTarget.overlayID); + } + + // send press event + if (this.deadspotExpired) { + sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget); + } else { + sendTouchEndEventToStylusTarget(this.hand, this.touchingEnterStylusTarget); + } + }; + + this.laserTouching = function (controllerData, dt) { + + this.touchingEnterTimer += dt; + + if (this.stylusTarget.entityID) { + this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); + } else if (this.stylusTarget.overlayID) { + this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); + } + + var TABLET_MIN_TOUCH_DISTANCE = -0.1; + var TABLET_MAX_TOUCH_DISTANCE = 0.01; + + if (this.stylusTarget) { + if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.stylusTarget.position2D, + this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { + sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + this.deadspotExpired = true; + } + } else { + this.stylusTouchingTarget = false; + } + } else { + this.stylusTouchingTarget = false; + } + }; + + this.pointingAtTablet = function(target) { + return (target === HMD.tabletID); + }; + + this.pointingAtTabletScreen = function(target) { + return (target === HMD.tabletScreenID); + } + + this.pointingAtHomeButton = function(target) { + return (target === HMD.homeButtonID); + } + + this.isReady = function (controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + return makeRunningValues(true, [], []); + } + } + //this.processLaser(controllerData); + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + var target = controllerData.rayPicks[this.hand].objectID; + if (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)) { + LaserPointers.disableLaserPointer(this.laserPointer); + return makeRunningValues(false, [], []); + } + this.updateLaserPointer(controllerData); + //this.updateFingerAsStylusSetting(); + +/* if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { + this.stylusTouchingEnter(); + } + if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { + this.stylusTouchingExit(); + } + this.previousStylusTouchingTarget = this.stylusTouchingTarget; + + if (this.stylusTouchingTarget) { + this.stylusTouching(controllerData, deltaTime); + }*/ + /*if (this.processLaser(controllerData)) { + return makeRunningValues(true, [], []); + } else { + return makeRunningValues(false, [], []); + }*/ + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + }; + + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + }; + + var leftTabletLaserInput = new TabletLaserInput(LEFT_HAND); + var rightTabletLaserInput = new TabletLaserInput(RIGHT_HAND); + + enableDispatcherModule("LeftTabletLaserInput", leftTabletLaserInput); + enableDispatcherModule("RightTabletLaserInput", rightTabletLaserInput); + + this.cleanup = function () { + leftTabletStylusInput.cleanup(); + rightTabletStylusInput.cleanup(); + disableDispatcherModule("LeftTabletLaserInput"); + disableDispatcherModule("RightTabletLaserInput"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index b69b2a6e3f..65d63b7bec 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -26,6 +26,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", "controllerModules/cloneEntity.js", + "controllerModules/tabletLaserInput.js", "teleport.js" ]; From fa676aaadf687ce3839ef2fcae12066a8ec7984a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 24 Aug 2017 17:42:27 -0700 Subject: [PATCH 32/78] lasers on tablet working --- .../controllerModules/tabletLaserInput.js | 444 +++++++----------- 1 file changed, 170 insertions(+), 274 deletions(-) diff --git a/scripts/system/controllers/controllerModules/tabletLaserInput.js b/scripts/system/controllers/controllerModules/tabletLaserInput.js index c7098d4e9a..992caeac48 100644 --- a/scripts/system/controllers/controllerModules/tabletLaserInput.js +++ b/scripts/system/controllers/controllerModules/tabletLaserInput.js @@ -85,125 +85,121 @@ Script.include("/~/system/libraries/controllers.js"); var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - function stylusTargetHasKeyboardFocus(stylusTarget) { - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - return Entities.keyboardFocusEntity === stylusTarget.entityID; - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - return Overlays.keyboardFocusOverlay === stylusTarget.overlayID; + function stylusTargetHasKeyboardFocus(laserTarget) { + if (laserTarget && laserTarget !== NULL_UUID) { + return Overlays.keyboardFocusOverlay === laserTarget; } } - function setKeyboardFocusOnStylusTarget(stylusTarget) { - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID && - Entities.wantsHandControllerPointerEvents(stylusTarget.entityID)) { - Overlays.keyboardFocusOverlay = NULL_UUID; - Entities.keyboardFocusEntity = stylusTarget.entityID; - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.keyboardFocusOverlay = stylusTarget.overlayID; + function setKeyboardFocusOnStylusTarget(laserTarget) { + if (laserTarget && laserTarget !== NULL_UUID) { + Overlays.keyboardFocusOverlay = laserTarget; Entities.keyboardFocusEntity = NULL_UUID; } } - function sendHoverEnterEventToStylusTarget(hand, stylusTarget) { + function sendHoverEnterEventToStylusTarget(hand, laserTarget) { + if (!laserTarget) { + return; + } var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), button: "None" }; - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendHoverEnterEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendHoverEnterOverlay(stylusTarget.overlayID, pointerEvent); + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { + Overlays.sendHoverEnterOverlay(laserTarget.overlayID, pointerEvent); } } - function sendHoverOverEventToStylusTarget(hand, stylusTarget) { + function sendHoverOverEventToStylusTarget(hand, laserTarget) { + + if (!laserTarget) { + return; + } var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), button: "None" }; - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoverOverEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); - Overlays.sendHoverOverOverlay(stylusTarget.overlayID, pointerEvent); + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(laserTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(laserTarget.overlayID, pointerEvent); } } - function sendTouchStartEventToStylusTarget(hand, stylusTarget) { + function sendTouchStartEventToStylusTarget(hand, laserTarget) { + if (!laserTarget) { + return; + } + var pointerEvent = { type: "Press", id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), button: "Primary", isPrimaryHeld: true }; - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMousePressOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendClickDownOnEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMousePressOnOverlay(stylusTarget.overlayID, pointerEvent); + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { + Overlays.sendMousePressOnOverlay(laserTarget.overlayID, pointerEvent); } } - function sendTouchEndEventToStylusTarget(hand, stylusTarget) { + function sendTouchEndEventToStylusTarget(hand, laserTarget) { + if (!laserTarget) { + return; + } var pointerEvent = { type: "Release", id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), button: "Primary" }; - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseReleaseOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendClickReleaseOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoverLeaveEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseReleaseOnOverlay(stylusTarget.overlayID, pointerEvent); + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); } } - function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { + function sendTouchMoveEventToStylusTarget(hand, laserTarget) { + if (!laserTarget) { + return; + } var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse - pos2D: stylusTarget.position2D, - pos3D: stylusTarget.position, - normal: stylusTarget.normal, - direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), button: "Primary", isPrimaryHeld: true }; - - if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { - Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); - Entities.sendHoldingClickOnEntity(stylusTarget.entityID, pointerEvent); - } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { - Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); } } // will return undefined if overlayID does not exist. - function calculateStylusTargetFromOverlay(stylusTip, overlayID) { + function calculateLaserTargetFromOverlay(laserTip, overlayID) { var overlayPosition = Overlays.getProperty(overlayID, "position"); if (overlayPosition === undefined) { return; @@ -215,8 +211,8 @@ Script.include("/~/system/libraries/controllers.js"); return; } var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); - var distance = Vec3.dot(Vec3.subtract(stylusTip.position, overlayPosition), normal); - var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + var distance = Vec3.dot(Vec3.subtract(laserTip, overlayPosition), normal); + var position = Vec3.subtract(laserTip, Vec3.multiply(normal, distance)); // calclulate normalized position var invRot = Quat.inverse(overlayRotation); @@ -267,92 +263,6 @@ Script.include("/~/system/libraries/controllers.js"); }; } - // will return undefined if entity does not exist. - function calculateStylusTargetFromEntity(stylusTip, props) { - if (props.rotation === undefined) { - // if rotation is missing from props object, then this entity has probably been deleted. - return; - } - - // project stylus tip onto entity plane. - var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); - Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); - var distance = Vec3.dot(Vec3.subtract(stylusTip.position, props.position), normal); - var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); - - // generate normalized coordinates - var invRot = Quat.inverse(props.rotation); - var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); - var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; - var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); - - // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { x: normalizedPosition.x * props.dimensions.x, - y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis - - return { - entityID: props.id, - entityProps: props, - overlayID: null, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - normalizedPosition: normalizedPosition, - dimensions: props.dimensions, - valid: true - }; - } - - function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - // check to see if the projected stylusTip is within within the 2d border - var borderMin = {x: -edgeBorder, y: -edgeBorder}; - var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; - if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && - stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && - stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { - return true; - } - } - return false; - } - - function calculateNearestStylusTarget(stylusTargets) { - var nearestStylusTarget; - - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && - stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && - stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { - nearestStylusTarget = stylusTarget; - } - } - - return nearestStylusTarget; - } - - function getFingerWorldLocation(hand) { - var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; - - var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); - var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); - var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); - var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); - var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); - - return { - position: worldFingerPosition, - orientation: worldFingerRotation, - rotation: worldFingerRotation, - valid: true - }; - } - function distance2D(a, b) { var dx = (a.x - b.x); var dy = (a.y - b.y); @@ -361,12 +271,14 @@ Script.include("/~/system/libraries/controllers.js"); function TabletLaserInput(hand) { this.hand = hand; - this.previousStylusTouchingTarget = false; - this.stylusTouchingTarget = false; + this.previousLaserClikcedTarget = false; + this.laserPressingTarget = false; this.tabletScreenID = HMD.tabletScreenID; - - this.useFingerInsteadOfStylus = false; - this.fingerPointing = false; + this.mode = "none"; + this.laserTargetID = null; + this.laserTarget = null; + this.pressEnterLaserTarget = null; + this.hover = false; this.parameters = makeDispatcherModuleParameters( @@ -384,159 +296,118 @@ Script.include("/~/system/libraries/controllers.js"); }; this.stealTouchFocus = function(laserTarget) { - // send hover events to target - // record the entity or overlay we are hovering over. - if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || - (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { - this.getOtherHandController().relinquishTouchFocus(); - } - this.requestTouchFocus(stylusTarget); + this.requestTouchFocus(laserTarget); }; this.requestTouchFocus = function(laserTarget) { - - // send hover events to target if we can. - // record the entity or overlay we are hovering over. - if (stylusTarget.entityID && - stylusTarget.entityID !== this.hoverEntity && - stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { - this.hoverEntity = stylusTarget.entityID; - sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); - } else if (stylusTarget.overlayID && - stylusTarget.overlayID !== this.hoverOverlay && - stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { - this.hoverOverlay = stylusTarget.overlayID; - sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); - } + sendHoverEnterEventToStylusTarget(this.hand, this.laserTarget); }; this.hasTouchFocus = function(laserTarget) { - return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || - (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); + return (this.laserTargetID === HMD.tabletScreenID); }; this.relinquishTouchFocus = function() { // send hover leave event. var pointerEvent = { type: "Move", id: this.hand + 1 }; - if (this.hoverEntity) { - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = null; - } else if (this.hoverOverlay) { - Overlays.sendMouseMoveOnOverlay(HMD.tabletScreenID, pointerEvent); - Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = null; - } + Overlays.sendMouseMoveOnOverlay(this.tabletScreenID, pointerEvent); + Overlays.sendHoverOverOverlay(this.tabletScreenID, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.tabletScreenID, pointerEvent); }; this.updateLaserPointer = function(controllerData) { var RADIUS = 0.005; var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; - - var mode = "none"; - if (controllerData.triggerClicks[this.hand]) { - mode = "full"; - } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - mode = "half"; - } else { - LaserPointers.disableLaserPointer(this.laserPointer); - return; - } - - if (mode === "full") { + if (this.mode === "full") { this.fullEnd.dimensions = dim; - LaserPointers.editRenderState(this.laserPointer, mode, {path: fullPath, end: this.fullEnd}); - } else if (mode === "half") { + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd}); + } else if (this.mode === "half") { this.halfEnd.dimensions = dim; - LaserPointers.editRenderState(this.laserPointer, mode, {path: halfPath, end: this.halfEnd}); + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd}); } LaserPointers.enableLaserPointer(this.laserPointer); - LaserPointers.setRenderState(this.laserPointer, mode); + LaserPointers.setRenderState(this.laserPointer, this.mode); }; - this.pointFinger = function(value) { - var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; - if (this.fingerPointing !== value) { - var message; - if (this.hand === RIGHT_HAND) { - message = { pointRightIndex: value }; - } else { - message = { pointLeftIndex: value }; - } - Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); - this.fingerPointing = value; - } - }; - - this.processLaser = function(controllerData) { - if (controllerData.rayPicks[this.hand].objectID === HMD.tabletScreenID) { + this.processControllerTriggers = function(controllerData) { + var rayPickIntersection = controllerData.rayPicks[this.hand].intersection; + this.laserTarget = calculateLaserTargetFromOverlay(rayPickIntersection, HMD.tabletScreenID); + if (controllerData.triggerClicks[this.hand]) { + this.mode = "full"; + this.laserPressingTarget = true; + this.hover = false; + } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.mode = "half"; + this.laserPressingTarget = false; + this.hover = true; + this.requestTouchFocus(HMD.tabletScreenID); + } else { + this.mode = "none"; + this.laserPressingTarget = false; + this.hover = false; + this.relinquishTouchFocus(); - } + } this.homeButtonTouched = false; }; - this.laserTouchingEnter = function () { - this.stealTouchFocus(this.stylusTarget); - sendTouchStartEventToStylusTarget(this.hand, this.stylusTarget); + this.hovering = function() { + if (this.hasTouchFocus(this.laserTargetID)) { + sendHoverOverEventToStylusTarget(this.hand, this.laserTarget); + } + }; + + this.laserPressEnter = function () { + this.stealTouchFocus(this.laserTarget.overlayID); + sendTouchStartEventToStylusTarget(this.hand, this.laserTarget); Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); this.touchingEnterTimer = 0; - this.touchingEnterStylusTarget = this.stylusTarget; + this.pressEnterLaserTarget = this.laserTarget; this.deadspotExpired = false; var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; }; - this.laserTouchingExit = function () { + this.laserPressExit = function () { - if (this.stylusTarget === undefined) { + if (this.laserTarget === undefined) { return; } // special case to handle home button. - if (this.stylusTarget.overlayID === HMD.homeButtonID) { - Messages.sendLocalMessage("home", this.stylusTarget.overlayID); + if (this.laserTargetID === HMD.homeButtonID) { + Messages.sendLocalMessage("home", this.laserTargetID); } // send press event if (this.deadspotExpired) { - sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget); + sendTouchEndEventToStylusTarget(this.hand, this.laserTarget); } else { - sendTouchEndEventToStylusTarget(this.hand, this.touchingEnterStylusTarget); + print(this.pressEnterLaserTarget); + sendTouchEndEventToStylusTarget(this.hand, this.pressEnterLaserTarget); } }; - this.laserTouching = function (controllerData, dt) { + this.laserPressing = function (controllerData, dt) { this.touchingEnterTimer += dt; - if (this.stylusTarget.entityID) { - this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); - } else if (this.stylusTarget.overlayID) { - this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); - } - - var TABLET_MIN_TOUCH_DISTANCE = -0.1; - var TABLET_MAX_TOUCH_DISTANCE = 0.01; - - if (this.stylusTarget) { - if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && - this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { - var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - distance2D(this.stylusTarget.position2D, - this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { - sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); - this.deadspotExpired = true; - } - } else { - this.stylusTouchingTarget = false; + if (this.laserTarget) { + + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.laserTarget.position2D, + this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { + sendTouchMoveEventToStylusTarget(this.hand, this.laserTarget); + this.deadspotExpired = true; } + } else { - this.stylusTouchingTarget = false; + this.laserPressingTarget = false; } }; @@ -556,38 +427,61 @@ Script.include("/~/system/libraries/controllers.js"); var target = controllerData.rayPicks[this.hand].objectID; if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.tabletScrenID = HMD.tabletScreenID; return makeRunningValues(true, [], []); } } - //this.processLaser(controllerData); return makeRunningValues(false, [], []); }; + this.reset = function() { + this.laserPressExit(); + this.hover = false; + this.pressEnterLaserTarget = null; + this.laserTarget = null; + this.laserTargetID = null; + this.laserPressingTarget = false; + this.previousLaserClickedTarget = null; + this.mode = "none"; + }; + this.run = function (controllerData, deltaTime) { var target = controllerData.rayPicks[this.hand].objectID; + + if (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)) { LaserPointers.disableLaserPointer(this.laserPointer); + this.laserPressExit(); + this.relinquishTouchFocus(); + this.reset(); + this.updateLaserPointer(); return makeRunningValues(false, [], []); } + + this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); - //this.updateFingerAsStylusSetting(); - -/* if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { - this.stylusTouchingEnter(); - } - if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { - this.stylusTouchingExit(); - } - this.previousStylusTouchingTarget = this.stylusTouchingTarget; - - if (this.stylusTouchingTarget) { - this.stylusTouching(controllerData, deltaTime); - }*/ - /*if (this.processLaser(controllerData)) { + var intersection = LaserPointers.getPrevRayPickResult(this.laserPointer); + this.laserTargetID = intersection.objectID; + this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID); + if (this.laserTarget === undefined) { return makeRunningValues(true, [], []); - } else { - return makeRunningValues(false, [], []); - }*/ + } + + if (!this.previousLaserClickedTarget && this.laserPressingTarget) { + this.laserPressEnter(); + } + if (this.previousLaserClickedTarget && !this.laserPressingTarget) { + this.laserPressExit(); + } + this.previousLaserClickedTarget = this.laserPressingTarget; + + if (this.laserPressingTarget) { + this.laserPressing(controllerData, deltaTime); + } + + if (this.hover) { + this.hovering(); + } return makeRunningValues(true, [], []); }; @@ -608,6 +502,8 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, defaultRenderStates: defaultRenderStates }); + + LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); }; var leftTabletLaserInput = new TabletLaserInput(LEFT_HAND); From b3ffa7b6b42ff503b655904463d383b1bcdaf604 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 25 Aug 2017 14:20:31 -0700 Subject: [PATCH 33/78] Working laser vs tablet --- .../controllerModules/tabletLaserInput.js | 155 ++++++++++-------- 1 file changed, 86 insertions(+), 69 deletions(-) diff --git a/scripts/system/controllers/controllerModules/tabletLaserInput.js b/scripts/system/controllers/controllerModules/tabletLaserInput.js index 992caeac48..27bfae9b2e 100644 --- a/scripts/system/controllers/controllerModules/tabletLaserInput.js +++ b/scripts/system/controllers/controllerModules/tabletLaserInput.js @@ -71,7 +71,7 @@ Script.include("/~/system/libraries/controllers.js"); drawInFront: true, // Even when burried inside of something, show it. parentID: AVATAR_SELF_ID }; - + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, {name: "full", path: fullPath, end: fullEnd}, {name: "hold", path: holdPath}]; @@ -79,26 +79,26 @@ Script.include("/~/system/libraries/controllers.js"); var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; - + // triggered when stylus presses a web overlay/entity var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - function stylusTargetHasKeyboardFocus(laserTarget) { + function laserTargetHasKeyboardFocus(laserTarget) { if (laserTarget && laserTarget !== NULL_UUID) { return Overlays.keyboardFocusOverlay === laserTarget; } } - function setKeyboardFocusOnStylusTarget(laserTarget) { + function setKeyboardFocusOnLaserTarget(laserTarget) { if (laserTarget && laserTarget !== NULL_UUID) { Overlays.keyboardFocusOverlay = laserTarget; Entities.keyboardFocusEntity = NULL_UUID; } } - function sendHoverEnterEventToStylusTarget(hand, laserTarget) { + function sendHoverEnterEventToLaserTarget(hand, laserTarget) { if (!laserTarget) { return; } @@ -117,8 +117,8 @@ Script.include("/~/system/libraries/controllers.js"); } } - function sendHoverOverEventToStylusTarget(hand, laserTarget) { - + function sendHoverOverEventToLaserTarget(hand, laserTarget) { + if (!laserTarget) { return; } @@ -138,11 +138,11 @@ Script.include("/~/system/libraries/controllers.js"); } } - function sendTouchStartEventToStylusTarget(hand, laserTarget) { + function sendTouchStartEventToLaserTarget(hand, laserTarget) { if (!laserTarget) { return; } - + var pointerEvent = { type: "Press", id: hand + 1, // 0 is reserved for hardware mouse @@ -159,7 +159,7 @@ Script.include("/~/system/libraries/controllers.js"); } } - function sendTouchEndEventToStylusTarget(hand, laserTarget) { + function sendTouchEndEventToLaserTarget(hand, laserTarget) { if (!laserTarget) { return; } @@ -175,10 +175,11 @@ Script.include("/~/system/libraries/controllers.js"); if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); + Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); } } - function sendTouchMoveEventToStylusTarget(hand, laserTarget) { + function sendTouchMoveEventToLaserTarget(hand, laserTarget) { if (!laserTarget) { return; } @@ -192,7 +193,7 @@ Script.include("/~/system/libraries/controllers.js"); button: "Primary", isPrimaryHeld: true }; - + if (laserTarget.overlayID && laserTarget.overlayID !== NULL_UUID) { Overlays.sendMouseReleaseOnOverlay(laserTarget.overlayID, pointerEvent); } @@ -202,13 +203,13 @@ Script.include("/~/system/libraries/controllers.js"); function calculateLaserTargetFromOverlay(laserTip, overlayID) { var overlayPosition = Overlays.getProperty(overlayID, "position"); if (overlayPosition === undefined) { - return; + return null; } // project stylusTip onto overlay plane. var overlayRotation = Overlays.getProperty(overlayID, "rotation"); if (overlayRotation === undefined) { - return; + return null; } var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); var distance = Vec3.dot(Vec3.subtract(laserTip, overlayPosition), normal); @@ -225,19 +226,19 @@ Script.include("/~/system/libraries/controllers.js"); // is used as a scale. var resolution = Overlays.getProperty(overlayID, "resolution"); if (resolution === undefined) { - return; + return null; } resolution.z = 1; // Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); if (scale === undefined) { - return; + return null; } scale.z = 0.01; // overlay dimensions are 2D, not 3D. dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); if (dimensions === undefined) { - return; + return null; } if (!dimensions.z) { dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. @@ -271,6 +272,7 @@ Script.include("/~/system/libraries/controllers.js"); function TabletLaserInput(hand) { this.hand = hand; + this.active = false; this.previousLaserClikcedTarget = false; this.laserPressingTarget = false; this.tabletScreenID = HMD.tabletScreenID; @@ -279,6 +281,7 @@ Script.include("/~/system/libraries/controllers.js"); this.laserTarget = null; this.pressEnterLaserTarget = null; this.hover = false; + this.lastValidTargetID = this.tabletTargetID; this.parameters = makeDispatcherModuleParameters( @@ -291,6 +294,10 @@ Script.include("/~/system/libraries/controllers.js"); return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; }; + this.getOtherModule = function() { + return (this.hand === RIGHT_HAND) ? leftTabletLaserInput : rightTabletLaserInput; + }; + this.handToController = function() { return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; @@ -300,7 +307,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.requestTouchFocus = function(laserTarget) { - sendHoverEnterEventToStylusTarget(this.hand, this.laserTarget); + if (laserTarget !== null || laserTarget !== undefined) { + sendHoverEnterEventToLaserTarget(this.hand, this.laserTarget); + this.lastValidTargetID = laserTarget; + } }; this.hasTouchFocus = function(laserTarget) { @@ -310,15 +320,15 @@ Script.include("/~/system/libraries/controllers.js"); this.relinquishTouchFocus = function() { // send hover leave event. var pointerEvent = { type: "Move", id: this.hand + 1 }; - Overlays.sendMouseMoveOnOverlay(this.tabletScreenID, pointerEvent); - Overlays.sendHoverOverOverlay(this.tabletScreenID, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.tabletScreenID, pointerEvent); + Overlays.sendMouseMoveOnOverlay(this.lastValidTargetID, pointerEvent); + Overlays.sendHoverOverOverlay(this.lastValidTargetID, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.lastValidID, pointerEvent); }; this.updateLaserPointer = function(controllerData) { var RADIUS = 0.005; var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; - + if (this.mode === "full") { this.fullEnd.dimensions = dim; LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd}); @@ -332,8 +342,6 @@ Script.include("/~/system/libraries/controllers.js"); }; this.processControllerTriggers = function(controllerData) { - var rayPickIntersection = controllerData.rayPicks[this.hand].intersection; - this.laserTarget = calculateLaserTargetFromOverlay(rayPickIntersection, HMD.tabletScreenID); if (controllerData.triggerClicks[this.hand]) { this.mode = "full"; this.laserPressingTarget = true; @@ -342,39 +350,36 @@ Script.include("/~/system/libraries/controllers.js"); this.mode = "half"; this.laserPressingTarget = false; this.hover = true; - this.requestTouchFocus(HMD.tabletScreenID); + this.requestTouchFocus(this.laserTargetID); } else { this.mode = "none"; this.laserPressingTarget = false; this.hover = false; this.relinquishTouchFocus(); - + } - this.homeButtonTouched = false; }; this.hovering = function() { if (this.hasTouchFocus(this.laserTargetID)) { - sendHoverOverEventToStylusTarget(this.hand, this.laserTarget); + sendHoverOverEventToLaserTarget(this.hand, this.laserTarget); } }; this.laserPressEnter = function () { - this.stealTouchFocus(this.laserTarget.overlayID); - sendTouchStartEventToStylusTarget(this.hand, this.laserTarget); + sendTouchStartEventToLaserTarget(this.hand, this.laserTarget); Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); this.touchingEnterTimer = 0; this.pressEnterLaserTarget = this.laserTarget; this.deadspotExpired = false; - var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; - this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; + var LASER_PRESS_TO_MOVE_DEADSPOT = 0.026; + this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT) * this.laserTarget.distance; }; this.laserPressExit = function () { - - if (this.laserTarget === undefined) { + if (this.laserTarget === null) { return; } @@ -385,27 +390,23 @@ Script.include("/~/system/libraries/controllers.js"); // send press event if (this.deadspotExpired) { - sendTouchEndEventToStylusTarget(this.hand, this.laserTarget); + sendTouchEndEventToLaserTarget(this.hand, this.laserTarget); } else { - print(this.pressEnterLaserTarget); - sendTouchEndEventToStylusTarget(this.hand, this.pressEnterLaserTarget); + sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); } }; this.laserPressing = function (controllerData, dt) { - this.touchingEnterTimer += dt; if (this.laserTarget) { - var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || distance2D(this.laserTarget.position2D, this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { - sendTouchMoveEventToStylusTarget(this.hand, this.laserTarget); + sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget); this.deadspotExpired = true; } - } else { this.laserPressingTarget = false; } @@ -423,19 +424,32 @@ Script.include("/~/system/libraries/controllers.js"); return (target === HMD.homeButtonID); } - this.isReady = function (controllerData) { - var target = controllerData.rayPicks[this.hand].objectID; - if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.tabletScrenID = HMD.tabletScreenID; - return makeRunningValues(true, [], []); - } - } - return makeRunningValues(false, [], []); + this.releaseTouchEvent = function() { + sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); + } + + + this.updateLaserTargets = function() { + var intersection = LaserPointers.getPrevRayPickResult(this.laserPointer); + this.laserTargetID = intersection.objectID; + this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID); }; + this.shouldExit = function(controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + var isLaserOffTablet = (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)); + return isLaserOffTablet; + } + + this.exitModule = function() { + this.releaseTouchEvent(); + this.relinquishTouchFocus(); + this.reset(); + this.updateLaserPointer(); + LaserPointers.disableLaserPointer(this.laserPointer); + } + this.reset = function() { - this.laserPressExit(); this.hover = false; this.pressEnterLaserTarget = null; this.laserTarget = null; @@ -443,29 +457,32 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressingTarget = false; this.previousLaserClickedTarget = null; this.mode = "none"; + this.active = false; + }; + + this.isReady = function (controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.getOtherModule().active) { + this.tabletScrenID = HMD.tabletScreenID; + this.active = true; + return makeRunningValues(true, [], []); + } + } + this.reset(); + return makeRunningValues(false, [], []); }; this.run = function (controllerData, deltaTime) { - var target = controllerData.rayPicks[this.hand].objectID; - - if (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)) { - LaserPointers.disableLaserPointer(this.laserPointer); - this.laserPressExit(); - this.relinquishTouchFocus(); - this.reset(); - this.updateLaserPointer(); + if (this.shouldExit(controllerData)) { + this.exitModule(); return makeRunningValues(false, [], []); } + this.updateLaserTargets(); this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); - var intersection = LaserPointers.getPrevRayPickResult(this.laserPointer); - this.laserTargetID = intersection.objectID; - this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID); - if (this.laserTarget === undefined) { - return makeRunningValues(true, [], []); - } if (!this.previousLaserClickedTarget && this.laserPressingTarget) { this.laserPressEnter(); @@ -478,7 +495,7 @@ Script.include("/~/system/libraries/controllers.js"); if (this.laserPressingTarget) { this.laserPressing(controllerData, deltaTime); } - + if (this.hover) { this.hovering(); } @@ -505,7 +522,7 @@ Script.include("/~/system/libraries/controllers.js"); LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); }; - + var leftTabletLaserInput = new TabletLaserInput(LEFT_HAND); var rightTabletLaserInput = new TabletLaserInput(RIGHT_HAND); @@ -513,8 +530,8 @@ Script.include("/~/system/libraries/controllers.js"); enableDispatcherModule("RightTabletLaserInput", rightTabletLaserInput); this.cleanup = function () { - leftTabletStylusInput.cleanup(); - rightTabletStylusInput.cleanup(); + leftTabletLaserInput.cleanup(); + rightTabletLaserInput.cleanup(); disableDispatcherModule("LeftTabletLaserInput"); disableDispatcherModule("RightTabletLaserInput"); }; From 0eceb7b382425c897657cf7e7a3b978bc162051c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 25 Aug 2017 17:40:30 -0700 Subject: [PATCH 34/78] unviersial web3d vs laser --- .../controllers/controllerDispatcher.js | 12 ++++++ .../controllerModules/farActionGrabEntity.js | 13 ++++--- ...aserInput.js => web3DOverlayLaserInput.js} | 38 +++++++------------ .../system/controllers/controllerScripts.js | 2 +- 4 files changed, 33 insertions(+), 32 deletions(-) rename scripts/system/controllers/controllerModules/{tabletLaserInput.js => web3DOverlayLaserInput.js} (93%) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index b5681fef3f..18b4c287c0 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -33,6 +33,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var totalVariance = 0; var highVarianceCount = 0; var veryhighVarianceCount = 0; + this.tabletID = null; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name @@ -126,8 +127,17 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return deltaTime; }; + this.setIgnoreTablet = function() { + if (HMD.tabletID !== _this.tabletID) { + RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); + RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); + tabletIgnored = true + } + } + this.update = function () { var deltaTime = this.updateTimings(); + this.setIgnoreTablet() if (controllerDispatcherPluginsNeedSort) { this.orderedPluginNames = []; @@ -336,6 +346,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }); + + this.cleanup = function () { Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 3c8fba6c24..44bb500605 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -317,17 +317,18 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; }; - this.pointingOnTablet = function(controllerData) { - var target = controllerData.rayPicks[this.hand].objectID; - - if (target === HMD.homeButtonID || target === HMD.tabletScreenID) { + this.pointingAtWebOverlay = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var overlayType = Overlays.getOverlayType(intersection.objectID); + print(JSON.stringify(Entities.getEntityProperties(intersection.objectID))); + if ((intersection.type === RayPick.INTERSECTED_OVERLAY && overlayType === "web3d") || intersection.objectID === HMD.tabletButtonID) { return true; } return false }; this.isReady = function (controllerData) { - if (this.pointingOnTablet(controllerData)) { + if (this.pointingAtWebOverlay(controllerData)) { return makeRunningValues(false, [], []); } @@ -343,7 +344,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingOnTablet(controllerData)) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingAtWebOverlay(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/tabletLaserInput.js b/scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js similarity index 93% rename from scripts/system/controllers/controllerModules/tabletLaserInput.js rename to scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js index 27bfae9b2e..501c37fd35 100644 --- a/scripts/system/controllers/controllerModules/tabletLaserInput.js +++ b/scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js @@ -275,7 +275,7 @@ Script.include("/~/system/libraries/controllers.js"); this.active = false; this.previousLaserClikcedTarget = false; this.laserPressingTarget = false; - this.tabletScreenID = HMD.tabletScreenID; + this.tabletScreenID = null; this.mode = "none"; this.laserTargetID = null; this.laserTarget = null; @@ -361,9 +361,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.hovering = function() { - if (this.hasTouchFocus(this.laserTargetID)) { - sendHoverOverEventToLaserTarget(this.hand, this.laserTarget); + if (!laserTargetHasKeyboardFocus(this.laserTagetID)) { + setKeyboardFocusOnLaserTarget(this.laserTargetID); } + sendHoverOverEventToLaserTarget(this.hand, this.laserTarget); }; this.laserPressEnter = function () { @@ -412,33 +413,21 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.pointingAtTablet = function(target) { - return (target === HMD.tabletID); - }; - - this.pointingAtTabletScreen = function(target) { - return (target === HMD.tabletScreenID); - } - - this.pointingAtHomeButton = function(target) { - return (target === HMD.homeButtonID); - } - this.releaseTouchEvent = function() { sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); } - this.updateLaserTargets = function() { - var intersection = LaserPointers.getPrevRayPickResult(this.laserPointer); + this.updateLaserTargets = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; this.laserTargetID = intersection.objectID; this.laserTarget = calculateLaserTargetFromOverlay(intersection.intersection, intersection.objectID); }; this.shouldExit = function(controllerData) { - var target = controllerData.rayPicks[this.hand].objectID; - var isLaserOffTablet = (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)); - return isLaserOffTablet; + var intersection = controllerData.rayPicks[this.hand]; + var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY) + return offOverlay; } this.exitModule = function() { @@ -461,10 +450,9 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - var target = controllerData.rayPicks[this.hand].objectID; - if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { + var intersection = controllerData.rayPicks[this.hand]; + if (intersection.type === RayPick.INTERSECTED_OVERLAY) { if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.getOtherModule().active) { - this.tabletScrenID = HMD.tabletScreenID; this.active = true; return makeRunningValues(true, [], []); } @@ -480,7 +468,7 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(false, [], []); } - this.updateLaserTargets(); + this.updateLaserTargets(controllerData); this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); @@ -511,7 +499,7 @@ Script.include("/~/system/libraries/controllers.js"); this.halfEnd = halfEnd; this.fullEnd = fullEnd; this.laserPointer = LaserPointers.createLaserPointer({ - joint: (this.hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + joint: (this.hand == RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_OVERLAYS, maxDistance: PICK_MAX_DISTANCE, posOffset: getGrabPointSphereOffset(this.handToController()), diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 65d63b7bec..9e6fd0c745 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -26,7 +26,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", "controllerModules/cloneEntity.js", - "controllerModules/tabletLaserInput.js", + "controllerModules/web3DOverlayLaserInput.js", "teleport.js" ]; From 4b592687b76ebe7edaea25915a32fd64458cca71 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 28 Aug 2017 17:35:34 -0700 Subject: [PATCH 35/78] finished webEntityLaserInput, now working on cloning --- .../controllerModules/equipEntity.js | 1 + .../controllerModules/farActionGrabEntity.js | 97 +++- .../controllerModules/nearParentGrabEntity.js | 14 +- .../controllerModules/webEntityLaserInput.js | 478 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 2 +- scripts/system/libraries/cloneEntityUtils.js | 91 ++++ 6 files changed, 667 insertions(+), 16 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/webEntityLaserInput.js create mode 100644 scripts/system/libraries/cloneEntityUtils.js diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 2197fdca28..b9b8279dbd 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -434,6 +434,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; this.startEquipEntity = function (controllerData) { + print("------> starting to equip entity <-------"); this.dropGestureReset(); this.clearEquipHaptics(); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 44bb500605..b84ee4b822 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -86,6 +86,24 @@ Script.include("/~/system/libraries/controllers.js"); {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + var GRABBABLE_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" + ]; + function FarActionGrabEntity(hand) { this.hand = hand; @@ -317,18 +335,61 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; }; - this.pointingAtWebOverlay = function(controllerData) { + this.pointingAtWebEntity = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; - var overlayType = Overlays.getOverlayType(intersection.objectID); - print(JSON.stringify(Entities.getEntityProperties(intersection.objectID))); - if ((intersection.type === RayPick.INTERSECTED_OVERLAY && overlayType === "web3d") || intersection.objectID === HMD.tabletButtonID) { + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") || intersection.objectID === HMD.tabletButtonID) { return true; } return false }; + this.distanceRotate = function(otherFarGrabModule) { + this.distanceRotating = true; + this.distanceHolding = false; + + var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; + var controllerRotationDelta = Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); + // Rotate entity by twice the delta rotation. + controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); + + // Perform the rotation in the translation controller's action update. + otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, + otherFarGrabModule.currentObjectRotation); + + // Rotate about the translation controller's target position. + this.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.offsetPosition); + otherFarGrabModule.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, + otherFarGrabModule.offsetPosition); + + this.updateLaserPointer(); + + this.previousWorldControllerRotation = worldControllerRotation; + }; + + this.prepareDistanceRotatingData = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); + this.currentObjectPosition = grabbedProperties.position; + this.grabRadius = intersection.distance; + + // Offset between controller vector at the grab radius and the entity position. + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // Initial controller rotation. + this.previousWorldControllerRotation = worldControllerRotation; + }; + this.isReady = function (controllerData) { - if (this.pointingAtWebOverlay(controllerData)) { + if (this.pointingAtWebEntity(controllerData)) { return makeRunningValues(false, [], []); } @@ -337,6 +398,7 @@ Script.include("/~/system/libraries/controllers.js"); if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { this.updateLaserPointer(controllerData); + this.prepareDistanceRotatingData(controllerData); return makeRunningValues(true, [], []); } else { return makeRunningValues(false, [], []); @@ -344,7 +406,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingAtWebOverlay(controllerData)) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingAtWebEntity(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); @@ -374,6 +436,7 @@ Script.include("/~/system/libraries/controllers.js"); } } + this.continueDistanceHolding(controllerData); // this.updateLaserPointer(controllerData, false, false); @@ -398,26 +461,32 @@ Script.include("/~/system/libraries/controllers.js"); "rotation", "dimensions", "density", "userData", "locked", "type"]); if (entityIsDistanceGrabbable(targetProps)) { - this.grabbedThingID = entityID; - this.grabbedDistance = rayPickInfo.distance; + if (!this.distanceRotating) { + this.grabbedThingID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } var otherModuleName = this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - if (otherFarGrabModule.grabbedThingID == this.grabbedThingID) { - this.distanceRotating = true; - this.distanceHolding = false; - // XXX rotate + if (otherFarGrabModule.grabbedThingID == this.grabbedThingID && otherFarGrabModule.distanceHolding) { + this.distanceRotate(otherFarGrabModule); } else { this.distanceHolding = true; this.distanceRotating = false; this.startFarGrabAction(controllerData, targetProps); } } - } else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { + } else if (this.distanceRotating) { + var otherModuleName = + this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + this.distanceRotate(otherFarGrabModule); + } + /* else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); - } + }*/ } return makeRunningValues(true, [], []); }; diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index cb004acd03..f05eab348d 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -14,6 +14,7 @@ */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/cloneEntityUtils.js"); (function() { @@ -187,7 +188,18 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); if (controllerData.triggerClicks[this.hand] == 1) { // switch to grab var targetProps = this.getTargetProps(controllerData); - if (targetProps) { + var targetCloneable = entityIsCloneable(targetProps); + + if (targetCloneable) { + var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; + var cloneID = cloneEntity(targetProps, worldEntityProps); + var cloneProps = Entities.getEntityProperties(cloneID); + + this.grabbing = true; + this.targetEntityID = cloneID + this.startNearParentingGrabEntity(controllerData, cloneProps); + + } else if (targetProps) { this.grabbing = true; this.startNearParentingGrabEntity(controllerData, targetProps); } diff --git a/scripts/system/controllers/controllerModules/webEntityLaserInput.js b/scripts/system/controllers/controllerModules/webEntityLaserInput.js new file mode 100644 index 0000000000..31e3f50bb6 --- /dev/null +++ b/scripts/system/controllers/controllerModules/webEntityLaserInput.js @@ -0,0 +1,478 @@ +"use strict"; + +// webEntityLaserInput.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/*jslint bitwise: true */ + +/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, + getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID, + enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, + makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, + +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; + + var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + + + // triggered when stylus presses a web overlay/entity + var HAPTIC_STYLUS_STRENGTH = 1.0; + var HAPTIC_STYLUS_DURATION = 20.0; + + function laserTargetHasKeyboardFocus(laserTarget) { + if (laserTarget && laserTarget !== NULL_UUID) { + return Entities.keyboardFocusOverlay === laserTarget; + } + } + + function setKeyboardFocusOnLaserTarget(laserTarget) { + if (laserTarget && laserTarget !== NULL_UUID) { + Entities.wantsHandControllerPointerEvents(laserTarget); + Overlays.keyboardFocusOverlay = NULL_UUID; + Entities.keyboardFocusEntity = laserTarget; + } + } + + function sendHoverEnterEventToLaserTarget(hand, laserTarget) { + if (!laserTarget) { + return; + } + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), + button: "None" + }; + + if (laserTarget.entityID && laserTarget.entityID !== NULL_UUID) { + Entities.sendHoverEnterEntity(laserTarget.entityID, pointerEvent); + } + } + + function sendHoverOverEventToLaserTarget(hand, laserTarget) { + + if (!laserTarget) { + return; + } + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), + button: "None" + }; + + if (laserTarget.entityID && laserTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(laserTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(laserTarget.entityID, pointerEvent); + } + } + + + function sendTouchStartEventToLaserTarget(hand, laserTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (laserTarget.entityID && laserTarget.entityID !== NULL_UUID) { + Entities.sendMousePressOnEntity(laserTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(laserTarget.entityID, pointerEvent); + } + } + + function sendTouchEndEventToLaserTarget(hand, laserTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), + button: "Primary" + }; + + if (laserTarget.entityID && laserTarget.entityID !== NULL_UUID) { + Entities.sendMouseReleaseOnEntity(laserTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(laserTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(laserTarget.entityID, pointerEvent); + } + } + + function sendTouchMoveEventToLaserTarget(hand, laserTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: laserTarget.position2D, + pos3D: laserTarget.position, + normal: laserTarget.normal, + direction: Vec3.subtract(ZERO_VEC, laserTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (laserTarget.entityID && laserTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(laserTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(laserTarget.entityID, pointerEvent); + } + } + + function calculateTargetFromEntity(intersection, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return null; + } + + // project stylus tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(intersection, props.position), normal); + var position = Vec3.subtract(intersection, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + + return { + entityID: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; + } + + function distance2D(a, b) { + var dx = (a.x - b.x); + var dy = (a.y - b.y); + return Math.sqrt(dx * dx + dy * dy); + } + + function WebEntityLaserInput(hand) { + this.hand = hand; + this.active = false; + this.previousLaserClickedTarget = false; + this.laserPressingTarget = false; + this.hover = false; + this.mode = "none"; + this.pressEnterLaserTarget = null; + this.laserTarget = null; + this.laserTargetID = null; + this.lastValidTargetID = null; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.getOtherModule = function() { + return (this.hand === RIGHT_HAND) ? leftWebEntityLaserInput : rightWebEntityLaserInput; + }; + + this.parameters = makeDispatcherModuleParameters( + 550, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.requestTouchFocus = function(laserTarget) { + if (laserTarget !== null || laserTarget !== undefined) { + sendHoverEnterEventToLaserTarget(this.hand, this.laserTarget); + this.lastValidTargetID = laserTarget; + } + }; + + this.relinquishTouchFocus = function() { + // send hover leave event. + var pointerEvent = { type: "Move", id: this.hand + 1 }; + Entities.sendMouseMoveOnEntity(this.lastValidTargetID, pointerEvent); + Entities.sendHoverOverEntity(this.lastValidTargetID, pointerEvent); + Entities.sendHoverLeaveEntity(this.lastValidID, pointerEvent); + }; + + this.updateLaserTargets = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + this.laserTargetID = intersection.objectID; + var props = Entities.getEntityProperties(intersection.objectID); + this.laserTarget = calculateTargetFromEntity(intersection.intersection, props); + }; + + this.processControllerTriggers = function(controllerData) { + if (controllerData.triggerClicks[this.hand]) { + this.mode = "full"; + this.laserPressingTarget = true; + this.hover = false; + } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.mode = "half"; + this.laserPressingTarget = false; + this.hover = true; + this.requestTouchFocus(this.laserTargetID); + } else { + this.mode = "none"; + this.laserPressingTarget = false; + this.hover = false; + this.relinquishTouchFocus(); + + } + }; + + this.hovering = function() { + if (!laserTargetHasKeyboardFocus(this.laserTagetID)) { + setKeyboardFocusOnLaserTarget(this.laserTargetID); + } + sendHoverOverEventToLaserTarget(this.hand, this.laserTarget); + }; + + this.laserPressEnter = function () { + sendTouchStartEventToLaserTarget(this.hand, this.laserTarget); + Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); + + this.touchingEnterTimer = 0; + this.pressEnterLaserTarget = this.laserTarget; + this.deadspotExpired = false; + + var LASER_PRESS_TO_MOVE_DEADSPOT = 0.026; + this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT) * this.laserTarget.distance; + }; + + this.laserPressExit = function () { + if (this.laserTarget === null) { + return; + } + + // send press event + if (this.deadspotExpired) { + sendTouchEndEventToLaserTarget(this.hand, this.laserTarget); + } else { + sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); + } + }; + + this.laserPressing = function (controllerData, dt) { + this.touchingEnterTimer += dt; + + if (this.laserTarget) { + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.laserTarget.position2D, + this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { + sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget); + this.deadspotExpired = true; + } + } else { + this.laserPressingTarget = false; + } + }; + + this.releaseTouchEvent = function() { + if (this.pressEnterLaserTarget === null) { + return; + } + + sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); + } + + this.updateLaserPointer = function(controllerData) { + var RADIUS = 0.005; + var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; + + if (this.mode === "full") { + fullEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: fullEnd}); + } else if (this.mode === "half") { + halfEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: halfEnd}); + } + + LaserPointers.enableLaserPointer(this.laserPointer); + LaserPointers.setRenderState(this.laserPointer, this.mode); + }; + + this.isPointingAtWebEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + + if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web")) { + return true; + } + return false + }; + + this.exitModule = function() { + this.releaseTouchEvent(); + this.relinquishTouchFocus(); + this.reset(); + this.updateLaserPointer(); + LaserPointers.disableLaserPointer(this.laserPointer); + }; + + this.reset = function() { + this.pressEnterLaserTarget = null; + this.laserTarget = null; + this.laserTargetID = null; + this.laserPressingTarget = false; + this.previousLaserClickedTarget = null; + this.mode = "none"; + this.active = false; + }; + + this.isReady = function(controllerData) { + var otherModule = this.getOtherModule(); + if (this.isPointingAtWebEntity(controllerData) && !otherModule.active) { + return makeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function(controllerData, deltaTime) { + if (!this.isPointingAtWebEntity(controllerData)) { + this.exitModule(); + return makeRunningValues(false, [], []); + } + + this.updateLaserTargets(controllerData); + this.processControllerTriggers(controllerData); + this.updateLaserPointer(controllerData); + + if (!this.previousLaserClickedTarget && this.laserPressingTarget) { + this.laserPressEnter(); + } + if (this.previousLaserClickedTarget && !this.laserPressingTarget) { + this.laserPressExit(); + } + this.previousLaserClickedTarget = this.laserPressingTarget; + + if (this.laserPressingTarget) { + this.laserPressing(controllerData, deltaTime); + } + + if (this.hover) { + this.hovering(); + } + return makeRunningValues(true, [], []); + }; + + this.cleanup = function() { + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + } + + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand == RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + }; + + + var leftWebEntityLaserInput = new WebEntityLaserInput(LEFT_HAND); + var rightWebEntityLaserInput = new WebEntityLaserInput(RIGHT_HAND); + + enableDispatcherModule("LeftWebEntityLaserInput", leftWebEntityLaserInput); + enableDispatcherModule("RightWebEntityLaserInput", rightWebEntityLaserInput); + + this.cleanup = function() { + leftWebEntityLaserInput.cleanup(); + rightWebEntityLaserInput.cleanup(); + disableDispatcherModule("LeftWebEntityLaserInput"); + disableDispatcherModule("RightWebEntityLaserInput"); + }; + Script.scriptEnding.connect(this.cleanup); + +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 9e6fd0c745..c9f030231a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -25,8 +25,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/tabletStylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", - "controllerModules/cloneEntity.js", "controllerModules/web3DOverlayLaserInput.js", + "controllerModules/webEntityLaserInput.js", "teleport.js" ]; diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js new file mode 100644 index 0000000000..a862108a87 --- /dev/null +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -0,0 +1,91 @@ +"use strict"; + +// cloneEntity.js +// +// 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/controllers/controllerDispatcherUtils.js"); + + +// Object assign polyfill +if (typeof Object.assign != 'function') { + Object.assign = function(target, varArgs) { + if (target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource !== null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +entityIsCloneable = function(props) { + if (props) { + var grabbableData = getGrabbableData(props); + return grabbableData.cloneable; + } + + return false; +} + + +cloneEntity = function(cloneableProps, worldEntityProps) { + if (!cloneableProps) { + return null; + } + + // we need all the properties, for this + cloneableProps = Entities.getEntityProperties(cloneableProps.id); + + var count = 0; + worldEntityProps.forEach(function(itemWE) { + if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) { + count++; + } + }); + + var grabInfo = getGrabbableData(cloneableProps); + + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; + if (count >= limit && limit !== 0) { + return null; + } + + cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var cUserData = Object.assign({}, cloneableProps.userData); + var cProperties = Object.assign({}, cloneableProps); + + try { + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id; + } catch(e) { + } + + cProperties.dynamic = dynamic; + cProperties.locked = false; + if (!cUserData.grabbableKey) { + cUserData.grabbableKey = {}; + } + cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + + var cloneID = Entities.addEntity(cProperties); + return cloneID; +} From ed52136fc5324d1203eba08256e1a8b79b8bd240 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 29 Aug 2017 15:18:28 -0700 Subject: [PATCH 36/78] finished equiping locked clone-able entities --- .../controllerModules/equipEntity.js | 46 +++++++++++++++++-- scripts/system/libraries/cloneEntityUtils.js | 29 ++++-------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index b9b8279dbd..08e4113c5d 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -15,6 +15,7 @@ Script.include("/~/system/libraries/Xform.js"); Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/cloneEntityUtils.js"); var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; @@ -321,7 +322,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (props.parentID === NULL_UUID) { hasParent = false; } - if (hasParent || props.locked || entityHasActions(hotspot.entityID)) { + + if (hasParent || entityHasActions(hotspot.entityID)) { return false; } @@ -370,6 +372,16 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa return equippableHotspots; }; + this.cloneHotspot = function(props, controllerData) { + if (entityIsCloneable(props)) { + var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; + var cloneID = cloneEntity(props, worldEntityProps); + return cloneID + } + + return null; + }; + this.chooseBestEquipHotspot = function(candidateEntityProps, controllerData) { var equippableHotspots = this.chooseNearEquipHotspots(candidateEntityProps, controllerData); if (equippableHotspots.length > 0) { @@ -434,11 +446,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; this.startEquipEntity = function (controllerData) { - print("------> starting to equip entity <-------"); this.dropGestureReset(); this.clearEquipHaptics(); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + var grabbedProperties = Entities.getEntityProperties(this.targetEntityID); + // if an object is "equipped" and has a predefined offset, use it. var offsets = getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); if (offsets) { @@ -467,7 +480,19 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa localPosition: this.offsetPosition, localRotation: this.offsetRotation }; - Entities.editEntity(this.targetEntityID, reparentProps); + var isClone = false; + if (entityIsCloneable(grabbedProperties)) { + var cloneID = this.cloneHotspot(grabbedProperties, controllerData); + this.targetEntityID = cloneID; + Entities.editEntity(this.targetEntityID, reparentProps); + isClone = true; + } else if (!grabbedProperties.locked) { + Entities.editEntity(this.targetEntityID, reparentProps); + } else { + this.grabbedHotspot = null; + this.targetEntityID = null; + return; + } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "startEquip", args); @@ -477,6 +502,18 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa grabbedEntity: this.targetEntityID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); + + var _this = this; + var grabEquipCheck = function() { + var args = [_this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(_this.targetEntityID, "startEquip", args); + }; + + if (isClone) { + // 100 ms seems to be sufficient time to force the check even occur after the object has been initialized. + Script.setTimeout(grabEquipCheck, 100); + } + }; this.endEquipEntity = function () { @@ -523,6 +560,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); + //if the potentialHotspot is cloneable, clone it and return it + // if the potentialHotspot os not cloneable and locked return null + if (potentialEquipHotspot) { if (this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) { this.grabbedHotspot = potentialEquipHotspot; diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index a862108a87..69c91fc398 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -39,13 +39,9 @@ entityIsCloneable = function(props) { } -cloneEntity = function(cloneableProps, worldEntityProps) { - if (!cloneableProps) { - return null; - } - +cloneEntity = function(props, worldEntityProps) { // we need all the properties, for this - cloneableProps = Entities.getEntityProperties(cloneableProps.id); + var cloneableProps = Entities.getEntityProperties(props.id); var count = 0; worldEntityProps.forEach(function(itemWE) { @@ -55,7 +51,6 @@ cloneEntity = function(cloneableProps, worldEntityProps) { }); var grabInfo = getGrabbableData(cloneableProps); - var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; if (count >= limit && limit !== 0) { return null; @@ -64,23 +59,19 @@ cloneEntity = function(cloneableProps, worldEntityProps) { cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; - var cUserData = Object.assign({}, cloneableProps.userData); + var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData)); var cProperties = Object.assign({}, cloneableProps); - try { - delete cUserData.grabbableKey.cloneLifetime; - delete cUserData.grabbableKey.cloneable; - delete cUserData.grabbableKey.cloneDynamic; - delete cUserData.grabbableKey.cloneLimit; - delete cProperties.id; - } catch(e) { - } + + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cProperties.id; + cProperties.dynamic = dynamic; cProperties.locked = false; - if (!cUserData.grabbableKey) { - cUserData.grabbableKey = {}; - } cUserData.grabbableKey.triggerable = true; cUserData.grabbableKey.grabbable = true; cProperties.lifetime = lifetime; From 3960ec7d5a31194cf565f28cae09ef62e45bfea1 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 30 Aug 2017 15:20:03 -0700 Subject: [PATCH 37/78] finish main handControllerGrab refactoring --- .../controllerModules/farActionGrabEntity.js | 95 +++++++++++++----- .../controllerModules/inEditMode.js | 99 +++++++++++++++++++ .../nearParentGrabOverlay.js | 2 +- ...rlayLaserInput.js => overlayLaserInput.js} | 50 ++++++---- .../controllerModules/tabletStylusInput.js | 2 +- .../system/controllers/controllerScripts.js | 7 +- 6 files changed, 205 insertions(+), 50 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/inEditMode.js rename scripts/system/controllers/controllerModules/{web3DOverlayLaserInput.js => overlayLaserInput.js} (92%) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index b84ee4b822..8c5a6bf5a3 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -109,6 +109,8 @@ Script.include("/~/system/libraries/controllers.js"); this.hand = hand; this.grabbedThingID = null; this.actionID = null; // action this script created... + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; var ACTION_TTL = 15; // seconds @@ -335,11 +337,11 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; }; - this.pointingAtWebEntity = function(controllerData) { + this.notPointingAtEntity = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; var entityProperty = Entities.getEntityProperties(intersection.objectID); var entityType = entityProperty.type; - if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") || intersection.objectID === HMD.tabletButtonID) { + if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") || intersection.type === RayPick.INTERSECTED_OVERLAY) { return true; } return false @@ -388,8 +390,15 @@ Script.include("/~/system/libraries/controllers.js"); this.previousWorldControllerRotation = worldControllerRotation; }; + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + } + } + this.isReady = function (controllerData) { - if (this.pointingAtWebEntity(controllerData)) { + if (this.notPointingAtEntity(controllerData)) { return makeRunningValues(false, [], []); } @@ -401,17 +410,28 @@ Script.include("/~/system/libraries/controllers.js"); this.prepareDistanceRotatingData(controllerData); return makeRunningValues(true, [], []); } else { + this.destroyContextOverlay(); return makeRunningValues(false, [], []); } }; + this.isPointingAtUI = function(controllerData) { + var hudRayPickInfo = controllerData.hudRayPicks[this.hand]; + var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection); + } + this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingAtWebEntity(controllerData)) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); } + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity !== this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } + // gather up the readiness of the near-grab modules var nearGrabNames = [ this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", @@ -456,37 +476,58 @@ Script.include("/~/system/libraries/controllers.js"); var hudRayPickInfo = controllerData.hudRayPicks[this.hand]; var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection); if (rayPickInfo.type == RayPick.INTERSECTED_ENTITY) { - var entityID = rayPickInfo.objectID; - var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", - "rotation", "dimensions", "density", - "userData", "locked", "type"]); - if (entityIsDistanceGrabbable(targetProps)) { - if (!this.distanceRotating) { - this.grabbedThingID = entityID; - this.grabbedDistance = rayPickInfo.distance; - } - var otherModuleName = - this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - if (otherFarGrabModule.grabbedThingID == this.grabbedThingID && otherFarGrabModule.distanceHolding) { - this.distanceRotate(otherFarGrabModule); - } else { - this.distanceHolding = true; - this.distanceRotating = false; - this.startFarGrabAction(controllerData, targetProps); + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type"]); + if (entityIsDistanceGrabbable(targetProps)) { + if (!this.distanceRotating) { + this.grabbedThingID = entityID; + this.grabbedDistance = rayPickInfo.distance; + } + var otherModuleName = + this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + if (otherFarGrabModule.grabbedThingID == this.grabbedThingID && otherFarGrabModule.distanceHolding) { + this.distanceRotate(otherFarGrabModule); + } else { + this.distanceHolding = true; + this.distanceRotating = false; + this.startFarGrabAction(controllerData, targetProps); + } } + } else if (!this.entityWithContextOverlay && !this.contextOverlayTimer) { + var _this = this; + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && _this.contextOverlayTimer) { + var props = Entities.getEntityProperties(rayPickInfo.objectID); + var pointerEvent = { + type: "Move", + id: this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, rayPickInfo.intersection, props), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); } } else if (this.distanceRotating) { - var otherModuleName = - this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherModuleName = + this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; var otherFarGrabModule = getEnabledModuleByName(otherModuleName); this.distanceRotate(otherFarGrabModule); - } - /* else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { + } else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); - }*/ + } } return makeRunningValues(true, [], []); }; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js new file mode 100644 index 0000000000..67da6c18ac --- /dev/null +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -0,0 +1,99 @@ +"use strict" + +// inEditMode.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/utils.js"); + +(function () { + + function InEditMode(hand) { + this.hand = hand; + + this.parameters = makeDispatcherModuleParameters( + 160, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + 100); + + this.nearTablet = function(overlays) { + for (var i = 0; i < overlays.length; i++) { + if (overlays[i] === HMD.tabletID) { + return true; + } + } + + return false; + }; + + this.pointingAtTablet = function(objectID) { + if (objectID === HMD.tabletScreenID || objectID === HMD.tabletButtonID) { + return true; + } + return false; + }; + + this.isReady = function(controllerData) { + var overlays = controllerData.nearbyOverlayIDs[this.hand]; + var objectID = controllerData.rayPicks[this.hand].objectID; + + if (isInEditMode()) { + return makeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function(controllerData) { + var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTabletStylusInput" : "LeftTabletStylusInput"); + if (tabletStylusInput) { + var tabletReady = tabletStylusInput.isReady(controllerData); + + if (tabletReady.active) { + return makeRunningValues(false, [], []); + } + } + + var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput"); + if (overlayLaser) { + var overlayLaserReady = overlayLaser.isReady(controllerData); + + if (overlayLaserReady.active && this.pointingAtTablet(overlayLaser.target)) { + return makeRunningValues(false, [], []); + } + } + + var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + if (nearOverlay) { + var nearOverlayReady = nearOverlay.isReady(controllerData); + + if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) { + return makeRunningValues(false, [], []); + } + } + + return this.isReady(controllerData); + }; + }; + + + var leftHandInEditMode = new InEditMode(LEFT_HAND); + var rightHandInEditMode = new InEditMode(RIGHT_HAND); + + enableDispatcherModule("LeftHandInEditMode", leftHandInEditMode); + enableDispatcherModule("RightHandInEditMode", rightHandInEditMode); + + this.cleanup = function() { + disableDispatcherModule("LeftHandInEditMode"); + disableDispatcherModule("RightHandInEditMode"); + }; +}()); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 399814a0ed..2cea81ce18 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -28,7 +28,7 @@ var GRAB_RADIUS = 0.35; this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 500, + 140, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js similarity index 92% rename from scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js rename to scripts/system/controllers/controllerModules/overlayLaserInput.js index 501c37fd35..8a14fc49ae 100644 --- a/scripts/system/controllers/controllerModules/web3DOverlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -1,6 +1,6 @@ "use strict" -// tabletLaserInput.js +// overlayLaserInput.js // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -270,7 +270,7 @@ Script.include("/~/system/libraries/controllers.js"); return Math.sqrt(dx * dx + dy * dy); } - function TabletLaserInput(hand) { + function OverlayLaserInput(hand) { this.hand = hand; this.active = false; this.previousLaserClikcedTarget = false; @@ -281,11 +281,12 @@ Script.include("/~/system/libraries/controllers.js"); this.laserTarget = null; this.pressEnterLaserTarget = null; this.hover = false; + this.target = null; this.lastValidTargetID = this.tabletTargetID; this.parameters = makeDispatcherModuleParameters( - 200, + 120, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -295,7 +296,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.getOtherModule = function() { - return (this.hand === RIGHT_HAND) ? leftTabletLaserInput : rightTabletLaserInput; + return (this.hand === RIGHT_HAND) ? leftOverlayLaserInput : rightOverlayLaserInput; }; this.handToController = function() { @@ -313,10 +314,6 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.hasTouchFocus = function(laserTarget) { - return (this.laserTargetID === HMD.tabletScreenID); - }; - this.relinquishTouchFocus = function() { // send hover leave event. var pointerEvent = { type: "Move", id: this.hand + 1 }; @@ -449,12 +446,28 @@ Script.include("/~/system/libraries/controllers.js"); this.active = false; }; + this.deleteContextOverlay = function() { + var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); + if (farGrabModule) { + var entityWithContextOverlay = farGrabModule.entityWithContextOverlay; + + if (entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(entityWithContextOverlay); + farGrabModule.entityWithContextOverlay = false; + } + } + }; + this.isReady = function (controllerData) { + this.target = null; var intersection = controllerData.rayPicks[this.hand]; if (intersection.type === RayPick.INTERSECTED_OVERLAY) { if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.getOtherModule().active) { + this.target = intersection.objectID; this.active = true; return makeRunningValues(true, [], []); + } else { + this.deleteContextOverlay(); } } this.reset(); @@ -462,12 +475,15 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData, deltaTime) { - if (this.shouldExit(controllerData)) { this.exitModule(); return makeRunningValues(false, [], []); } + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + this.deleteContextOverlay(); + } + this.updateLaserTargets(controllerData); this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); @@ -511,17 +527,17 @@ Script.include("/~/system/libraries/controllers.js"); LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); }; - var leftTabletLaserInput = new TabletLaserInput(LEFT_HAND); - var rightTabletLaserInput = new TabletLaserInput(RIGHT_HAND); + var leftOverlayLaserInput = new OverlayLaserInput(LEFT_HAND); + var rightOverlayLaserInput = new OverlayLaserInput(RIGHT_HAND); - enableDispatcherModule("LeftTabletLaserInput", leftTabletLaserInput); - enableDispatcherModule("RightTabletLaserInput", rightTabletLaserInput); + enableDispatcherModule("LeftOverlayLaserInput", leftOverlayLaserInput); + enableDispatcherModule("RightOverlayLaserInput", rightOverlayLaserInput); this.cleanup = function () { - leftTabletLaserInput.cleanup(); - rightTabletLaserInput.cleanup(); - disableDispatcherModule("LeftTabletLaserInput"); - disableDispatcherModule("RightTabletLaserInput"); + leftOverlayLaserInput.cleanup(); + rightOverlayLaserInput.cleanup(); + disableDispatcherModule("LeftOverlayLaserInput"); + disableDispatcherModule("RightOverlayLaserInput"); }; Script.scriptEnding.connect(this.cleanup); }()); diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 8ada1b31d7..bbe8f935ae 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -319,7 +319,7 @@ Script.include("/~/system/libraries/controllers.js"); this.parameters = makeDispatcherModuleParameters( - 400, + 100, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index c9f030231a..cd572b901a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -12,11 +12,9 @@ var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", - // "handControllerGrab.js", "handControllerPointer.js", - // "grab.js", + "grab.js", "toggleAdvancedMovementForHandControllers.js", - "ControllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", @@ -25,8 +23,9 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/tabletStylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", - "controllerModules/web3DOverlayLaserInput.js", + "controllerModules/overlayLaserInput.js", "controllerModules/webEntityLaserInput.js", + "controllerModules/inEditMode.js", "teleport.js" ]; From 47699d4439c304004892a0371c2ecb48decbe883 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 30 Aug 2017 16:52:59 -0700 Subject: [PATCH 38/78] fixed edit.js --- .../controllerModules/inEditMode.js | 145 +++++++++++++++++- .../controllerModules/webEntityLaserInput.js | 4 +- 2 files changed, 146 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 67da6c18ac..782fb5dbc2 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -16,9 +16,73 @@ Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/utils.js"); (function () { + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; + + var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; function InEditMode(hand) { this.hand = hand; + this.triggerClicked = false; + this.mode = "none"; this.parameters = makeDispatcherModuleParameters( 160, @@ -31,10 +95,40 @@ Script.include("/~/system/libraries/utils.js"); return true; } } - return false; }; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.processControllerTriggers = function(controllerData) { + if (controllerData.triggerClicks[this.hand]) { + this.mode = "full"; + } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + this.mode = "half"; + } else { + this.mode = "none"; + } + }; + + this.updateLaserPointer = function(controllerData) { + var RADIUS = 0.005; + var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; + + if (this.mode === "full") { + this.fullEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd}); + } else if (this.mode === "half") { + this.halfEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd}); + } + + LaserPointers.enableLaserPointer(this.laserPointer); + LaserPointers.setRenderState(this.laserPointer, this.mode); + }; + this.pointingAtTablet = function(objectID) { if (objectID === HMD.tabletScreenID || objectID === HMD.tabletButtonID) { return true; @@ -42,11 +136,33 @@ Script.include("/~/system/libraries/utils.js"); return false; }; + this.sendPickData = function(controllerData) { + if (controllerData.triggerClicks[this.hand] && !this.triggerClicked) { + var intersection = controllerData.rayPicks[this.hand]; + if (intersection.type === RayPick.INTERSECTED_ENTITY) { + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectEntity", + entityID: intersection.objectID + })); + } else if (intersection.type === RayPick.INTERSECTED_OVERLAY) { + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectOverlay", + overlayID: intersection.objectID + })); + } + + this.triggerClicked = true; + } else { + this.triggerClicked = false; + } + }; + this.isReady = function(controllerData) { var overlays = controllerData.nearbyOverlayIDs[this.hand]; var objectID = controllerData.rayPicks[this.hand].objectID; if (isInEditMode()) { + this.triggerClicked = false; return makeRunningValues(true, [], []); } @@ -80,9 +196,34 @@ Script.include("/~/system/libraries/utils.js"); return makeRunningValues(false, [], []); } } + this.processControllerTriggers(controllerData); + this.updateLaserPointer(controllerData); + this.sendPickData(controllerData); + return this.isReady(controllerData); }; + + this.cleanup = function() { + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + } + + + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + + LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]); }; @@ -93,6 +234,8 @@ Script.include("/~/system/libraries/utils.js"); enableDispatcherModule("RightHandInEditMode", rightHandInEditMode); this.cleanup = function() { + leftHandInEditMode.cleanup(); + rightHandInEditMode.cleanup(); disableDispatcherModule("LeftHandInEditMode"); disableDispatcherModule("RightHandInEditMode"); }; diff --git a/scripts/system/controllers/controllerModules/webEntityLaserInput.js b/scripts/system/controllers/controllerModules/webEntityLaserInput.js index 31e3f50bb6..e88e75684c 100644 --- a/scripts/system/controllers/controllerModules/webEntityLaserInput.js +++ b/scripts/system/controllers/controllerModules/webEntityLaserInput.js @@ -256,7 +256,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.getOtherModule = function() { - return (this.hand === RIGHT_HAND) ? leftWebEntityLaserInput : rightWebEntityLaserInput; + return (this.hand === RIGHT_HAND) ? leftWebEntityLaserInput : rightWebEntityLaserInput; }; this.parameters = makeDispatcherModuleParameters( @@ -450,7 +450,7 @@ Script.include("/~/system/libraries/controllers.js"); } this.laserPointer = LaserPointers.createLaserPointer({ - joint: (this.hand == RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", + joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES, maxDistance: PICK_MAX_DISTANCE, posOffset: getGrabPointSphereOffset(this.handToController()), From 7cf27c18d3450b83908ca0fe0c140031bbb6419f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 31 Aug 2017 18:06:55 -0700 Subject: [PATCH 39/78] code cleanup and fix broken features --- .eslintrc.js | 5 +- .../controllers/controllerDispatcher.js | 51 +++++- .../controllerModules/cloneEntity.js | 159 ------------------ .../controllerModules/equipEntity.js | 98 ++++++++--- .../controllerModules/farActionGrabEntity.js | 106 ++++++------ .../controllerModules/inEditMode.js | 71 ++++---- .../nearParentGrabOverlay.js | 2 +- .../controllerModules/overlayLaserInput.js | 63 +++---- .../controllerModules/tabletStylusInput.js | 44 ++--- .../controllerModules/webEntityLaserInput.js | 68 ++++---- 10 files changed, 316 insertions(+), 351 deletions(-) delete mode 100644 scripts/system/controllers/controllerModules/cloneEntity.js diff --git a/.eslintrc.js b/.eslintrc.js index b4d88777f2..83fda730e5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,7 +54,10 @@ module.exports = { "Window": false, "XMLHttpRequest": false, "location": false, - "print": false + "print": false, + "RayPick": false, + "LaserPointers": false, + "ContextOverlay": false }, "rules": { "brace-style": ["error", "1tbs", { "allowSingleLine": false }], diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 18b4c287c0..9469b59a95 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -34,6 +34,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var highVarianceCount = 0; var veryhighVarianceCount = 0; this.tabletID = null; + this.blacklist = []; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name @@ -293,6 +294,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } }; + this.setBlacklist = function() { + RayPick.setIgnoreEntities(_this.leftControllerRayPick, this.blacklist); + RayPick.setIgnoreEntities(_this.rightControllerRayPick, this.blacklist); + + }; + var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress); @@ -324,27 +331,60 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); joint: "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) }); this.leftControllerHudRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_HUD, enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) }); this.rightControllerRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_RIGHTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) }); this.rightControllerHudRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_RIGHTHAND", filter: RayPick.PICK_HUD, enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) }); + this.handleHandMessage = function(channel, message, sender) { + var data + if (sender === MyAvatar.sessionUUID) { + try { + if (channel === 'Hifi-Hand-RayPick-Blacklist') { + data = JSON.parse(message); + var action = data.action; + var id = data.id; + var index = this.blacklis.indexOf(id); + + if (action === 'add' && index === -1) { + this.blacklist.push(id); + //this.setBlacklist(); + } + + if (action === 'remove') { + if (index > -1) { + blacklist.splice(index, 1); + //this.setBlacklist(); + } + } + } + + } catch (e) { + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); + } + } + }; + @@ -357,7 +397,8 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); RayPick.removeRayPick(_this.rightControllerHudRayPick); RayPick.removeRayPick(_this.leftControllerHudRayPick); }; - + Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); + Messages.messageReceived.connect(this.handleHandMessage); Script.scriptEnding.connect(this.cleanup); Script.update.connect(this.update); }()); diff --git a/scripts/system/controllers/controllerModules/cloneEntity.js b/scripts/system/controllers/controllerModules/cloneEntity.js deleted file mode 100644 index cfe9cb56da..0000000000 --- a/scripts/system/controllers/controllerModules/cloneEntity.js +++ /dev/null @@ -1,159 +0,0 @@ -"use strict"; - -// cloneEntity.js -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - - -/* global Script, Entities, RIGHT_HAND, LEFT_HAND, - enableDispatcherModule, disableDispatcherModule, getGrabbableData, Vec3, - TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS -*/ - -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); - -// Object assign polyfill -if (typeof Object.assign != 'function') { - Object.assign = function(target, varArgs) { - if (target === null) { - throw new TypeError('Cannot convert undefined or null to object'); - } - var to = Object(target); - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - if (nextSource !== null) { - for (var nextKey in nextSource) { - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }; -} - -(function() { - - function entityIsCloneable(props) { - var grabbableData = getGrabbableData(props); - return grabbableData.cloneable; - } - - function CloneEntity(hand) { - this.hand = hand; - this.grabbing = false; - this.previousParentID = {}; - this.previousParentJointIndex = {}; - this.previouslyUnhooked = {}; - - this.parameters = makeDispatcherModuleParameters( - 300, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100); - - this.getTargetProps = function (controllerData) { - // nearbyEntityProperties is already sorted by length from controller - var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; - for (var i = 0; i < nearbyEntityProperties.length; i++) { - var props = nearbyEntityProperties[i]; - var handPosition = controllerData.controllerLocations[this.hand].position; - var distance = Vec3.distance(props.position, handPosition); - if (distance > NEAR_GRAB_RADIUS) { - break; - } - if (entityIsCloneable(props)) { - return props; - } - } - return null; - }; - - this.isReady = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { - this.waiting = false; - return makeRunningValues(false, [], []); - } - - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - if (!this.waiting) { - this.waiting = true; - return makeRunningValues(true, [], []); - } - } - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData, deltaTime) { - var cloneableProps = this.getTargetProps(controllerData); - if (!cloneableProps) { - return makeRunningValues(false, [], []); - } - - // we need all the properties, for this - cloneableProps = Entities.getEntityProperties(cloneableProps.id); - - var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; - var count = 0; - worldEntityProps.forEach(function(itemWE) { - if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) { - count++; - } - }); - - var grabInfo = getGrabbableData(cloneableProps); - - var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; - if (count >= limit && limit !== 0) { - return makeRunningValues(false, [], []); - } - - cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; - var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; - var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; - var cUserData = Object.assign({}, cloneableProps.userData); - var cProperties = Object.assign({}, cloneableProps); - - try { - delete cUserData.grabbableKey.cloneLifetime; - delete cUserData.grabbableKey.cloneable; - delete cUserData.grabbableKey.cloneDynamic; - delete cUserData.grabbableKey.cloneLimit; - delete cProperties.id; - } catch(e) { - } - - cProperties.dynamic = dynamic; - cProperties.locked = false; - if (!cUserData.grabbableKey) { - cUserData.grabbableKey = {}; - } - cUserData.grabbableKey.triggerable = true; - cUserData.grabbableKey.grabbable = true; - cProperties.lifetime = lifetime; - cProperties.userData = JSON.stringify(cUserData); - // var cloneID = - Entities.addEntity(cProperties); - return makeRunningValues(false, [], []); - }; - - this.cleanup = function () { - }; - } - - var leftCloneEntity = new CloneEntity(LEFT_HAND); - var rightCloneEntity = new CloneEntity(RIGHT_HAND); - - enableDispatcherModule("LeftCloneEntity", leftCloneEntity); - enableDispatcherModule("RightCloneEntity", rightCloneEntity); - - this.cleanup = function () { - leftNearParentingGrabEntity.cleanup(); - rightNearParentingGrabEntity.cleanup(); - disableDispatcherModule("LeftNearParentingGrabEntity"); - disableDispatcherModule("RightNearParentingGrabEntity"); - }; - Script.scriptEnding.connect(this.cleanup); -}()); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 08e4113c5d..5ad7fc9e16 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -9,7 +9,8 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, - Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic + Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, + cloneEntity */ Script.include("/~/system/libraries/Xform.js"); @@ -105,14 +106,14 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var overlayInfoSet = this.map[keys[i]]; // this overlayInfo is highlighted. - if (this.highlightedHotspots.indexOf(keys[i]) != -1) { + if (this.highlightedHotspots.indexOf(keys[i]) !== -1) { overlayInfoSet.targetSize = HIGHLIGHT_SIZE; } else { overlayInfoSet.targetSize = NORMAL_SIZE; } // start to fade out this hotspot. - if (overlayInfoSet.timestamp != timestamp) { + if (overlayInfoSet.timestamp !== timestamp) { overlayInfoSet.targetSize = 0; } @@ -124,7 +125,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau; - if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { + if (overlayInfoSet.timestamp !== timestamp && overlayInfoSet.currentSize <= 0.05) { // this is an old overlay, that has finished fading out, delete it! overlayInfoSet.overlays.forEach(Overlays.deleteOverlay); delete this.map[keys[i]]; @@ -136,7 +137,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var position = entityXform.xformPoint(overlayInfoSet.localPosition); var dimensions; - if (overlayInfoSet.type == "sphere") { + if (overlayInfoSet.type === "sphere") { dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; } else { dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; @@ -157,8 +158,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } }; - - (function() { var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; @@ -185,6 +184,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (!props.userDataParsed) { props.userDataParsed = JSON.parse(props.userData); } + wearable = props.userDataParsed.wearable ? props.userDataParsed.wearable : {}; } catch (err) { } @@ -196,6 +196,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (!props.userDataParsed) { props.userDataParsed = JSON.parse(props.userData); } + equipHotspots = props.userDataParsed.equipHotspots ? props.userDataParsed.equipHotspots : []; } catch (err) { } @@ -249,6 +250,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.prevHandIsUpsideDown = false; this.triggerValue = 0; + this.messageGrabEntity = false; + this.grabEntityProps = null; this.parameters = makeDispatcherModuleParameters( 300, @@ -258,6 +261,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); + this.setMessageGrabData = function(entityProperties) { + if (entityProperties) { + this.messageGrabEntity = true; + this.grabEntityProps = entityProperties; + } + }; + // returns a list of all equip-hotspots assosiated with this entity. // @param {UUID} entityID // @returns {Object[]} array of objects with the following fields. @@ -322,7 +332,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (props.parentID === NULL_UUID) { hasParent = false; } - + if (hasParent || entityHasActions(hotspot.entityID)) { return false; } @@ -376,7 +386,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (entityIsCloneable(props)) { var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; var cloneID = cloneEntity(props, worldEntityProps); - return cloneID + return cloneID; } return null; @@ -420,7 +430,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa handIsUpsideDown = true; } - if (handIsUpsideDown != this.prevHandIsUpsideDown) { + if (handIsUpsideDown !== this.prevHandIsUpsideDown) { this.prevHandIsUpsideDown = handIsUpsideDown; Controller.triggerHapticPulse(HAPTIC_DEQUIP_STRENGTH, HAPTIC_DEQUIP_DURATION, this.hand); } @@ -486,7 +496,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = cloneID; Entities.editEntity(this.targetEntityID, reparentProps); isClone = true; - } else if (!grabbedProperties.locked) { + } else if (!grabbedProperties.locked) { Entities.editEntity(this.targetEntityID, reparentProps); } else { this.grabbedHotspot = null; @@ -508,12 +518,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var args = [_this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(_this.targetEntityID, "startEquip", args); }; - + if (isClone) { // 100 ms seems to be sufficient time to force the check even occur after the object has been initialized. Script.setTimeout(grabEquipCheck, 100); - } - + } + }; this.endEquipEntity = function () { @@ -546,8 +556,18 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand]; - - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData); + + + var potentialEquipHotspot = null; + if (this.messageGrabEntity) { + var hotspots = this.collectEquipHotspots(this.grabEntityProps); + if (hotspots.length > -1) { + potentialEquipHotspot = hotspots[0]; + } + } else { + potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData); + } + if (!this.waitForTriggerRelease) { this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition); } @@ -560,17 +580,19 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspotBuddy.update(deltaTime, timestamp, controllerData); - //if the potentialHotspot is cloneable, clone it and return it + // if the potentialHotspot is cloneable, clone it and return it // if the potentialHotspot os not cloneable and locked return null - + if (potentialEquipHotspot) { - if (this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) { + if ((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity) { this.grabbedHotspot = potentialEquipHotspot; this.targetEntityID = this.grabbedHotspot.entityID; this.startEquipEntity(controllerData); + this.messageGrabEnity = false; } return makeRunningValues(true, [potentialEquipHotspot.entityID], []); } else { + this.messageGrabEnity = false; return makeRunningValues(false, [], []); } }; @@ -610,7 +632,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.waitForTriggerRelease = false; } - if (dropDetected && this.prevDropDetected != dropDetected) { + if (dropDetected && this.prevDropDetected !== dropDetected) { this.waitForTriggerRelease = true; } @@ -627,7 +649,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var prefprops = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]); if (prefprops && prefprops.localPosition && prefprops.localRotation) { storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, - prefprops.localPosition, prefprops.localRotation); + prefprops.localPosition, prefprops.localRotation); } } @@ -651,6 +673,40 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; } + var handleMessage = function(channel, message, sender) { + var data; + print(channel); + if (sender === MyAvatar.sessionUUID) { + if (channel === 'Hifi-Hand-Grab') { + try { + data = JSON.parse(message); + var equipModule = (data.hand === 'left') ? leftEquipEntity : rightEquipEntity; + var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); + entityProperties.id = data.entityID; + equipModule.setMessageGrabData(entityProperties); + + } catch (e) { + print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); + } + } + } else if (channel === 'Hifi-Hand-Drop') { + data = JSON.parse(message); + if (data.hand === 'left') { + leftEquipEntity.endEquipEntity(); + } else if (data.hand === 'right') { + rightEquipEntity.endEquipEntity(); + } else if (data.hand === 'both') { + leftEquipEntity.endEquipEntity(); + rightEquipEntity.endEquipEntity(); + } + } + + }; + + Messages.subscribe('Hifi-Hand-Grab'); + Messages.subscribe('Hifi-Hand-Drop'); + Messages.messageReceived.connect(handleMessage); + var leftEquipEntity = new EquipEntity(LEFT_HAND); var rightEquipEntity = new EquipEntity(RIGHT_HAND); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 8c5a6bf5a3..2b748e60d6 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -5,14 +5,15 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*jslint bitwise: true */ +/* jslint bitwise: true */ /* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID, enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, - AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, + AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, + getControllerWorldLocation, projectOntoEntityXYPlane */ @@ -77,14 +78,18 @@ Script.include("/~/system/libraries/controllers.js"); drawInFront: true, // Even when burried inside of something, show it. parentID: AVATAR_SELF_ID }; - - var renderStates = [{name: "half", path: halfPath, end: halfEnd}, - {name: "full", path: fullPath, end: fullEnd}, - {name: "hold", path: holdPath}]; - var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, - {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, - {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + var renderStates = [ + {name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath} + ]; + + var defaultRenderStates = [ + {name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath} + ]; var GRABBABLE_PROPERTIES = [ "position", @@ -132,10 +137,7 @@ Script.include("/~/system/libraries/controllers.js"); var dim = {x: radius, y: radius, z: radius}; var mode = "hold"; if (!this.distanceHolding && !this.distanceRotating) { - // mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "half"; - if (controllerData.triggerClicks[this.hand] - // || this.secondarySqueezed() // XXX - ) { + if (controllerData.triggerClicks[this.hand]) { mode = "full"; } else { mode = "half"; @@ -339,44 +341,44 @@ Script.include("/~/system/libraries/controllers.js"); this.notPointingAtEntity = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; - var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityProperty = Entities.getEntityProperties(intersection.objectID); var entityType = entityProperty.type; - if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") || intersection.type === RayPick.INTERSECTED_OVERLAY) { + if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web") || intersection.type === RayPick.INTERSECTED_OVERLAY) { return true; } - return false + return false; }; this.distanceRotate = function(otherFarGrabModule) { - this.distanceRotating = true; + this.distanceRotating = true; this.distanceHolding = false; var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; var controllerRotationDelta = Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); // Rotate entity by twice the delta rotation. controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); - + // Perform the rotation in the translation controller's action update. otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, - otherFarGrabModule.currentObjectRotation); - + otherFarGrabModule.currentObjectRotation); + // Rotate about the translation controller's target position. this.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.offsetPosition); otherFarGrabModule.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, - otherFarGrabModule.offsetPosition); - + otherFarGrabModule.offsetPosition); + this.updateLaserPointer(); - + this.previousWorldControllerRotation = worldControllerRotation; }; this.prepareDistanceRotatingData = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; - + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; - + var grabbedProperties = Entities.getEntityProperties(intersection.objectID, GRABBABLE_PROPERTIES); this.currentObjectPosition = grabbedProperties.position; this.grabRadius = intersection.distance; @@ -385,7 +387,7 @@ Script.include("/~/system/libraries/controllers.js"); var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); targetPosition = Vec3.sum(targetPosition, worldControllerPosition); this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - + // Initial controller rotation. this.previousWorldControllerRotation = worldControllerRotation; }; @@ -395,13 +397,13 @@ Script.include("/~/system/libraries/controllers.js"); ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); this.entityWithContextOverlay = false; } - } + }; this.isReady = function (controllerData) { if (this.notPointingAtEntity(controllerData)) { return makeRunningValues(false, [], []); } - + this.distanceHolding = false; this.distanceRotating = false; @@ -418,10 +420,15 @@ Script.include("/~/system/libraries/controllers.js"); this.isPointingAtUI = function(controllerData) { var hudRayPickInfo = controllerData.hudRayPicks[this.hand]; var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection); - } + if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { + return true; + } + + return false; + }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData)) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData) || this.isPointingAtUI(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); @@ -432,12 +439,16 @@ Script.include("/~/system/libraries/controllers.js"); this.destroyContextOverlay(); } + var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; + var otherFarGrabModule = getEnabledModuleByName(otherModuleName); + // gather up the readiness of the near-grab modules var nearGrabNames = [ this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" ]; + var nearGrabReadiness = []; for (var i = 0; i < nearGrabNames.length; i++) { var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); @@ -449,19 +460,14 @@ Script.include("/~/system/libraries/controllers.js"); // if we are doing a distance grab and the object gets close enough to the controller, // stop the far-grab so the near-grab or equip can take over. for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] == this.grabbedThingID) { + if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) { this.laserPointerOff(); this.endNearGrabAction(); return makeRunningValues(false, [], []); } } - this.continueDistanceHolding(controllerData); - // this.updateLaserPointer(controllerData, false, false); - - // var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - // Entities.callEntityMethod(this.grabbedThingID, "continueFarGrab", args); } else { // if we are doing a distance search and this controller moves into a position // where it could near-grab something, stop searching. @@ -473,23 +479,22 @@ Script.include("/~/system/libraries/controllers.js"); } var rayPickInfo = controllerData.rayPicks[this.hand]; - var hudRayPickInfo = controllerData.hudRayPicks[this.hand]; - var hudPoint2d = HMD.overlayFromWorldPoint(hudRayPickInfo.intersection); - if (rayPickInfo.type == RayPick.INTERSECTED_ENTITY) { + if (rayPickInfo.type === RayPick.INTERSECTED_ENTITY) { if (controllerData.triggerClicks[this.hand]) { var entityID = rayPickInfo.objectID; - var targetProps = Entities.getEntityProperties(entityID, ["dynamic", "shapeType", "position", - "rotation", "dimensions", "density", - "userData", "locked", "type"]); + var targetProps = Entities.getEntityProperties(entityID, [ + "dynamic", "shapeType", "position", + "rotation", "dimensions", "density", + "userData", "locked", "type" + ]); + if (entityIsDistanceGrabbable(targetProps)) { if (!this.distanceRotating) { this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; } - var otherModuleName = - this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - if (otherFarGrabModule.grabbedThingID == this.grabbedThingID && otherFarGrabModule.distanceHolding) { + + if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && otherFarGrabModule.distanceHolding) { this.distanceRotate(otherFarGrabModule); } else { this.distanceHolding = true; @@ -519,14 +524,7 @@ Script.include("/~/system/libraries/controllers.js"); }, 500); } } else if (this.distanceRotating) { - var otherModuleName = - this.hand == RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); this.distanceRotate(otherFarGrabModule); - } else if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(hudPoint2d || Reticle.position)) { - this.endNearGrabAction(); - this.laserPointerOff(); - return makeRunningValues(false, [], []); } } return makeRunningValues(true, [], []); @@ -540,7 +538,7 @@ Script.include("/~/system/libraries/controllers.js"); this.halfEnd = halfEnd; this.fullEnd = fullEnd; this.laserPointer = LaserPointers.createLaserPointer({ - joint: (this.hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + joint: (this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, maxDistance: PICK_MAX_DISTANCE, posOffset: getGrabPointSphereOffset(this.handToController()), diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 782fb5dbc2..fc7e0b526e 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -1,4 +1,4 @@ -"use strict" +"use strict"; // inEditMode.js // @@ -8,7 +8,10 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, - AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, + isInEditMode */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -71,13 +74,17 @@ Script.include("/~/system/libraries/utils.js"); parentID: AVATAR_SELF_ID }; - var renderStates = [{name: "half", path: halfPath, end: halfEnd}, - {name: "full", path: fullPath, end: fullEnd}, - {name: "hold", path: holdPath}]; - - var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, - {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, - {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + var renderStates = [ + {name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath} + ]; + + var defaultRenderStates = [ + {name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath} + ]; function InEditMode(hand) { this.hand = hand; @@ -97,7 +104,7 @@ Script.include("/~/system/libraries/utils.js"); } return false; }; - + this.handToController = function() { return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; @@ -116,7 +123,7 @@ Script.include("/~/system/libraries/utils.js"); this.updateLaserPointer = function(controllerData) { var RADIUS = 0.005; var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; - + if (this.mode === "full") { this.fullEnd.dimensions = dim; LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: this.fullEnd}); @@ -124,11 +131,11 @@ Script.include("/~/system/libraries/utils.js"); this.halfEnd.dimensions = dim; LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: this.halfEnd}); } - + LaserPointers.enableLaserPointer(this.laserPointer); LaserPointers.setRenderState(this.laserPointer, this.mode); }; - + this.pointingAtTablet = function(objectID) { if (objectID === HMD.tabletScreenID || objectID === HMD.tabletButtonID) { return true; @@ -157,16 +164,21 @@ Script.include("/~/system/libraries/utils.js"); } }; - this.isReady = function(controllerData) { - var overlays = controllerData.nearbyOverlayIDs[this.hand]; - var objectID = controllerData.rayPicks[this.hand].objectID; + this.exitModule = function() { + this.disableLasers(); + return makeRunningValues(false, [], []); + }; + this.disableLasers = function() { + LaserPointers.disableLaserPointer(this.laserPointer); + }; + + this.isReady = function(controllerData) { if (isInEditMode()) { this.triggerClicked = false; return makeRunningValues(true, [], []); } - - return makeRunningValues(false, [], []); + return this.exitModule(); }; this.run = function(controllerData) { @@ -175,44 +187,44 @@ Script.include("/~/system/libraries/utils.js"); var tabletReady = tabletStylusInput.isReady(controllerData); if (tabletReady.active) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } - var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput"); + var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput"); if (overlayLaser) { var overlayLaserReady = overlayLaser.isReady(controllerData); if (overlayLaserReady.active && this.pointingAtTablet(overlayLaser.target)) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } - var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); if (nearOverlay) { var nearOverlayReady = nearOverlay.isReady(controllerData); if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) { - return makeRunningValues(false, [], []); + return this.exitModule(); } } this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); this.sendPickData(controllerData); - + return this.isReady(controllerData); }; - this.cleanup = function() { + this.cleanup = function() { LaserPointers.disableLaserPointer(this.laserPointer); LaserPointers.removeLaserPointer(this.laserPointer); - } + }; this.halfEnd = halfEnd; this.fullEnd = fullEnd; - + this.laserPointer = LaserPointers.createLaserPointer({ joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, @@ -222,10 +234,9 @@ Script.include("/~/system/libraries/utils.js"); faceAvatar: true, defaultRenderStates: defaultRenderStates }); - - LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]); - }; + LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]); + } var leftHandInEditMode = new InEditMode(LEFT_HAND); var rightHandInEditMode = new InEditMode(RIGHT_HAND); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 2cea81ce18..f0a5b9f07d 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -28,7 +28,7 @@ var GRAB_RADIUS = 0.35; this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 140, + 90, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index 8a14fc49ae..ec2aa7750a 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -1,4 +1,4 @@ -"use strict" +"use strict"; // overlayLaserInput.js // @@ -8,16 +8,16 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, - AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE */ - - Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { - var halfPath = { + var halfPath = { type: "line3d", color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, visible: true, @@ -72,13 +72,17 @@ Script.include("/~/system/libraries/controllers.js"); parentID: AVATAR_SELF_ID }; - var renderStates = [{name: "half", path: halfPath, end: halfEnd}, - {name: "full", path: fullPath, end: fullEnd}, - {name: "hold", path: holdPath}]; + var renderStates = [ + {name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath} + ]; - var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, - {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, - {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + var defaultRenderStates = [ + {name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath} + ]; // triggered when stylus presses a web overlay/entity @@ -200,7 +204,7 @@ Script.include("/~/system/libraries/controllers.js"); } // will return undefined if overlayID does not exist. - function calculateLaserTargetFromOverlay(laserTip, overlayID) { + function calculateLaserTargetFromOverlay(worldPos, overlayID) { var overlayPosition = Overlays.getProperty(overlayID, "position"); if (overlayPosition === undefined) { return null; @@ -212,12 +216,11 @@ Script.include("/~/system/libraries/controllers.js"); return null; } var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); - var distance = Vec3.dot(Vec3.subtract(laserTip, overlayPosition), normal); - var position = Vec3.subtract(laserTip, Vec3.multiply(normal, distance)); + var distance = Vec3.dot(Vec3.subtract(worldPos, overlayPosition), normal); // calclulate normalized position var invRot = Quat.inverse(overlayRotation); - var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, overlayPosition)); var dpi = Overlays.getProperty(overlayID, "dpi"); var dimensions; @@ -228,12 +231,12 @@ Script.include("/~/system/libraries/controllers.js"); if (resolution === undefined) { return null; } - resolution.z = 1; // Circumvent divide-by-zero. + resolution.z = 1;// Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); if (scale === undefined) { return null; } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. + scale.z = 0.01;// overlay dimensions are 2D, not 3D. dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); @@ -241,21 +244,23 @@ Script.include("/~/system/libraries/controllers.js"); return null; } if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + dimensions.z = 0.01;// sometimes overlay dimensions are 2D, not 3D. } } var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { x: normalizedPosition.x * dimensions.x, - y: (1 - normalizedPosition.y) * dimensions.y }; // flip y-axis + var position2D = { + x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis + }; return { entityID: null, overlayID: overlayID, distance: distance, - position: position, + position: worldPos, position2D: position2D, normal: normal, normalizedPosition: normalizedPosition, @@ -400,8 +405,8 @@ Script.include("/~/system/libraries/controllers.js"); if (this.laserTarget) { var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - distance2D(this.laserTarget.position2D, - this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { + distance2D( this.laserTarget.position2D, + this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget); this.deadspotExpired = true; } @@ -412,7 +417,7 @@ Script.include("/~/system/libraries/controllers.js"); this.releaseTouchEvent = function() { sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); - } + }; this.updateLaserTargets = function(controllerData) { @@ -423,9 +428,9 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldExit = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; - var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY) + var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY); return offOverlay; - } + }; this.exitModule = function() { this.releaseTouchEvent(); @@ -433,7 +438,7 @@ Script.include("/~/system/libraries/controllers.js"); this.reset(); this.updateLaserPointer(); LaserPointers.disableLaserPointer(this.laserPointer); - } + }; this.reset = function() { this.hover = false; @@ -515,7 +520,7 @@ Script.include("/~/system/libraries/controllers.js"); this.halfEnd = halfEnd; this.fullEnd = fullEnd; this.laserPointer = LaserPointers.createLaserPointer({ - joint: (this.hand == RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", + joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_OVERLAYS, maxDistance: PICK_MAX_DISTANCE, posOffset: getGrabPointSphereOffset(this.handToController()), @@ -525,7 +530,7 @@ Script.include("/~/system/libraries/controllers.js"); }); LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); - }; + } var leftOverlayLaserInput = new OverlayLaserInput(LEFT_HAND); var rightOverlayLaserInput = new OverlayLaserInput(RIGHT_HAND); diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index bbe8f935ae..9720bc8022 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -171,12 +171,12 @@ Script.include("/~/system/libraries/controllers.js"); if (resolution === undefined) { return; } - resolution.z = 1; // Circumvent divide-by-zero. + resolution.z = 1; // Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); if (scale === undefined) { return; } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. + scale.z = 0.01; // overlay dimensions are 2D, not 3D. dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); @@ -184,15 +184,17 @@ Script.include("/~/system/libraries/controllers.js"); return; } if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. } } var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { x: normalizedPosition.x * dimensions.x, - y: (1 - normalizedPosition.y) * dimensions.y }; // flip y-axis + var position2D = { + x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis + }; return { entityID: null, @@ -227,8 +229,10 @@ Script.include("/~/system/libraries/controllers.js"); var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { x: normalizedPosition.x * props.dimensions.x, - y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + var position2D = { + x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis + }; return { entityID: props.id, @@ -354,7 +358,7 @@ Script.include("/~/system/libraries/controllers.js"); // translate tip forward according to constant. var TIP_OFFSET = {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0}; this.stylusTip.position = Vec3.sum(this.stylusTip.position, - Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); + Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); } // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. @@ -363,9 +367,8 @@ Script.include("/~/system/libraries/controllers.js"); var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation)); var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity); - var tipVelocity = Vec3.sum(worldControllerLinearVel, - Vec3.cross(worldControllerAngularVel, - Vec3.subtract(this.stylusTip.position, worldControllerPos))); + var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel, + Vec3.subtract(this.stylusTip.position, worldControllerPos))); this.stylusTip.velocity = tipVelocity; } else { this.stylusTip.velocity = {x: 0, y: 0, z: 0}; @@ -381,10 +384,11 @@ Script.include("/~/system/libraries/controllers.js"); name: "stylus", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", loadPriority: 10.0, - localPosition: Vec3.sum({ x: 0.0, - y: WEB_TOUCH_Y_OFFSET, - z: 0.0 }, - getGrabPointSphereOffset(this.handToController())), + localPosition: Vec3.sum({ + x: 0.0, + y: WEB_TOUCH_Y_OFFSET, + z: 0.0 + }, getGrabPointSphereOffset(this.handToController())), localRotation: Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), dimensions: { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }, solid: true, @@ -393,8 +397,8 @@ Script.include("/~/system/libraries/controllers.js"); drawInFront: false, parentID: AVATAR_SELF_ID, parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND") + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND") }; this.stylus = Overlays.addOverlay("model", stylusProperties); }; @@ -524,8 +528,8 @@ Script.include("/~/system/libraries/controllers.js"); } this.isNearStylusTarget = isNearStylusTarget(stylusTargets, EDGE_BORDER + hysteresisOffset, - TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset, - WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset); + TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset, + WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset); if (this.isNearStylusTarget) { if (!this.useFingerInsteadOfStylus) { @@ -630,7 +634,7 @@ Script.include("/~/system/libraries/controllers.js"); var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || distance2D(this.stylusTarget.position2D, - this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { + this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); this.deadspotExpired = true; } diff --git a/scripts/system/controllers/controllerModules/webEntityLaserInput.js b/scripts/system/controllers/controllerModules/webEntityLaserInput.js index e88e75684c..747e1bae44 100644 --- a/scripts/system/controllers/controllerModules/webEntityLaserInput.js +++ b/scripts/system/controllers/controllerModules/webEntityLaserInput.js @@ -5,14 +5,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*jslint bitwise: true */ +/* jslint bitwise: true */ /* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID, enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, - AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, + AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC */ @@ -20,7 +20,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { - var halfPath = { + var halfPath = { type: "line3d", color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, visible: true, @@ -75,13 +75,17 @@ Script.include("/~/system/libraries/controllers.js"); parentID: AVATAR_SELF_ID }; - var renderStates = [{name: "half", path: halfPath, end: halfEnd}, - {name: "full", path: fullPath, end: fullEnd}, - {name: "hold", path: holdPath}]; + var renderStates = [ + {name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath} + ]; - var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, - {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, - {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + var defaultRenderStates = [ + {name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath} + ]; // triggered when stylus presses a web overlay/entity @@ -143,7 +147,7 @@ Script.include("/~/system/libraries/controllers.js"); } - function sendTouchStartEventToLaserTarget(hand, laserTarget) { + function sendTouchStartEventToLaserTarget(hand, laserTarget) { var pointerEvent = { type: "Press", id: hand + 1, // 0 is reserved for hardware mouse @@ -208,16 +212,18 @@ Script.include("/~/system/libraries/controllers.js"); Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); var distance = Vec3.dot(Vec3.subtract(intersection, props.position), normal); var position = Vec3.subtract(intersection, Vec3.multiply(normal, distance)); - + // generate normalized coordinates var invRot = Quat.inverse(props.rotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); - + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. - var position2D = { x: normalizedPosition.x * props.dimensions.x, - y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + var position2D = { + x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis + }; return { entityID: props.id, @@ -258,7 +264,7 @@ Script.include("/~/system/libraries/controllers.js"); this.getOtherModule = function() { return (this.hand === RIGHT_HAND) ? leftWebEntityLaserInput : rightWebEntityLaserInput; }; - + this.parameters = makeDispatcherModuleParameters( 550, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], @@ -288,7 +294,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.processControllerTriggers = function(controllerData) { - if (controllerData.triggerClicks[this.hand]) { + if (controllerData.triggerClicks[this.hand]) { this.mode = "full"; this.laserPressingTarget = true; this.hover = false; @@ -316,7 +322,7 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressEnter = function () { sendTouchStartEventToLaserTarget(this.hand, this.laserTarget); Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - + this.touchingEnterTimer = 0; this.pressEnterLaserTarget = this.laserTarget; this.deadspotExpired = false; @@ -340,12 +346,12 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressing = function (controllerData, dt) { this.touchingEnterTimer += dt; - + if (this.laserTarget) { var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || distance2D(this.laserTarget.position2D, - this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { + this.pressEnterLaserTarget.position2D) > this.deadspotRadius) { sendTouchMoveEventToLaserTarget(this.hand, this.laserTarget); this.deadspotExpired = true; } @@ -353,19 +359,19 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressingTarget = false; } }; - + this.releaseTouchEvent = function() { if (this.pressEnterLaserTarget === null) { return; } sendTouchEndEventToLaserTarget(this.hand, this.pressEnterLaserTarget); - } + }; this.updateLaserPointer = function(controllerData) { var RADIUS = 0.005; var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; - + if (this.mode === "full") { fullEnd.dimensions = dim; LaserPointers.editRenderState(this.laserPointer, this.mode, {path: fullPath, end: fullEnd}); @@ -373,7 +379,7 @@ Script.include("/~/system/libraries/controllers.js"); halfEnd.dimensions = dim; LaserPointers.editRenderState(this.laserPointer, this.mode, {path: halfPath, end: halfEnd}); } - + LaserPointers.enableLaserPointer(this.laserPointer); LaserPointers.setRenderState(this.laserPointer, this.mode); }; @@ -386,7 +392,7 @@ Script.include("/~/system/libraries/controllers.js"); if ((intersection.type === RayPick.INTERSECTED_ENTITY && entityType === "Web")) { return true; } - return false + return false; }; this.exitModule = function() { @@ -396,7 +402,7 @@ Script.include("/~/system/libraries/controllers.js"); this.updateLaserPointer(); LaserPointers.disableLaserPointer(this.laserPointer); }; - + this.reset = function() { this.pressEnterLaserTarget = null; this.laserTarget = null; @@ -412,7 +418,7 @@ Script.include("/~/system/libraries/controllers.js"); if (this.isPointingAtWebEntity(controllerData) && !otherModule.active) { return makeRunningValues(true, [], []); } - + return makeRunningValues(false, [], []); }; @@ -433,7 +439,7 @@ Script.include("/~/system/libraries/controllers.js"); this.laserPressExit(); } this.previousLaserClickedTarget = this.laserPressingTarget; - + if (this.laserPressingTarget) { this.laserPressing(controllerData, deltaTime); } @@ -447,7 +453,7 @@ Script.include("/~/system/libraries/controllers.js"); this.cleanup = function() { LaserPointers.disableLaserPointer(this.laserPointer); LaserPointers.removeLaserPointer(this.laserPointer); - } + }; this.laserPointer = LaserPointers.createLaserPointer({ joint: (this.hand === RIGHT_HAND) ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND", @@ -458,13 +464,13 @@ Script.include("/~/system/libraries/controllers.js"); faceAvatar: true, defaultRenderStates: defaultRenderStates }); - }; + } var leftWebEntityLaserInput = new WebEntityLaserInput(LEFT_HAND); var rightWebEntityLaserInput = new WebEntityLaserInput(RIGHT_HAND); - enableDispatcherModule("LeftWebEntityLaserInput", leftWebEntityLaserInput); + enableDispatcherModule("LeftWebEntityLaserInput", leftWebEntityLaserInput); enableDispatcherModule("RightWebEntityLaserInput", rightWebEntityLaserInput); this.cleanup = function() { @@ -474,5 +480,5 @@ Script.include("/~/system/libraries/controllers.js"); disableDispatcherModule("RightWebEntityLaserInput"); }; Script.scriptEnding.connect(this.cleanup); - + }()); From 29480619047eba2a7d1e1bd4ab00b808723a077d Mon Sep 17 00:00:00 2001 From: druiz17 Date: Thu, 31 Aug 2017 22:30:30 -0700 Subject: [PATCH 40/78] code cleanup --- interface/src/Application.cpp | 1 - .../controllerModules/equipEntity.js | 2 +- .../controllerModules/nearParentGrabEntity.js | 21 ++++++++++--------- .../controllerModules/nearTrigger.js | 2 +- .../controllerModules/overlayLaserInput.js | 3 ++- scripts/system/controllers/grab.js | 8 ------- .../controllers/handControllerPointer.js | 1 - 7 files changed, 15 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 533b5a4bee..027b7efe37 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1891,7 +1891,6 @@ QString Application::getUserAgent() { void Application::toggleTabletUI(bool shouldOpen) const { auto tabletScriptingInterface = DependencyManager::get(); auto hmd = DependencyManager::get(); - qDebug() << "Application::toggleTabletUI" << shouldOpen << hmd->getShouldShowTablet(); if (!(shouldOpen && hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 5ad7fc9e16..5b73204c8e 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity + cloneEntity, DISPATCHER_PROPERTIES */ Script.include("/~/system/libraries/Xform.js"); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index f05eab348d..00fba45ae6 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, - findGroupParent, Vec3 + findGroupParent, Vec3, cloneEntity, entityIsCloneable */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -46,19 +46,20 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); } var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (props.parentJointIndex == handJointIndex) { + if (props.parentJointIndex === handJointIndex) { return true; } var controllerJointIndex = this.controllerJointIndex; - if (props.parentJointIndex == controllerJointIndex) { + if (props.parentJointIndex === controllerJointIndex) { return true; } var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - if (props.parentJointIndex == controllerCRJointIndex) { + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + + if (props.parentJointIndex === controllerCRJointIndex) { return true; } @@ -172,7 +173,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.run = function (controllerData, deltaTime) { if (this.grabbing) { - if (controllerData.triggerClicks[this.hand] == 0) { + if (controllerData.triggerClicks[this.hand] === 0) { this.endNearParentingGrabEntity(); return makeRunningValues(false, [], []); } @@ -185,7 +186,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!readiness.active) { return readiness; } - if (controllerData.triggerClicks[this.hand] == 1) { + if (controllerData.triggerClicks[this.hand] === 1) { // switch to grab var targetProps = this.getTargetProps(controllerData); var targetCloneable = entityIsCloneable(targetProps); @@ -196,9 +197,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var cloneProps = Entities.getEntityProperties(cloneID); this.grabbing = true; - this.targetEntityID = cloneID + this.targetEntityID = cloneID; this.startNearParentingGrabEntity(controllerData, cloneProps); - + } else if (targetProps) { this.grabbing = true; this.startNearParentingGrabEntity(controllerData, targetProps); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index fd24aeefa2..239778040c 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -84,7 +84,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { + if (controllerData.triggerClicks[this.hand] === 0) { this.endNearTrigger(controllerData); return makeRunningValues(false, [], []); } diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index ec2aa7750a..86623dbc72 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -10,7 +10,8 @@ Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, - DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE + DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, TRIGGER_OFF_VALUE, getEnabledModuleByName, PICK_MAX_DISTANCE, + DISPATCHER_PROPERTIES */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 50f9a5c5f0..2844940d2b 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -21,8 +21,6 @@ (function() { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/utils.js"); -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds @@ -253,12 +251,6 @@ function Grabber() { z: 0 }; - this.paramters = makeDispatcherModuleParameters( - 300, - "mouse", - [], - 100); - this.targetPosition = null; this.targetRotation = null; diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index fbbb708a92..538fe0b1e4 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -639,7 +639,6 @@ function update() { // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. if (isPointingAtOverlay(hudPoint2d) && isPointerEnabled) { - //print("--------> pointing at HUD <--------"); if (HMD.active) { Reticle.depth = hudReticleDistance(); From 3b357ad61ed22cd9400ddbe6abc925e1bae7faeb Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 1 Sep 2017 17:07:26 -0700 Subject: [PATCH 41/78] finshed handControllerGrab refactoring --- .../controllers/controllerDispatcher.js | 86 ++++++--------- .../controllers/controllerDispatcherUtils.js | 101 +++++++++--------- .../controllerModules/disableOtherModule.js | 85 +++++++++++++++ .../controllerModules/equipEntity.js | 11 +- .../controllerModules/farActionGrabEntity.js | 13 +++ .../controllerModules/inEditMode.js | 2 +- .../controllerModules/nearActionGrabEntity.js | 21 ++-- .../controllerModules/nearParentGrabEntity.js | 32 +++++- .../nearParentGrabOverlay.js | 62 +++++++---- .../controllerModules/nearTrigger.js | 2 +- .../system/controllers/controllerScripts.js | 1 + scripts/system/controllers/teleport.js | 7 +- scripts/system/libraries/cloneEntityUtils.js | 29 +++-- 13 files changed, 302 insertions(+), 150 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/disableOtherModule.js diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 9469b59a95..3826222666 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -5,11 +5,12 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*jslint bitwise: true */ +/* jslint bitwise: true */ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, - controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable, - LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES + controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, entityIsGrabbable:true, + LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, + getGrabPointSphereOffset */ controllerDispatcherPlugins = {}; @@ -21,18 +22,16 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { var _this = this; - - var NEAR_MIN_RADIUS = 0.1; var NEAR_MAX_RADIUS = 1.0; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; - var lastInterval = Date.now(); - var intervalCount = 0; - var totalDelta = 0; - var totalVariance = 0; - var highVarianceCount = 0; - var veryhighVarianceCount = 0; + this.lastInterval = Date.now(); + this.intervalCount = 0; + this.totalDelta = 0; + this.totalVariance = 0; + this.highVarianceCount = 0; + this.veryhighVarianceCount = 0; this.tabletID = null; this.blacklist = []; @@ -63,7 +62,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.unmarkSlotsForPluginName = function (runningPluginName) { // this is used to free activity-slots when a plugin is deactivated while it's running. for (var activitySlot in _this.activitySlots) { - if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] == runningPluginName) { + if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) { _this.activitySlots[activitySlot] = false; } } @@ -106,23 +105,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.updateTimings = function () { - intervalCount++; + _this.intervalCount++; var thisInterval = Date.now(); - var deltaTimeMsec = thisInterval - lastInterval; + var deltaTimeMsec = thisInterval - _this.lastInterval; var deltaTime = deltaTimeMsec / 1000; - lastInterval = thisInterval; + _this.lastInterval = thisInterval; - totalDelta += deltaTimeMsec; + _this.totalDelta += deltaTimeMsec; var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); - totalVariance += variance; + _this.totalVariance += variance; if (variance > 1) { - highVarianceCount++; + _this.highVarianceCount++; } if (variance > 5) { - veryhighVarianceCount++; + _this.veryhighVarianceCount++; } return deltaTime; @@ -132,13 +131,12 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); if (HMD.tabletID !== _this.tabletID) { RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); - tabletIgnored = true } - } + }; this.update = function () { - var deltaTime = this.updateTimings(); - this.setIgnoreTablet() + var deltaTime = this.updateTimings(); + this.setIgnoreTablet(); if (controllerDispatcherPluginsNeedSort) { this.orderedPluginNames = []; @@ -167,8 +165,10 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); controllerDispatcherPluginsNeedSort = false; } - var controllerLocations = [_this.dataGatherers.leftControllerLocation(), - _this.dataGatherers.rightControllerLocation()]; + var controllerLocations = [ + _this.dataGatherers.leftControllerLocation(), + _this.dataGatherers.rightControllerLocation() + ]; // find 3d overlays near each hand var nearbyOverlayIDs = []; @@ -221,7 +221,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); length: 1000 }; - if (rayPicks[h].type == RayPick.INTERSECTED_ENTITY) { + if (rayPicks[h].type === RayPick.INTERSECTED_ENTITY) { // XXX check to make sure this one isn't already in nearbyEntityProperties? if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) { var nearEntityID = rayPicks[h].objectID; @@ -259,7 +259,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { - //print(orderedPluginName); var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the @@ -297,7 +296,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.setBlacklist = function() { RayPick.setIgnoreEntities(_this.leftControllerRayPick, this.blacklist); RayPick.setIgnoreEntities(_this.rightControllerRayPick, this.blacklist); - + }; var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; @@ -314,19 +313,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Controller.enableMapping(MAPPING_NAME); - - this.mouseRayPick = RayPick.createRayPick({ - joint: "Mouse", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, - enabled: true - }); - - this.mouseHudRayPick = RayPick.createRayPick({ - joint: "Mouse", - filter: RayPick.PICK_HUD, - enabled: true - }); - this.leftControllerRayPick = RayPick.createRayPick({ joint: "_CONTROLLER_LEFTHAND", filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, @@ -357,7 +343,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }); this.handleHandMessage = function(channel, message, sender) { - var data + var data; if (sender === MyAvatar.sessionUUID) { try { if (channel === 'Hifi-Hand-RayPick-Blacklist') { @@ -365,33 +351,29 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var action = data.action; var id = data.id; var index = this.blacklis.indexOf(id); - + if (action === 'add' && index === -1) { this.blacklist.push(id); - //this.setBlacklist(); + this.setBlacklist(); } - + if (action === 'remove') { if (index > -1) { - blacklist.splice(index, 1); - //this.setBlacklist(); + this.blacklist.splice(index, 1); + this.setBlacklist(); } } } - + } catch (e) { print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } } }; - - - this.cleanup = function () { Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); - // RayPick.removeRayPick(_this.mouseRayPick); RayPick.removeRayPick(_this.leftControllerRayPick); RayPick.removeRayPick(_this.rightControllerRayPick); RayPick.removeRayPick(_this.rightControllerHudRayPick); diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/controllers/controllerDispatcherUtils.js index 808623fc79..773f79754d 100644 --- a/scripts/system/controllers/controllerDispatcherUtils.js +++ b/scripts/system/controllers/controllerDispatcherUtils.js @@ -6,37 +6,37 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins, Quat, Vec3, Overlays, - MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES, - HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, ZERO_VEC, ONE_VEC, DEFAULT_REGISTRATION_POINT, INCHES_TO_METERS, - TRIGGER_OFF_VALUE, - TRIGGER_ON_VALUE, - PICK_MAX_DISTANCE, - DEFAULT_SEARCH_SPHERE_DISTANCE, - NEAR_GRAB_PICK_RADIUS, - COLORS_GRAB_SEARCHING_HALF_SQUEEZE, - COLORS_GRAB_SEARCHING_FULL_SQUEEZE, - COLORS_GRAB_DISTANCE_HOLD, - NEAR_GRAB_RADIUS, - DISPATCHER_PROPERTIES, +/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, + MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, NULL_UUID:true, AVATAR_SELF_ID:true, FORBIDDEN_GRAB_TYPES:true, + HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, + TRIGGER_OFF_VALUE:true, + TRIGGER_ON_VALUE:true, + PICK_MAX_DISTANCE:true, + DEFAULT_SEARCH_SPHERE_DISTANCE:true, + NEAR_GRAB_PICK_RADIUS:true, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE:true, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE:true, + COLORS_GRAB_DISTANCE_HOLD:true, + NEAR_GRAB_RADIUS:true, + DISPATCHER_PROPERTIES:true, Entities, - makeDispatcherModuleParameters, - makeRunningValues, - enableDispatcherModule, - disableDispatcherModule, - getEnabledModuleByName, - getGrabbableData, - entityIsGrabbable, - entityIsDistanceGrabbable, - getControllerJointIndex, - propsArePhysical, - controllerDispatcherPluginsNeedSort, - projectOntoXYPlane, - projectOntoEntityXYPlane, - projectOntoOverlayXYPlane, - entityHasActions, - ensureDynamic, - findGroupParent + makeDispatcherModuleParameters:true, + makeRunningValues:true, + enableDispatcherModule:true, + disableDispatcherModule:true, + getEnabledModuleByName:true, + getGrabbableData:true, + entityIsGrabbable:true, + entityIsDistanceGrabbable:true, + getControllerJointIndex:true, + propsArePhysical:true, + controllerDispatcherPluginsNeedSort:true, + projectOntoXYPlane:true, + projectOntoEntityXYPlane:true, + projectOntoOverlayXYPlane:true, + entityHasActions:true, + ensureDynamic:true, + findGroupParent:true */ MSECS_PER_SEC = 1000.0; @@ -69,11 +69,8 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; - NEAR_GRAB_RADIUS = 0.1; - - DISPATCHER_PROPERTIES = [ "position", "registrationPoint", @@ -92,9 +89,6 @@ DISPATCHER_PROPERTIES = [ "userData" ]; - - - // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step // activitySlots -- indicates which "slots" must not yet be in use for this module to start // requiredDataForReady -- which "situation" parts this module looks at to decide if it will start @@ -211,12 +205,12 @@ getControllerJointIndex = function (hand) { var controllerJointIndex = -1; if (Camera.mode === "first person") { controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CONTROLLER_RIGHTHAND" : - "_CONTROLLER_LEFTHAND"); + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); } else if (Camera.mode === "third person") { controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } return controllerJointIndex; @@ -229,19 +223,24 @@ propsArePhysical = function (props) { if (!props.dynamic) { return false; } - var isPhysical = (props.shapeType && props.shapeType != 'none'); + var isPhysical = (props.shapeType && props.shapeType !== 'none'); return isPhysical; }; projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) { var invRot = Quat.inverse(rotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position)); - var invDimensions = { x: 1 / dimensions.x, - y: 1 / dimensions.y, - z: 1 / dimensions.z }; + var invDimensions = { + x: 1 / dimensions.x, + y: 1 / dimensions.y, + z: 1 / dimensions.z + }; + var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint); - return { x: normalizedPos.x * dimensions.x, - y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis + return { + x: normalizedPos.x * dimensions.x, + y: (1 - normalizedPos.y) * dimensions.y // flip y-axis + }; }; projectOntoEntityXYPlane = function (entityID, worldPos, props) { @@ -257,14 +256,14 @@ projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldP if (dpi) { // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. var resolution = Overlays.getProperty(overlayID, "resolution"); - resolution.z = 1; // Circumvent divide-by-zero. + resolution.z = 1; // Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. + scale.z = 0.01; // overlay dimensions are 2D, not 3D. dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); if (dimensions.z) { - dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. } } @@ -279,7 +278,7 @@ ensureDynamic = function (entityID) { // if we distance hold something and keep it very still before releasing it, it ends up // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. var props = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); - if (props.dynamic && props.parentID == NULL_UUID) { + if (props.dynamic && props.parentID === NULL_UUID) { var velocity = props.velocity; if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD velocity = { x: 0.0, y: 0.2, z: 0.0 }; @@ -289,7 +288,7 @@ ensureDynamic = function (entityID) { }; findGroupParent = function (controllerData, targetProps) { - while (targetProps.parentID && targetProps.parentID != NULL_UUID) { + while (targetProps.parentID && targetProps.parentID !== NULL_UUID) { // XXX use controllerData.nearbyEntityPropertiesByID ? var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); if (!parentProps) { diff --git a/scripts/system/controllers/controllerModules/disableOtherModule.js b/scripts/system/controllers/controllerModules/disableOtherModule.js new file mode 100644 index 0000000000..65732dd08d --- /dev/null +++ b/scripts/system/controllers/controllerModules/disableOtherModule.js @@ -0,0 +1,85 @@ +"use strict"; + +// nearTrigger.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + enableDispatcherModule, disableDispatcherModule, getGrabbableData, Vec3, + TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS, + getEnabledModuleByName +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); + +(function() { + function DisableModules(hand) { + this.hand = hand; + this.disableModules = false; + this.parameters = makeDispatcherModuleParameters( + 90, + this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], + 100); + + this.isReady = function(controllerData) { + if (this.disableModules) { + return makeRunningValues(true, [], []); + } + return false; + }; + + this.run = function(controllerData) { + var teleportModuleName = this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter"; + var teleportModule = getEnabledModuleByName(teleportModuleName); + + if (teleportModule) { + var ready = teleportModule.isReady(controllerData); + if (ready) { + return makeRunningValues(false, [], []); + } + } + if (!this.disablemodules) { + return makeRunningValues(false, [], []); + } + return makeRunningValues(true, [], []); + }; + } + + var leftDisableModules = new DisableModules(LEFT_HAND); + var rightDisableModules = new DisableModules(RIGHT_HAND); + + enableDispatcherModule("LeftDisableModules", leftDisableModules); + enableDispatcherModule("RightDisableModules", rightDisableModules); + this.handleMessage = function(channel, message, sender) { + if (sender === MyAvatar.sessionUUID) { + if (channel === 'Hifi-Hand-Disabler') { + if (message === 'left') { + leftDisableModules.disableModules = true; + } + if (message === 'right') { + rightDisableModules.disableModules = true; + + } + if (message === 'both' || message === 'none') { + if (message === 'both') { + leftDisableModules.disableModules = true; + rightDisableModules.disableModules = true; + } else if (message === 'none') { + leftDisableModules.disableModules = false; + rightDisableModules.disableModules = false; + } + } + } + } + }; + + Messages.subscribe('Hifi-Hand-Disabler'); + this.cleanup = function() { + disableDispatcherModule("LeftDisableModules"); + disableDispatcherModule("RightDisableModules"); + }; + Messages.messageReceived.connect(this.handleMessage); + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 5b73204c8e..a9e62de4df 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -187,6 +187,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa wearable = props.userDataParsed.wearable ? props.userDataParsed.wearable : {}; } catch (err) { + // don't want to spam the logs } return wearable; } @@ -199,6 +200,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa equipHotspots = props.userDataParsed.equipHotspots ? props.userDataParsed.equipHotspots : []; } catch (err) { + // don't want to spam the logs } return equipHotspots; } @@ -255,7 +257,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.parameters = makeDispatcherModuleParameters( 300, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip"] : ["leftHand", "rightHandEquip"], [], 100); @@ -556,8 +558,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var controllerLocation = getControllerWorldLocation(this.handToController(), true); var worldHandPosition = controllerLocation.position; var candidateEntityProps = controllerData.nearbyEntityProperties[this.hand]; - - + + var potentialEquipHotspot = null; if (this.messageGrabEntity) { var hotspots = this.collectEquipHotspots(this.grabEntityProps); @@ -567,7 +569,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } else { potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntityProps, controllerData); } - + if (!this.waitForTriggerRelease) { this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition); } @@ -675,7 +677,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var handleMessage = function(channel, message, sender) { var data; - print(channel); if (sender === MyAvatar.sessionUUID) { if (channel === 'Hifi-Hand-Grab') { try { diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 2b748e60d6..7883222c73 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -527,6 +527,19 @@ Script.include("/~/system/libraries/controllers.js"); this.distanceRotate(otherFarGrabModule); } } + return this.exitIfDisabled(); + }; + + this.exitIfDisabled = function() { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.laserPointerOff(); + this.endNearGrabAction(); + return makeRunningValues(false, [], []); + } + } return makeRunningValues(true, [], []); }; diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index fc7e0b526e..c6ac0c01bc 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -93,7 +93,7 @@ Script.include("/~/system/libraries/utils.js"); this.parameters = makeDispatcherModuleParameters( 160, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], 100); this.nearTablet = function(overlays) { diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index a2673f8bca..2b3a2405fb 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -9,11 +9,12 @@ getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, - TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent + TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/cloneEntityUtils.js"); (function() { @@ -172,7 +173,7 @@ Script.include("/~/system/libraries/controllers.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (!propsArePhysical(targetProps)) { + if (!propsArePhysical(targetProps) && !propsAreCloneDynamic) { return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it } else { this.targetEntityID = targetProps.id; @@ -185,13 +186,12 @@ Script.include("/~/system/libraries/controllers.js"); this.run = function (controllerData) { if (this.actionID) { - if (controllerData.triggerClicks[this.hand] == 0) { + if (controllerData.triggerClicks[this.hand] === 0) { this.endNearGrabAction(); return makeRunningValues(false, [], []); } this.refreshNearGrabAction(controllerData); - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); } else { @@ -204,9 +204,18 @@ Script.include("/~/system/libraries/controllers.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (controllerData.triggerClicks[this.hand] == 1) { + if (controllerData.triggerClicks[this.hand] === 1) { // switch to grabbing - this.startNearGrabAction(controllerData, targetProps); + var targetCloneable = entityIsCloneable(targetProps); + if (targetCloneable) { + var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; + var cloneID = cloneEntity(targetProps, worldEntityProps); + var cloneProps = Entities.getEntityProperties(cloneID); + this.targetEntityID = cloneID; + this.startNearGrabAction(controllerData, cloneProps); + } else { + this.startNearGrabAction(controllerData, targetProps); + } } } } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 00fba45ae6..4a59fd727f 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, - findGroupParent, Vec3, cloneEntity, entityIsCloneable + findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -40,6 +40,14 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); this.controllerJointIndex = getControllerJointIndex(this.hand); + this.getOtherModule = function() { + return (this.hand === RIGHT_HAND) ? leftNearParentingGrabEntity : rightNearParentingGrabEntity; + }; + + this.otherHandIsParent = function(props) { + return this.getOtherModule().thisHandIsParent(props); + }; + this.thisHandIsParent = function(props) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { return false; @@ -67,7 +75,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); }; this.startNearParentingGrabEntity = function (controllerData, targetProps) { - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); var handJointIndex; @@ -92,10 +99,18 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); // this should never happen, but if it does, don't set previous parent to be this hand. // this.previousParentID[targetProps.id] = NULL; // this.previousParentJointIndex[targetProps.id] = -1; + } else if (this.otherHandIsParent(targetProps)) { + // the other hand is parent. Steal the object and information + var otherModule = this.getOtherModule(); + this.previousParentID[targetProps.id] = otherModule.previousParentID[targetProps.id]; + this.previousParentJointIndex[targetProps.id] = otherModule.previousParentJointIndex[targetProps.id]; + otherModule.endNearParentingGrabEntity(); } else { this.previousParentID[targetProps.id] = targetProps.parentID; this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex; } + + this.targetEntityID = targetProps.id; Entities.editEntity(targetProps.id, reparentProps); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ @@ -103,10 +118,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); grabbedEntity: targetProps.id, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); + this.grabbing = true; }; this.endNearParentingGrabEntity = function () { - if (this.previousParentID[this.targetEntityID] === NULL_UUID) { + if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) { Entities.editEntity(this.targetEntityID, { parentID: this.previousParentID[this.targetEntityID], parentJointIndex: this.previousParentJointIndex[this.targetEntityID] @@ -123,7 +139,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - this.grabbing = false; this.targetEntityID = null; }; @@ -160,7 +175,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (propsArePhysical(targetProps)) { + if (propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) { return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it } else { this.targetEntityID = targetProps.id; @@ -178,6 +193,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); return makeRunningValues(false, [], []); } + var props = Entities.getEntityProperties(this.targetEntityID); + if (!this.thisHandIsParent(props)) { + this.grabbing = false; + this.targetEntityID = null; + return makeRunningValues(false, [], []); + } + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); } else { diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index f0a5b9f07d..46ed8df271 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -38,31 +38,52 @@ var GRAB_RADIUS = 0.35; this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); this.controllerJointIndex = getControllerJointIndex(this.hand); + this.getOtherModule = function() { + return (this.hand === RIGHT_HAND) ? leftNearParentingGrabOverlay : rightNearParentingGrabOverlay; + }; + + this.otherHandIsParent = function(props) { + return this.getOtherModule().thisHandIsParent(props); + }; + this.thisHandIsParent = function(props) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { return false; } var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (props.parentJointIndex == handJointIndex) { + if (props.parentJointIndex === handJointIndex) { return true; } var controllerJointIndex = this.controllerJointIndex; - if (props.parentJointIndex == controllerJointIndex) { + if (props.parentJointIndex === controllerJointIndex) { return true; } var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - if (props.parentJointIndex == controllerCRJointIndex) { + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + + if (props.parentJointIndex === controllerCRJointIndex) { return true; } return false; }; + this.getGrabbedProperties = function() { + return { + position: Overlays.getProperty(this.grabbedThingID, "position"), + rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), + parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), + parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), + dynamic: false, + shapeType: "none" + }; + }; + + this.startNearParentingGrabOverlay = function (controllerData) { Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); @@ -74,15 +95,8 @@ var GRAB_RADIUS = 0.35; // } handJointIndex = this.controllerJointIndex; - var grabbedProperties = { - position: Overlays.getProperty(this.grabbedThingID, "position"), - rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), - parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), - parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), - dynamic: false, - shapeType: "none" - }; - + var grabbedProperties = this.getGrabbedProperties(); + var reparentProps = { parentID: AVATAR_SELF_ID, parentJointIndex: handJointIndex, @@ -94,6 +108,12 @@ var GRAB_RADIUS = 0.35; // this should never happen, but if it does, don't set previous parent to be this hand. // this.previousParentID[this.grabbedThingID] = NULL; // this.previousParentJointIndex[this.grabbedThingID] = -1; + } else if (this.otherHandIsParent(grabbedProperties)) { + // the other hand is parent. Steal the object and information + var otherModule = this.getOtherModule(); + this.previousParentID[this.grabbedThingID] = otherModule.previousParentID[this.garbbedThingID]; + this.previousParentJointIndex[this.grabbedThingID] = otherModule.previousParentJointIndex[this.grabbedThingID]; + } else { this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID; this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; @@ -117,7 +137,7 @@ var GRAB_RADIUS = 0.35; // before we grabbed it, overlay was a child of something; put it back. Overlays.editOverlay(this.grabbedThingID, { parentID: this.previousParentID[this.grabbedThingID], - parentJointIndex: this.previousParentJointIndex[this.grabbedThingID], + parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] }); } @@ -135,10 +155,10 @@ var GRAB_RADIUS = 0.35; } return null; }; - + this.isReady = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { + if (controllerData.triggerClicks[this.hand] === 0) { return makeRunningValues(false, [], []); } @@ -160,10 +180,16 @@ var GRAB_RADIUS = 0.35; }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] == 0) { + if (controllerData.triggerClicks[this.hand] === 0) { this.endNearParentingGrabOverlay(); return makeRunningValues(false, [], []); } else { + // check if someone stole the target from us + var grabbedProperties = this.getGrabbedProperties(); + if (!this.thisHandIsParent(grabbedProperties)) { + return makeRunningValues(false, [], []); + } + return makeRunningValues(true, [this.grabbedThingID], []); } }; diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 239778040c..4d31a8a22b 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -30,7 +30,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.parameters = makeDispatcherModuleParameters( 520, - this.hand === RIGHT_HAND ? ["rightHandTrigger"] : ["leftHandTrigger"], + this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index cd572b901a..60b820f06b 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -26,6 +26,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/overlayLaserInput.js", "controllerModules/webEntityLaserInput.js", "controllerModules/inEditMode.js", + "controllerModules/disableOtherModule.js", "teleport.js" ]; diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 6246c0f03a..17ac36d955 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -14,6 +14,7 @@ Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ Script.include("/~/system/libraries/Xform.js"); Script.include("/~/system/controllers/controllerDispatcherUtils.js"); @@ -225,7 +226,7 @@ function Teleporter(hand) { } this.parameters = makeDispatcherModuleParameters( - 300, + 80, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -338,12 +339,12 @@ function Teleporter(hand) { } }; } - + // related to repositioning the avatar after you teleport var FOOT_JOINT_NAMES = ["RightToe_End", "RightToeBase", "RightFoot"]; var DEFAULT_ROOT_TO_FOOT_OFFSET = 0.5; function getAvatarFootOffset() { - + // find a valid foot jointIndex var footJointIndex = -1; var i, l = FOOT_JOINT_NAMES.length; diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index 69c91fc398..a8e8bc227d 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -5,11 +5,13 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global entityIsCloneable:true, getGrabbableData:true, cloneEntity:true propsAreCloneDynamic:true */ + Script.include("/~/system/controllers/controllerDispatcherUtils.js"); // Object assign polyfill -if (typeof Object.assign != 'function') { +if (typeof Object.assign !== 'function') { Object.assign = function(target, varArgs) { if (target === null) { throw new TypeError('Cannot convert undefined or null to object'); @@ -36,7 +38,18 @@ entityIsCloneable = function(props) { } return false; -} +}; + +propsAreCloneDynamic = function(props) { + var cloneable = entityIsCloneable(props); + if (cloneable) { + var grabInfo = getGrabbableData(props); + if (grabInfo.cloneDynamic) { + return true; + } + } + return false; +}; cloneEntity = function(props, worldEntityProps) { @@ -49,26 +62,26 @@ cloneEntity = function(props, worldEntityProps) { count++; } }); - + var grabInfo = getGrabbableData(cloneableProps); var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; if (count >= limit && limit !== 0) { return null; } - + cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData)); var cProperties = Object.assign({}, cloneableProps); - + delete cUserData.grabbableKey.cloneLifetime; delete cUserData.grabbableKey.cloneable; delete cUserData.grabbableKey.cloneDynamic; delete cUserData.grabbableKey.cloneLimit; delete cProperties.id; - + cProperties.dynamic = dynamic; cProperties.locked = false; @@ -76,7 +89,7 @@ cloneEntity = function(props, worldEntityProps) { cUserData.grabbableKey.grabbable = true; cProperties.lifetime = lifetime; cProperties.userData = JSON.stringify(cUserData); - + var cloneID = Entities.addEntity(cProperties); return cloneID; -} +}; From 03e076046bb08f6fb6a7c9b9ad8b90b9abe5aca1 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 4 Sep 2017 22:16:02 +0300 Subject: [PATCH 42/78] Added Joint Name check for translations This way if there is a mismatch in the skeleton, the translations are applied to the models correctly --- .../src/RenderableModelEntityItem.cpp | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 22b2cffbcb..01885e014e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -909,8 +909,15 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy - auto& fbxJoints = _animation->getGeometry().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& animationGeometry = _animation->getGeometry(); + auto& animationJointNames = animationGeometry.getJointNames(); + auto& fbxJoints = animationGeometry.joints; + auto& _debugFbxJoints = animationGeometry.jointIndices; + + auto& originalFbx = _model->getFBXGeometry(); + auto& originalFbxJoints = originalFbx.joints; + auto& originalFbxIndices = originalFbx.jointIndices; + bool allowTranslation = entity->getAnimationAllowTranslation(); int frameCount = frames.size(); if (frameCount <= 0) { @@ -952,13 +959,22 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { jointsData.resize(_jointMapping.size()); for (int j = 0; j < _jointMapping.size(); j++) { int index = _jointMapping[j]; + if (index >= 0) { glm::mat4 translationMat; - if (!allowTranslation){ - translationMat = glm::translate(originalFbxJoints[index].translation); - } else if (index < translations.size()) { + + if (allowTranslation && index < translations.size()) { translationMat = glm::translate(translations[index]); - } + } else if (!allowTranslation && index < animationJointNames.size()){ + QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation + + + if (originalFbxIndices.contains(jointName)) { + // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. + int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + } + } glm::mat4 rotationMat; if (index < rotations.size()) { rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); @@ -975,7 +991,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { jointData.rotationSet = true; } } - // Set the data in the entity entity->setAnimationJointsData(jointsData); From 119a2703f79d09aeb1e8c7ccacc1ea500cdbc5b5 Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 4 Sep 2017 22:20:00 +0300 Subject: [PATCH 43/78] Removed Debug Joints that wasnt used anylonger --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 01885e014e..a08621f827 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -912,7 +912,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { auto& animationGeometry = _animation->getGeometry(); auto& animationJointNames = animationGeometry.getJointNames(); auto& fbxJoints = animationGeometry.joints; - auto& _debugFbxJoints = animationGeometry.jointIndices; auto& originalFbx = _model->getFBXGeometry(); auto& originalFbxJoints = originalFbx.joints; @@ -967,8 +966,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { translationMat = glm::translate(translations[index]); } else if (!allowTranslation && index < animationJointNames.size()){ QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation - - + if (originalFbxIndices.contains(jointName)) { // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. From 856452405e1145847a4dfdd3f23b286288602fe9 Mon Sep 17 00:00:00 2001 From: Menithal Date: Tue, 5 Sep 2017 08:46:37 +0300 Subject: [PATCH 44/78] Made Code abit more concise for better compilation --- .../src/RenderableModelEntityItem.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a08621f827..cc9dcf8ac5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -909,13 +909,11 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy - auto& animationGeometry = _animation->getGeometry(); - auto& animationJointNames = animationGeometry.getJointNames(); - auto& fbxJoints = animationGeometry.joints; + QStringList animationJointNames = _animation->getGeometry().getJointNames(); + auto& fbxJoints = _animation->getGeometry().joints; - auto& originalFbx = _model->getFBXGeometry(); - auto& originalFbxJoints = originalFbx.joints; - auto& originalFbxIndices = originalFbx.jointIndices; + auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; bool allowTranslation = entity->getAnimationAllowTranslation(); int frameCount = frames.size(); From 28694dcf8a6e76918d1f1130d61abc28c2576d73 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Sep 2017 11:46:06 -0700 Subject: [PATCH 45/78] small change to contextOVerlays in farActionGrab --- .../controllers/controllerModules/farActionGrabEntity.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 7883222c73..6e265742a6 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -434,11 +434,6 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(false, [], []); } - var targetEntity = controllerData.rayPicks[this.hand].objectID; - if (targetEntity !== this.entityWithContextOverlay) { - this.destroyContextOverlay(); - } - var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; var otherFarGrabModule = getEnabledModuleByName(otherModuleName); From cefa65de5e5069020d4b27fad840c8a6b1830536 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Sep 2017 12:06:32 -0700 Subject: [PATCH 46/78] some more small changes --- .../controllers/controllerModules/farActionGrabEntity.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 6e265742a6..822ad55f88 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -483,6 +483,10 @@ Script.include("/~/system/libraries/controllers.js"); "userData", "locked", "type" ]); + if (entityID !== this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } + if (entityIsDistanceGrabbable(targetProps)) { if (!this.distanceRotating) { this.grabbedThingID = entityID; From 0d66bfca0570aa480d4e891ea78e3a2de3c5f630 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Wed, 6 Sep 2017 14:58:04 -0700 Subject: [PATCH 47/78] added far trigger --- .../controllerModules/equipEntity.js | 21 +- .../controllerModules/farActionGrabEntity.js | 1 + .../controllerModules/farTrigger.js | 234 ++++++++++++++++++ .../system/controllers/controllerScripts.js | 1 + 4 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/farTrigger.js diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index a9e62de4df..211989d661 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -257,7 +257,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.parameters = makeDispatcherModuleParameters( 300, - this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip"] : ["leftHand", "rightHandEquip"], + this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip"] : ["leftHand", "leftHandEquip"], [], 100); @@ -539,6 +539,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa ensureDynamic(this.targetEntityID); this.targetEntityID = null; + this.messageGrabEntity = false; + this.grabEntityProps = null; }; this.updateInputs = function (controllerData) { @@ -594,11 +596,18 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } return makeRunningValues(true, [potentialEquipHotspot.entityID], []); } else { - this.messageGrabEnity = false; return makeRunningValues(false, [], []); } }; + this.isTargetIDValid = function() { + var entityProperties = Entities.getEntityProperties(this.targetEntityID); + for (var propertry in entityProperties) { + return true; + } + return false; + }; + this.isReady = function (controllerData, deltaTime) { var timestamp = Date.now(); this.updateInputs(controllerData); @@ -609,6 +618,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); + if (!this.isTargetIDValid()) { + this.endEquipEntity(); + return makeRunningValues(false, [], []); + } + if (!this.targetEntityID) { return this.checkNearbyHotspots(controllerData, deltaTime, timestamp); } @@ -681,7 +695,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (channel === 'Hifi-Hand-Grab') { try { data = JSON.parse(message); - var equipModule = (data.hand === 'left') ? leftEquipEntity : rightEquipEntity; + print(JSON.stringify(data)); + var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; equipModule.setMessageGrabData(entityProperties); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 822ad55f88..44360c2e39 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -439,6 +439,7 @@ Script.include("/~/system/libraries/controllers.js"); // gather up the readiness of the near-grab modules var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" diff --git a/scripts/system/controllers/controllerModules/farTrigger.js b/scripts/system/controllers/controllerModules/farTrigger.js new file mode 100644 index 0000000000..671fc58c7d --- /dev/null +++ b/scripts/system/controllers/controllerModules/farTrigger.js @@ -0,0 +1,234 @@ +"use strict"; + +// farTrigger.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Controller, LaserPointers, RayPick, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, + getGrabPointSphereOffset, getEnabledModuleByName, makeRunningValues, Entities, NULL_UUID, + enableDispatcherModule, disableDispatcherModule, entityIsDistanceGrabbable, + makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, + PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, + AVATAR_SELF_ID, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, + getControllerWorldLocation, projectOntoEntityXYPlane, getGrabbableData + +*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [ + {name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath} + ]; + + var defaultRenderStates = [ + {name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath} + ]; + + function entityWantsNearTrigger(props) { + var grabbableData = getGrabbableData(props); + return grabbableData.triggerable || grabbableData.wantsTrigger; + } + + function FarTriggerEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.grabbing = false; + this.previousParentID = {}; + this.previousParentJointIndex = {}; + this.previouslyUnhooked = {}; + + this.parameters = makeDispatcherModuleParameters( + 520, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.updateLaserPointer = function(controllerData) { + var SEARCH_SPHERE_SIZE = 0.011; + var MIN_SPHERE_SIZE = 0.0005; + var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE); + var dim = {x: radius, y: radius, z: radius}; + var mode = "none"; + if (controllerData.triggerClicks[this.hand]) { + mode = "full"; + } else { + mode = "half"; + } + + var laserPointerID = this.laserPointer; + if (mode === "full") { + var fullEndToEdit = this.fullEnd; + fullEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: fullPath, end: fullEndToEdit}); + } else if (mode === "half") { + var halfEndToEdit = this.halfEnd; + halfEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit}); + } + LaserPointers.enableLaserPointer(laserPointerID); + LaserPointers.setRenderState(laserPointerID, mode); + }; + + this.laserPointerOff = function() { + LaserPointers.disableLaserPointer(this.laserPointer); + }; + + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by length from controller + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity) { + var targetProperties = Entities.getEntityProperties(targetEntity); + if (entityWantsNearTrigger(targetProperties)) { + return targetProperties; + } + } + return null; + }; + + this.startFarTrigger = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "startFarTrigger", args); + }; + + this.continueFarTrigger = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueFarTrigger", args); + }; + + this.endFarTrigger = function (controllerData) { + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "stopFarTrigger", args); + this.laserPointerOff(); + }; + + this.isReady = function (controllerData) { + this.targetEntityID = null; + if (controllerData.triggerClicks[this.hand] === 0) { + return makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.targetEntityID = targetProps.id; + this.startFarTrigger(controllerData); + return makeRunningValues(true, [this.targetEntityID], []); + } else { + return makeRunningValues(false, [], []); + } + }; + + this.run = function (controllerData) { + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (controllerData.triggerClicks[this.hand] === 0 || this.targetEntityID !== targetEntity) { + this.endFarTrigger(controllerData); + return makeRunningValues(false, [], []); + } + + this.updateLaserPointer(controllerData); + this.continueFarTrigger(controllerData); + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + + this.cleanup = function () { + if (this.targetEntityID) { + this.endFarTrigger(); + } + + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + }; + } + + var leftFarTriggerEntity = new FarTriggerEntity(LEFT_HAND); + var rightFarTriggerEntity = new FarTriggerEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarTriggerEntity", leftFarTriggerEntity); + enableDispatcherModule("RightFarTriggerEntity", rightFarTriggerEntity); + + this.cleanup = function () { + leftFarTriggerEntity.cleanup(); + rightFarTriggerEntity.cleanup(); + disableDispatcherModule("LeftFarTriggerEntity"); + disableDispatcherModule("RightFarTriggerEntity"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 60b820f06b..569fad4855 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -27,6 +27,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/webEntityLaserInput.js", "controllerModules/inEditMode.js", "controllerModules/disableOtherModule.js", + "controllerModules/farTrigger.js", "teleport.js" ]; From cc7a22237454c760ccd1ddb87f4b54414c13979a Mon Sep 17 00:00:00 2001 From: druiz17 Date: Wed, 6 Sep 2017 17:15:05 -0700 Subject: [PATCH 48/78] fixing Hifi-Hand-Drop for equipEntity --- .../controllerModules/equipEntity.js | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 211989d661..40e0e19cf3 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -492,6 +492,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa localPosition: this.offsetPosition, localRotation: this.offsetRotation }; + var isClone = false; if (entityIsCloneable(grabbedProperties)) { var cloneID = this.cloneHotspot(grabbedProperties, controllerData); @@ -622,7 +623,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.endEquipEntity(); return makeRunningValues(false, [], []); } - + if (!this.targetEntityID) { return this.checkNearbyHotspots(controllerData, deltaTime, timestamp); } @@ -695,7 +696,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (channel === 'Hifi-Hand-Grab') { try { data = JSON.parse(message); - print(JSON.stringify(data)); var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; @@ -704,19 +704,17 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); } - } - } else if (channel === 'Hifi-Hand-Drop') { - data = JSON.parse(message); - if (data.hand === 'left') { - leftEquipEntity.endEquipEntity(); - } else if (data.hand === 'right') { - rightEquipEntity.endEquipEntity(); - } else if (data.hand === 'both') { - leftEquipEntity.endEquipEntity(); - rightEquipEntity.endEquipEntity(); + } else if (channel === 'Hifi-Hand-Drop') { + if (message === "left") { + leftEquipEntity.endEquipEntity(); + } else if (message === "right") { + rightEquipEntity.endEquipEntity(); + } else if (message === "both") { + leftEquipEntity.endEquipEntity(); + rightEquipEntity.endEquipEntity(); + } } } - }; Messages.subscribe('Hifi-Hand-Grab'); From ef050a09f9caf64daef0263cf54e90308d5b5587 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Thu, 7 Sep 2017 14:42:46 -0700 Subject: [PATCH 49/78] made requested changes and also allow entities/overlays to be grabbed by the grip button --- .../controllers/controllerDispatcher.js | 682 +++++++++--------- .../controllerModules/disableOtherModule.js | 3 +- .../controllerModules/equipEntity.js | 2 +- .../controllerModules/farActionGrabEntity.js | 2 +- .../controllerModules/farTrigger.js | 2 +- .../controllerModules/inEditMode.js | 3 +- .../controllerModules/nearActionGrabEntity.js | 2 +- .../controllerModules/nearParentGrabEntity.js | 8 +- .../nearParentGrabOverlay.js | 6 +- .../controllerModules/nearTrigger.js | 2 +- .../controllerModules/overlayLaserInput.js | 2 +- .../controllerModules/tabletStylusInput.js | 2 +- .../{ => controllerModules}/teleport.js | 8 +- .../controllerModules/webEntityLaserInput.js | 2 +- .../system/controllers/controllerScripts.js | 2 +- .../controllerDispatcherUtils.js | 0 16 files changed, 371 insertions(+), 357 deletions(-) rename scripts/system/controllers/{ => controllerModules}/teleport.js (98%) rename scripts/system/{controllers => libraries}/controllerDispatcherUtils.js (100%) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 3826222666..43ad2ccf51 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -21,366 +21,378 @@ Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/controllers/controllerDispatcherUtils.js"); (function() { - var _this = this; var NEAR_MAX_RADIUS = 1.0; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; - this.lastInterval = Date.now(); - this.intervalCount = 0; - this.totalDelta = 0; - this.totalVariance = 0; - this.highVarianceCount = 0; - this.veryhighVarianceCount = 0; - this.tabletID = null; - this.blacklist = []; - // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are - // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name - // is stored as the value, rather than false. - this.activitySlots = { - leftHand: false, - rightHand: false, - mouse: false - }; + function ControllerDispatcher() { + var _this = this + this.lastInterval = Date.now(); + this.intervalCount = 0; + this.totalDelta = 0; + this.totalVariance = 0; + this.highVarianceCount = 0; + this.veryhighVarianceCount = 0; + this.tabletID = null; + this.blacklist = []; - this.slotsAreAvailableForPlugin = function (plugin) { - for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { - if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { - return false; // something is already using a slot which _this plugin requires - } - } - return true; - }; - - this.markSlots = function (plugin, used) { - for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { - _this.activitySlots[plugin.parameters.activitySlots[i]] = used; - } - }; - - this.unmarkSlotsForPluginName = function (runningPluginName) { - // this is used to free activity-slots when a plugin is deactivated while it's running. - for (var activitySlot in _this.activitySlots) { - if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) { - _this.activitySlots[activitySlot] = false; - } - } - }; - - this.runningPluginNames = {}; - this.leftTriggerValue = 0; - this.leftTriggerClicked = 0; - this.rightTriggerValue = 0; - this.rightTriggerClicked = 0; - this.leftSecondaryValue = 0; - this.rightSecondaryValue = 0; - - this.leftTriggerPress = function (value) { - _this.leftTriggerValue = value; - }; - this.leftTriggerClick = function (value) { - _this.leftTriggerClicked = value; - }; - this.rightTriggerPress = function (value) { - _this.rightTriggerValue = value; - }; - this.rightTriggerClick = function (value) { - _this.rightTriggerClicked = value; - }; - this.leftSecondaryPress = function (value) { - _this.leftSecondaryValue = value; - }; - this.rightSecondaryPress = function (value) { - _this.rightSecondaryValue = value; - }; - - - this.dataGatherers = {}; - this.dataGatherers.leftControllerLocation = function () { - return getControllerWorldLocation(Controller.Standard.LeftHand, true); - }; - this.dataGatherers.rightControllerLocation = function () { - return getControllerWorldLocation(Controller.Standard.RightHand, true); - }; - - this.updateTimings = function () { - _this.intervalCount++; - var thisInterval = Date.now(); - var deltaTimeMsec = thisInterval - _this.lastInterval; - var deltaTime = deltaTimeMsec / 1000; - _this.lastInterval = thisInterval; - - _this.totalDelta += deltaTimeMsec; - - var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); - _this.totalVariance += variance; - - if (variance > 1) { - _this.highVarianceCount++; - } - - if (variance > 5) { - _this.veryhighVarianceCount++; - } - - return deltaTime; - }; - - this.setIgnoreTablet = function() { - if (HMD.tabletID !== _this.tabletID) { - RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); - RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); - } - }; - - this.update = function () { - var deltaTime = this.updateTimings(); - this.setIgnoreTablet(); - - if (controllerDispatcherPluginsNeedSort) { - this.orderedPluginNames = []; - for (var pluginName in controllerDispatcherPlugins) { - if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { - this.orderedPluginNames.push(pluginName); - } - } - this.orderedPluginNames.sort(function (a, b) { - return controllerDispatcherPlugins[a].parameters.priority - - controllerDispatcherPlugins[b].parameters.priority; - }); - - // print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames)); - var output = "controllerDispatcher -- new plugin order: "; - for (var k = 0; k < this.orderedPluginNames.length; k++) { - var dbgPluginName = this.orderedPluginNames[k]; - var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority; - output += dbgPluginName + ":" + priority; - if (k + 1 < this.orderedPluginNames.length) { - output += ", "; - } - } - print(output); - - controllerDispatcherPluginsNeedSort = false; - } - - var controllerLocations = [ - _this.dataGatherers.leftControllerLocation(), - _this.dataGatherers.rightControllerLocation() - ]; - - // find 3d overlays near each hand - var nearbyOverlayIDs = []; - var h; - for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { - // todo: check controllerLocations[h].valid - var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS); - nearbyOverlays.sort(function (a, b) { - var aPosition = Overlays.getProperty(a, "position"); - var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); - var bPosition = Overlays.getProperty(b, "position"); - var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); - return aDistance - bDistance; - }); - nearbyOverlayIDs.push(nearbyOverlays); - } - - // find entities near each hand - var nearbyEntityProperties = [[], []]; - var nearbyEntityPropertiesByID = {}; - for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { - // todo: check controllerLocations[h].valid - var controllerPosition = controllerLocations[h].position; - var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MAX_RADIUS); - for (var j = 0; j < nearbyEntityIDs.length; j++) { - var entityID = nearbyEntityIDs[j]; - var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - props.id = entityID; - nearbyEntityPropertiesByID[entityID] = props; - nearbyEntityProperties[h].push(props); - } - } - - // raypick for each controller - var rayPicks = [ - RayPick.getPrevRayPickResult(_this.leftControllerRayPick), - RayPick.getPrevRayPickResult(_this.rightControllerRayPick) - ]; - var hudRayPicks = [ - RayPick.getPrevRayPickResult(_this.leftControllerHudRayPick), - RayPick.getPrevRayPickResult(_this.rightControllerHudRayPick) - ]; - // if the pickray hit something very nearby, put it into the nearby entities list - for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { - - // XXX find a way to extract searchRay from samuel's stuff - rayPicks[h].searchRay = { - origin: controllerLocations[h].position, - direction: Quat.getUp(controllerLocations[h].orientation), - length: 1000 - }; - - if (rayPicks[h].type === RayPick.INTERSECTED_ENTITY) { - // XXX check to make sure this one isn't already in nearbyEntityProperties? - if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) { - var nearEntityID = rayPicks[h].objectID; - var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); - nearbyProps.id = nearEntityID; - nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; - nearbyEntityProperties[h].push(nearbyProps); - } - } - - // sort by distance from each hand - nearbyEntityProperties[h].sort(function (a, b) { - var aDistance = Vec3.distance(a.position, controllerLocations[h].position); - var bDistance = Vec3.distance(b.position, controllerLocations[h].position); - return aDistance - bDistance; - }); - } - - // bundle up all the data about the current situation - var controllerData = { - triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], - triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], - secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], - controllerLocations: controllerLocations, - nearbyEntityProperties: nearbyEntityProperties, - nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, - nearbyOverlayIDs: nearbyOverlayIDs, - rayPicks: rayPicks, - hudRayPicks: hudRayPicks + // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are + // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name + // is stored as the value, rather than false. + this.activitySlots = { + leftHand: false, + rightHand: false, + rightHandTrigger: false, + leftHandTrigger: false, + rightHandEquip: false, + leftHandEquip: false, + mouse: false }; - // check for plugins that would like to start. ask in order of increasing priority value - for (var pluginIndex = 0; pluginIndex < this.orderedPluginNames.length; pluginIndex++) { - var orderedPluginName = this.orderedPluginNames[pluginIndex]; - var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; - - if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { - var readiness = candidatePlugin.isReady(controllerData, deltaTime); - if (readiness.active) { - // this plugin will start. add it to the list of running plugins and mark the - // activity-slots which this plugin consumes as "in use" - _this.runningPluginNames[orderedPluginName] = true; - _this.markSlots(candidatePlugin, orderedPluginName); + this.slotsAreAvailableForPlugin = function (plugin) { + for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { + if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { + return false; // something is already using a slot which _this plugin requires } } - } + return true; + }; - // print("QQQ running plugins: " + JSON.stringify(_this.runningPluginNames)); + this.markSlots = function (plugin, pluginName) { + for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { + _this.activitySlots[plugin.parameters.activitySlots[i]] = pluginName; + } + }; - // give time to running plugins - for (var runningPluginName in _this.runningPluginNames) { - if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) { - var plugin = controllerDispatcherPlugins[runningPluginName]; - if (!plugin) { - // plugin was deactivated while running. find the activity-slots it was using and make - // them available. - delete _this.runningPluginNames[runningPluginName]; - _this.unmarkSlotsForPluginName(runningPluginName); + this.unmarkSlotsForPluginName = function (runningPluginName) { + // this is used to free activity-slots when a plugin is deactivated while it's running. + for (var activitySlot in _this.activitySlots) { + if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) { + _this.activitySlots[activitySlot] = false; + } + } + }; + + this.runningPluginNames = {}; + this.leftTriggerValue = 0; + this.leftTriggerClicked = 0; + this.rightTriggerValue = 0; + this.rightTriggerClicked = 0; + this.leftSecondaryValue = 0; + this.rightSecondaryValue = 0; + + this.leftTriggerPress = function (value) { + _this.leftTriggerValue = value; + }; + this.leftTriggerClick = function (value) { + _this.leftTriggerClicked = value; + }; + this.rightTriggerPress = function (value) { + _this.rightTriggerValue = value; + }; + this.rightTriggerClick = function (value) { + _this.rightTriggerClicked = value; + }; + this.leftSecondaryPress = function (value) { + _this.leftSecondaryValue = value; + }; + this.rightSecondaryPress = function (value) { + _this.rightSecondaryValue = value; + }; + + + this.dataGatherers = {}; + this.dataGatherers.leftControllerLocation = function () { + return getControllerWorldLocation(Controller.Standard.LeftHand, true); + }; + this.dataGatherers.rightControllerLocation = function () { + return getControllerWorldLocation(Controller.Standard.RightHand, true); + }; + + this.updateTimings = function () { + _this.intervalCount++; + var thisInterval = Date.now(); + var deltaTimeMsec = thisInterval - _this.lastInterval; + var deltaTime = deltaTimeMsec / 1000; + _this.lastInterval = thisInterval; + + _this.totalDelta += deltaTimeMsec; + + var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); + _this.totalVariance += variance; + + if (variance > 1) { + _this.highVarianceCount++; + } + + if (variance > 5) { + _this.veryhighVarianceCount++; + } + + return deltaTime; + }; + + this.setIgnoreTablet = function() { + if (HMD.tabletID !== _this.tabletID) { + RayPick.setIgnoreOverlays(_this.leftControllerRayPick, [HMD.tabletID]); + RayPick.setIgnoreOverlays(_this.rightControllerRayPick, [HMD.tabletID]); + } + }; + + this.update = function () { + var deltaTime = _this.updateTimings(); + _this.setIgnoreTablet(); + + if (controllerDispatcherPluginsNeedSort) { + _this.orderedPluginNames = []; + for (var pluginName in controllerDispatcherPlugins) { + if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { + _this.orderedPluginNames.push(pluginName); + } + } + _this.orderedPluginNames.sort(function (a, b) { + return controllerDispatcherPlugins[a].parameters.priority - + controllerDispatcherPlugins[b].parameters.priority; + }); + + // print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames)); + var output = "controllerDispatcher -- new plugin order: "; + for (var k = 0; k < _this.orderedPluginNames.length; k++) { + var dbgPluginName = _this.orderedPluginNames[k]; + var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority; + output += dbgPluginName + ":" + priority; + if (k + 1 < _this.orderedPluginNames.length) { + output += ", "; + } + } + + controllerDispatcherPluginsNeedSort = false; + } + + var controllerLocations = [ + _this.dataGatherers.leftControllerLocation(), + _this.dataGatherers.rightControllerLocation() + ]; + + // find 3d overlays near each hand + var nearbyOverlayIDs = []; + var h; + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + if (controllerLocations[h].valid) { + var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS); + nearbyOverlays.sort(function (a, b) { + var aPosition = Overlays.getProperty(a, "position"); + var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); + var bPosition = Overlays.getProperty(b, "position"); + var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); + return aDistance - bDistance; + }); + nearbyOverlayIDs.push(nearbyOverlays); } else { - var runningness = plugin.run(controllerData, deltaTime); - if (!runningness.active) { - // plugin is finished running, for now. remove it from the list - // of running plugins and mark its activity-slots as "not in use" - delete _this.runningPluginNames[runningPluginName]; - _this.markSlots(plugin, false); + nearbyOverlayIDs.push([]); + } + } + + // find entities near each hand + var nearbyEntityProperties = [[], []]; + var nearbyEntityPropertiesByID = {}; + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + if (controllerLocations[h].valid) { + var controllerPosition = controllerLocations[h].position; + var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MAX_RADIUS); + for (var j = 0; j < nearbyEntityIDs.length; j++) { + var entityID = nearbyEntityIDs[j]; + var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + props.id = entityID; + nearbyEntityPropertiesByID[entityID] = props; + nearbyEntityProperties[h].push(props); } } } - } - }; - this.setBlacklist = function() { - RayPick.setIgnoreEntities(_this.leftControllerRayPick, this.blacklist); - RayPick.setIgnoreEntities(_this.rightControllerRayPick, this.blacklist); + // raypick for each controller + var rayPicks = [ + RayPick.getPrevRayPickResult(_this.leftControllerRayPick), + RayPick.getPrevRayPickResult(_this.rightControllerRayPick) + ]; + var hudRayPicks = [ + RayPick.getPrevRayPickResult(_this.leftControllerHudRayPick), + RayPick.getPrevRayPickResult(_this.rightControllerHudRayPick) + ]; + // if the pickray hit something very nearby, put it into the nearby entities list + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { - }; + // XXX find a way to extract searchRay from samuel's stuff + rayPicks[h].searchRay = { + origin: controllerLocations[h].position, + direction: Quat.getUp(controllerLocations[h].orientation), + length: 1000 + }; - var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; - var mapping = Controller.newMapping(MAPPING_NAME); - mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress); - mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick); - mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress); - mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick); - - mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress); - mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress); - mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress); - mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress); - - Controller.enableMapping(MAPPING_NAME); - - this.leftControllerRayPick = RayPick.createRayPick({ - joint: "_CONTROLLER_LEFTHAND", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, - enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, - posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) - }); - this.leftControllerHudRayPick = RayPick.createRayPick({ - joint: "_CONTROLLER_LEFTHAND", - filter: RayPick.PICK_HUD, - enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, - posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) - }); - this.rightControllerRayPick = RayPick.createRayPick({ - joint: "_CONTROLLER_RIGHTHAND", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, - enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, - posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) - }); - this.rightControllerHudRayPick = RayPick.createRayPick({ - joint: "_CONTROLLER_RIGHTHAND", - filter: RayPick.PICK_HUD, - enabled: true, - maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, - posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) - }); - - this.handleHandMessage = function(channel, message, sender) { - var data; - if (sender === MyAvatar.sessionUUID) { - try { - if (channel === 'Hifi-Hand-RayPick-Blacklist') { - data = JSON.parse(message); - var action = data.action; - var id = data.id; - var index = this.blacklis.indexOf(id); - - if (action === 'add' && index === -1) { - this.blacklist.push(id); - this.setBlacklist(); + if (rayPicks[h].type === RayPick.INTERSECTED_ENTITY) { + // XXX check to make sure this one isn't already in nearbyEntityProperties? + if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) { + var nearEntityID = rayPicks[h].objectID; + var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); + nearbyProps.id = nearEntityID; + nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; + nearbyEntityProperties[h].push(nearbyProps); } + } - if (action === 'remove') { - if (index > -1) { - this.blacklist.splice(index, 1); - this.setBlacklist(); + // sort by distance from each hand + nearbyEntityProperties[h].sort(function (a, b) { + var aDistance = Vec3.distance(a.position, controllerLocations[h].position); + var bDistance = Vec3.distance(b.position, controllerLocations[h].position); + return aDistance - bDistance; + }); + } + + // bundle up all the data about the current situation + var controllerData = { + triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], + triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], + secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], + controllerLocations: controllerLocations, + nearbyEntityProperties: nearbyEntityProperties, + nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, + nearbyOverlayIDs: nearbyOverlayIDs, + rayPicks: rayPicks, + hudRayPicks: hudRayPicks + }; + + // check for plugins that would like to start. ask in order of increasing priority value + for (var pluginIndex = 0; pluginIndex < _this.orderedPluginNames.length; pluginIndex++) { + var orderedPluginName = _this.orderedPluginNames[pluginIndex]; + var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; + + if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { + var readiness = candidatePlugin.isReady(controllerData, deltaTime); + if (readiness.active) { + // this plugin will start. add it to the list of running plugins and mark the + // activity-slots which this plugin consumes as "in use" + _this.runningPluginNames[orderedPluginName] = true; + _this.markSlots(candidatePlugin, orderedPluginName); + } + } + } + + // print("QQQ running plugins: " + JSON.stringify(_this.runningPluginNames)); + + // give time to running plugins + for (var runningPluginName in _this.runningPluginNames) { + if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) { + var plugin = controllerDispatcherPlugins[runningPluginName]; + if (!plugin) { + // plugin was deactivated while running. find the activity-slots it was using and make + // them available. + delete _this.runningPluginNames[runningPluginName]; + _this.unmarkSlotsForPluginName(runningPluginName); + } else { + var runningness = plugin.run(controllerData, deltaTime); + if (!runningness.active) { + // plugin is finished running, for now. remove it from the list + // of running plugins and mark its activity-slots as "not in use" + delete _this.runningPluginNames[runningPluginName]; + _this.markSlots(plugin, false); } } } - - } catch (e) { - print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); } - } - }; + }; - this.cleanup = function () { - Script.update.disconnect(_this.update); - Controller.disableMapping(MAPPING_NAME); - RayPick.removeRayPick(_this.leftControllerRayPick); - RayPick.removeRayPick(_this.rightControllerRayPick); - RayPick.removeRayPick(_this.rightControllerHudRayPick); - RayPick.removeRayPick(_this.leftControllerHudRayPick); - }; + this.setBlacklist = function() { + RayPick.setIgnoreEntities(_this.leftControllerRayPick, this.blacklist); + RayPick.setIgnoreEntities(_this.rightControllerRayPick, this.blacklist); + + }; + + var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; + var mapping = Controller.newMapping(MAPPING_NAME); + mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress); + mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick); + mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress); + mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick); + + mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress); + mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress); + mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress); + mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress); + + Controller.enableMapping(MAPPING_NAME); + + this.leftControllerRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) + }); + this.leftControllerHudRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_HUD, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.LeftHand) + }); + this.rightControllerRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_RIGHTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) + }); + this.rightControllerHudRayPick = RayPick.createRayPick({ + joint: "_CONTROLLER_RIGHTHAND", + filter: RayPick.PICK_HUD, + enabled: true, + maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE, + posOffset: getGrabPointSphereOffset(Controller.Standard.RightHand) + }); + + this.handleHandMessage = function(channel, message, sender) { + var data; + if (sender === MyAvatar.sessionUUID) { + try { + if (channel === 'Hifi-Hand-RayPick-Blacklist') { + data = JSON.parse(message); + var action = data.action; + var id = data.id; + var index = _this.blacklis.indexOf(id); + + if (action === 'add' && index === -1) { + _this.blacklist.push(id); + _this.setBlacklist(); + } + + if (action === 'remove') { + if (index > -1) { + _this.blacklist.splice(index, 1); + _this.setBlacklist(); + } + } + } + + } catch (e) { + print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); + } + } + }; + + this.cleanup = function () { + Script.update.disconnect(_this.update); + Controller.disableMapping(MAPPING_NAME); + RayPick.removeRayPick(_this.leftControllerRayPick); + RayPick.removeRayPick(_this.rightControllerRayPick); + RayPick.removeRayPick(_this.rightControllerHudRayPick); + RayPick.removeRayPick(_this.leftControllerHudRayPick); + }; + } + + var controllerDispatcher = new ControllerDispatcher(); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); - Messages.messageReceived.connect(this.handleHandMessage); - Script.scriptEnding.connect(this.cleanup); - Script.update.connect(this.update); + Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); + Script.scriptEnding.connect(controllerDispatcher.cleanup); + Script.update.connect(controllerDispatcher.update); }()); diff --git a/scripts/system/controllers/controllerModules/disableOtherModule.js b/scripts/system/controllers/controllerModules/disableOtherModule.js index 65732dd08d..d6079ffafb 100644 --- a/scripts/system/controllers/controllerModules/disableOtherModule.js +++ b/scripts/system/controllers/controllerModules/disableOtherModule.js @@ -12,7 +12,7 @@ getEnabledModuleByName */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { function DisableModules(hand) { @@ -21,6 +21,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.parameters = makeDispatcherModuleParameters( 90, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], + [], 100); this.isReady = function(controllerData) { diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 40e0e19cf3..fa1321b168 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -14,7 +14,7 @@ */ Script.include("/~/system/libraries/Xform.js"); -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/cloneEntityUtils.js"); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 44360c2e39..56513ab766 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -17,7 +17,7 @@ */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { diff --git a/scripts/system/controllers/controllerModules/farTrigger.js b/scripts/system/controllers/controllerModules/farTrigger.js index 671fc58c7d..38152aac91 100644 --- a/scripts/system/controllers/controllerModules/farTrigger.js +++ b/scripts/system/controllers/controllerModules/farTrigger.js @@ -16,7 +16,7 @@ */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index c6ac0c01bc..cf82de1820 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -14,7 +14,7 @@ isInEditMode */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/utils.js"); @@ -94,6 +94,7 @@ Script.include("/~/system/libraries/utils.js"); this.parameters = makeDispatcherModuleParameters( 160, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], + [], 100); this.nearTablet = function(overlays) { diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 2b3a2405fb..7c856535ab 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -12,7 +12,7 @@ TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/cloneEntityUtils.js"); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 4a59fd727f..47f80932bb 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -13,7 +13,7 @@ findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/cloneEntityUtils.js"); (function() { @@ -169,7 +169,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.targetEntityID = null; this.grabbing = false; - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { return makeRunningValues(false, [], []); } @@ -188,7 +188,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.run = function (controllerData, deltaTime) { if (this.grabbing) { - if (controllerData.triggerClicks[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { this.endNearParentingGrabEntity(); return makeRunningValues(false, [], []); } @@ -208,7 +208,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!readiness.active) { return readiness; } - if (controllerData.triggerClicks[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand] === 1 || controllerData.secondaryValues[this.hand] === 1) { // switch to grab var targetProps = this.getTargetProps(controllerData); var targetCloneable = entityIsCloneable(targetProps); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 46ed8df271..9c8e6265ed 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -12,7 +12,7 @@ makeDispatcherModuleParameters, Overlays, makeRunningValues */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var GRAB_RADIUS = 0.35; (function() { @@ -158,7 +158,7 @@ var GRAB_RADIUS = 0.35; this.isReady = function (controllerData) { - if (controllerData.triggerClicks[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { return makeRunningValues(false, [], []); } @@ -180,7 +180,7 @@ var GRAB_RADIUS = 0.35; }; this.run = function (controllerData) { - if (controllerData.triggerClicks[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { this.endNearParentingGrabOverlay(); return makeRunningValues(false, [], []); } else { diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 4d31a8a22b..03fc7f8f64 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -11,7 +11,7 @@ TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, makeRunningValues, NEAR_GRAB_RADIUS */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index 86623dbc72..4b17f90524 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -14,7 +14,7 @@ DISPATCHER_PROPERTIES */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 9720bc8022..9e6062e3c3 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -11,7 +11,7 @@ AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/controllerModules/teleport.js similarity index 98% rename from scripts/system/controllers/teleport.js rename to scripts/system/controllers/controllerModules/teleport.js index 17ac36d955..fcf5ea0ed2 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -17,7 +17,7 @@ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ Script.include("/~/system/libraries/Xform.js"); -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { // BEGIN LOCAL_SCOPE @@ -27,9 +27,9 @@ var inTeleportMode = false; var SMOOTH_ARRIVAL_SPACING = 33; var NUMBER_OF_STEPS = 6; -var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx"); -var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx"); -var SEAT_MODEL_URL = Script.resolvePath("../assets/models/teleport-seat.fbx"); +var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleport-destination.fbx"); +var TOO_CLOSE_MODEL_URL = Script.resolvePath("../../assets/models/teleport-cancel.fbx"); +var SEAT_MODEL_URL = Script.resolvePath("../../assets/models/teleport-seat.fbx"); var TARGET_MODEL_DIMENSIONS = { x: 1.15, diff --git a/scripts/system/controllers/controllerModules/webEntityLaserInput.js b/scripts/system/controllers/controllerModules/webEntityLaserInput.js index 747e1bae44..1e954d5917 100644 --- a/scripts/system/controllers/controllerModules/webEntityLaserInput.js +++ b/scripts/system/controllers/controllerModules/webEntityLaserInput.js @@ -16,7 +16,7 @@ */ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 569fad4855..e3375a5a27 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -28,7 +28,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/inEditMode.js", "controllerModules/disableOtherModule.js", "controllerModules/farTrigger.js", - "teleport.js" + "controllerModules/teleport.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/controllers/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js similarity index 100% rename from scripts/system/controllers/controllerDispatcherUtils.js rename to scripts/system/libraries/controllerDispatcherUtils.js From f0c12e50dd36588de651ff4e34a5500201d23c1e Mon Sep 17 00:00:00 2001 From: druiz17 Date: Fri, 8 Sep 2017 11:50:19 -0700 Subject: [PATCH 50/78] some small bug fixes --- .../controllers/controllerModules/nearActionGrabEntity.js | 6 +++--- .../controllers/controllerModules/nearParentGrabOverlay.js | 3 ++- .../controllers/controllerModules/overlayLaserInput.js | 5 ++++- scripts/system/libraries/cloneEntityUtils.js | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 7c856535ab..932cbda179 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -167,7 +167,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.isReady = function (controllerData) { this.targetEntityID = null; - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { return makeRunningValues(false, [], []); } @@ -186,7 +186,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.run = function (controllerData) { if (this.actionID) { - if (controllerData.triggerClicks[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { this.endNearGrabAction(); return makeRunningValues(false, [], []); } @@ -204,7 +204,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (controllerData.triggerClicks[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand] === 1 || controllerData.secondaryValues[this.hand] === 1) { // switch to grabbing var targetCloneable = entityIsCloneable(targetProps); if (targetCloneable) { diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 9c8e6265ed..54257ea6af 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -128,7 +128,8 @@ var GRAB_RADIUS = 0.35; }; this.endNearParentingGrabOverlay = function () { - if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { + var previousParentID = this.previousParentID[this.grabbedThingID]; + if (previousParentID === NULL_UUID || previousParentID === null || previousParentID === undefined) { Overlays.editOverlay(this.grabbedThingID, { parentID: NULL_UUID, parentJointIndex: -1 diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index 4b17f90524..71fc31a500 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -429,8 +429,11 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldExit = function(controllerData) { var intersection = controllerData.rayPicks[this.hand]; + var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; + var nearGrabModule = getEnabledModuleByName(nearGrabName); + var status = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY); - return offOverlay; + return offOverlay || status.active; }; this.exitModule = function() { diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index a8e8bc227d..26d8051f89 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -72,6 +72,7 @@ cloneEntity = function(props, worldEntityProps) { cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var triggerable = grabInfo.triggerable ? grabInfo.triggerable : false; var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData)); var cProperties = Object.assign({}, cloneableProps); @@ -85,7 +86,7 @@ cloneEntity = function(props, worldEntityProps) { cProperties.dynamic = dynamic; cProperties.locked = false; - cUserData.grabbableKey.triggerable = true; + cUserData.grabbableKey.triggerable = triggerable; cUserData.grabbableKey.grabbable = true; cProperties.lifetime = lifetime; cProperties.userData = JSON.stringify(cUserData); From e33012e522fa83dafa052e7eacbc9d32bfae487d Mon Sep 17 00:00:00 2001 From: druiz17 Date: Fri, 8 Sep 2017 13:30:30 -0700 Subject: [PATCH 51/78] laser on tablet update --- .../system/controllers/controllerModules/overlayLaserInput.js | 3 ++- .../system/controllers/controllerModules/tabletStylusInput.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index 71fc31a500..b1ffc77afe 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -433,7 +433,8 @@ Script.include("/~/system/libraries/controllers.js"); var nearGrabModule = getEnabledModuleByName(nearGrabName); var status = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY); - return offOverlay || status.active; + var triggerOff = (controllerData.triggerValues[this.hand] === 0); + return offOverlay || status.active || triggerOff; }; this.exitModule = function() { diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 9e6062e3c3..a70b9867d8 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllers.js"); var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; + var WEB_DISPLAY_STYLUS_DISTANCE = 0.2; var WEB_STYLUS_LENGTH = 0.2; var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand From 5d1a5eea4c48872379afda0b7f08f961598a460f Mon Sep 17 00:00:00 2001 From: druiz17 Date: Fri, 8 Sep 2017 13:45:45 -0700 Subject: [PATCH 52/78] added test file --- .../developer/tests/controllerTableTest.js | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 scripts/developer/tests/controllerTableTest.js diff --git a/scripts/developer/tests/controllerTableTest.js b/scripts/developer/tests/controllerTableTest.js new file mode 100644 index 0000000000..239c7fd0ce --- /dev/null +++ b/scripts/developer/tests/controllerTableTest.js @@ -0,0 +1,278 @@ +"use strict"; + +/* jslint bitwise: true */ +/* global Script, Entities, MyAvatar, Vec3, Quat, Mat4 */ + +(function() { // BEGIN LOCAL_SCOPE + + // var lifetime = -1; + var lifetime = 600; + var tableSections = 32; + var tableRadius = 9; + + var sectionRelativeRotation = 0; + var sectionRotation = 0; + var sectionRelativeCenterA = 0; + var sectionRelativeCenterB = 0; + var sectionRelativeCenterSign = 0; + var sectionCenterA = 0; + var sectionCenterB = 0; + var sectionCenterSign = 0; + var yFlip = 0; + + var objects = []; + var overlays = []; + + var testNames = [ + "FarActionGrab", + "NearParentGrabEntity", + "NearParentGrabOverlay", + "Clone Entity (dynamic)", + "Clone Entity (non-dynamic" + ]; + + function createCloneDynamicEntity(index) { + createPropsCube(index, false, false, true, true); + createPropsModel(index, false, false, true, true); + createSign(index, "Clone Dynamic Entity"); + }; + + function createCloneEntity(index) { + createPropsCube(index, false, false, true, false); + createPropsModel(index, false, false, true, false); + createSign(index, "Clone Non-Dynamic Entity"); + }; + + function createNearGrabOverlay(index) { + createPropsCubeOverlay(index, false, false, true, true); + createPropsModelOverlay(index, false, false, true, true); + createSign(index, "Near Grab Overlay"); + }; + + function createNearGrabEntity(index) { + createPropsCube(index, false, false, false, false); + createPropsModel(index, false, false, false, false); + createSign(index, "Near Grab Entity"); + }; + + function createFarGrabEntity(index) { + createPropsCube(index, true, false, false, false); + createPropsModel(index, true, false, false, false); + createSign(index, "Far Grab Entity"); + }; + + function createPropsModel(i, dynamic, collisionless, clone, cloneDynamic) { + var propsModel = { + name: "controller-tests model object " + i, + type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/controller-tests/color-cube.obj", + + position: sectionCenterA, + rotation: sectionRotation, + + gravity: (dynamic && !collisionless) ? { x: 0, y: -1, z: 0 } : { x: 0, y: 0, z: 0 }, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + cloneLimit: 10, + cloneable: clone, + cloneDynamic: cloneDynamic + }, + controllerTestEntity: true + }), + lifetime: lifetime, + shapeType: "box", + dynamic: dynamic, + collisionless: collisionless + }; + objects.push(Entities.addEntity(propsModel)); + } + + function createPropsModelOverlay(i, dynamic, collisionless, clone, cloneDynamic) { + var propsModel = { + name: "controller-tests model object " + i, + type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/controller-tests/color-cube.obj", + url: "http://headache.hungry.com/~seth/hifi/controller-tests/color-cube.obj", + grabbable: true, + position: sectionCenterA, + rotation: sectionRotation, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + }, + controllerTestEntity: true + }), + lifetime: lifetime, + visible: true, + }; + overlays.push(Overlays.addOverlay("model", propsModel)); + } + + + function createPropsCubeOverlay(i, dynamic, collisionless, clone, cloneDynamic) { + var propsCube = { + name: "controller-tests cube object " + i, + type: "Box", + color: { "blue": 200, "green": 10, "red": 20 }, + position: sectionCenterB, + rotation: sectionRotation, + grabbable: true, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + }, + controllerTestEntity: true + }), + lifetime: lifetime, + solid: true, + visible: true, + }; + overlays.push(Overlays.addOverlay("cube", propsCube)); + } + + function createPropsCube(i, dynamic, collisionless, clone, cloneDynamic) { + var propsCube = { + name: "controller-tests cube object " + i, + type: "Box", + shape: "Cube", + color: { "blue": 200, "green": 10, "red": 20 }, + position: sectionCenterB, + rotation: sectionRotation, + gravity: dynamic ? { x: 0, y: -1, z: 0 } : { x: 0, y: 0, z: 0 }, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + cloneLimit: 10, + cloneable: clone, + cloneDynamic: cloneDynamic + }, + controllerTestEntity: true + }), + lifetime: lifetime, + shapeType: "box", + dynamic: dynamic, + collisionless: collisionless + }; + objects.push(Entities.addEntity(propsCube)); + } + + function createSign(i, signText) { + var propsLabel = { + name: "controller-tests sign " + i, + type: "Text", + lineHeight: 0.125, + position: sectionCenterSign, + rotation: Quat.multiply(sectionRotation, yFlip), + text: signText, + dimensions: { x: 1, y: 1, z: 0.01 }, + lifetime: lifetime, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false, + }, + controllerTestEntity: true + }) + }; + objects.push(Entities.addEntity(propsLabel)); + } + + function chooseType(index) { + switch (index) { + case 0: + createFarGrabEntity(index); + break; + case 1: + createNearGrabEntity(index); + break; + case 2: + createNearGrabOverlay(index); + break; + case 3: + createCloneDynamicEntity(); + break; + case 4: + createCloneEntity(index); + break; + } + } + + function setupControllerTests(testBaseTransform) { + // var tableID = + objects.push(Entities.addEntity({ + name: "controller-tests table", + type: "Model", + modelURL: "http://headache.hungry.com/~seth/hifi/controller-tests/controller-tests-table.obj.gz", + position: Mat4.transformPoint(testBaseTransform, { x: 0, y: 1, z: 0 }), + rotation: Mat4.extractRotation(testBaseTransform), + userData: JSON.stringify({ + grabbableKey: { grabbable: false }, + soundKey: { + url: "http://headache.hungry.com/~seth/hifi/sound/clock-ticking-3.wav", + volume: 0.4, + loop: true, + playbackGap: 0, + playbackGapRange: 0 + }, + controllerTestEntity: true + }), + shapeType: "static-mesh", + lifetime: lifetime + })); + + var Xdynamic = 1; + var Xcollisionless = 2; + var Xkinematic = 4; + var XignoreIK = 8; + + yFlip = Quat.fromPitchYawRollDegrees(0, 180, 0); + + for (var i = 0; i < 16; i++) { + sectionRelativeRotation = Quat.fromPitchYawRollDegrees(0, -360 * i / tableSections, 0); + sectionRotation = Quat.multiply(Mat4.extractRotation(testBaseTransform), sectionRelativeRotation); + sectionRelativeCenterA = Vec3.multiplyQbyV(sectionRotation, { x: -0.2, y: 1.25, z: tableRadius - 0.8 }); + sectionRelativeCenterB = Vec3.multiplyQbyV(sectionRotation, { x: 0.2, y: 1.25, z: tableRadius - 0.8 }); + sectionRelativeCenterSign = Vec3.multiplyQbyV(sectionRotation, { x: 0, y: 1.5, z: tableRadius + 1.0 }); + sectionCenterA = Mat4.transformPoint(testBaseTransform, sectionRelativeCenterA); + sectionCenterB = Mat4.transformPoint(testBaseTransform, sectionRelativeCenterB); + sectionCenterSign = Mat4.transformPoint(testBaseTransform, sectionRelativeCenterSign); + + var dynamic = (i & Xdynamic) ? true : false; + var collisionless = (i & Xcollisionless) ? true : false; + var kinematic = (i & Xkinematic) ? true : false; + var ignoreIK = (i & XignoreIK) ? true : false; + + chooseType(i); + } + } + + // This assumes the avatar is standing on a flat floor with plenty of space. + // Find the floor: + var pickRay = { + origin: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 2, z: -1 })), + direction: { x: 0, y: -1, z: 0 }, + length: 20 + }; + var intersection = Entities.findRayIntersection(pickRay, true, [], [], true); + + if (intersection.intersects) { + var testBaseTransform = Mat4.createFromRotAndTrans(MyAvatar.rotation, intersection.intersection); + setupControllerTests(testBaseTransform); + } + + Script.scriptEnding.connect(function () { + for (var i = 0; i < objects.length; i++) { + var nearbyID = objects[i]; + Entities.deleteEntity(nearbyID); + } + + for (var i = 0; i < overlays.length; i++) { + var overlayID = overlays[i]; + Overlays.deleteOverlay(overlayID); + } + }); +}()); // END LOCAL_SCOPE From 42ef397b5e4d6f120fa32c6751eac23022b806f8 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Fri, 8 Sep 2017 15:13:19 -0700 Subject: [PATCH 53/78] fixed include path --- scripts/system/controllers/controllerDispatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 43ad2ccf51..759d9c8f31 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -18,7 +18,7 @@ controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { var NEAR_MAX_RADIUS = 1.0; From a1ffc7abd01cc59a848310aa450e8634a9d5983c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 8 Sep 2017 17:04:09 -0700 Subject: [PATCH 54/78] distance from controller to object is sometimes decided by pickray --- scripts/system/controllers/controllerDispatcher.js | 6 +++--- .../controllers/controllerModules/nearActionGrabEntity.js | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 759d9c8f31..c52b3d56df 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -205,6 +205,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; + props.distance = Vec3.distance(props.position, controllerLocations[h].position) nearbyEntityPropertiesByID[entityID] = props; nearbyEntityProperties[h].push(props); } @@ -236,6 +237,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var nearEntityID = rayPicks[h].objectID; var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); nearbyProps.id = nearEntityID; + nearbyProps.distance = rayPicks[h].distance; nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; nearbyEntityProperties[h].push(nearbyProps); } @@ -243,9 +245,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // sort by distance from each hand nearbyEntityProperties[h].sort(function (a, b) { - var aDistance = Vec3.distance(a.position, controllerLocations[h].position); - var bDistance = Vec3.distance(b.position, controllerLocations[h].position); - return aDistance - bDistance; + return a.distance - b.distance; }); } diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 932cbda179..d76de2b428 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -148,8 +148,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); for (var i = 0; i < nearbyEntityProperties.length; i++) { var props = nearbyEntityProperties[i]; var handPosition = controllerData.controllerLocations[this.hand].position; - var distance = Vec3.distance(props.position, handPosition); - if (distance > NEAR_GRAB_RADIUS) { + if (props.distance > NEAR_GRAB_RADIUS) { break; } if (entityIsGrabbable(props)) { From 405d8c3afc335028d9ef1043b6a86620808a1f7b Mon Sep 17 00:00:00 2001 From: druiz17 Date: Fri, 8 Sep 2017 17:07:27 -0700 Subject: [PATCH 55/78] fixing cloning and laser --- .../controllers/controllerModules/farActionGrabEntity.js | 6 +++--- .../controllers/controllerModules/nearActionGrabEntity.js | 4 ++-- .../controllers/controllerModules/tabletStylusInput.js | 2 +- scripts/system/libraries/cloneEntityUtils.js | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 56513ab766..3355901e6e 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -137,7 +137,7 @@ Script.include("/~/system/libraries/controllers.js"); var dim = {x: radius, y: radius, z: radius}; var mode = "hold"; if (!this.distanceHolding && !this.distanceRotating) { - if (controllerData.triggerClicks[this.hand]) { + if (controllerData.triggerValues[this.hand] === 1) { mode = "full"; } else { mode = "half"; @@ -367,8 +367,6 @@ Script.include("/~/system/libraries/controllers.js"); otherFarGrabModule.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, otherFarGrabModule.offsetPosition); - this.updateLaserPointer(); - this.previousWorldControllerRotation = worldControllerRotation; }; @@ -434,6 +432,8 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(false, [], []); } + this.updateLaserPointer(controllerData); + var otherModuleName =this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; var otherFarGrabModule = getEnabledModuleByName(otherModuleName); diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 932cbda179..802540af17 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -152,7 +152,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (distance > NEAR_GRAB_RADIUS) { break; } - if (entityIsGrabbable(props)) { + if (entityIsGrabbable(props) || entityIsCloneable(props)) { // if we've attempted to grab a child, roll up to the root of the tree var groupRootProps = findGroupParent(controllerData, props); if (entityIsGrabbable(groupRootProps)) { @@ -173,7 +173,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (!propsArePhysical(targetProps) && !propsAreCloneDynamic) { + if (!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) { return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it } else { this.targetEntityID = targetProps.id; diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index a70b9867d8..8a1262bac0 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllers.js"); var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - var WEB_DISPLAY_STYLUS_DISTANCE = 0.2; + var WEB_DISPLAY_STYLUS_DISTANCE = 0.1; var WEB_STYLUS_LENGTH = 0.2; var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index 26d8051f89..500c6a0696 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -36,7 +36,6 @@ entityIsCloneable = function(props) { var grabbableData = getGrabbableData(props); return grabbableData.cloneable; } - return false; }; From 40d339a8318e7b014c60da12a6817aa684d44a9b Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 11 Sep 2017 20:33:54 +0300 Subject: [PATCH 56/78] 21484: Updated code to go with PR feedback --- .../src/RenderableModelEntityItem.cpp | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index cc9dcf8ac5..c6bad008e4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -909,13 +909,6 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector jointsData; const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy - QStringList animationJointNames = _animation->getGeometry().getJointNames(); - auto& fbxJoints = _animation->getGeometry().joints; - - auto& originalFbxJoints = _model->getFBXGeometry().joints; - auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; - - bool allowTranslation = entity->getAnimationAllowTranslation(); int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -950,6 +943,14 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { return; } + QStringList animationJointNames = _animation->getGeometry().getJointNames(); + auto& fbxJoints = _animation->getGeometry().joints; + + auto& originalFbxJoints = _model->getFBXGeometry().joints; + auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + + bool allowTranslation = entity->getAnimationAllowTranslation(); + const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; const QVector& translations = frames[_lastKnownCurrentFrame].translations; @@ -960,9 +961,11 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { if (index >= 0) { glm::mat4 translationMat; - if (allowTranslation && index < translations.size()) { - translationMat = glm::translate(translations[index]); - } else if (!allowTranslation && index < animationJointNames.size()){ + if (allowTranslation) { + if(index < translations.size()){ + translationMat = glm::translate(translations[index]); + } + } else if (index < animationJointNames.size()){ QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation if (originalFbxIndices.contains(jointName)) { From 4acbc2bfbaea7767754e0cee6fcc40d966c13d43 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 11 Sep 2017 13:03:21 -0700 Subject: [PATCH 57/78] Just write the image file in the format we will use later --- interface/src/commerce/Wallet.cpp | 45 ++++++++++++++++++++++++------- interface/src/commerce/Wallet.h | 4 +-- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index a0664f4f9b..9f7c79226d 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -40,6 +40,8 @@ static const char* KEY_FILE = "hifikey"; static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile +static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; +static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; void initialize() { static bool initialized = false; @@ -131,8 +133,6 @@ bool writeKeys(const char* filename, RSA* keys) { return retval; } - -// BEGIN copied code - this will be removed/changed at some point soon // copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. // We will have a different implementation in practice, but this gives us a start for now // @@ -288,7 +288,7 @@ void Wallet::setPassphrase(const QString& passphrase) { } // encrypt some stuff -bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) { +bool Wallet::writeSecurityImageFile(const QString& inputFilePath, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys @@ -338,15 +338,18 @@ bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFile qCDebug(commerce) << "encrypted buffer size" << outSize; QByteArray output((const char*)outputFileBuffer, outSize); QFile outputFile(outputFilePath); - outputFile.open(QIODevice::WriteOnly); - outputFile.write(output); + outputFile.open(QIODevice::WriteOnly| QIODevice::Text); + outputFile.write(IMAGE_HEADER); + outputFile.write(output.toBase64()); + outputFile.write("\n"); + outputFile.write(IMAGE_FOOTER); outputFile.close(); delete[] outputFileBuffer; return true; } -bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { +bool Wallet::readSecurityImageFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); @@ -357,9 +360,31 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; return false; } - inputFile.open(QIODevice::ReadOnly); - QByteArray encryptedBuffer = inputFile.readAll(); + inputFile.open(QIODevice::ReadOnly|QIODevice::Text); + bool foundHeader = false; + bool foundFooter = false; + + QByteArray base64EncryptedBuffer; + + while(!inputFile.atEnd()) { + QString line(inputFile.readLine()); + if (!foundHeader) { + foundHeader = (line == IMAGE_HEADER); + } else { + foundFooter = (line == IMAGE_FOOTER); + if (!foundFooter) { + base64EncryptedBuffer.append(line); + } + } + } inputFile.close(); + if (! (foundHeader && foundFooter)) { + qCDebug(commerce) << "couldn't parse" << inputFilePath << foundHeader << foundFooter; + return false; + } + + // convert to bytes + auto encryptedBuffer = QByteArray::fromBase64(base64EncryptedBuffer); // setup decrypted buffer unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()]; @@ -504,7 +529,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { _securityImage->load(path); // encrypt it and save. - if (encryptFile(path, imageFilePath())) { + if (writeSecurityImageFile(path, imageFilePath())) { qCDebug(commerce) << "emitting pixmap"; updateImageProvider(); @@ -529,7 +554,7 @@ void Wallet::getSecurityImage() { // decrypt and return QString filePath(imageFilePath()); QFileInfo fileInfo(filePath); - if (fileInfo.exists() && decryptFile(filePath, &data, &dataLen)) { + if (fileInfo.exists() && readSecurityImageFile(filePath, &data, &dataLen)) { // create the pixmap _securityImage = new QPixmap(); _securityImage->loadFromData(data, dataLen, "jpg"); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index f3c2ac399b..798c713bf1 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -61,8 +61,8 @@ private: QString* _passphrase { new QString("") }; void updateImageProvider(); - bool encryptFile(const QString& inputFilePath, const QString& outputFilePath); - bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); + bool writeSecurityImageFile(const QString& inputFilePath, const QString& outputFilePath); + bool readSecurityImageFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; #endif // hifi_Wallet_h From 30c979bd3122ac0f48143b10e2195b00f19fccee Mon Sep 17 00:00:00 2001 From: Menithal Date: Mon, 11 Sep 2017 23:41:13 +0300 Subject: [PATCH 58/78] Added Clap App to Repository Addede Clap App Icons to tablet icons (there needs to be another way to do this than link directly to link or use this method!) Added module as valid global to eslintrc --- .eslintrc.js | 3 +- .../resources/icons/tablet-icons/clap-a.svg | 104 +++++ .../resources/icons/tablet-icons/clap-i.svg | 104 +++++ .../clap/animations/ClapAnimation.json | 386 ++++++++++++++++++ .../marketplace/clap/animations/Clap_left.fbx | Bin 0 -> 142012 bytes .../clap/animations/Clap_right.fbx | Bin 0 -> 142028 bytes .../marketplace/clap/clapApp.js | 56 +++ .../clap/entities/ClapParticle.json | 58 +++ .../clap/icons/clap-black-icon.svg | 104 +++++ .../clap/icons/clap-white-icon.svg | 104 +++++ .../marketplace/clap/scripts/ClapDebugger.js | 165 ++++++++ .../marketplace/clap/scripts/ClapEngine.js | 318 +++++++++++++++ .../marketplace/clap/sounds/clap1.wav | Bin 0 -> 33170 bytes .../marketplace/clap/sounds/clap2.wav | Bin 0 -> 24564 bytes .../marketplace/clap/sounds/clap3.wav | Bin 0 -> 30704 bytes .../marketplace/clap/sounds/clap4.wav | Bin 0 -> 21700 bytes .../marketplace/clap/sounds/clap5.wav | Bin 0 -> 28942 bytes .../marketplace/clap/sounds/clap6.wav | Bin 0 -> 25992 bytes 18 files changed, 1401 insertions(+), 1 deletion(-) create mode 100644 interface/resources/icons/tablet-icons/clap-a.svg create mode 100644 interface/resources/icons/tablet-icons/clap-i.svg create mode 100644 unpublishedScripts/marketplace/clap/animations/ClapAnimation.json create mode 100644 unpublishedScripts/marketplace/clap/animations/Clap_left.fbx create mode 100644 unpublishedScripts/marketplace/clap/animations/Clap_right.fbx create mode 100644 unpublishedScripts/marketplace/clap/clapApp.js create mode 100644 unpublishedScripts/marketplace/clap/entities/ClapParticle.json create mode 100644 unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg create mode 100644 unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg create mode 100644 unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js create mode 100644 unpublishedScripts/marketplace/clap/scripts/ClapEngine.js create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap1.wav create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap2.wav create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap3.wav create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap4.wav create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap5.wav create mode 100644 unpublishedScripts/marketplace/clap/sounds/clap6.wav diff --git a/.eslintrc.js b/.eslintrc.js index b4d88777f2..535abb807e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,7 +54,8 @@ module.exports = { "Window": false, "XMLHttpRequest": false, "location": false, - "print": false + "print": false, + "module": false }, "rules": { "brace-style": ["error", "1tbs", { "allowSingleLine": false }], diff --git a/interface/resources/icons/tablet-icons/clap-a.svg b/interface/resources/icons/tablet-icons/clap-a.svg new file mode 100644 index 0000000000..10a8e3ea98 --- /dev/null +++ b/interface/resources/icons/tablet-icons/clap-a.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/clap-i.svg b/interface/resources/icons/tablet-icons/clap-i.svg new file mode 100644 index 0000000000..3e4ecb0b64 --- /dev/null +++ b/interface/resources/icons/tablet-icons/clap-i.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json new file mode 100644 index 0000000000..2dca45a0de --- /dev/null +++ b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json @@ -0,0 +1,386 @@ +{ + "RightShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": -0.44214877486228943, + "z": 0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "RightArm": { + "rotations": { + "x": 0.6266362071037292, + "y": -0.02882515825331211, + "z": -0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "RightForeArm": { + "rotations": { + "x": 0.00004018074105260894, + "y": 0.06418406218290329, + "z": -0.5193823575973511, + "w": 0.8521281480789185 + } + }, + "RightHand": { + "rotations": { + "x": -0.21368850767612457, + "y": 0.02043931558728218, + "z": -0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "RightHandPinky1": { + "rotations": { + "x": -0.003497191471979022, + "y": -0.01721140556037426, + "z": 0.19736060500144958, + "w": 0.980173647403717 + } + }, + "RightHandPinky2": { + "rotations": { + "x": 0.000005483317181642633, + "y": -0.00008023700502235442, + "z": -0.06867633014917374, + "w": 0.997639000415802 + } + }, + "RightHandPinky3": { + "rotations": { + "x": 7.18885644346301e-8, + "y": -2.7131383717460267e-7, + "z": 0.007620905060321093, + "w": 0.9999709725379944 + } + }, + "RightHandPinky4": { + "rotations": { + "x": -5.719068774112657e-9, + "y": 5.142213126418937e-7, + "z": -0.0075718737207353115, + "w": 0.999971330165863 + } + }, + "RightHandRing1": { + "rotations": { + "x": -0.0013452530838549137, + "y": -0.017564140260219574, + "z": 0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "RightHandRing2": { + "rotations": { + "x": 0.0000010375651982030831, + "y": -0.00002211921673733741, + "z": -0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "RightHandRing3": { + "rotations": { + "x": -2.7102969868408877e-10, + "y": 1.9202734335976857e-7, + "z": 0.0016911650309339166, + "w": 0.9999985694885254 + } + }, + "RightHandRing4": { + "rotations": { + "x": 2.3246689018208144e-9, + "y": -3.364403156069784e-8, + "z": 0.0004951066803187132, + "w": 0.9999998807907104 + } + }, + "RightHandMiddle1": { + "rotations": { + "x": 0.0012630893616005778, + "y": -0.017612185329198837, + "z": -0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "RightHandMiddle2": { + "rotations": { + "x": 2.3561028683616314e-7, + "y": 0.000020313073036959395, + "z": 0.011195243336260319, + "w": 0.9999373555183411 + } + }, + "RightHandMiddle3": { + "rotations": { + "x": 6.375214667286855e-8, + "y": 4.750924631480302e-7, + "z": 0.00237679248675704, + "w": 0.9999971985816956 + } + }, + "RightHandMiddle4": { + "rotations": { + "x": 6.717256439969788e-8, + "y": 3.876683507542111e-8, + "z": -0.005236906465142965, + "w": 0.9999862909317017 + } + }, + "RightHandIndex1": { + "rotations": { + "x": 0.002164300065487623, + "y": -0.017346171662211418, + "z": -0.12158434838056564, + "w": 0.9924271702766418 + } + }, + "RightHandIndex2": { + "rotations": { + "x": -0.00000143755482895358, + "y": -0.0001614764187252149, + "z": 0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "RightHandIndex3": { + "rotations": { + "x": 7.458467621290765e-8, + "y": 5.365728839024086e-7, + "z": 0.03373909369111061, + "w": 0.999430775642395 + } + }, + "RightHandIndex4": { + "rotations": { + "x": 4.511302997833866e-10, + "y": -2.259726272768603e-7, + "z": -0.009632252156734467, + "w": 0.9999536275863647 + } + }, + "RightHandThumb1": { + "rotations": { + "x": -0.0783928632736206, + "y": -0.3033908009529114, + "z": -0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "RightHandThumb2": { + "rotations": { + "x": 0.0031029442325234413, + "y": 0.07382386922836304, + "z": 0.005253761075437069, + "w": 0.9972526431083679 + } + }, + "RightHandThumb3": { + "rotations": { + "x": 0.0040440745651721954, + "y": -0.04943573474884033, + "z": 0.007246015593409538, + "w": 0.9987428188323975 + } + }, + "RightHandThumb4": { + "rotations": { + "x": -0.00009280416270485148, + "y": -0.01658034883439541, + "z": -0.00014316302258521318, + "w": 0.999862551689148 + } + }, + "LeftShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": 0.44214877486228943, + "z": -0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "LeftArm": { + "rotations": { + "x": 0.626636266708374, + "y": 0.028824958950281143, + "z": 0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "LeftForeArm": { + "rotations": { + "x": 0.00004015670492663048, + "y": -0.06418408453464508, + "z": 0.5193824768066406, + "w": 0.8521282076835632 + } + }, + "LeftHand": { + "rotations": { + "x": -0.21368853747844696, + "y": -0.02043931558728218, + "z": 0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "LeftHandPinky1": { + "rotations": { + "x": -0.003497188910841942, + "y": 0.01721140556037426, + "z": -0.19736060500144958, + "w": 0.980173647403717 + } + }, + "LeftHandPinky2": { + "rotations": { + "x": 0.000005479304491018411, + "y": 0.00008023556438274682, + "z": 0.06867631524801254, + "w": 0.997639000415802 + } + }, + "LeftHandPinky3": { + "rotations": { + "x": 7.229602516645173e-8, + "y": 2.709063835482084e-7, + "z": -0.007620909716933966, + "w": 0.9999709725379944 + } + }, + "LeftHandPinky4": { + "rotations": { + "x": -5.52988677071653e-9, + "y": -5.13755651354586e-7, + "z": 0.007571868598461151, + "w": 0.999971330165863 + } + }, + "LeftHandRing1": { + "rotations": { + "x": -0.001345252152532339, + "y": 0.017564138397574425, + "z": -0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "LeftHandRing2": { + "rotations": { + "x": 0.000001034482806971937, + "y": 0.000022119218556326814, + "z": 0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "LeftHandRing3": { + "rotations": { + "x": -2.8012464570181805e-10, + "y": -1.923183816643359e-7, + "z": -0.0016911652637645602, + "w": 0.9999985694885254 + } + }, + "LeftHandRing4": { + "rotations": { + "x": -1.1168596047994583e-9, + "y": 3.33529932561305e-8, + "z": -0.0004951058072037995, + "w": 0.9999998807907104 + } + }, + "LeftHandMiddle1": { + "rotations": { + "x": 0.0012630895944312215, + "y": 0.01761218160390854, + "z": 0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "LeftHandMiddle2": { + "rotations": { + "x": 2.37378529277521e-7, + "y": -0.00002031277836067602, + "z": -0.011195233091711998, + "w": 0.9999373555183411 + } + }, + "LeftHandMiddle3": { + "rotations": { + "x": 7.146466884933034e-8, + "y": -4.7555812443533796e-7, + "z": -0.0023767934180796146, + "w": 0.9999971985816956 + } + }, + "LeftHandMiddle4": { + "rotations": { + "x": 6.549178976911207e-8, + "y": -3.865041975359418e-8, + "z": 0.005236904136836529, + "w": 0.9999862909317017 + } + }, + "LeftHandIndex1": { + "rotations": { + "x": 0.002164299599826336, + "y": 0.017346171662211418, + "z": 0.12158433347940445, + "w": 0.9924271702766418 + } + }, + "LeftHandIndex2": { + "rotations": { + "x": -0.000001437780269952782, + "y": 0.00016147761198226362, + "z": -0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "LeftHandIndex3": { + "rotations": { + "x": 7.61426193207626e-8, + "y": -5.373883027459669e-7, + "z": -0.03373908996582031, + "w": 0.999430775642395 + } + }, + "LeftHandIndex4": { + "rotations": { + "x": -5.311697748311417e-10, + "y": 2.26380109324964e-7, + "z": 0.009632255882024765, + "w": 0.9999536275863647 + } + }, + "LeftHandThumb1": { + "rotations": { + "x": -0.07839284837245941, + "y": 0.3033908009529114, + "z": 0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "LeftHandThumb2": { + "rotations": { + "x": 0.0031029372476041317, + "y": -0.07382386922836304, + "z": -0.005253763869404793, + "w": 0.9972526431083679 + } + }, + "LeftHandThumb3": { + "rotations": { + "x": 0.004044072702527046, + "y": 0.049435727298259735, + "z": -0.0072460174560546875, + "w": 0.9987428188323975 + } + }, + "LeftHandThumb4": { + "rotations": { + "x": -0.00009280881931772456, + "y": 0.016580356284976006, + "z": 0.00014314628788270056, + "w": 0.999862551689148 + } + } +} diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx new file mode 100644 index 0000000000000000000000000000000000000000..a3830b983835fa48e521e24c35abdaed5531db59 GIT binary patch literal 142012 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 literal 0 HcmV?d00001 diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx new file mode 100644 index 0000000000000000000000000000000000000000..1877e963f9e2538c77739e6766b8c9878ac4bdf9 GIT binary patch literal 142028 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 Hkvi`$Js~Wt literal 0 HcmV?d00001 diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js new file mode 100644 index 0000000000..a32937d6dd --- /dev/null +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -0,0 +1,56 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 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 +*/ + +// Entry Script for the clap app + +// Load up engine +var APP_NAME = "CLAP"; +var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v6")); +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +// Define Menu +var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg"); +var whiteIcon =Script.resolvePath("icons/tablet-icons/clap-i.svg"); +var isActive = true; + +var activeButton = tablet.addButton({ + icon: whiteIcon, + activeIcon: blackIcon, + text: APP_NAME, + isActive: isActive, + sortOrder: 11 +}); + +if (isActive) { + ClapEngine.connectEngine(); +} + +function onClick(enabled) { + + isActive = !isActive; + activeButton.editProperties({ + isActive: isActive + }); + if (isActive) { + ClapEngine.connectEngine(); + } else { + ClapEngine.disconnectEngine(); + } +} +activeButton.clicked.connect(onClick); + +Script.scriptEnding.connect(function () { + ClapEngine.disconnectEngine(); + activeButton.clicked.disconnect(onClick); + tablet.removeButton(activeButton); +}); diff --git a/unpublishedScripts/marketplace/clap/entities/ClapParticle.json b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json new file mode 100644 index 0000000000..bf1b70665b --- /dev/null +++ b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json @@ -0,0 +1,58 @@ +{ + "alpha": 0.01, + "alphaFinish": 0.0, + "alphaSpread": 1, + "alphaStart": 0.05, + "color": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorFinish": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorStart": { + "blue": 200, + "green": 200, + "red": 200 + }, + "created": "2017-09-09T16:01:38Z", + "dimensions": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "x": 0.25, + "y": 0.25, + "z": 0.25 + }, + "emitOrientation": { + "w": 1, + "x": 0, + "y": 0, + "z": 0 + }, + "emitRate": 100, + "emitSpeed": 0.125, + "lifespan": 0.5, + "lifetime": 2, + "script": "(function(){return{preload:function(id){Script.setTimeout(function(){Entities.editEntity(id,{isEmitting:false});},200);}}})", + "maxParticles": 100, + "particleRadius": 0.05, + "polarFinish": 1.4311699867248535, + "polarStart": 1.3962633609771729, + "radiusFinish": 0.01, + "radiusSpread": 0.2, + "radiusStart": 0.05, + "speedSpread": 0, + "emitShouldTrail": true, + "type": "ParticleEffect" +} diff --git a/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg b/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg new file mode 100644 index 0000000000..10a8e3ea98 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg b/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg new file mode 100644 index 0000000000..3e4ecb0b64 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js new file mode 100644 index 0000000000..247664c157 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js @@ -0,0 +1,165 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/scripts/clapDebugger.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 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 +*/ + +var DEBUG_RIGHT_HAND; +var DEBUG_LEFT_HAND; +var DEBUG_CLAP_LEFT; +var DEBUG_CLAP_RIGHT; +var DEBUG_CLAP; +var DEBUG_CLAP_DIRECTION; + +// Debug Values: +var DEBUG_CORRECT = { + red: 0, + green: 255, + blue: 0 +}; +var DEBUG_WRONG = { + red: 255, + green: 0, + blue: 0 +}; + +var DEBUG_VOLUME = { + red: 255, + green: 255, + blue: 128 +}; + +module.exports = { + disableDebug: function () { + Overlays.deleteOverlay(DEBUG_RIGHT_HAND); + Overlays.deleteOverlay(DEBUG_LEFT_HAND); + Overlays.deleteOverlay(DEBUG_CLAP_LEFT); + Overlays.deleteOverlay(DEBUG_CLAP_RIGHT); + Overlays.deleteOverlay(DEBUG_CLAP); + Overlays.deleteOverlay(DEBUG_CLAP_DIRECTION); + }, + + debugPositions: function (leftAlignmentWorld, leftHandPositionOffset, leftHandDownWorld, rightAlignmentWorld, rightHandPositionOffset, rightHandDownWorld, tolerance) { + + Overlays.editOverlay(DEBUG_CLAP_LEFT, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: leftHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_CLAP_RIGHT, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: rightHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_LEFT_HAND, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: leftHandPositionOffset, + end: Vec3.sum(leftHandPositionOffset, Vec3.multiply(leftHandDownWorld, 0.2)) + }); + + Overlays.editOverlay(DEBUG_RIGHT_HAND, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: rightHandPositionOffset, + end: Vec3.sum(rightHandPositionOffset, Vec3.multiply(rightHandDownWorld, 0.2)) + }); + }, + + debugClapLine: function (start, end, visible) { + Overlays.editOverlay(DEBUG_CLAP_DIRECTION, { + start: start, + end: end, + visible: visible + }); + }, + + clapSphere: function (pos, vol) { + Overlays.editOverlay(DEBUG_CLAP, { + position: pos, + scale: { + x: vol, + y: vol, + z: vol + } + }); + }, + + enableDebug: function () { + DEBUG_RIGHT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_LEFT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_CLAP_LEFT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + DEBUG_CLAP_RIGHT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + DEBUG_CLAP = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_VOLUME, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + DEBUG_CLAP_DIRECTION = Overlays.addOverlay("line3d", { + color: DEBUG_VOLUME, + start: MyAvatar.position, + end: MyAvatar.position, + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + } +}; diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js new file mode 100644 index 0000000000..f36aa9716b --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -0,0 +1,318 @@ +"use strict"; + +/* + clapEngine.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 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 + + + Main Heart of the clap script> Does both keyboard binding and tracking of the gear.. + +*/ +var DEG_TO_RAD = Math.PI / 180; +// If angle is closer to 0 from 25 degrees, then "clap" can happen; +var COS_OF_TOLERANCE = Math.cos(25 * DEG_TO_RAD); + +var CONTROL_MAP_PACKAGE = "com.highfidelity.avatar.clap.active"; + +var CLAP_MENU = "Clap"; +var ENABLE_PARTICLE_MENU = "Enable Clap Particles"; +var ENABLE_DEBUG_MENU = "Enable Clap Debug"; + +var sounds = [ + "clap1.wav", + "clap2.wav", + "clap3.wav", + "clap4.wav", + "clap5.wav", + "clap6.wav" +]; + + +var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V2")); +var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V2")); +var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V2")); + +var settingDebug = false; +var settingParticlesOn = true; + +function setJointRotation(map) { + Object.keys(map).forEach(function (key, index) { + MyAvatar.setJointRotation(MyAvatar.getJointIndex(key), map[key].rotations); + }); +} +// Load Sounds to Cache +var cache = []; +for (var index in sounds) { + cache.push(SoundCache.getSound(Script.resolvePath("../sounds/" + sounds[index]))); +} + + +var previousIndex; +var clapOn = false; +var animClap = false; +var animThrottle; + +function clap(volume, position, rotation) { + var index; + // Make sure one does not generate consequtive sounds + do { + index = Math.floor(Math.random() * cache.length); + } while (index === previousIndex); + + previousIndex = index; + + Audio.playSound(cache[index], { + position: position, + volume: volume / 4 + Math.random() * (volume / 3) + }); + + if (settingParticlesOn) { + ClapParticle.orientation = Quat.multiply(MyAvatar.orientation, rotation); + ClapParticle.position = position; + ClapParticle.emitSpeed = volume > 1 ? 1 : volume; + Entities.addEntity(ClapParticle, true); + } + + + if (settingDebug) { + ClapDebugger.clapSphere(position, volume); + } + +} +// Helper Functions +function getHandFingerAnim(side) { + return Script.resolvePath("../animations/Clap_" + side + '.fbx'); +} + +// Disable all role animations related to fingers for side +function overrideFingerRoleAnimation(side) { + var anim = getHandFingerAnim(side); + MyAvatar.overrideRoleAnimation(side + "HandGraspOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandGraspClosed", anim, 30, true, 0, 0); + if (HMD.active) { + MyAvatar.overrideRoleAnimation(side + "HandPointIntro", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointHold", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointOutro", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "IndexPointOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointClosed", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseClosed", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseClosed", anim, 30, true, 0, 0); + } +} +// Re-enable all role animations for fingers +function restoreFingerRoleAnimation(side) { + MyAvatar.restoreRoleAnimation(side + "HandGraspOpen"); + MyAvatar.restoreRoleAnimation(side + "HandGraspClosed"); + if (HMD.active) { + MyAvatar.restoreRoleAnimation(side + "HandPointIntro"); + MyAvatar.restoreRoleAnimation(side + "HandPointHold"); + MyAvatar.restoreRoleAnimation(side + "HandPointOutro"); + MyAvatar.restoreRoleAnimation(side + "IndexPointOpen"); + + MyAvatar.restoreRoleAnimation(side + "IndexPointClosed"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseClosed"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseClosed"); + } +} + + +function menuListener(menuItem) { + if (menuItem === ENABLE_PARTICLE_MENU) { + settingParticlesOn = Menu.isOptionChecked(ENABLE_PARTICLE_MENU); + } else if (menuItem === ENABLE_DEBUG_MENU) { + var debugOn = Menu.isOptionChecked(ENABLE_DEBUG_MENU); + + if (debugOn) { + settingDebug = true; + ClapDebugger.enableDebug(); + } else { + settingDebug = false; + ClapDebugger.disableDebug(); + } + } +} + +function update(dt) { + + // NOTICE: Someof this stuff is unnessary for the actual: But they are done for Debug Purposes! + // Forexample, the controller doesnt really need to know where it is in the world, only its relation to the other controller! + + var leftHand = Controller.getPoseValue(Controller.Standard.LeftHand); + var rightHand = Controller.getPoseValue(Controller.Standard.RightHand); + + // Get Offset position for palms, not the controllers that are at the wrists (7.5 cm up) + + var leftWorldRotation = Quat.multiply(MyAvatar.orientation, leftHand.rotation); + var rightWorldRotation = Quat.multiply(MyAvatar.orientation, rightHand.rotation); + + var leftWorldUpNormal = Quat.getUp(leftWorldRotation); + var rightWorldUpNormal = Quat.getUp(rightWorldRotation); + + var leftHandDownWorld = Vec3.multiply(-1, Quat.getForward(leftWorldRotation)); + var rightHandDownWorld = Vec3.multiply(-1, Quat.getForward(rightWorldRotation)); + + // + var leftHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, leftHand.translation)); + var rightHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, rightHand.translation)); + + var rightHandPositionOffset = Vec3.sum(rightHandWorldPosition, Vec3.multiply(rightWorldUpNormal, 0.035)); + var leftHandPositionOffset = Vec3.sum(leftHandWorldPosition, Vec3.multiply(leftWorldUpNormal, 0.035)); + + var leftToRightWorld = Vec3.subtract(leftHandPositionOffset, rightHandPositionOffset); + var rightToLeftWorld = Vec3.subtract(rightHandPositionOffset, leftHandPositionOffset); + + var leftAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(leftToRightWorld), leftHandDownWorld); + var rightAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(rightToLeftWorld), rightHandDownWorld); + + var distance = Vec3.distance(rightHandPositionOffset, leftHandPositionOffset); + + var matchTolerance = leftAlignmentWorld > COS_OF_TOLERANCE && rightAlignmentWorld > COS_OF_TOLERANCE; + + if (settingDebug) { + ClapDebugger.debugPositions(leftAlignmentWorld, + leftHandPositionOffset, leftHandDownWorld, + rightAlignmentWorld, rightHandPositionOffset, + rightHandDownWorld, COS_OF_TOLERANCE); + } + // Using subtract, because these will be heading to opposite directions + var angularVelocity = Vec3.length(Vec3.subtract(leftHand.angularVelocity, rightHand.angularVelocity)); + var velocity = Vec3.length(Vec3.subtract(leftHand.velocity, rightHand.velocity)); + + if (matchTolerance && distance < 0.3 && !animClap) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, true); + } + if (!animThrottle) { + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + animClap = true; + } else { + Script.clearTimeout(animThrottle); + animThrottle = false; + } + } else if (animClap && distance > 0.3) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, false); + } + animThrottle = Script.setTimeout(function () { + restoreFingerRoleAnimation("left"); + restoreFingerRoleAnimation("right"); + animClap = false; + }, 500); + } + + if (distance < 0.22 && matchTolerance && !clapOn) { + clapOn = true; + + var midClap = Vec3.mix(rightHandPositionOffset, leftHandPositionOffset, 0.5); + var volume = velocity / 2 + angularVelocity / 5; + + clap(volume, midClap, Quat.lookAtSimple(rightHandPositionOffset, leftHandPositionOffset)); + } else if (distance > 0.22 && !matchTolerance) { + clapOn = false; + } +} + + +module.exports = { + connectEngine: function () { + if (!Menu.menuExists(CLAP_MENU)) { + Menu.addMenu(CLAP_MENU); + } + + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_PARTICLE_MENU, + isCheckable: true, + isChecked: settingParticlesOn + }); + } + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_DEBUG_MENU, + isCheckable: true, + isChecked: settingDebug + }); + } + + + Menu.menuItemEvent.connect(menuListener); + + var controls = Controller.newMapping(CONTROL_MAP_PACKAGE); + var Keyboard = Controller.Hardware.Keyboard; + + controls.from(Keyboard.K).to(function (down) { + if (down) { + + setJointRotation(ClapAnimation); + Script.setTimeout(function () { + // As soon as an animation bug is fixed, this will kick and get fixed.s. + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + + var lh = MyAvatar.getJointPosition("LeftHand"); + var rh = MyAvatar.getJointPosition("RightHand"); + var midClap = Vec3.mix(rh, lh, 0.5); + var volume = 0.5 + Math.random() * 0.5; + var position = midClap; + + clap(volume, position, Quat.fromVec3Degrees(0, 0, 0)); + }, 50); // delay is present to allow for frames to catch up. + } else { + + restoreFingerRoleAnimation("left"); + + restoreFingerRoleAnimation("right"); + MyAvatar.clearJointsData(); + } + }); + Controller.enableMapping(CONTROL_MAP_PACKAGE); + + // settingDebug STUFF + if (settingDebug) { + ClapDebugger.enableDebug(); + } + + + Script.update.connect(update); + + Script.scriptEnding.connect(this.disconnectEngine); + }, + disconnectEngine: function () { + if (settingDebug) { + ClapDebugger.disableDebug(); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_PARTICLE_MENU); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU); + } + + if (Menu.menuExists(CLAP_MENU)) { + Menu.removeMenu(CLAP_MENU); + } + restoreFingerRoleAnimation('left'); + restoreFingerRoleAnimation('right'); + Controller.disableMapping(CONTROL_MAP_PACKAGE); + try { + Script.update.disconnect(update); + } catch (e) { + print("Script.update connection did not exist on disconnection. Skipping") + } + } +}; diff --git a/unpublishedScripts/marketplace/clap/sounds/clap1.wav b/unpublishedScripts/marketplace/clap/sounds/clap1.wav new file mode 100644 index 0000000000000000000000000000000000000000..679cb7e7321078ad4597824d1eb6f65954ccee76 GIT binary patch literal 33170 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 literal 0 HcmV?d00001 diff --git a/unpublishedScripts/marketplace/clap/sounds/clap4.wav b/unpublishedScripts/marketplace/clap/sounds/clap4.wav new file mode 100644 index 0000000000000000000000000000000000000000..4e8dfca1a029b8b3debfd8969d7a8c60d73b0a6b GIT binary patch literal 21700 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ĖD9;L z_ZxoBl4+9R>#b_s9jsX3xjDc*nG-jel7d^%^~b}8Z;!%;?*!uGeAw`vk=yY5gR_Qz zHW)ShfgLpbu@}Cywd0w`ea%L_PDMAVTc*O-S4qJsyA=#v za&Sz3tnX?{UMJNfNvfD(;z*Fwf(4$FxX$|3qhwpucky?vVKF1wmi*bshc&pM6TZNV z*i(}6f1kZ;ebEeOQxaQHd8vpZDH0SaA21aC9hQD=%_kCyh-RE*{Da0}7b1RYaaaYQ ztB7rBMUk%ZPZBM8zy2QmG%46(xZX+ z3q(&PPK<=g*Zw&C2>{>^ymQf4eF*?~w2cL#5fk%^maDp+4aCoAfauMtYkzQ7-=7bw Q>(~pA0C=B8+Zt@}H^E-{)Bpeg literal 0 HcmV?d00001 diff --git a/unpublishedScripts/marketplace/clap/sounds/clap5.wav b/unpublishedScripts/marketplace/clap/sounds/clap5.wav new file mode 100644 index 0000000000000000000000000000000000000000..1f38864db48298fce75afd26b5c5c1a24ac3aee4 GIT binary patch literal 28942 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 literal 0 HcmV?d00001 diff --git a/unpublishedScripts/marketplace/clap/sounds/clap6.wav b/unpublishedScripts/marketplace/clap/sounds/clap6.wav new file mode 100644 index 0000000000000000000000000000000000000000..4591f79d9d4420503dd45afe2855835e47324ebd GIT binary patch literal 25992 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 Date: Mon, 11 Sep 2017 15:07:14 -0700 Subject: [PATCH 59/78] fixed teleport, added avatar scaling, allow laser to be used on tablet when stylus is shown --- .../controllerModules/farActionGrabEntity.js | 1 + .../controllerModules/inEditMode.js | 11 +++ .../controllerModules/scaleAvatar.js | 84 +++++++++++++++++++ .../controllerModules/tabletStylusInput.js | 12 ++- .../controllers/controllerModules/teleport.js | 29 ++++++- .../system/controllers/controllerScripts.js | 3 +- .../libraries/controllerDispatcherUtils.js | 11 +++ 7 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/scaleAvatar.js diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 3355901e6e..9a6f39c490 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -439,6 +439,7 @@ Script.include("/~/system/libraries/controllers.js"); // gather up the readiness of the near-grab modules var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index cf82de1820..916487277a 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -209,6 +209,15 @@ Script.include("/~/system/libraries/utils.js"); return this.exitModule(); } } + + var teleport = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter"); + if (teleport) { + var teleportReady = teleport.isReady(controllerData); + if (teleportReady.active) { + return this.exitModule(); + } + } + this.processControllerTriggers(controllerData); this.updateLaserPointer(controllerData); this.sendPickData(controllerData); @@ -251,4 +260,6 @@ Script.include("/~/system/libraries/utils.js"); disableDispatcherModule("LeftHandInEditMode"); disableDispatcherModule("RightHandInEditMode"); }; + + Script.scriptEnding.connect(this.cleanup); }()); diff --git a/scripts/system/controllers/controllerModules/scaleAvatar.js b/scripts/system/controllers/controllerModules/scaleAvatar.js new file mode 100644 index 0000000000..2c85b75ea9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/scaleAvatar.js @@ -0,0 +1,84 @@ +// handControllerGrab.js +// +// Created by Dante Ruiz on 9/11/17 +// +// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, + Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, + setGrabCommunications, Menu, HMD, isInEditMode, AvatarList */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +//Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +(function () { + var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); + + function ScaleAvatar(hand) { + this.hand = hand; + this.scalingStartAvatarScale = 0; + this.scalingStartDistance = 0; + + this.parameters = dispatcherUtils.makeDispatcherModuleParameters( + 120, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100 + ); + + this.otherHand = function() { + return this.hand === dispatcherUtils.RIGHT_HAND ? dispatcherUtils.LEFT_HAND : dispatcherUtils.RIGHT_HAND; + }; + + this.getOtherModule = function() { + var otherModule = this.hand === dispatcherUtils.RIGHT_HAND ? leftScaleAvatar : rightScaleAvatar; + return otherModule; + }; + + this.triggersPressed = function(controllerData) { + if (controllerData.triggerValues[this.hand] === 1 && controllerData.secondaryValues[this.hand] === 1) { + return true; + } + return false; + }; + + this.isReady = function(controllerData) { + var otherModule = this.getOtherModule(); + if (this.triggersPressed(controllerData) && otherModule.triggersPressed(controllerData)) { + this.scalingStartAvatarScale = MyAvatar.scale; + this.scalingStartDistance = Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position, + controllerData.controllerLocations[this.otherHand()].position)); + return dispatcherUtils.makeRunningValues(true, [], []); + } + return dispatcherUtils.makeRunningValues(false, [], []); + }; + + this.run = function(controllerData) { + var otherModule = this.getOtherModule(); + if (this.triggersPressed(controllerData) && otherModule.triggersPressed(controllerData)) { + if (this.hand === dispatcherUtils.RIGHT_HAND) { + var scalingCurrentDistance = Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position, + controllerData.controllerLocations[this.otherHand()].position)); + + var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale; + MyAvatar.scale = newAvatarScale; + } + return dispatcherUtils.makeRunningValues(true, [], []); + } + return dispatcherUtils.makeRunningValues(false, [], []); + }; + } + + var leftScaleAvatar = new ScaleAvatar(dispatcherUtils.LEFT_HAND); + var rightScaleAvatar = new ScaleAvatar(dispatcherUtils.RIGHT_HAND); + + dispatcherUtils.enableDispatcherModule("LeftScaleAvatar", leftScaleAvatar); + dispatcherUtils.enableDispatcherModule("RightScaleAvatar", rightScaleAvatar); + + this.cleanup = function() { + dispatcherUtils.disableDispatcherModule("LeftScaleAvatar"); + dispatcherUtils.disableDispatcherModule("RightScaleAvatar"); + }; +})(); diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 8a1262bac0..0d4f4ff78a 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllers.js"); var HAPTIC_STYLUS_STRENGTH = 1.0; var HAPTIC_STYLUS_DURATION = 20.0; - var WEB_DISPLAY_STYLUS_DISTANCE = 0.1; + var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; var WEB_STYLUS_LENGTH = 0.2; var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand @@ -474,7 +474,7 @@ Script.include("/~/system/libraries/controllers.js"); this.processStylus = function(controllerData) { this.updateStylusTip(); - if (!this.stylusTip.valid) { + if (!this.stylusTip.valid || this.overlayLaserActive(controllerData)) { this.pointFinger(false); this.hideStylus(); return false; @@ -646,6 +646,14 @@ Script.include("/~/system/libraries/controllers.js"); } }; + this.overlayLaserActive = function(controllerData) { + var overlayLaserModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightOverlayLaserInput" : "LeftOverlayLaserInput"); + if (overlayLaserModule) { + return overlayLaserModule.isReady(controllerData).active; + } + return false; + }; + this.isReady = function (controllerData) { if (this.processStylus(controllerData)) { return makeRunningValues(true, [], []); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index fcf5ea0ed2..9bb3bcb2f9 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -170,6 +170,11 @@ function Teleporter(hand) { this.currentTarget = TARGET.INVALID; this.currentResult = null; + this.getOtherModule = function() { + var otherModule = this.hand === RIGHT_HAND ? leftTeleporter : rightTeleporter; + return otherModule; + }; + this.teleportRayHandVisible = LaserPointers.createLaserPointer({ joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", filter: RayPick.PICK_ENTITIES, @@ -231,15 +236,32 @@ function Teleporter(hand) { [], 100); + this.enterTeleport = function() { + if (coolInTimeout !== null) { + Script.clearTimeout(coolInTimeout); + } + + this.state = TELEPORTER_STATES.COOL_IN; + coolInTimeout = Script.setTimeout(function() { + if (_this.state === TELEPORTER_STATES.COOL_IN) { + _this.state = TELEPORTER_STATES.TARGETTING; + } + }, COOL_IN_DURATION); + }; + + this.isReady = function(controllerData, deltaTime) { - if (_this.buttonValue !== 0) { + var otherModule = this.getOtherModule(); + if (_this.buttonValue !== 0 && !otherModule.active) { + this.active = true; + this.enterTeleport(); return makeRunningValues(true, [], []); } return makeRunningValues(false, [], []); }; this.run = function(controllerData, deltaTime) { - _this.state = TELEPORTER_STATES.TARGETTING; + //_this.state = TELEPORTER_STATES.TARGETTING; // Get current hand pose information to see if the pose is valid var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); @@ -306,7 +328,7 @@ function Teleporter(hand) { return makeRunningValues(true, [], []); } - if (target === TARGET.NONE || target === TARGET.INVALID) { + if (target === TARGET.NONE || target === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) { // Do nothing } else if (target === TARGET.SEAT) { Entities.callEntityMethod(result.objectID, 'sit'); @@ -319,6 +341,7 @@ function Teleporter(hand) { } this.disableLasers(); + this.active = false; return makeRunningValues(false, [], []); }; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index e3375a5a27..3fcc158893 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -28,7 +28,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/inEditMode.js", "controllerModules/disableOtherModule.js", "controllerModules/farTrigger.js", - "controllerModules/teleport.js" + "controllerModules/teleport.js", + "controllerModules/scaleAvatar.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 773f79754d..bc2aa1a9c8 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -301,3 +301,14 @@ findGroupParent = function (controllerData, targetProps) { return targetProps; }; + +if (typeof module !== 'undefined') { + module.exports = { + makeDispatcherModuleParameters: makeDispatcherModuleParameters, + enableDispatcherModule: enableDispatcherModule, + disableDispatcherModule: disableDispatcherModule, + makeRunningValues: makeRunningValues, + LEFT_HAND: LEFT_HAND, + RIGHT_HAND: RIGHT_HAND + }; +} From 5c12403b7ce484804a0df8ff4b7ec5d39b857c52 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 11 Sep 2017 16:40:21 -0700 Subject: [PATCH 60/78] first working pass at single file. Cleanup needed --- interface/src/commerce/Wallet.cpp | 85 +++++++++++++++++++------------ interface/src/commerce/Wallet.h | 2 +- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 9f7c79226d..e2ce3cec94 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -39,7 +40,6 @@ #endif static const char* KEY_FILE = "hifikey"; -static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; @@ -57,10 +57,6 @@ QString keyFilePath() { return PathUtils::getAppDataFilePath(KEY_FILE); } -QString imageFilePath() { - return PathUtils::getAppDataFilePath(IMAGE_FILE); -} - // use the cached _passphrase if it exists, otherwise we need to prompt int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { // just return a hardcoded pwd for now @@ -136,8 +132,9 @@ bool writeKeys(const char* filename, RSA* keys) { // copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. // We will have a different implementation in practice, but this gives us a start for now // -// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably +// TODO: we don't really use the private keys returned - we can see how this evolves, but probably // we should just return a list of public keys? +// or perhaps return the RSA* instead? QPair generateRSAKeypair() { RSA* keyPair = RSA_new(); @@ -287,8 +284,7 @@ void Wallet::setPassphrase(const QString& passphrase) { _publicKeys.clear(); } -// encrypt some stuff -bool Wallet::writeSecurityImageFile(const QString& inputFilePath, const QString& outputFilePath) { +bool Wallet::writeSecurityImageFile(const QPixmap* pixmap, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys @@ -299,16 +295,12 @@ bool Wallet::writeSecurityImageFile(const QString& inputFilePath, const QString& initializeAESKeys(ivec, ckey, _salt); int tempSize, outSize; + QByteArray inputFileBuffer; + QBuffer buffer(&inputFileBuffer); + buffer.open(QIODevice::WriteOnly); - // read entire unencrypted file into memory - QFile inputFile(inputFilePath); - if (!inputFile.exists()) { - qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist"; - return false; - } - inputFile.open(QIODevice::ReadOnly); - QByteArray inputFileBuffer = inputFile.readAll(); - inputFile.close(); + // another spot where we are assuming only jpgs + pixmap->save(&buffer, "jpg"); // reserve enough capacity for encrypted bytes unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE]; @@ -337,8 +329,10 @@ bool Wallet::writeSecurityImageFile(const QString& inputFilePath, const QString& EVP_CIPHER_CTX_free(ctx); qCDebug(commerce) << "encrypted buffer size" << outSize; QByteArray output((const char*)outputFileBuffer, outSize); + + // now APPEND to the file, QFile outputFile(outputFilePath); - outputFile.open(QIODevice::WriteOnly| QIODevice::Text); + outputFile.open(QIODevice::Append); outputFile.write(IMAGE_HEADER); outputFile.write(output.toBase64()); outputFile.write("\n"); @@ -360,7 +354,7 @@ bool Wallet::readSecurityImageFile(const QString& inputFilePath, unsigned char** qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist"; return false; } - inputFile.open(QIODevice::ReadOnly|QIODevice::Text); + inputFile.open(QIODevice::ReadOnly | QIODevice::Text); bool foundHeader = false; bool foundFooter = false; @@ -453,6 +447,9 @@ bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateRSAKeypair(); + + // TODO: redo this soon -- need error checking and so on + writeSecurityImageFile(_securityImage, keyFilePath()); sendKeyFilePathIfExists(); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); @@ -528,17 +525,44 @@ void Wallet::chooseSecurityImage(const QString& filename) { qCDebug(commerce) << "loading data for pixmap from" << path; _securityImage->load(path); - // encrypt it and save. - if (writeSecurityImageFile(path, imageFilePath())) { - qCDebug(commerce) << "emitting pixmap"; - - updateImageProvider(); + // update the image now + updateImageProvider(); + // we could be choosing the _inital_ security image. If so, there + // will be no hifikey file yet. If that is the case, we are done. If + // there _is_ a keyfile, we need to update it (similar to changing the + // passphrase, we need to do so into a temp file and move it). + if (!QFile(keyFilePath()).exists()) { emit securityImageResult(true); - } else { - qCDebug(commerce) << "failed to encrypt security image"; - emit securityImageResult(false); + return; } + + bool success = false; + RSA* keys = readKeys(keyFilePath().toStdString().c_str()); + if (keys) { + QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { + if (writeSecurityImageFile(_securityImage, tempFileName)) { + // ok, now move the temp file to the correct spot + // TODO: error checking here! + QFile(QString(keyFilePath())).remove(); + QFile(tempFileName).rename(QString(keyFilePath())); + qCDebug(commerce) << "passphrase changed successfully"; + updateImageProvider(); + + success = true; + } else { + qCDebug(commerce) << "couldn't write security image to" << tempFileName; + } + } else { + qCDebug(commerce) << "couldn't write keys to" << tempFileName; + } + } else { + qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing"; + setPassphrase(QString("")); + } + + emit securityImageResult(success); } void Wallet::getSecurityImage() { @@ -552,9 +576,8 @@ void Wallet::getSecurityImage() { } // decrypt and return - QString filePath(imageFilePath()); - QFileInfo fileInfo(filePath); - if (fileInfo.exists() && readSecurityImageFile(filePath, &data, &dataLen)) { + QFileInfo fileInfo(keyFilePath()); + if (fileInfo.exists() && readSecurityImageFile(keyFilePath(), &data, &dataLen)) { // create the pixmap _securityImage = new QPixmap(); _securityImage->loadFromData(data, dataLen, "jpg"); @@ -591,9 +614,7 @@ void Wallet::reset() { QFile keyFile(keyFilePath()); - QFile imageFile(imageFilePath()); keyFile.remove(); - imageFile.remove(); } bool Wallet::changePassphrase(const QString& newPassphrase) { diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 798c713bf1..40f6bd1933 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -61,7 +61,7 @@ private: QString* _passphrase { new QString("") }; void updateImageProvider(); - bool writeSecurityImageFile(const QString& inputFilePath, const QString& outputFilePath); + bool writeSecurityImageFile(const QPixmap* pixmap, const QString& outputFilePath); bool readSecurityImageFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; From 3a75dcf84db6ff132d5ffe24813d26b1018a0d97 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 7 Sep 2017 14:32:50 -0700 Subject: [PATCH 61/78] Fix crashes in entity rendering on OSX --- .../RenderableParticleEffectEntityItem.cpp | 93 ++++++++++--------- .../src/RenderableParticleEffectEntityItem.h | 3 +- .../src/RenderableTextEntityItem.cpp | 5 +- .../src/RenderableTextEntityItem.h | 5 +- .../entities/src/ParticleEffectEntityItem.cpp | 15 ++- .../entities/src/ParticleEffectEntityItem.h | 4 +- 6 files changed, 70 insertions(+), 55 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 7349e9147e..3328076911 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -91,20 +91,27 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi qCWarning(entitiesrenderer) << "Bad particle properties"; } } - if (_particleProperties != newParticleProperties) { + + if (resultWithReadLock([&]{ return _particleProperties != newParticleProperties; })) { _timeUntilNextEmit = 0; - _particleProperties = newParticleProperties; + withWriteLock([&]{ + _particleProperties = newParticleProperties; + }); } _emitting = entity->getIsEmitting(); - if (_particleProperties.textures.isEmpty()) { + bool hasTexture = resultWithReadLock([&]{ return _particleProperties.textures.isEmpty(); }); + if (hasTexture) { if (_networkTexture) { withWriteLock([&] { _networkTexture.reset(); }); } } else { - if (!_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures)) { + bool textureNeedsUpdate = resultWithReadLock([&]{ + return !_networkTexture || _networkTexture->getURL() != QUrl(_particleProperties.textures); + }); + if (textureNeedsUpdate) { withWriteLock([&] { _networkTexture = DependencyManager::get()->getTexture(_particleProperties.textures); }); @@ -115,15 +122,17 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { // Fill in Uniforms structure ParticleUniforms particleUniforms; - particleUniforms.radius.start = _particleProperties.radius.range.start; - particleUniforms.radius.middle = _particleProperties.radius.gradient.target; - particleUniforms.radius.finish = _particleProperties.radius.range.finish; - particleUniforms.radius.spread = _particleProperties.radius.gradient.spread; - particleUniforms.color.start = _particleProperties.getColorStart(); - particleUniforms.color.middle = _particleProperties.getColorMiddle(); - particleUniforms.color.finish = _particleProperties.getColorFinish(); - particleUniforms.color.spread = _particleProperties.getColorSpread(); - particleUniforms.lifespan = _particleProperties.lifespan; + withReadLock([&]{ + particleUniforms.radius.start = _particleProperties.radius.range.start; + particleUniforms.radius.middle = _particleProperties.radius.gradient.target; + particleUniforms.radius.finish = _particleProperties.radius.range.finish; + particleUniforms.radius.spread = _particleProperties.radius.gradient.spread; + particleUniforms.color.start = _particleProperties.getColorStart(); + particleUniforms.color.middle = _particleProperties.getColorMiddle(); + particleUniforms.color.finish = _particleProperties.getColorFinish(); + particleUniforms.color.spread = _particleProperties.getColorSpread(); + particleUniforms.lifespan = _particleProperties.lifespan; + }); // Update particle uniforms memcpy(&_uniformBuffer.edit(), &particleUniforms, sizeof(ParticleUniforms)); } @@ -146,35 +155,26 @@ Item::Bound ParticleEffectEntityRenderer::getBound() { static const size_t VERTEX_PER_PARTICLE = 4; -bool ParticleEffectEntityRenderer::emitting() const { - return ( - _emitting && - _particleProperties.emission.rate > 0.0f && - _particleProperties.lifespan > 0.0f && - _particleProperties.polar.start <= _particleProperties.polar.finish - ); -} - -void ParticleEffectEntityRenderer::createParticle(uint64_t now) { +ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties) { CpuParticle particle; - const auto& accelerationSpread = _particleProperties.emission.acceleration.spread; - const auto& azimuthStart = _particleProperties.azimuth.start; - const auto& azimuthFinish = _particleProperties.azimuth.finish; - const auto& emitDimensions = _particleProperties.emission.dimensions; - const auto& emitAcceleration = _particleProperties.emission.acceleration.target; - auto emitOrientation = _particleProperties.emission.orientation; - const auto& emitRadiusStart = glm::max(_particleProperties.radiusStart, EPSILON); // Avoid math complications at center - const auto& emitSpeed = _particleProperties.emission.speed.target; - const auto& speedSpread = _particleProperties.emission.speed.spread; - const auto& polarStart = _particleProperties.polar.start; - const auto& polarFinish = _particleProperties.polar.finish; + const auto& accelerationSpread = particleProperties.emission.acceleration.spread; + const auto& azimuthStart = particleProperties.azimuth.start; + const auto& azimuthFinish = particleProperties.azimuth.finish; + const auto& emitDimensions = particleProperties.emission.dimensions; + const auto& emitAcceleration = particleProperties.emission.acceleration.target; + auto emitOrientation = particleProperties.emission.orientation; + const auto& emitRadiusStart = glm::max(particleProperties.radiusStart, EPSILON); // Avoid math complications at center + const auto& emitSpeed = particleProperties.emission.speed.target; + const auto& speedSpread = particleProperties.emission.speed.spread; + const auto& polarStart = particleProperties.polar.start; + const auto& polarFinish = particleProperties.polar.finish; particle.seed = randFloatInRange(-1.0f, 1.0f); - particle.expiration = now + (uint64_t)(_particleProperties.lifespan * USECS_PER_SECOND); - if (_particleProperties.emission.shouldTrail) { - particle.position = _modelTransform.getTranslation(); - emitOrientation = _modelTransform.getRotation() * emitOrientation; + particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND); + if (particleProperties.emission.shouldTrail) { + particle.position = baseTransform.getTranslation(); + emitOrientation = baseTransform.getRotation() * emitOrientation; } // Position, velocity, and acceleration @@ -232,7 +232,7 @@ void ParticleEffectEntityRenderer::createParticle(uint64_t now) { particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; } - _cpuParticles.push_back(particle); + return particle; } void ParticleEffectEntityRenderer::stepSimulation() { @@ -244,14 +244,19 @@ void ParticleEffectEntityRenderer::stepSimulation() { const auto now = usecTimestampNow(); const auto interval = std::min(USECS_PER_SECOND / 60, now - _lastSimulated); _lastSimulated = now; + + particle::Properties particleProperties; + withReadLock([&]{ + particleProperties = _particleProperties; + }); - if (emitting()) { - uint64_t emitInterval = (uint64_t)(USECS_PER_SECOND / _particleProperties.emission.rate); - if (interval >= _timeUntilNextEmit) { + if (_emitting && particleProperties.emitting()) { + uint64_t emitInterval = particleProperties.emitIntervalUsecs(); + if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; while (timeRemaining > _timeUntilNextEmit) { // emit particle - createParticle(now); + _cpuParticles.push_back(createParticle(now, _modelTransform, particleProperties)); _timeUntilNextEmit = emitInterval; if (emitInterval < timeRemaining) { timeRemaining -= emitInterval; @@ -263,7 +268,7 @@ void ParticleEffectEntityRenderer::stepSimulation() { } // Kill any particles that have expired or are over the max size - while (_cpuParticles.size() > _particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration <= now)) { + while (_cpuParticles.size() > particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration <= now)) { _cpuParticles.pop_front(); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index e4c1d4be13..be2641c0c9 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -93,9 +93,8 @@ private: }; - void createParticle(uint64_t now); + static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); void stepSimulation(); - bool emitting() const; particle::Properties _particleProperties; CpuParticles _cpuParticles; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index d39139257b..8757bcbb0f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -30,14 +30,11 @@ TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) : } - -void TextEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { +TextEntityRenderer::~TextEntityRenderer() { auto geometryCache = DependencyManager::get(); if (_geometryID && geometryCache) { geometryCache->releaseID(_geometryID); } - delete _textRenderer; - _textRenderer = nullptr; } bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 1d8227069e..b0a72cf253 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -24,14 +24,13 @@ class TextEntityRenderer : public TypedEntityRenderer { using Pointer = std::shared_ptr; public: TextEntityRenderer(const EntityItemPointer& entity); - + ~TextEntityRenderer(); private: - virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; int _geometryID{ 0 }; - TextRenderer3D* _textRenderer; + std::shared_ptr _textRenderer; bool _faceCamera; glm::vec3 _dimensions; glm::vec3 _textColor; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index bc6830e1a7..9bbf4323da 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -104,7 +104,7 @@ bool operator!=(const Properties& a, const Properties& b) { return !(a == b); } -bool particle::Properties::valid() const { +bool Properties::valid() const { if (glm::any(glm::isnan(emission.orientation))) { qCWarning(entities) << "Bad particle data"; return false; @@ -133,6 +133,19 @@ bool particle::Properties::valid() const { (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)); } +bool Properties::emitting() const { + return emission.rate > 0.0f && lifespan > 0.0f && polar.start <= polar.finish; + +} + +uint64_t Properties::emitIntervalUsecs() const { + if (emission.rate > 0.0f) { + return (uint64_t)(USECS_PER_SECOND / emission.rate); + } + return 0; +} + + EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity { new ParticleEffectEntityItem(entityID) }; entity->setProperties(properties); diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 6a3791f77e..9c0fd0ef95 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -160,7 +160,9 @@ namespace particle { Properties() {}; Properties(const Properties& other) { *this = other; } bool valid() const; - + bool emitting() const; + uint64_t emitIntervalUsecs() const; + Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; From 0f465da92f2c684bcd9836118ca3e679c3ade064 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 7 Sep 2017 14:45:35 -0700 Subject: [PATCH 62/78] Various Mac fixes --- interface/src/main.cpp | 23 ++++++++++++++----- .../src/EntityTreeRenderer.cpp | 7 +++++- libraries/gl/src/gl/GLHelpers.cpp | 7 ++++-- libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 12 ++++++---- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 11 +++++---- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 503daa177d..cb90160cfe 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -144,25 +144,33 @@ int main(int argc, const char* argv[]) { #endif } + + // FIXME this method of checking the OpenGL version screws up the `QOpenGLContext::globalShareContext()` value, which in turn + // leads to crashes when creating the real OpenGL instance. Disabling for now until we come up with a better way of checking + // the GL version on the system without resorting to creating a full Qt application +#if 0 // Check OpenGL version. // This is done separately from the main Application so that start-up and shut-down logic within the main Application is // not made more complicated than it already is. - bool override = false; + bool overrideGLCheck = false; + QJsonObject glData; { OpenGLVersionChecker openGLVersionChecker(argc, const_cast(argv)); bool valid = true; - glData = openGLVersionChecker.checkVersion(valid, override); + glData = openGLVersionChecker.checkVersion(valid, overrideGLCheck); if (!valid) { - if (override) { + if (overrideGLCheck) { auto glVersion = glData["version"].toString(); - qCDebug(interfaceapp, "Running on insufficient OpenGL version: %s.", glVersion.toStdString().c_str()); + qCWarning(interfaceapp, "Running on insufficient OpenGL version: %s.", glVersion.toStdString().c_str()); } else { - qCDebug(interfaceapp, "Early exit due to OpenGL version."); + qCWarning(interfaceapp, "Early exit due to OpenGL version."); return 0; } } } +#endif + // Debug option to demonstrate that the client's local time does not // need to be in sync with any other network node. This forces clock @@ -223,8 +231,9 @@ int main(int argc, const char* argv[]) { Application app(argcExtended, const_cast(argvExtended.data()), startupTime, runningMarkerExisted); +#if 0 // If we failed the OpenGLVersion check, log it. - if (override) { + if (overrideGLcheck) { auto accountManager = DependencyManager::get(); if (accountManager->isLoggedIn()) { UserActivityLogger::getInstance().insufficientGLVersion(glData); @@ -238,6 +247,8 @@ int main(int argc, const char* argv[]) { }); } } +#endif + // Setup local server QLocalServer server { &app }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9f4a9a3c4a..d754d59721 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -91,7 +91,12 @@ void entitiesScriptEngineDeleter(ScriptEngine* engine) { }; // Wait for the scripting thread from the thread pool to avoid hanging the main thread - QThreadPool::globalInstance()->start(new WaitRunnable(engine)); + auto threadPool = QThreadPool::globalInstance(); + if (threadPool) { + threadPool->start(new WaitRunnable(engine)); + } else { + delete engine; + } } void EntityTreeRenderer::resetEntitiesScriptEngine() { diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index ab91ca0902..28982703dd 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -40,8 +40,11 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { int glVersionToInteger(QString glVersion) { QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); - int majorNumber = versionParts[0].toInt(); - int minorNumber = versionParts[1].toInt(); + int majorNumber = 0, minorNumber = 0; + if (versionParts.size() >= 2) { + majorNumber = versionParts[0].toInt(); + minorNumber = versionParts[1].toInt(); + } return (majorNumber << 16) | minorNumber; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 7758ddaf49..943b8148ef 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -467,12 +467,14 @@ void GLVariableAllocationSupport::updateMemoryPressure() { _demoteQueue = WorkQueue(); // Populate the existing textures into the queue - for (const auto& texture : strongTextures) { - // Race conditions can still leave nulls in the list, so we need to check - if (!texture) { - continue; + if (_memoryPressureState != MemoryPressureState::Idle) { + for (const auto& texture : strongTextures) { + // Race conditions can still leave nulls in the list, so we need to check + if (!texture) { + continue; + } + addToWorkQueue(texture); } - addToWorkQueue(texture); } } } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 84466f41b0..11790041db 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -528,6 +528,12 @@ void OffscreenQmlSurface::create() { connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // acquireEngine interrogates the GL context, so we need to have the context current here + if (!_canvas->makeCurrent()) { + qFatal("Failed to make context current for QML Renderer"); + return; + } + // Create a QML engine. auto qmlEngine = acquireEngine(_quickWindow); @@ -540,11 +546,6 @@ void OffscreenQmlSurface::create() { // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext)); - - if (!_canvas->makeCurrent()) { - qWarning("Failed to make context current for QML Renderer"); - return; - } _renderControl->initialize(_canvas->getContext()); // When Quick says there is a need to render, we will not render immediately. Instead, From 45a2c3d5d9d1b4a7a6762e36fa0e8f9724a995f2 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 8 Sep 2017 11:36:32 -0700 Subject: [PATCH 63/78] Modifying SDL initialization due to crash on OSX --- plugins/hifiSdl2/src/SDL2Manager.cpp | 98 +++++++++++++++------------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 021cb4dfec..a5376af24e 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -54,49 +54,6 @@ SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { } void SDL2Manager::init() { - loadSettings(); - - auto preferences = DependencyManager::get(); - static const QString SDL2_PLUGIN { "Game Controller" }; - { - auto getter = [this]()->bool { return _isEnabled; }; - auto setter = [this](bool value) { - _isEnabled = value; - saveSettings(); - }; - auto preference = new CheckPreference(SDL2_PLUGIN, "Enabled", getter, setter); - preferences->addPreference(preference); - } - - bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == 0); - - if (initSuccess) { - int joystickCount = SDL_NumJoysticks(); - - for (int i = 0; i < joystickCount; i++) { - SDL_GameController* controller = SDL_GameControllerOpen(i); - - if (controller) { - SDL_JoystickID id = getInstanceId(controller); - if (!_openJoysticks.contains(id)) { - //Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); - Joystick::Pointer joystick = std::make_shared(id, controller); - _openJoysticks[id] = joystick; - auto userInputMapper = DependencyManager::get(); - userInputMapper->registerDevice(joystick); - auto name = SDL_GameControllerName(controller); - _subdeviceNames << name; - emit joystickAdded(joystick.get()); - emit subdeviceConnected(getName(), name); - } - } - } - - _isInitialized = true; - } - else { - qDebug() << "Error initializing SDL2 Manager"; - } } QStringList SDL2Manager::getSubdeviceNames() { @@ -110,8 +67,61 @@ void SDL2Manager::deinit() { } bool SDL2Manager::activate() { + + // FIXME for some reason calling this code in the `init` function triggers a crash + // on OSX in PR builds, but not on my local debug build. Attempting a workaround by + // + static std::once_flag once; + std::call_once(once, [&]{ + loadSettings(); + + auto preferences = DependencyManager::get(); + static const QString SDL2_PLUGIN { "Game Controller" }; + { + auto getter = [this]()->bool { return _isEnabled; }; + auto setter = [this](bool value) { + _isEnabled = value; + saveSettings(); + }; + auto preference = new CheckPreference(SDL2_PLUGIN, "Enabled", getter, setter); + preferences->addPreference(preference); + } + + bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == 0); + + if (initSuccess) { + int joystickCount = SDL_NumJoysticks(); + + for (int i = 0; i < joystickCount; i++) { + SDL_GameController* controller = SDL_GameControllerOpen(i); + + if (controller) { + SDL_JoystickID id = getInstanceId(controller); + if (!_openJoysticks.contains(id)) { + //Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + Joystick::Pointer joystick = std::make_shared(id, controller); + _openJoysticks[id] = joystick; + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(joystick); + auto name = SDL_GameControllerName(controller); + _subdeviceNames << name; + emit joystickAdded(joystick.get()); + emit subdeviceConnected(getName(), name); + } + } + } + + _isInitialized = true; + } else { + qDebug() << "Error initializing SDL2 Manager"; + } + }); + + if (!_isInitialized) { + return false; + } + InputPlugin::activate(); - auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->registerDevice(joystick); From deee6aab1a9ecf1d70439fa630038dc7240d8f5e Mon Sep 17 00:00:00 2001 From: beholder Date: Tue, 12 Sep 2017 04:24:18 +0300 Subject: [PATCH 64/78] 7469 Mouse Pointer Depth is Incorrect in HMD Mode --- libraries/shared/src/RegisteredMetaTypes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 5f66a78679..8257c883a2 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -761,6 +761,7 @@ QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResu obj.setProperty("distance", rayPickResult.distance); QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection); obj.setProperty("intersection", intersection); + obj.setProperty("intersects", rayPickResult.type != NONE); QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); return obj; From 61e8458d138035586049cc365ad61fde8c5fee1e Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 12 Sep 2017 08:08:41 -0700 Subject: [PATCH 65/78] some cleanup --- interface/src/commerce/Wallet.cpp | 90 ++++++++++++++----------------- interface/src/commerce/Wallet.h | 5 +- 2 files changed, 43 insertions(+), 52 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index e2ce3cec94..227d2de96b 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -284,7 +284,7 @@ void Wallet::setPassphrase(const QString& passphrase) { _publicKeys.clear(); } -bool Wallet::writeSecurityImageFile(const QPixmap* pixmap, const QString& outputFilePath) { +bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath) { // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be // a constant. We can review this later - there are ways to generate keys @@ -343,7 +343,7 @@ bool Wallet::writeSecurityImageFile(const QPixmap* pixmap, const QString& output return true; } -bool Wallet::readSecurityImageFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { +bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) { unsigned char ivec[16]; unsigned char ckey[32]; initializeAESKeys(ivec, ckey, _salt); @@ -449,7 +449,7 @@ bool Wallet::generateKeyPair() { auto keyPair = generateRSAKeypair(); // TODO: redo this soon -- need error checking and so on - writeSecurityImageFile(_securityImage, keyFilePath()); + writeSecurityImage(_securityImage, keyFilePath()); sendKeyFilePathIfExists(); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); @@ -515,10 +515,10 @@ void Wallet::chooseSecurityImage(const QString& filename) { if (_securityImage) { delete _securityImage; } - // temporary... QString path = qApp->applicationDirPath(); path.append("/resources/qml/hifi/commerce/wallet/"); path.append(filename); + // now create a new security image pixmap _securityImage = new QPixmap(); @@ -537,31 +537,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { return; } - bool success = false; - RSA* keys = readKeys(keyFilePath().toStdString().c_str()); - if (keys) { - QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); - if (writeKeys(tempFileName.toStdString().c_str(), keys)) { - if (writeSecurityImageFile(_securityImage, tempFileName)) { - // ok, now move the temp file to the correct spot - // TODO: error checking here! - QFile(QString(keyFilePath())).remove(); - QFile(tempFileName).rename(QString(keyFilePath())); - qCDebug(commerce) << "passphrase changed successfully"; - updateImageProvider(); - - success = true; - } else { - qCDebug(commerce) << "couldn't write security image to" << tempFileName; - } - } else { - qCDebug(commerce) << "couldn't write keys to" << tempFileName; - } - } else { - qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing"; - setPassphrase(QString("")); - } - + bool success = writeWallet(); emit securityImageResult(success); } @@ -577,7 +553,7 @@ void Wallet::getSecurityImage() { // decrypt and return QFileInfo fileInfo(keyFilePath()); - if (fileInfo.exists() && readSecurityImageFile(keyFilePath(), &data, &dataLen)) { + if (fileInfo.exists() && readSecurityImage(keyFilePath(), &data, &dataLen)) { // create the pixmap _securityImage = new QPixmap(); _securityImage->loadFromData(data, dataLen, "jpg"); @@ -616,31 +592,45 @@ void Wallet::reset() { QFile keyFile(keyFilePath()); keyFile.remove(); } - -bool Wallet::changePassphrase(const QString& newPassphrase) { - qCDebug(commerce) << "changing passphrase"; +bool Wallet::writeWallet(const QString& newPassphrase) { RSA* keys = readKeys(keyFilePath().toStdString().c_str()); if (keys) { // we read successfully, so now write to a new temp file - // save old passphrase just in case - // TODO: force re-enter? - QString oldPassphrase = *_passphrase; - setPassphrase(newPassphrase); QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp")); - if (writeKeys(tempFileName.toStdString().c_str(), keys)) { - // ok, now move the temp file to the correct spot - QFile(QString(keyFilePath())).remove(); - QFile(tempFileName).rename(QString(keyFilePath())); - qCDebug(commerce) << "passphrase changed successfully"; - return true; - } else { - qCDebug(commerce) << "couldn't write keys"; - QFile(tempFileName).remove(); - setPassphrase(oldPassphrase); - return false; + QString oldPassphrase = *_passphrase; + if (!newPassphrase.isEmpty()) { + setPassphrase(newPassphrase); } + if (writeKeys(tempFileName.toStdString().c_str(), keys)) { + if (writeSecurityImage(_securityImage, tempFileName)) { + // ok, now move the temp file to the correct spot + QFile(QString(keyFilePath())).remove(); + QFile(tempFileName).rename(QString(keyFilePath())); + qCDebug(commerce) << "wallet written successfully"; + return true; + } else { + qCDebug(commerce) << "couldn't write security image to temp wallet"; + } + } else { + qCDebug(commerce) << "couldn't write keys to temp wallet"; + } + // if we are here, we failed, so cleanup + QFile(tempFileName).remove(); + if (!newPassphrase.isEmpty()) { + setPassphrase(oldPassphrase); + } + + } else { + qCDebug(commerce) << "couldn't read wallet - bad passphrase?"; + // TODO: review this, but it seems best to reset the passphrase + // since we couldn't decrypt the existing wallet (or is doesn't + // exist perhaps). + setPassphrase(""); } - qCDebug(commerce) << "couldn't decrypt keys with current passphrase, clearing"; - setPassphrase(QString("")); return false; } + +bool Wallet::changePassphrase(const QString& newPassphrase) { + qCDebug(commerce) << "changing passphrase"; + return writeWallet(newPassphrase); +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 40f6bd1933..013f038fb2 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -60,9 +60,10 @@ private: QByteArray _ckey; QString* _passphrase { new QString("") }; + bool writeWallet(const QString& newPassphrase = QString("")); void updateImageProvider(); - bool writeSecurityImageFile(const QPixmap* pixmap, const QString& outputFilePath); - bool readSecurityImageFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); + bool writeSecurityImage(const QPixmap* pixmap, const QString& outputFilePath); + bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; #endif // hifi_Wallet_h From 2850029f2620fe393f8b08b90563f09c6e9c3a12 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 Sep 2017 10:00:32 -0700 Subject: [PATCH 66/78] add profiling to controller-dispatcher --- .../controllers/controllerDispatcher.js | 51 ++++++++++++++++--- .../libraries/controllerDispatcherUtils.js | 3 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index c52b3d56df..f4567bd295 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -8,9 +8,9 @@ /* jslint bitwise: true */ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, - controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, entityIsGrabbable:true, + controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, - getGrabPointSphereOffset + getGrabPointSphereOffset, HMD, MyAvatar, Messages */ controllerDispatcherPlugins = {}; @@ -26,8 +26,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; + var PROFILE = true; + function ControllerDispatcher() { - var _this = this + var _this = this; this.lastInterval = Date.now(); this.intervalCount = 0; this.totalDelta = 0; @@ -141,6 +143,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.update = function () { + if (PROFILE) { + Script.beginProfileRange("dispatch.pre"); + } var deltaTime = _this.updateTimings(); _this.setIgnoreTablet(); @@ -156,7 +161,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); controllerDispatcherPlugins[b].parameters.priority; }); - // print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames)); var output = "controllerDispatcher -- new plugin order: "; for (var k = 0; k < _this.orderedPluginNames.length; k++) { var dbgPluginName = _this.orderedPluginNames[k]; @@ -170,6 +174,14 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); controllerDispatcherPluginsNeedSort = false; } + if (PROFILE) { + Script.endProfileRange("dispatch.pre"); + } + + if (PROFILE) { + Script.beginProfileRange("dispatch.gather"); + } + var controllerLocations = [ _this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation() @@ -205,7 +217,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; - props.distance = Vec3.distance(props.position, controllerLocations[h].position) + props.distance = Vec3.distance(props.position, controllerLocations[h].position); nearbyEntityPropertiesByID[entityID] = props; nearbyEntityProperties[h].push(props); } @@ -261,13 +273,22 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); rayPicks: rayPicks, hudRayPicks: hudRayPicks }; + if (PROFILE) { + Script.endProfileRange("dispatch.gather"); + } + if (PROFILE) { + Script.beginProfileRange("dispatch.isReady"); + } // check for plugins that would like to start. ask in order of increasing priority value for (var pluginIndex = 0; pluginIndex < _this.orderedPluginNames.length; pluginIndex++) { var orderedPluginName = _this.orderedPluginNames[pluginIndex]; var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { + if (PROFILE) { + Script.beginProfileRange("dispatch.isReady." + orderedPluginName); + } var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the @@ -275,11 +296,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); } + if (PROFILE) { + Script.endProfileRange("dispatch.isReady." + orderedPluginName); + } } } + if (PROFILE) { + Script.endProfileRange("dispatch.isReady"); + } - // print("QQQ running plugins: " + JSON.stringify(_this.runningPluginNames)); - + if (PROFILE) { + Script.beginProfileRange("dispatch.run"); + } // give time to running plugins for (var runningPluginName in _this.runningPluginNames) { if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) { @@ -290,6 +318,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); delete _this.runningPluginNames[runningPluginName]; _this.unmarkSlotsForPluginName(runningPluginName); } else { + if (PROFILE) { + Script.beginProfileRange("dispatch.run." + runningPluginName); + } var runningness = plugin.run(controllerData, deltaTime); if (!runningness.active) { // plugin is finished running, for now. remove it from the list @@ -297,9 +328,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); } + if (PROFILE) { + Script.endProfileRange("dispatch.run." + runningPluginName); + } } } } + if (PROFILE) { + Script.endProfileRange("dispatch.run"); + } }; this.setBlacklist = function() { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 773f79754d..bb39b1a7f8 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -8,7 +8,8 @@ /* global Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, NULL_UUID:true, AVATAR_SELF_ID:true, FORBIDDEN_GRAB_TYPES:true, - HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, + HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, + DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, TRIGGER_OFF_VALUE:true, TRIGGER_ON_VALUE:true, PICK_MAX_DISTANCE:true, From 315f874824c2df6bc73662059aff76b9f0020673 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 Sep 2017 10:44:09 -0700 Subject: [PATCH 67/78] add profiling to controller-dispatcher --- scripts/system/controllers/controllerScripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index e3375a5a27..4e63d023fc 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -15,7 +15,7 @@ var CONTOLLER_SCRIPTS = [ "handControllerPointer.js", "grab.js", "toggleAdvancedMovementForHandControllers.js", - "ControllerDispatcher.js", + "controllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", "controllerModules/nearActionGrabEntity.js", From e9918ca59964aed6f6ffd935115da06db001a246 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 Sep 2017 10:52:52 -0700 Subject: [PATCH 68/78] add profiling to controller-dispatcher --- scripts/system/controllers/controllerDispatcher.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index f4567bd295..5b72e63054 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -26,7 +26,11 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; - var PROFILE = true; + var PROFILE = false; + + if (typeof Test !== "undefined") { + PROFILE = true; + } function ControllerDispatcher() { var _this = this; From b634292261a49d26d17bee5360a2e5b03c955be5 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 12 Sep 2017 10:53:04 -0700 Subject: [PATCH 69/78] fixing some small issues --- .../controllerModules/farActionGrabEntity.js | 2 +- .../controllerModules/nearActionGrabEntity.js | 12 ++++++++++-- .../controllerModules/nearParentGrabEntity.js | 13 +++++++++++-- .../controllerModules/overlayLaserInput.js | 2 +- .../controllers/controllerModules/scaleAvatar.js | 4 ++-- .../system/libraries/controllerDispatcherUtils.js | 5 +++++ 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 9a6f39c490..80718bc68d 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -137,7 +137,7 @@ Script.include("/~/system/libraries/controllers.js"); var dim = {x: radius, y: radius, z: radius}; var mode = "hold"; if (!this.distanceHolding && !this.distanceRotating) { - if (controllerData.triggerValues[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand]) { mode = "full"; } else { mode = "half"; diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 98a3b9395d..ee44fb4907 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -9,7 +9,8 @@ getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, - TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity + TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, + HAPTIC_PULSE_STRENGTH, HAPTIC_STYLUS_DURATION */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -22,6 +23,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hand = hand; this.targetEntityID = null; this.actionID = null; // action this script created... + this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( 500, @@ -152,6 +154,10 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); break; } if (entityIsGrabbable(props) || entityIsCloneable(props)) { + if (props.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = props.id; + } // if we've attempted to grab a child, roll up to the root of the tree var groupRootProps = findGroupParent(controllerData, props); if (entityIsGrabbable(groupRootProps)) { @@ -166,11 +172,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.isReady = function (controllerData) { this.targetEntityID = null; + var targetProps = this.getTargetProps(controllerData); if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { return makeRunningValues(false, [], []); } - var targetProps = this.getTargetProps(controllerData); if (targetProps) { if (!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) { return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it @@ -179,6 +185,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); return makeRunningValues(true, [this.targetEntityID], []); } } else { + this.hapticTargetID = null; return makeRunningValues(false, [], []); } }; @@ -187,6 +194,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (this.actionID) { if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { this.endNearGrabAction(); + this.hapticTargetID = null; return makeRunningValues(false, [], []); } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 47f80932bb..30cce1b315 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, - findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic + findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_STYLUS_DURATION */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -28,6 +28,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.previousParentID = {}; this.previousParentJointIndex = {}; this.previouslyUnhooked = {}; + this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( 500, @@ -154,6 +155,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); continue; } if (entityIsGrabbable(props)) { + // give haptic feedback + if (props.id !== this.hapticTargetID) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + this.hapticTargetID = props.id; + } // if we've attempted to grab a child, roll up to the root of the tree var groupRootProps = findGroupParent(controllerData, props); if (entityIsGrabbable(groupRootProps)) { @@ -169,11 +175,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.targetEntityID = null; this.grabbing = false; + var targetProps = this.getTargetProps(controllerData); if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { return makeRunningValues(false, [], []); } - var targetProps = this.getTargetProps(controllerData); if (targetProps) { if (propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) { return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it @@ -182,6 +188,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); return makeRunningValues(true, [this.targetEntityID], []); } } else { + this.hapticTargetID = null; return makeRunningValues(false, [], []); } }; @@ -190,6 +197,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (this.grabbing) { if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { this.endNearParentingGrabEntity(); + this.hapticTargetID = null; return makeRunningValues(false, [], []); } @@ -197,6 +205,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!this.thisHandIsParent(props)) { this.grabbing = false; this.targetEntityID = null; + this.hapticTargetID = null; return makeRunningValues(false, [], []); } diff --git a/scripts/system/controllers/controllerModules/overlayLaserInput.js b/scripts/system/controllers/controllerModules/overlayLaserInput.js index b1ffc77afe..2c950fd4df 100644 --- a/scripts/system/controllers/controllerModules/overlayLaserInput.js +++ b/scripts/system/controllers/controllerModules/overlayLaserInput.js @@ -433,7 +433,7 @@ Script.include("/~/system/libraries/controllers.js"); var nearGrabModule = getEnabledModuleByName(nearGrabName); var status = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); var offOverlay = (intersection.type !== RayPick.INTERSECTED_OVERLAY); - var triggerOff = (controllerData.triggerValues[this.hand] === 0); + var triggerOff = (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE); return offOverlay || status.active || triggerOff; }; diff --git a/scripts/system/controllers/controllerModules/scaleAvatar.js b/scripts/system/controllers/controllerModules/scaleAvatar.js index 2c85b75ea9..04c400f2bc 100644 --- a/scripts/system/controllers/controllerModules/scaleAvatar.js +++ b/scripts/system/controllers/controllerModules/scaleAvatar.js @@ -15,7 +15,7 @@ //Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function () { var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); - + var BUMPER_ON_VALUE = 0.5; function ScaleAvatar(hand) { this.hand = hand; this.scalingStartAvatarScale = 0; @@ -38,7 +38,7 @@ }; this.triggersPressed = function(controllerData) { - if (controllerData.triggerValues[this.hand] === 1 && controllerData.secondaryValues[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand] && controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { return true; } return false; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index bc2aa1a9c8..dcdbed6fb2 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -19,6 +19,8 @@ COLORS_GRAB_DISTANCE_HOLD:true, NEAR_GRAB_RADIUS:true, DISPATCHER_PROPERTIES:true, + HAPTIC_PULSE_STRENGTH:true, + HAPTIC_PULSE_DURATION:true, Entities, makeDispatcherModuleParameters:true, makeRunningValues:true, @@ -42,6 +44,9 @@ MSECS_PER_SEC = 1000.0; INCHES_TO_METERS = 1.0 / 39.3701; +HAPTIC_PULSE_STRENGTH = 1.0; +HAPTIC_PULSE_DURATION = 13.0; + ZERO_VEC = { x: 0, y: 0, z: 0 }; ONE_VEC = { x: 1, y: 1, z: 1 }; From 187ed71a8ae1fc480bbbba7609619b052b14dc8b Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 12 Sep 2017 10:55:27 -0700 Subject: [PATCH 70/78] more cleanup, plus now keyfile associated with user directly --- interface/src/commerce/Wallet.cpp | 29 ++++++++++++++++++--------- interface/src/commerce/Wallet.h | 2 +- libraries/ui/src/ui/ImageProvider.cpp | 6 +++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 227d2de96b..9b850cdb49 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -54,7 +55,8 @@ void initialize() { } QString keyFilePath() { - return PathUtils::getAppDataFilePath(KEY_FILE); + auto accountManager = DependencyManager::get(); + return PathUtils::getAppDataFilePath(QString("%1.%2").arg(accountManager->getAccountInfo().getUsername(), KEY_FILE)); } // use the cached _passphrase if it exists, otherwise we need to prompt @@ -262,6 +264,15 @@ RSA* readPrivateKey(const char* filename) { return key; } +// QT's QByteArray will convert to Base64 without any embedded newlines. This just +// writes it with embedded newlines, which is more readable. +void outputBase64WithNewlines(QFile& file, const QByteArray& b64Array) { + for (int i = 0; i < b64Array.size(); i += 64) { + file.write(b64Array.mid(i, 64)); + file.write("\n"); + } +} + void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) { // use the ones in the wallet auto wallet = DependencyManager::get(); @@ -331,11 +342,11 @@ bool Wallet::writeSecurityImage(const QPixmap* pixmap, const QString& outputFile QByteArray output((const char*)outputFileBuffer, outSize); // now APPEND to the file, + QByteArray b64output = output.toBase64(); QFile outputFile(outputFilePath); outputFile.open(QIODevice::Append); outputFile.write(IMAGE_HEADER); - outputFile.write(output.toBase64()); - outputFile.write("\n"); + outputBase64WithNewlines(outputFile, b64output); outputFile.write(IMAGE_FOOTER); outputFile.close(); @@ -551,9 +562,11 @@ void Wallet::getSecurityImage() { return; } - // decrypt and return + bool success = false; + // decrypt and return. Don't bother if we have no file to decrypt, or + // no salt set yet. QFileInfo fileInfo(keyFilePath()); - if (fileInfo.exists() && readSecurityImage(keyFilePath(), &data, &dataLen)) { + if (fileInfo.exists() && _salt.size() > 0 && readSecurityImage(keyFilePath(), &data, &dataLen)) { // create the pixmap _securityImage = new QPixmap(); _securityImage->loadFromData(data, dataLen, "jpg"); @@ -562,11 +575,9 @@ void Wallet::getSecurityImage() { updateImageProvider(); delete[] data; - emit securityImageResult(true); - } else { - qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)"; - emit securityImageResult(false); + success = true; } + emit securityImageResult(success); } void Wallet::sendKeyFilePathIfExists() { QString filePath(keyFilePath()); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 013f038fb2..b8913e9a5e 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -55,7 +55,7 @@ signals: private: QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; - QByteArray _salt {"iamsalt!"}; + QByteArray _salt; QByteArray _iv; QByteArray _ckey; QString* _passphrase { new QString("") }; diff --git a/libraries/ui/src/ui/ImageProvider.cpp b/libraries/ui/src/ui/ImageProvider.cpp index c74ed0cb44..7bbad43f2e 100644 --- a/libraries/ui/src/ui/ImageProvider.cpp +++ b/libraries/ui/src/ui/ImageProvider.cpp @@ -51,5 +51,9 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize return _securityImage->copy(); } } - return QPixmap(); + // otherwise just return a grey pixmap. This avoids annoying error messages in qml we would get + // when sending a 'null' pixmap (QPixmap()) + QPixmap greyPixmap(200, 200); + greyPixmap.fill(QColor("darkGrey")); + return greyPixmap; } From ca2d22a19208088454425a3bf2e501de5b101ddd Mon Sep 17 00:00:00 2001 From: Menithal Date: Tue, 12 Sep 2017 21:22:40 +0300 Subject: [PATCH 71/78] Updated Scripts --- .../resources/icons/tablet-icons/clap-a.svg | 64 +++++----- .../resources/icons/tablet-icons/clap-i.svg | 66 ++++++----- .../marketplace/clap/clapApp.js | 6 +- .../marketplace/clap/icons/clap-a.svg | 112 ++++++++++++++++++ .../clap/icons/clap-black-icon.svg | 104 ---------------- .../marketplace/clap/icons/clap-i.svg | 112 ++++++++++++++++++ .../clap/icons/clap-white-icon.svg | 104 ---------------- .../marketplace/clap/scripts/ClapDebugger.js | 10 -- .../marketplace/clap/scripts/ClapEngine.js | 41 ++++--- 9 files changed, 324 insertions(+), 295 deletions(-) create mode 100644 unpublishedScripts/marketplace/clap/icons/clap-a.svg delete mode 100644 unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg create mode 100644 unpublishedScripts/marketplace/clap/icons/clap-i.svg delete mode 100644 unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg diff --git a/interface/resources/icons/tablet-icons/clap-a.svg b/interface/resources/icons/tablet-icons/clap-a.svg index 10a8e3ea98..60df3e0795 100644 --- a/interface/resources/icons/tablet-icons/clap-a.svg +++ b/interface/resources/icons/tablet-icons/clap-a.svg @@ -9,13 +9,13 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="210mm" - height="297mm" - viewBox="0 0 210 297" + width="225pt" + height="225pt" + viewBox="0 0 79.374998 79.374998" version="1.1" id="svg8" inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" - sodipodi:docname="ClapBlack.svg"> + sodipodi:docname="clap-a.svg"> + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="pt" /> @@ -51,7 +53,8 @@ + id="layer1" + transform="translate(0,-217.62501)"> + d="m 287.04656,288.04665 23.71378,9.0592 c 0,0 0.39967,-4.79605 4.5296,-6.92762 4.12992,-2.13158 7.40168,-15.99614 7.40168,-15.99614 l 2.5266,-9.92668 4.1934,-9.24819 c 0,0 -1.13082,-3.82467 -4.67519,-0.19524 -8.8408,9.05294 -3.51813,14.25651 -8.14833,14.16202 -3.77977,-0.75596 -0.13245,-10.86933 0.26722,-16.16497 0.26644,-3.06413 0.69941,-12.98928 0.29974,-18.85112 -0.39966,-5.86184 -0.93256,-6.66118 -2.66446,-6.52795 -1.73191,0.13322 -2.93093,0.93257 -4.39638,9.05919 -1.46546,8.12664 -3.33058,15.72038 -3.06414,18.91774 0.26644,3.19737 -4.36307,0.64443 -2.89762,-5.48385 1.46546,-6.12827 4.82299,-19.49577 4.95621,-21.36089 0.13322,-1.86512 -0.52548,-5.99423 -3.45641,-1.46464 -2.93091,4.52961 -8.39307,31.77464 -8.39307,31.77464 0,0 -4.61911,1.35544 4.75249,-29.62183 0.37058,-1.22492 -1.2231,-1.82036 -2.02245,-0.35491 -0.79933,1.46547 -2.93556,6.41177 -3.60169,9.47592 -0.66612,3.06413 -4.12526,18.76747 -5.99039,20.76582 -1.86514,1.99836 -0.77914,-1.64516 2.43172,-19.62594 -3.46015,-8.7099 -7.6029,16.6051 -8.29355,25.75422 -1.73191,11.05755 2.66446,14.65459 2.53124,22.78122 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 326.28601,276.79194 4.85752,2.59518 c 0,0 5.99506,0.26645 7.86018,1.19902 1.86513,0.93256 14.52136,-19.31742 14.52136,-19.31742 0,0 -1.59867,-0.39967 -3.19735,-5.32893 -1.59868,-4.92926 -7.59374,-19.98353 -9.45888,-23.44734 -1.86513,-3.46381 -2.53124,-4.66283 -3.4638,-7.46051 -0.93257,-2.7977 -2.13159,-5.0625 -3.73027,-0.13323 -1.59868,4.92927 -1.66343,10.45537 -0.46441,13.91919 1.38613,5.18454 3.8952,15.07683 1.97631,17.7369 -3.13067,-0.0635 -4.1718,-8.94257 -4.84078,-11.98701 l -10.45574,-8.48036 -1.15458,18.13553 c 0,0 3.30736,-4.0697 9.65298,-5.19422 4.51067,-0.79933 2.81751,6.4678 1.21188,14.09965 -1.34794,6.40707 -3.31442,13.66355 -3.31442,13.66355 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 291.74698,236.36722 c 0,0 -1.36535,-0.56172 -0.0701,-3.22296 1.29528,-2.66125 3.72103,-1.62503 4.12138,-1.41306 -1.04349,0.50764 -3.37926,4.82292 -4.05132,4.63602 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 278.60514,277.01718 c 0,0 -0.98806,-8.05217 1.36702,-17.09567 5.11868,-12.29479 -7.76345,3.61446 -1.36702,17.09567 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> + d="m 283.64252,261.72126 c 0,-0.0431 -1.44898,3.96362 -0.80247,9.53246 0.42679,3.67631 0.64575,8.36037 1.90705,11.72782 3.10143,7.14376 -8.41694,-7.43352 -1.10458,-21.26028 z" + style="fill:#000000;stroke:#000000;stroke-width:0.11350404px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="cscc" /> + d="m 318.12446,294.21149 c 0,0 6.68844,-4.71017 9.13772,-13.37686 -5.91279,4.63412 -6.77642,9.57974 -9.13772,13.37686 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> + d="m 344.21877,231.00112 c 0,0 1.60041,9.36884 7.07841,21.7522 5.35526,12.10587 4.05178,-4.80879 -7.07841,-21.7522 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="csc" /> + d="m 349.44951,230.90023 c -0.11119,-0.0477 0.96041,8.99393 4.75948,15.37263 1.48524,4.17497 3.73499,-6.08181 -4.75948,-15.37263 z" + style="fill:#000000;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> diff --git a/interface/resources/icons/tablet-icons/clap-i.svg b/interface/resources/icons/tablet-icons/clap-i.svg index 3e4ecb0b64..fbd9ed0f9c 100644 --- a/interface/resources/icons/tablet-icons/clap-i.svg +++ b/interface/resources/icons/tablet-icons/clap-i.svg @@ -9,13 +9,13 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="210mm" - height="297mm" - viewBox="0 0 210 297" + width="225pt" + height="225pt" + viewBox="0 0 79.374998 79.374998" version="1.1" id="svg8" inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" - sodipodi:docname="ClapWhite.svg"> + sodipodi:docname="clap-i.svg"> + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="pt" /> @@ -51,54 +53,60 @@ + id="layer1" + transform="translate(0,-217.62501)"> + style="fill:#000000"> + d="m 287.04656,288.04665 23.71378,9.0592 c 0,0 0.39967,-4.79605 4.5296,-6.92762 4.12992,-2.13158 7.40168,-15.99614 7.40168,-15.99614 l 2.5266,-9.92668 4.1934,-9.24819 c 0,0 -1.13082,-3.82467 -4.67519,-0.19524 -8.8408,9.05294 -3.51813,14.25651 -8.14833,14.16202 -3.77977,-0.75596 -0.13245,-10.86933 0.26722,-16.16497 0.26644,-3.06413 0.69941,-12.98928 0.29974,-18.85112 -0.39966,-5.86184 -0.93256,-6.66118 -2.66446,-6.52795 -1.73191,0.13322 -2.93093,0.93257 -4.39638,9.05919 -1.46546,8.12664 -3.33058,15.72038 -3.06414,18.91774 0.26644,3.19737 -4.36307,0.64443 -2.89762,-5.48385 1.46546,-6.12827 4.82299,-19.49577 4.95621,-21.36089 0.13322,-1.86512 -0.52548,-5.99423 -3.45641,-1.46464 -2.93091,4.52961 -8.39307,31.77464 -8.39307,31.77464 0,0 -4.61911,1.35544 4.75249,-29.62183 0.37058,-1.22492 -1.2231,-1.82036 -2.02245,-0.35491 -0.79933,1.46547 -2.93556,6.41177 -3.60169,9.47592 -0.66612,3.06413 -4.12526,18.76747 -5.99039,20.76582 -1.86514,1.99836 -0.77914,-1.64516 2.43172,-19.62594 -3.46015,-8.7099 -7.6029,16.6051 -8.29355,25.75422 -1.73191,11.05755 2.66446,14.65459 2.53124,22.78122 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 326.28601,276.79194 4.85752,2.59518 c 0,0 5.99506,0.26645 7.86018,1.19902 1.86513,0.93256 14.52136,-19.31742 14.52136,-19.31742 0,0 -1.59867,-0.39967 -3.19735,-5.32893 -1.59868,-4.92926 -7.59374,-19.98353 -9.45888,-23.44734 -1.86513,-3.46381 -2.53124,-4.66283 -3.4638,-7.46051 -0.93257,-2.7977 -2.13159,-5.0625 -3.73027,-0.13323 -1.59868,4.92927 -1.66343,10.45537 -0.46441,13.91919 1.38613,5.18454 3.8952,15.07683 1.97631,17.7369 -3.13067,-0.0635 -4.1718,-8.94257 -4.84078,-11.98701 l -10.45574,-8.48036 -1.15458,18.13553 c 0,0 3.30736,-4.0697 9.65298,-5.19422 4.51067,-0.79933 2.81751,6.4678 1.21188,14.09965 -1.34794,6.40707 -3.31442,13.66355 -3.31442,13.66355 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 291.74698,236.36722 c 0,0 -1.36535,-0.56172 -0.0701,-3.22296 1.29528,-2.66125 3.72103,-1.62503 4.12138,-1.41306 -1.04349,0.50764 -3.37926,4.82292 -4.05132,4.63602 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + d="m 278.60514,277.01718 c 0,0 -0.98806,-8.05217 1.36702,-17.09567 5.11868,-12.29479 -7.76345,3.61446 -1.36702,17.09567 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> + d="m 283.64252,261.72126 c 0,-0.0431 -1.44898,3.96362 -0.80247,9.53246 0.42679,3.67631 0.64575,8.36037 1.90705,11.72782 3.10143,7.14376 -8.41694,-7.43352 -1.10458,-21.26028 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.11350404px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="cscc" /> + d="m 318.12446,294.21149 c 0,0 6.68844,-4.71017 9.13772,-13.37686 -5.91279,4.63412 -6.77642,9.57974 -9.13772,13.37686 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> + d="m 344.21877,231.00112 c 0,0 1.60041,9.36884 7.07841,21.7522 5.35526,12.10587 4.05178,-4.80879 -7.07841,-21.7522 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="csc" /> + d="m 349.44951,230.90023 c -0.11119,-0.0477 0.96041,8.99393 4.75948,15.37263 1.48524,4.17497 3.73499,-6.08181 -4.75948,-15.37263 z" + style="fill:#ffffff;stroke:#000000;stroke-width:0.09325644px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + sodipodi:nodetypes="ccc" /> diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index a32937d6dd..294a7171b3 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -15,12 +15,12 @@ // Load up engine var APP_NAME = "CLAP"; -var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v6")); +var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v9")); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // Define Menu -var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg"); -var whiteIcon =Script.resolvePath("icons/tablet-icons/clap-i.svg"); +var blackIcon = Script.resolvePath("http://mpassets.highfidelity.com/797180be-1d5d-4465-816e-fada92aa727a-v1/icons/clap-a.svg?foxv2"); +var whiteIcon = Script.resolvePath("http://mpassets.highfidelity.com/797180be-1d5d-4465-816e-fada92aa727a-v1/icons/clap-i.svg?foxv2"); var isActive = true; var activeButton = tablet.addButton({ diff --git a/unpublishedScripts/marketplace/clap/icons/clap-a.svg b/unpublishedScripts/marketplace/clap/icons/clap-a.svg new file mode 100644 index 0000000000..60df3e0795 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-a.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg b/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg deleted file mode 100644 index 10a8e3ea98..0000000000 --- a/unpublishedScripts/marketplace/clap/icons/clap-black-icon.svg +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/clap/icons/clap-i.svg b/unpublishedScripts/marketplace/clap/icons/clap-i.svg new file mode 100644 index 0000000000..fbd9ed0f9c --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-i.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg b/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg deleted file mode 100644 index 3e4ecb0b64..0000000000 --- a/unpublishedScripts/marketplace/clap/icons/clap-white-icon.svg +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js index 247664c157..04a3ebc4af 100644 --- a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js +++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js @@ -42,7 +42,6 @@ module.exports = { Overlays.deleteOverlay(DEBUG_LEFT_HAND); Overlays.deleteOverlay(DEBUG_CLAP_LEFT); Overlays.deleteOverlay(DEBUG_CLAP_RIGHT); - Overlays.deleteOverlay(DEBUG_CLAP); Overlays.deleteOverlay(DEBUG_CLAP_DIRECTION); }, @@ -141,15 +140,6 @@ module.exports = { } }); - DEBUG_CLAP = Overlays.addOverlay("sphere", { - position: MyAvatar.position, - color: DEBUG_VOLUME, - scale: { - x: 0.05, - y: 0.05, - z: 0.05 - } - }); DEBUG_CLAP_DIRECTION = Overlays.addOverlay("line3d", { color: DEBUG_VOLUME, diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js index f36aa9716b..96dd05713e 100644 --- a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -17,12 +17,12 @@ var DEG_TO_RAD = Math.PI / 180; // If angle is closer to 0 from 25 degrees, then "clap" can happen; var COS_OF_TOLERANCE = Math.cos(25 * DEG_TO_RAD); - +var DISTANCE = 0.3; var CONTROL_MAP_PACKAGE = "com.highfidelity.avatar.clap.active"; var CLAP_MENU = "Clap"; var ENABLE_PARTICLE_MENU = "Enable Clap Particles"; -var ENABLE_DEBUG_MENU = "Enable Clap Debug"; +var ENABLE_DEBUG_MENU = "Enable Claping Helper"; var sounds = [ "clap1.wav", @@ -34,9 +34,9 @@ var sounds = [ ]; -var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V2")); -var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V2")); -var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V2")); +var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V3")); +var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V3")); +var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V3")); var settingDebug = false; var settingParticlesOn = true; @@ -190,7 +190,7 @@ function update(dt) { var angularVelocity = Vec3.length(Vec3.subtract(leftHand.angularVelocity, rightHand.angularVelocity)); var velocity = Vec3.length(Vec3.subtract(leftHand.velocity, rightHand.velocity)); - if (matchTolerance && distance < 0.3 && !animClap) { + if (matchTolerance && distance < DISTANCE && !animClap) { if (settingDebug) { ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, true); } @@ -202,7 +202,7 @@ function update(dt) { Script.clearTimeout(animThrottle); animThrottle = false; } - } else if (animClap && distance > 0.3) { + } else if (animClap && distance > DISTANCE) { if (settingDebug) { ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, false); } @@ -213,14 +213,14 @@ function update(dt) { }, 500); } - if (distance < 0.22 && matchTolerance && !clapOn) { + if (distance < DISTANCE && matchTolerance && !clapOn) { clapOn = true; var midClap = Vec3.mix(rightHandPositionOffset, leftHandPositionOffset, 0.5); var volume = velocity / 2 + angularVelocity / 5; clap(volume, midClap, Quat.lookAtSimple(rightHandPositionOffset, leftHandPositionOffset)); - } else if (distance > 0.22 && !matchTolerance) { + } else if (distance > DISTANCE && !matchTolerance) { clapOn = false; } } @@ -240,13 +240,20 @@ module.exports = { isChecked: settingParticlesOn }); } - if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { - Menu.addMenuItem({ - menuName: CLAP_MENU, - menuItemName: ENABLE_DEBUG_MENU, - isCheckable: true, - isChecked: settingDebug - }); + if (HMD.active) { + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_DEBUG_MENU, + isCheckable: true, + isChecked: settingDebug + }); + } + } else { + ClapDebugger.disableDebug(); + if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU); + } } @@ -312,7 +319,7 @@ module.exports = { try { Script.update.disconnect(update); } catch (e) { - print("Script.update connection did not exist on disconnection. Skipping") + print("Script.update connection did not exist on disconnection. Skipping"); } } }; From 5aab3f3cf34569b092f17d174164f68bf4c497dd Mon Sep 17 00:00:00 2001 From: Menithal Date: Tue, 12 Sep 2017 21:24:14 +0300 Subject: [PATCH 72/78] Switched from marketplace links to local --- unpublishedScripts/marketplace/clap/clapApp.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index 294a7171b3..de58b08e8e 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -19,8 +19,8 @@ var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v9")); var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // Define Menu -var blackIcon = Script.resolvePath("http://mpassets.highfidelity.com/797180be-1d5d-4465-816e-fada92aa727a-v1/icons/clap-a.svg?foxv2"); -var whiteIcon = Script.resolvePath("http://mpassets.highfidelity.com/797180be-1d5d-4465-816e-fada92aa727a-v1/icons/clap-i.svg?foxv2"); +var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2"); +var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2"); var isActive = true; var activeButton = tablet.addButton({ From 79603765c4ba9d7f1c70943d74f24c134523e749 Mon Sep 17 00:00:00 2001 From: Menithal Date: Tue, 12 Sep 2017 21:40:15 +0300 Subject: [PATCH 73/78] Removed unused check --- .../marketplace/clap/scripts/ClapEngine.js | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js index 96dd05713e..7f57e9f4aa 100644 --- a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -240,20 +240,13 @@ module.exports = { isChecked: settingParticlesOn }); } - if (HMD.active) { - if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { - Menu.addMenuItem({ - menuName: CLAP_MENU, - menuItemName: ENABLE_DEBUG_MENU, - isCheckable: true, - isChecked: settingDebug - }); - } - } else { - ClapDebugger.disableDebug(); - if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { - Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU); - } + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_DEBUG_MENU, + isCheckable: true, + isChecked: settingDebug + }); } From 40beb4bca8acbd1e6616a68f47fbe91cc57b04a1 Mon Sep 17 00:00:00 2001 From: Menithal Date: Tue, 12 Sep 2017 22:12:30 +0300 Subject: [PATCH 74/78] Some minor tweaks --- unpublishedScripts/marketplace/clap/clapApp.js | 7 ++++++- .../marketplace/clap/scripts/ClapDebugger.js | 11 ----------- .../marketplace/clap/scripts/ClapEngine.js | 7 +------ 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index de58b08e8e..05df03baf6 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -21,7 +21,11 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); // Define Menu var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2"); var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2"); -var isActive = true; + +if ( Settings.getValue("clapAppEnabled") === undefined){ + Settings.setValue("clapAppEnabled", true); +} +var isActive = Settings.getValue("clapAppEnabled"); var activeButton = tablet.addButton({ icon: whiteIcon, @@ -38,6 +42,7 @@ if (isActive) { function onClick(enabled) { isActive = !isActive; + Settings.setValue("clapAppEnabled", isActive); activeButton.editProperties({ isActive: isActive }); diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js index 04a3ebc4af..9e7a592c01 100644 --- a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js +++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js @@ -78,17 +78,6 @@ module.exports = { }); }, - clapSphere: function (pos, vol) { - Overlays.editOverlay(DEBUG_CLAP, { - position: pos, - scale: { - x: vol, - y: vol, - z: vol - } - }); - }, - enableDebug: function () { DEBUG_RIGHT_HAND = Overlays.addOverlay("line3d", { color: DEBUG_WRONG, diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js index 7f57e9f4aa..1a5f6665e1 100644 --- a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -80,10 +80,6 @@ function clap(volume, position, rotation) { } - if (settingDebug) { - ClapDebugger.clapSphere(position, volume); - } - } // Helper Functions function getHandFingerAnim(side) { @@ -282,12 +278,10 @@ module.exports = { }); Controller.enableMapping(CONTROL_MAP_PACKAGE); - // settingDebug STUFF if (settingDebug) { ClapDebugger.enableDebug(); } - Script.update.connect(update); Script.scriptEnding.connect(this.disconnectEngine); @@ -308,6 +302,7 @@ module.exports = { } restoreFingerRoleAnimation('left'); restoreFingerRoleAnimation('right'); + Controller.disableMapping(CONTROL_MAP_PACKAGE); try { Script.update.disconnect(update); From b3ff5c86f77a2a9cbfcb8f75b98d74237d3a08af Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 12 Sep 2017 14:43:06 -0700 Subject: [PATCH 75/78] fixed secondary triggers and chest lid --- .../controllers/controllerModules/nearActionGrabEntity.js | 8 ++++---- .../controllers/controllerModules/nearParentGrabEntity.js | 6 +++--- .../system/controllers/controllerModules/scaleAvatar.js | 5 ++--- scripts/system/libraries/controllerDispatcherUtils.js | 8 +++++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index ee44fb4907..399388d614 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -10,7 +10,7 @@ propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, - HAPTIC_PULSE_STRENGTH, HAPTIC_STYLUS_DURATION + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -63,7 +63,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var grabbableData = getGrabbableData(targetProps); this.ignoreIK = grabbableData.ignoreIK; - this.kinematicGrab = grabbableData.kinematicGrab; + this.kinematicGrab = grabbableData.kinematic; var handRotation; var handPosition; @@ -192,7 +192,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.run = function (controllerData) { if (this.actionID) { - if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { this.endNearGrabAction(); this.hapticTargetID = null; return makeRunningValues(false, [], []); @@ -211,7 +211,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (controllerData.triggerClicks[this.hand] === 1 || controllerData.secondaryValues[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { // switch to grabbing var targetCloneable = entityIsCloneable(targetProps); if (targetCloneable) { diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 30cce1b315..6cd52fef07 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, NULL_UUID, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, - findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_STYLUS_DURATION + findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -195,7 +195,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.run = function (controllerData, deltaTime) { if (this.grabbing) { - if (controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) { + if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { this.endNearParentingGrabEntity(); this.hapticTargetID = null; return makeRunningValues(false, [], []); @@ -217,7 +217,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (!readiness.active) { return readiness; } - if (controllerData.triggerClicks[this.hand] === 1 || controllerData.secondaryValues[this.hand] === 1) { + if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { // switch to grab var targetProps = this.getTargetProps(controllerData); var targetCloneable = entityIsCloneable(targetProps); diff --git a/scripts/system/controllers/controllerModules/scaleAvatar.js b/scripts/system/controllers/controllerModules/scaleAvatar.js index 04c400f2bc..b98e26b1da 100644 --- a/scripts/system/controllers/controllerModules/scaleAvatar.js +++ b/scripts/system/controllers/controllerModules/scaleAvatar.js @@ -12,10 +12,9 @@ setGrabCommunications, Menu, HMD, isInEditMode, AvatarList */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ -//Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function () { var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); - var BUMPER_ON_VALUE = 0.5; + function ScaleAvatar(hand) { this.hand = hand; this.scalingStartAvatarScale = 0; @@ -38,7 +37,7 @@ }; this.triggersPressed = function(controllerData) { - if (controllerData.triggerClicks[this.hand] && controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { + if (controllerData.triggerClicks[this.hand] && controllerData.secondaryValues[this.hand] > dispatcherUtils.BUMPER_ON_VALUE) { return true; } return false; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 1781c0dff0..7bed6413da 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -66,6 +66,7 @@ DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; TRIGGER_OFF_VALUE = 0.1; TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab +BUMPER_ON_VALUE = 0.5; PICK_MAX_DISTANCE = 500; // max length of pick-ray DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? @@ -157,8 +158,8 @@ getGrabbableData = function (props) { if (!grabbableData.hasOwnProperty("ignoreIK")) { grabbableData.ignoreIK = true; } - if (!grabbableData.hasOwnProperty("kinematicGrab")) { - grabbableData.kinematicGrab = true; + if (!grabbableData.hasOwnProperty("kinematic")) { + grabbableData.kinematic = true; } if (!grabbableData.hasOwnProperty("wantsTrigger")) { grabbableData.wantsTrigger = false; @@ -315,6 +316,7 @@ if (typeof module !== 'undefined') { disableDispatcherModule: disableDispatcherModule, makeRunningValues: makeRunningValues, LEFT_HAND: LEFT_HAND, - RIGHT_HAND: RIGHT_HAND + RIGHT_HAND: RIGHT_HAND, + BUMPER_ON_VALUE: BUMPER_ON_VALUE }; } From bce92a622e266e10ba8d907f93bf87f8511ca724 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 13 Sep 2017 09:45:02 -0700 Subject: [PATCH 76/78] cr feedback --- interface/src/commerce/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 9b850cdb49..04cf5a671e 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -371,7 +371,7 @@ bool Wallet::readSecurityImage(const QString& inputFilePath, unsigned char** out QByteArray base64EncryptedBuffer; - while(!inputFile.atEnd()) { + while (!inputFile.atEnd()) { QString line(inputFile.readLine()); if (!foundHeader) { foundHeader = (line == IMAGE_HEADER); From 59746c564b1386867471c4a110b70bbbb72a31d9 Mon Sep 17 00:00:00 2001 From: Menithal Date: Wed, 13 Sep 2017 21:24:24 +0300 Subject: [PATCH 77/78] 21536: Applied forgotten beutify to file --- unpublishedScripts/marketplace/clap/clapApp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js index 05df03baf6..b2d8ce55db 100644 --- a/unpublishedScripts/marketplace/clap/clapApp.js +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -22,7 +22,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2"); var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2"); -if ( Settings.getValue("clapAppEnabled") === undefined){ +if (Settings.getValue("clapAppEnabled") === undefined) { Settings.setValue("clapAppEnabled", true); } var isActive = Settings.getValue("clapAppEnabled"); From 1b288ef860e4a40a1dbb3d8c1db16bcf4ba6affa Mon Sep 17 00:00:00 2001 From: druiz17 Date: Wed, 13 Sep 2017 16:15:36 -0700 Subject: [PATCH 78/78] make garbbing radius the same as master --- scripts/system/controllers/controllerDispatcher.js | 2 +- scripts/system/libraries/controllerDispatcherUtils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 5b72e63054..990f156ba8 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { - var NEAR_MAX_RADIUS = 1.0; + var NEAR_MAX_RADIUS = 0.1; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 7bed6413da..715d520501 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -76,7 +76,7 @@ COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; -NEAR_GRAB_RADIUS = 0.1; +NEAR_GRAB_RADIUS = 1.0; DISPATCHER_PROPERTIES = [ "position",