From f0c9f1d120522f18c232a55d06520f6b81508b1c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Jul 2017 15:32:13 -0700 Subject: [PATCH 01/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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 28694dcf8a6e76918d1f1130d61abc28c2576d73 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Sep 2017 11:46:06 -0700 Subject: [PATCH 42/65] 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 43/65] 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 44/65] 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 45/65] 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 46/65] 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 47/65] 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 48/65] 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 49/65] 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 50/65] 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 51/65] 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 52/65] 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 a46e41607cc658ac510e452aa4af4c549b0b8b23 Mon Sep 17 00:00:00 2001 From: druiz17 Date: Mon, 11 Sep 2017 15:07:14 -0700 Subject: [PATCH 53/65] 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 3a75dcf84db6ff132d5ffe24813d26b1018a0d97 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 7 Sep 2017 14:32:50 -0700 Subject: [PATCH 54/65] 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 55/65] 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 56/65] 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 2850029f2620fe393f8b08b90563f09c6e9c3a12 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 Sep 2017 10:00:32 -0700 Subject: [PATCH 57/65] 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 58/65] 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 59/65] 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 60/65] 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 b3ff5c86f77a2a9cbfcb8f75b98d74237d3a08af Mon Sep 17 00:00:00 2001 From: druiz17 Date: Tue, 12 Sep 2017 14:43:06 -0700 Subject: [PATCH 61/65] 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 1b288ef860e4a40a1dbb3d8c1db16bcf4ba6affa Mon Sep 17 00:00:00 2001 From: druiz17 Date: Wed, 13 Sep 2017 16:15:36 -0700 Subject: [PATCH 62/65] 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", From 745d2ff39536754c3219479efde62a1a98eebed8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 13 Sep 2017 17:36:56 -0700 Subject: [PATCH 63/65] Remove handControllerGrab.js, see controllerDispatcher.js instead. --- .../system/controllers/handControllerGrab.js | 4192 ----------------- 1 file changed, 4192 deletions(-) delete mode 100644 scripts/system/controllers/handControllerGrab.js diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js deleted file mode 100644 index f6eec7ab76..0000000000 --- a/scripts/system/controllers/handControllerGrab.js +++ /dev/null @@ -1,4192 +0,0 @@ -"use strict"; - -// handControllerGrab.js -// -// Created by Eric Levin on 9/2/15 -// Additions by James B. Pollack @imgntn on 9/24/2015 -// Additions By Seth Alves on 10/20/2015 -// Copyright 2015 High Fidelity, Inc. -// -// 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 }] */ - -(function() { // BEGIN LOCAL_SCOPE - -Script.include("/~/system/libraries/utils.js"); -Script.include("/~/system/libraries/Xform.js"); -Script.include("/~/system/libraries/controllers.js"); - -// -// add lines where the hand ray picking is happening -// - -var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; -var WANT_DEBUG_SEARCH_NAME = null; - -var UPDATE_SLEEP_MS = 16; // how many milliseconds to wait between "update" calls - -var FORCE_IGNORE_IK = false; -var SHOW_GRAB_POINT_SPHERE = false; - -// -// these tune time-averaging and "on" value for analog trigger -// - -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; - -var THUMB_ON_VALUE = 0.5; - -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; - -// triggered when stylus presses a web overlay/entity -var HAPTIC_STYLUS_STRENGTH = 1.0; -var HAPTIC_STYLUS_DURATION = 20.0; - -// triggerd when ui laser presses a web overlay/entity -var HAPTIC_LASER_UI_STRENGTH = 1.0; -var HAPTIC_LASER_UI_DURATION = 20.0; - -var PICK_WITH_HAND_RAY = true; - -var EQUIP_SPHERE_SCALE_FACTOR = 0.65; - -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 - -// -// distant manipulation -// -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 -var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects - -var COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { - red: 10, - green: 10, - blue: 255 -}; - -var COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { - red: 250, - green: 10, - blue: 10 -}; - -var COLORS_GRAB_DISTANCE_HOLD = { - red: 238, - green: 75, - blue: 214 -}; - -var PICK_MAX_DISTANCE = 500; // max length of pick-ray - -// -// near grabbing -// - -var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. -// if EQUIP_HOTSPOT_RENDER_RADIUS is greater than zero, the hotspot will appear before the hand -// has reached the required position, and then grow larger once the hand is close enough to equip. -var EQUIP_HOTSPOT_RENDER_RADIUS = 0.0; // radius used for palm vs equip-hotspot for rendering hot-spots -var MAX_EQUIP_HOTSPOT_RADIUS = 1.0; -var MAX_FAR_TO_NEAR_EQUIP_HOTSPOT_RADIUS = 0.5; // radius used for far to near equipping object. -var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position - -var NEAR_GRAB_RADIUS = 0.1; // radius used for palm vs object for near grabbing. -var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand -var FAR_TO_NEAR_GRAB_MAX_DISTANCE = 0.3; // In far to near grabbing conversion,grab the object if distancetoObject from hand is less than this. -var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. -var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed - -// if an equipped item is "adjusted" to be too far from the hand it's in, it will be unequipped. -var CHECK_TOO_FAR_UNEQUIP_TIME = 0.3; // seconds, duration between checks - - -var GRAB_POINT_SPHERE_RADIUS = NEAR_GRAB_RADIUS; -var GRAB_POINT_SPHERE_COLOR = { red: 240, green: 240, blue: 240 }; -var GRAB_POINT_SPHERE_ALPHA = 0.85; - -// -// other constants -// -var RIGHT_HAND = 1; -var LEFT_HAND = 0; - -var ZERO_VEC = { - x: 0, - y: 0, - z: 0 -}; - -var ONE_VEC = { - x: 1, - y: 1, - z: 1 -}; - -var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; -var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; - -var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; -var INCHES_TO_METERS = 1.0 / 39.3701; - - -// these control how long an abandoned pointer line or action will hang around -var ACTION_TTL = 15; // seconds -var ACTION_TTL_REFRESH = 5; -var MSECS_PER_SEC = 1000.0; -var GRABBABLE_PROPERTIES = [ - "position", - "registrationPoint", - "rotation", - "gravity", - "collidesWith", - "dynamic", - "collisionless", - "locked", - "name", - "shapeType", - "parentID", - "parentJointIndex", - "density", - "dimensions", - "userData" -]; - -var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js - -var DEFAULT_GRABBABLE_DATA = { - disableReleaseVelocity: false -}; - -// sometimes we want to exclude objects from being picked -var USE_BLACKLIST = true; -var blacklist = []; - -var hoveredEntityID = false; -var contextOverlayTimer = false; -var entityWithContextOverlay = false; -var contextualHand = -1; - -var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; -var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; - -var holdEnabled = true; -var nearGrabEnabled = true; -var farGrabEnabled = true; -var myAvatarScalingEnabled = true; -var objectScalingEnabled = true; -var mostRecentSearchingHand = RIGHT_HAND; -var DEFAULT_SPHERE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/equip-Fresnel-3.fbx"; -var HARDWARE_MOUSE_ID = 0; // Value reserved for hardware mouse. - -// states for the state machine -var STATE_OFF = 0; -var STATE_SEARCHING = 1; -var STATE_DISTANCE_HOLDING = 2; -var STATE_DISTANCE_ROTATING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 5; -var STATE_FAR_TRIGGER = 6; -var STATE_HOLD = 7; -var STATE_ENTITY_LASER_TOUCHING = 8; -var STATE_OVERLAY_LASER_TOUCHING = 9; -var STATE_STYLUS_TOUCHING = 10; - -var CONTROLLER_STATE_MACHINE = {}; - -CONTROLLER_STATE_MACHINE[STATE_OFF] = { - name: "off", - enterMethod: "offEnter", - updateMethod: "off" -}; -CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { - name: "searching", - enterMethod: "searchEnter", - exitMethod: "searchExit", - updateMethod: "search" -}; -CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { - name: "distance_holding", - enterMethod: "distanceHoldingEnter", - updateMethod: "distanceHolding" -}; -CONTROLLER_STATE_MACHINE[STATE_DISTANCE_ROTATING] = { - name: "distance_rotating", - enterMethod: "distanceRotatingEnter", - updateMethod: "distanceRotating" -}; -CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { - name: "near_grabbing", - enterMethod: "nearGrabbingEnter", - updateMethod: "nearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_HOLD] = { - name: "hold", - enterMethod: "nearGrabbingEnter", - updateMethod: "nearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { - name: "trigger", - enterMethod: "nearTriggerEnter", - updateMethod: "nearTrigger" -}; -CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { - name: "far_trigger", - enterMethod: "farTriggerEnter", - updateMethod: "farTrigger" -}; -CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = { - name: "entityLaserTouching", - enterMethod: "entityLaserTouchingEnter", - exitMethod: "entityLaserTouchingExit", - updateMethod: "entityLaserTouching" -}; -CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = { - name: "overlayLaserTouching", - enterMethod: "overlayLaserTouchingEnter", - exitMethod: "overlayLaserTouchingExit", - updateMethod: "overlayLaserTouching" -}; -CONTROLLER_STATE_MACHINE[STATE_STYLUS_TOUCHING] = { - name: "stylusTouching", - enterMethod: "stylusTouchingEnter", - exitMethod: "stylusTouchingExit", - updateMethod: "stylusTouching" -}; - -function distance2D(a, b) { - var dx = (a.x - b.x); - var dy = (a.y - b.y); - return Math.sqrt(dx * dx + dy * dy); -} - -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 - }; -} - -// 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 distanceBetweenPointAndEntityBoundingBox(point, entityProps) { - var entityXform = new Xform(entityProps.rotation, entityProps.position); - var localPoint = entityXform.inv().xformPoint(point); - var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); - var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); - var localMin = Vec3.subtract(entityXform.trans, minOffset); - var localMax = Vec3.sum(entityXform.trans, maxOffset); - - var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; - v.x = Math.max(v.x, localMin.x); - v.x = Math.min(v.x, localMax.x); - v.y = Math.max(v.y, localMin.y); - v.y = Math.min(v.y, localMax.y); - v.z = Math.max(v.z, localMin.z); - v.z = Math.min(v.z, localMax.z); - - return Vec3.distance(v, localPoint); -} - -function projectOntoXYPlane(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 -} - -function projectOntoEntityXYPlane(entityID, worldPos) { - var props = entityPropertiesCache.getProps(entityID); - if (props && props.position && props.rotation && props.dimensions && props.registrationPoint) { - return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); - } -} - -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"); - if (scale) { - 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 && dimensions.z) { - dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. - } - } - - if (position && rotation && dimensions) { - return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); - } -} - -function handLaserIntersectItem(position, rotation, start) { - var worldHandPosition = start.position; - var worldHandRotation = start.orientation; - - if (position) { - var planePosition = position; - var planeNormal = Vec3.multiplyQbyV(rotation, {x: 0, y: 0, z: 1.0}); - var rayStart = worldHandPosition; - var rayDirection = Quat.getUp(worldHandRotation); - var intersectionInfo = rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection); - - var intersectionPoint = planePosition; - if (intersectionInfo.hit && intersectionInfo.distance > 0) { - intersectionPoint = Vec3.sum(rayStart, Vec3.multiply(intersectionInfo.distance, rayDirection)); - } else { - intersectionPoint = planePosition; - } - intersectionInfo.point = intersectionPoint; - intersectionInfo.normal = planeNormal; - intersectionInfo.searchRay = { - origin: rayStart, - direction: rayDirection, - length: PICK_MAX_DISTANCE - }; - - return intersectionInfo; - } else { - // entity has been destroyed? or is no longer in cache - return null; - } -} - -function handLaserIntersectEntity(entityID, start) { - var props = entityPropertiesCache.getProps(entityID); - return handLaserIntersectItem(props.position, props.rotation, start); -} - -function handLaserIntersectOverlay(overlayID, start) { - var position = Overlays.getProperty(overlayID, "position"); - var rotation = Overlays.getProperty(overlayID, "rotation"); - return handLaserIntersectItem(position, rotation, start); -} - -function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) { - var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal); - if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) { - var rayStartDotPlaneNormal = Vec3.dot(Vec3.subtract(planePosition, rayStart), planeNormal); - var distance = rayStartDotPlaneNormal / rayDirectionDotPlaneNormal; - return {hit: true, distance: distance}; - } else { - // ray is parallel to the plane - return {hit: false, distance: 0}; - } -} - -function stateToName(state) { - return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; -} - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -function colorPow(color, power) { - return { - red: Math.pow(color.red / 255.0, power) * 255, - green: Math.pow(color.green / 255.0, power) * 255, - blue: Math.pow(color.blue / 255.0, power) * 255 - }; -} - -function entityHasActions(entityID) { - return Entities.getActionIDs(entityID).length > 0; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments.tag; - if (tag === getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - var GRAB_PREFIX_LENGTH = 5; - var UUID_LENGTH = 38; - if (tag && tag.slice(0, GRAB_PREFIX_LENGTH) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return tag.slice(GRAB_PREFIX_LENGTH, GRAB_PREFIX_LENGTH + UUID_LENGTH - 1); - } - } - return null; -} - -function propsArePhysical(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; -} - -var USE_ATTACH_POINT_SETTINGS = true; - -var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; - -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); -} - -// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, -// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. -var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; - -function isEditing() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && isInEditMode(); -} - -function isIn2DMode() { - // In this version, we make our own determination of whether we're aimed a HUD element, - // because other scripts (such as handControllerPointer) might be using some other visualization - // instead of setting Reticle.visible. - return (EXTERNALLY_MANAGED_2D_MINOR_MODE && - (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position))); -} - -function restore2DMode() { - if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { - Reticle.setVisible(true); - } -} - -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 entity does not exist. -function calculateStylusTargetFromEntity(stylusTip, entityID) { - var props = entityPropertiesCache.getProps(entityID); - 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: entityID, - overlayID: null, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - normalizedPosition: normalizedPosition, - dimensions: props.dimensions, - valid: true - }; -} - -// 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 - }; -} - -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; -} - -// EntityPropertiesCache is a helper class that contains a cache of entity properties. -// the hope is to prevent excess calls to Entity.getEntityProperties() -// -// usage: -// call EntityPropertiesCache.addEntities with all the entities that you are interested in. -// This will fetch their properties. Then call EntityPropertiesCache.getProps to receive an object -// containing a cache of all the properties previously fetched. -function EntityPropertiesCache() { - this.cache = {}; -} -EntityPropertiesCache.prototype.clear = function() { - this.cache = {}; -}; -EntityPropertiesCache.prototype.addEntity = function(entityID) { - var cacheEntry = this.cache[entityID]; - if (cacheEntry && cacheEntry.refCount) { - cacheEntry.refCount += 1; - } else { - this._updateCacheEntry(entityID); - } -}; -EntityPropertiesCache.prototype.addEntities = function(entities) { - var _this = this; - entities.forEach(function(entityID) { - _this.addEntity(entityID); - }); -}; -EntityPropertiesCache.prototype._updateCacheEntry = function(entityID) { - var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); - - // convert props.userData from a string to an object. - var userData = {}; - if (props.userData) { - try { - userData = JSON.parse(props.userData); - } catch (err) { - print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); - } - } - props.userData = userData; - props.refCount = 1; - - this.cache[entityID] = props; -}; -EntityPropertiesCache.prototype.update = function() { - // delete any cacheEntries with zero refCounts. - var entities = Object.keys(this.cache); - for (var i = 0; i < entities.length; i++) { - var props = this.cache[entities[i]]; - if (props.refCount === 0) { - delete this.cache[entities[i]]; - } else { - props.refCount = 0; - } - } -}; -EntityPropertiesCache.prototype.getProps = function(entityID) { - var obj = this.cache[entityID]; - return obj ? obj : undefined; -}; -EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { - var props = this.cache[entityID]; - if (props) { - return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; - } else { - return undefined; - } -}; -EntityPropertiesCache.prototype.getGrabProps = function(entityID) { - var props = this.cache[entityID]; - if (props) { - return props.userData.grabKey ? props.userData.grabKey : {}; - } else { - return undefined; - } -}; -EntityPropertiesCache.prototype.getWearableProps = function(entityID) { - var props = this.cache[entityID]; - if (props) { - return props.userData.wearable ? props.userData.wearable : {}; - } else { - return undefined; - } -}; -EntityPropertiesCache.prototype.getEquipHotspotsProps = function(entityID) { - var props = this.cache[entityID]; - if (props) { - return props.userData.equipHotspots ? props.userData.equipHotspots : {}; - } else { - return undefined; - } -}; - -// global cache -var entityPropertiesCache = new EntityPropertiesCache(); - -// 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"; - this.map[hotspot.key] = overlayInfoSet; - } else { - 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) { - - 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) { - // because this item timestamp has expired, it might not be in the cache anymore.... - entityPropertiesCache.addEntity(overlayInfoSet.entityID); - 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) { - // 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. - - var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); - 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 - }); - }); - } - } -}; - -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"); -} - -// global EquipHotspotBuddy instance -var equipHotspotBuddy = new EquipHotspotBuddy(); - -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 headRenderStates = [{name: "half", end: halfEnd}, - {name: "full", end: fullEnd}, - {name: "hold", path: holdPath}]; - -// how far from camera to search intersection? -var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; -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 MyController(hand) { - this.hand = hand; - this.autoUnequipCounter = 0; - this.grabPointIntersectsEntity = false; - this.stylus = null; - this.homeButtonTouched = false; - this.editTriggered = false; - - // Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems - // when more than one avatar does parenting grabs on things. This script tries to work - // around this with two associative arrays: previousParentID and previousParentJointIndex. If - // (1) avatar-A does a parenting grab on something, and then (2) avatar-B takes it, and (3) avatar-A - // releases it and then (4) avatar-B releases it, then avatar-B will set the parent back to - // avatar-A's hand. Avatar-A is no longer grabbing it, so it will end up triggering avatar-A's - // checkForUnexpectedChildren which will put it back to wherever it was when avatar-A initially grabbed it. - // this will work most of the time, unless avatar-A crashes or logs out while avatar-B is grabbing the - // entity. This can also happen when a single avatar passes something from hand to hand. - this.previousParentID = {}; - this.previousParentJointIndex = {}; - this.previouslyUnhooked = {}; - - this.shouldScale = false; - this.isScalingAvatar = false; - - // 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.handToController = function() { - return (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.actionID = null; // action this script created... - this.grabbedThingID = null; // on this entity. - this.grabbedOverlay = null; - this.state = STATE_OFF; - - this.triggerValue = 0; // rolling average of trigger value - this.triggerClicked = false; - this.rawTriggerValue = 0; - this.rawSecondaryValue = 0; - this.rawThumbValue = 0; - - // for visualizations - this.halfEnd = halfEnd; - this.fullEnd = fullEnd; - this.laserPointer = LaserPointers.createLaserPointer({ - joint: (hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_INCLUDE_NONCOLLIDABLE, - maxDistance: PICK_MAX_DISTANCE, - posOffset: getGrabPointSphereOffset(this.handToController()), - renderStates: renderStates, - faceAvatar: true, - defaultRenderStates: defaultRenderStates - }); - this.headLaserPointer = LaserPointers.createLaserPointer({ - joint: "Avatar", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_INCLUDE_NONCOLLIDABLE, - maxDistance: PICK_MAX_DISTANCE, - renderStates: headRenderStates, - faceAvatar: true, - defaultRenderStates: defaultRenderStates - }); - LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); - LaserPointers.setIgnoreOverlays(this.headLaserPointer, [HMD.tabletID]); - - this.otherGrabbingUUID = null; - - this.waitForTriggerRelease = false; - - this.intersectionDistance = 0.0; - - this.ignoreIK = false; - this.offsetPosition = Vec3.ZERO; - this.offsetRotation = Quat.IDENTITY; - - this.lastPickTime = 0; - this.lastUnequipCheckTime = 0; - - this.equipOverlayInfoSetMap = {}; - - this.tabletStabbed = false; - this.tabletStabbedPos2D = null; - this.tabletStabbedPos3D = null; - - 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; - - var _this = this; - - var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; - this.ignoreInput = function() { - // We've made the decision to use 'this' for new code, even though it is fragile, - // in order to keep/ the code uniform without making any no-op line changes. - return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); - }; - - 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 then 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.update = function(deltaTime, timestamp) { - this.updateSmoothedTrigger(); - this.maybeScaleMyAvatar(); - - this.updateStylusTip(); - - 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; - } - - if (this.ignoreInput()) { - - // Most hand input is disabled, because we are interacting with the 2d hud. - // However, we still should check for collisions of the stylus with the web overlay. - this.processStylus(); - this.turnOffVisualizations(); - return; - } - - if (CONTROLLER_STATE_MACHINE[this.state]) { - var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; - var updateMethod = this[updateMethodName]; - if (updateMethod) { - updateMethod.call(this, deltaTime, timestamp); - } else { - print("WARNING: could not find updateMethod for state " + stateToName(this.state)); - } - } else { - print("WARNING: could not find state " + this.state + " in state machine"); - } - }; - - this.callEntityMethodOnGrabbed = function(entityMethodName) { - if (this.grabbedIsOverlay) { - return; - } - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, entityMethodName, args); - }; - - this.setState = function(newState, reason) { - if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) && - (newState !== STATE_OFF && - newState !== STATE_SEARCHING && - newState !== STATE_STYLUS_TOUCHING && - newState !== STATE_OVERLAY_LASER_TOUCHING)) { - return; - } - setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || - (newState === STATE_DISTANCE_ROTATING) || - (newState === STATE_NEAR_GRABBING)); - if (WANT_DEBUG || WANT_DEBUG_STATE) { - var oldStateName = stateToName(this.state); - var newStateName = stateToName(newState); - print("STATE (" + this.hand + "): " + this.state + "-" + newStateName + - " <-- " + oldStateName + ", reason = " + reason); - } - - // exit the old state - if (CONTROLLER_STATE_MACHINE[this.state]) { - var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; - var exitMethod = this[exitMethodName]; - if (exitMethod) { - exitMethod.call(this); - } - } else { - print("WARNING: could not find state " + this.state + " in state machine"); - } - - this.state = newState; - - // enter the new state - if (CONTROLLER_STATE_MACHINE[newState]) { - var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; - var enterMethod = this[enterMethodName]; - if (enterMethod) { - enterMethod.call(this); - } - } else { - print("WARNING: could not find newState " + newState + " in state machine"); - } - }; - - this.grabPointSphereOn = function() { - if (!SHOW_GRAB_POINT_SPHERE) { - return; - } - - if (!this.grabPointSphere) { - this.grabPointSphere = Overlays.addOverlay("sphere", { - name: "grabPointSphere", - localPosition: getGrabPointSphereOffset(this.handToController()), - localRotation: { x: 0, y: 0, z: 0, w: 1 }, - dimensions: GRAB_POINT_SPHERE_RADIUS * 2, - color: GRAB_POINT_SPHERE_COLOR, - alpha: GRAB_POINT_SPHERE_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false, - parentID: AVATAR_SELF_ID, - parentJointIndex: this.controllerJointIndex - }); - } - }; - - this.grabPointSphereOff = function() { - if (this.grabPointSphere) { - Overlays.deleteOverlay(this.grabPointSphere); - this.grabPointSphere = null; - } - }; - - 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.updateLaserPointer = function() { - 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 (this.state !== STATE_DISTANCE_HOLDING && this.state !== STATE_DISTANCE_ROTATING) { - mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "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 (this.state === STATE_DISTANCE_HOLDING || this.state === STATE_DISTANCE_ROTATING) { - 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.evalLightWorldTransform = function(modelPos, modelRot) { - - var MODEL_LIGHT_POSITION = { - x: 0, - y: -0.3, - z: 0 - }; - - var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - }); - - return { - p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), - q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) - }; - }; - - this.turnOffVisualizations = function() { - this.grabPointSphereOff(); - this.laserPointerOff(); - restore2DMode(); - }; - - this.triggerPress = function(value) { - _this.rawTriggerValue = value; - }; - - this.triggerClick = function(value) { - _this.triggerClicked = value; - }; - - this.secondaryPress = function(value) { - _this.rawSecondaryValue = value; - }; - - this.updateSmoothedTrigger = function() { - var triggerValue = this.rawTriggerValue; - // 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.triggerOrsecondarySqueezed = function () { - // return triggerSmoothedSqueezed() || secondarySqueezed(); - // } - - // this.triggerAndSecondaryReleased = function () { - // return triggerSmoothedReleased() && secondaryReleased(); - // } - - this.thumbPress = function(value) { - _this.rawThumbValue = value; - }; - - this.thumbPressed = function() { - return _this.rawThumbValue > THUMB_ON_VALUE; - }; - - this.thumbReleased = function() { - return _this.rawThumbValue < THUMB_ON_VALUE; - }; - - 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() { - if (!this.stylusTip.valid) { - this.pointFinger(false); - this.hideStylus(); - return; - } - - if (this.useFingerInsteadOfStylus) { - this.hideStylus(); - } - - var tipPosition = this.stylusTip.position; - - // build list of stylus targets, near the stylusTip - var stylusTargets = []; - var candidateEntities = Entities.findEntities(tipPosition, WEB_DISPLAY_STYLUS_DISTANCE); - entityPropertiesCache.addEntities(candidateEntities); - var i, props, stylusTarget; - for (i = 0; i < candidateEntities.length; i++) { - props = entityPropertiesCache.getProps(candidateEntities[i]); - if (props && (props.type === "Web" || this.isTablet(candidateEntities[i]))) { - 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) { - - var name; - if (nearestStylusTarget.entityID) { - name = entityPropertiesCache.getProps(nearestStylusTarget.entityID).name; - this.stylusTarget = nearestStylusTarget; - this.setState(STATE_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); - } else if (nearestStylusTarget.overlayID) { - name = Overlays.getProperty(nearestStylusTarget.overlayID, "name"); - this.stylusTarget = nearestStylusTarget; - this.setState(STATE_STYLUS_TOUCHING, "begin touching overlay '" + name + "'"); - } - } - } else { - this.relinquishTouchFocus(); - } - - this.homeButtonTouched = false; - }; - - this.off = function(deltaTime, timestamp) { - - this.controllerJointIndex = getControllerJointIndex(this.hand); - this.checkForUnexpectedChildren(); - - if (this.editTriggered) { - this.editTriggered = false; - } - - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.waitForTriggerRelease = false; - } - if (!this.waitForTriggerRelease && (this.triggerSmoothedSqueezed() || this.secondarySqueezed())) { - this.lastPickTime = 0; - this.startingHandRotation = getControllerWorldLocation(this.handToController(), true).orientation; - this.searchStartTime = Date.now(); - this.setState(STATE_SEARCHING, "trigger squeeze detected"); - return; - } - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - - var candidateEntities = Entities.findEntities(worldHandPosition, MAX_EQUIP_HOTSPOT_RADIUS); - entityPropertiesCache.addEntities(candidateEntities); - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); - if (!this.waitForTriggerRelease) { - this.updateEquipHaptics(potentialEquipHotspot, worldHandPosition); - } - - var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); - if (potentialEquipHotspot) { - equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); - } - - // when the grab-point enters a grabable entity, give a haptic pulse - candidateEntities = Entities.findEntities(worldHandPosition, NEAR_GRAB_RADIUS); - var grabbableEntities = candidateEntities.filter(function(entity) { - return _this.entityIsNearGrabbable(entity, worldHandPosition, NEAR_GRAB_MAX_DISTANCE); - }); - if (grabbableEntities.length > 0) { - if (!this.grabPointIntersectsEntity) { - // don't do haptic pulse for tablet - var nonTabletEntities = grabbableEntities.filter(function(entityID) { - return entityID != HMD.tabletID && entityID != HMD.homeButtonID; - }); - if (nonTabletEntities.length > 0) { - Controller.triggerHapticPulse(1, 20, this.hand); - } - this.grabPointIntersectsEntity = true; - this.grabPointSphereOn(); - } - } else { - this.grabPointIntersectsEntity = false; - this.grabPointSphereOff(); - } - - this.processStylus(); - - if (isInEditMode() && !this.isNearStylusTarget && HMD.isHandControllerAvailable()) { - // Always showing lasers while in edit mode and hands/stylus is not active. - - var rayPickInfo = this.calcRayPickInfo(this.hand); - if (rayPickInfo.isValid) { - this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; - this.updateLaserPointer(); - } else { - this.laserPointerOff(); - } - } else { - this.laserPointerOff(); - } - }; - - this.handleLaserOnHomeButton = function(rayPickInfo) { - if (rayPickInfo.overlayID && this.triggerSmoothedGrab()) { - var homeButton = rayPickInfo.overlayID; - var hmdHomeButton = HMD.homeButtonID; - if (homeButton === hmdHomeButton) { - if (this.homeButtonTouched === false) { - this.homeButtonTouched = true; - Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); - Messages.sendLocalMessage("home", homeButton); - } - } else { - this.homeButtonTouched = false; - } - } else { - this.homeButtonTouched = false; - } - }; - - 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; - }; - - // Performs ray pick test from the hand controller into the world - // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - // @returns {object} returns object with two keys entityID and distance - // - this.calcRayPickInfo = function(hand) { - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; - - var pickRay; - var valid = true - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; - valid = !(worldHandPosition === undefined); - - pickRay = { - origin: PICK_WITH_HAND_RAY ? worldHandPosition : MyAvatar.getHeadPosition(), - direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Quat.getFront(Camera.orientation), - length: PICK_MAX_DISTANCE - }; - - var result = { - entityID: null, - overlayID: null, - searchRay: pickRay, - distance: PICK_MAX_DISTANCE, - isValid: valid - }; - - var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; - var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); - - if (intersection.type != RayPick.INTERSECTED_NONE) { - return { - entityID: intersection.type == RayPick.INTERSECTED_ENTITY ? intersection.objectID : null, - overlayID: intersection.type == RayPick.INTERSECTED_OVERLAY ? intersection.objectID : null, - searchRay: pickRay, - distance: intersection.distance, - intersection: intersection.intersection, - normal: intersection.surfaceNormal - }; - } else { - return result; - } - }; - - this.entityWantsTrigger = function(entityID) { - var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); - return grabbableProps && grabbableProps.wantsTrigger; - }; - - // 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(entityID) { - var result = []; - var props = entityPropertiesCache.getProps(entityID); - var entityXform = new Xform(props.rotation, props.position); - var equipHotspotsProps = entityPropertiesCache.getEquipHotspotsProps(entityID); - 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 = entityPropertiesCache.getWearableProps(entityID); - 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) { - var props = entityPropertiesCache.getProps(hotspot.entityID); - var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - - var otherHandControllerState = this.getOtherHandController().state; - var okToEquipFromOtherHand = ((otherHandControllerState === STATE_NEAR_GRABBING || - otherHandControllerState === STATE_DISTANCE_HOLDING || - otherHandControllerState === STATE_DISTANCE_ROTATING) && - this.getOtherHandController().grabbedThingID === hotspot.entityID); - var hasParent = true; - if (props.parentID === NULL_UUID) { - hasParent = false; - } - if ((hasParent || entityHasActions(hotspot.entityID)) && !okToEquipFromOtherHand) { - if (debug) { - print("equip is skipping '" + props.name + "': grabbed by someone else"); - } - return false; - } - - return true; - }; - this.entityIsCloneable = function(entityID) { - var entityProps = entityPropertiesCache.getGrabbableProps(entityID); - var props = entityPropertiesCache.getProps(entityID); - if (!props) { - return false; - } - - if (entityProps.hasOwnProperty("cloneable")) { - return entityProps.cloneable; - } - return false; - }; - this.entityIsGrabbable = function(entityID) { - var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); - var props = entityPropertiesCache.getProps(entityID); - if (!props) { - return false; - } - var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - var grabbable = propsArePhysical(props); - if (grabbableProps.hasOwnProperty("grabbable")) { - grabbable = grabbableProps.grabbable; - } - - if (!grabbable && !grabbableProps.wantsTrigger) { - if (debug) { - print("grab is skipping '" + props.name + "': not grabbable."); - } - return false; - } - if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { - if (debug) { - print("grab is skipping '" + props.name + "': forbidden entity type."); - } - return false; - } - if (props.locked && !grabbableProps.wantsTrigger) { - if (debug) { - print("grab is skipping '" + props.name + "': locked and not triggerable."); - } - return false; - } - if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { - if (debug) { - print("grab is skipping '" + props.name + "': forbidden name."); - } - return false; - } - - return true; - }; - - this.entityIsDistanceGrabbable = function(entityID, handPosition) { - if (!this.entityIsGrabbable(entityID)) { - return false; - } - - var props = entityPropertiesCache.getProps(entityID); - var distance = Vec3.distance(props.position, handPosition); - var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - - // we can't distance-grab non-physical - var isPhysical = propsArePhysical(props); - if (!isPhysical) { - if (debug) { - print("distance grab is skipping '" + props.name + "': not physical"); - } - return false; - } - - if (distance > PICK_MAX_DISTANCE) { - // too far away, don't grab - if (debug) { - print("distance grab is skipping '" + props.name + "': too far away."); - } - return false; - } - - 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; - }; - - this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { - - if (!this.entityIsCloneable(entityID) && !this.entityIsGrabbable(entityID)) { - return false; - } - - var props = entityPropertiesCache.getProps(entityID); - var distance = Vec3.distance(props.position, handPosition); - var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - - if (distance > maxDistance) { - // too far away, don't grab - if (debug) { - print(" grab is skipping '" + props.name + "': too far away."); - } - return false; - } - - return true; - }; - - this.entityIsFarToNearGrabbable = function (objectPosition, handPosition, maxDistance) { - var distanceToObjectFromHand = Vec3.length(Vec3.subtract(handPosition, objectPosition)); - - if (distanceToObjectFromHand > maxDistance) { - return false; - } - return true; - }; - - this.chooseNearEquipHotspots = function(candidateEntities, distance) { - var equippableHotspots = flatten(candidateEntities.map(function(entityID) { - return _this.collectEquipHotspots(entityID); - })).filter(function(hotspot) { - return (_this.hotspotIsEquippable(hotspot) && - Vec3.distance(hotspot.worldPosition, getControllerWorldLocation(_this.handToController(), true).position) < - hotspot.radius + distance); - }); - return equippableHotspots; - }; - - this.chooseBestEquipHotspot = function(candidateEntities) { - var DISTANCE = 0; - var equippableHotspots = this.chooseNearEquipHotspots(candidateEntities, DISTANCE); - var _this = this; - if (equippableHotspots.length > 0) { - // sort by distance - equippableHotspots.sort(function(a, b) { - var handControllerLocation = getControllerWorldLocation(_this.handToController(), true); - var aDistance = Vec3.distance(a.worldPosition, handControllerLocation.position); - var bDistance = Vec3.distance(b.worldPosition, handControllerLocation.position); - return aDistance - bDistance; - }); - return equippableHotspots[0]; - } else { - return null; - } - }; - - this.chooseNearEquipHotspotsForFarToNearEquip = function(candidateEntities, distance) { - var equippableHotspots = flatten(candidateEntities.map(function(entityID) { - return _this.collectEquipHotspots(entityID); - })).filter(function(hotspot) { - return (Vec3.distance(hotspot.worldPosition, getControllerWorldLocation(_this.handToController(), true).position) < - hotspot.radius + distance); - }); - return equippableHotspots; - }; - - this.chooseBestEquipHotspotForFarToNearEquip = function(candidateEntities) { - var DISTANCE = 1; - var equippableHotspots = this.chooseNearEquipHotspotsForFarToNearEquip(candidateEntities, DISTANCE); - var _this = this; - if (equippableHotspots.length > 0) { - // sort by distance - equippableHotspots.sort(function(a, b) { - var handControllerLocation = getControllerWorldLocation(_this.handToController(), true); - var aDistance = Vec3.distance(a.worldPosition, handControllerLocation.position); - var bDistance = Vec3.distance(b.worldPosition, handControllerLocation.position); - return aDistance - bDistance; - }); - return equippableHotspots[0]; - } else { - return null; - } - }; - - this.searchEnter = function() { - mostRecentSearchingHand = this.hand; - var rayPickInfo = this.calcRayPickInfo(this.hand); - if (rayPickInfo.entityID || rayPickInfo.overlayID) { - this.intersectionDistance = rayPickInfo.distance; - } - }; - - this.searchExit = function () { - contextualHand = -1; - if (hoveredEntityID) { - Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent); - } - hoveredEntityID = false; - }; - - this.search = function(deltaTime, timestamp) { - var _this = this; - var name; - var FAR_SEARCH_DELAY = 0; // msecs before search beam appears - - var farSearching = this.triggerSmoothedSqueezed() && (Date.now() - this.searchStartTime > FAR_SEARCH_DELAY); - - this.grabbedThingID = null; - this.grabbedOverlay = null; - this.isInitialGrab = false; - this.preparingHoldRelease = false; - - this.checkForUnexpectedChildren(); - - if ((this.triggerSmoothedReleased() && this.secondaryReleased())) { - this.grabbedThingID = null; - this.setState(STATE_OFF, "trigger released"); - return; - } - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var handPosition = controllerLocation.position; - - var rayPickInfo = this.calcRayPickInfo(this.hand); - - if (rayPickInfo.entityID) { - entityPropertiesCache.addEntity(rayPickInfo.entityID); - } - - pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - if (rayPickInfo.entityID) { - if (hoveredEntityID !== rayPickInfo.entityID) { - if (contextOverlayTimer) { - Script.clearTimeout(contextOverlayTimer); - contextOverlayTimer = false; - } - if (hoveredEntityID) { - Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent); - } - hoveredEntityID = rayPickInfo.entityID; - Entities.sendHoverEnterEntity(hoveredEntityID, pointerEvent); - } - - // If we already have a context overlay, we don't want to move it to - // another entity while we're searching. - if (!entityWithContextOverlay && !contextOverlayTimer) { - contextOverlayTimer = Script.setTimeout(function () { - if (rayPickInfo.entityID === hoveredEntityID && - !entityWithContextOverlay && - contextualHand !== -1 && - contextOverlayTimer) { - var pointerEvent = { - type: "Move", - id: contextualHand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.entityID, pointerEvent)) { - entityWithContextOverlay = rayPickInfo.entityID; - hoveredEntityID = false; - } - } - contextOverlayTimer = false; - }, 500); - contextualHand = this.hand; - } - } else { - if (hoveredEntityID) { - Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent); - hoveredEntityID = false; - } - if (contextOverlayTimer) { - Script.clearTimeout(contextOverlayTimer); - contextOverlayTimer = false; - } - } - - var candidateHotSpotEntities = Entities.findEntities(handPosition, MAX_EQUIP_HOTSPOT_RADIUS); - entityPropertiesCache.addEntities(candidateHotSpotEntities); - - var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateHotSpotEntities); - if (potentialEquipHotspot) { - if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) { - this.grabbedHotspot = potentialEquipHotspot; - this.grabbedThingID = potentialEquipHotspot.entityID; - this.grabbedIsOverlay = false; - this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'"); - - return; - } - } - - var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); - var grabbableEntities = candidateEntities.filter(function(entity) { - return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); - }); - - var candidateOverlays = Overlays.findOverlays(handPosition, NEAR_GRAB_RADIUS); - var grabbableOverlays = candidateOverlays.filter(function(overlayID) { - return Overlays.getProperty(overlayID, "grabbable"); - }); - - if (rayPickInfo.entityID) { - this.intersectionDistance = rayPickInfo.distance; - if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { - grabbableEntities.push(rayPickInfo.entityID); - } - } else if (rayPickInfo.overlayID) { - this.intersectionDistance = rayPickInfo.distance; - } else { - this.intersectionDistance = 0; - } - - if (grabbableOverlays.length > 0) { - grabbableOverlays.sort(function(a, b) { - var aPosition = Overlays.getProperty(a, "position"); - var aDistance = Vec3.distance(aPosition, handPosition); - var bPosition = Overlays.getProperty(b, "position"); - var bDistance = Vec3.distance(bPosition, handPosition); - return aDistance - bDistance; - }); - this.grabbedThingID = grabbableOverlays[0]; - this.grabbedIsOverlay = true; - if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { - this.setState(STATE_NEAR_GRABBING, "near grab overlay '" + - Overlays.getProperty(this.grabbedThingID, "name") + "'"); - return; - } - } - - var entity; - if (grabbableEntities.length > 0) { - // sort by distance - grabbableEntities.sort(function(a, b) { - var aDistance = Vec3.distance(entityPropertiesCache.getProps(a).position, handPosition); - var bDistance = Vec3.distance(entityPropertiesCache.getProps(b).position, handPosition); - return aDistance - bDistance; - }); - entity = grabbableEntities[0]; - if (!isInEditMode() || entity == HMD.tabletID) { // tablet is grabbable, even when editing - name = entityPropertiesCache.getProps(entity).name; - this.grabbedThingID = entity; - this.grabbedIsOverlay = false; - if (this.entityWantsTrigger(entity)) { - if (this.triggerSmoothedGrab()) { - this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); - return; - } - } else { - // If near something grabbable, grab it! - if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) { - this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'"); - return; - } - } - } - } - - if (rayPickInfo.distance >= WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET) { - this.handleLaserOnHomeButton(rayPickInfo); - if (this.handleLaserOnWebEntity(rayPickInfo)) { - return; - } - if (this.handleLaserOnWebOverlay(rayPickInfo)) { - return; - } - } - - if (isInEditMode()) { - this.updateLaserPointer(); - if (this.triggerSmoothedGrab()) { - if (!this.editTriggered){ - if (rayPickInfo.entityID) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "selectEntity", - entityID: rayPickInfo.entityID - })); - } else if (rayPickInfo.overlayID) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "selectOverlay", - overlayID: rayPickInfo.overlayID - })); - } - } - this.editTriggered = true; - } - Reticle.setVisible(false); - return; - } - - if (rayPickInfo.entityID) { - entity = rayPickInfo.entityID; - name = entityPropertiesCache.getProps(entity).name; - if (this.entityWantsTrigger(entity)) { - if (this.triggerSmoothedGrab()) { - this.grabbedThingID = entity; - this.grabbedIsOverlay = false; - this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); - return; - } else { - // potentialFarTriggerEntity = entity; - } - this.laserPointerOff(); - } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { - if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - this.grabbedThingID = entity; - this.grabbedIsOverlay = false; - this.grabbedDistance = rayPickInfo.distance; - if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) { - this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'"); - } else { - this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); - } - return; - } else { - // potentialFarGrabEntity = entity; - } - this.laserPointerOff(); - } else if (this.otherGrabbingUUID !== null) { - if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - this.updateLaserPointer(); - } else { - this.laserPointerOff(); - } - } else { - this.laserPointerOff(); - } - } else { - this.laserPointerOff(); - } - - this.updateEquipHaptics(potentialEquipHotspot, handPosition); - - var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); - if (potentialEquipHotspot) { - equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); - } - - if (farGrabEnabled && farSearching) { - this.updateLaserPointer(); - } - Reticle.setVisible(false); - }; - - this.isTablet = function (entityID) { - if (entityID === HMD.tabletID) { - return true; - } - return false; - }; - - this.handleLaserOnWebEntity = function (rayPickInfo) { - var pointerEvent; - - if (rayPickInfo.entityID && Entities.wantsHandControllerPointerEvents(rayPickInfo.entityID)) { - var entity = rayPickInfo.entityID; - var name = entityPropertiesCache.getProps(entity).name; - - if (Entities.keyboardFocusEntity != entity) { - Overlays.keyboardFocusOverlay = 0; - Entities.keyboardFocusEntity = entity; - - pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - if (this.hoverEntity !== entity) { - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = entity; - Entities.sendHoverEnterEntity(this.hoverEntity, pointerEvent); - } - } - - // send mouse events for button highlights and tooltips. - if (this.hand == mostRecentSearchingHand || - (this.hand !== mostRecentSearchingHand && - this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { - - // most recently searching hand has priority over other hand, for the purposes of button highlighting. - pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - Entities.sendMouseMoveOnEntity(entity, pointerEvent); - Entities.sendHoverOverEntity(entity, pointerEvent); - } - - if (this.triggerSmoothedGrab()) { - this.grabbedThingID = entity; - this.grabbedIsOverlay = false; - this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'"); - return true; - } - - } else if (this.hoverEntity) { - pointerEvent = { - type: "Move", - id: this.hand + 1 - }; - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = null; - } - - return false; - }; - - this.handleLaserOnWebOverlay = function (rayPickInfo) { - var pointerEvent; - if (rayPickInfo.overlayID) { - var overlay = rayPickInfo.overlayID; - if ((Overlays.getProperty(overlay, "type") === "web3d") && Overlays.keyboardFocusOverlay != overlay) { - Entities.keyboardFocusEntity = null; - Overlays.keyboardFocusOverlay = overlay; - } - - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID, - pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - if (this.hoverOverlay !== overlay) { - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = overlay; - Overlays.sendHoverEnterOverlay(this.hoverOverlay, pointerEvent); - } - - // Send mouse events for button highlights and tooltips. - if (this.hand == mostRecentSearchingHand || - (this.hand !== mostRecentSearchingHand && - this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { - - // most recently searching hand has priority over other hand, for the purposes of button highlighting. - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID, - pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - Overlays.sendMouseMoveOnOverlay(overlay, pointerEvent); - Overlays.sendHoverOverOverlay(overlay, pointerEvent); - } - - if (this.triggerSmoothedGrab()) { - this.grabbedOverlay = overlay; - this.setState(STATE_OVERLAY_LASER_TOUCHING, "begin touching overlay '" + overlay + "'"); - return true; - } - - } else if (this.hoverOverlay) { - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID - }; - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = null; - } - - return false; - }; - - 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.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.distanceHoldingEnter = function() { - this.clearEquipHaptics(); - this.grabPointSphereOff(); - - this.shouldScale = false; - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - 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 grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - 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: getTag(), - ttl: ACTION_TTL - }); - if (this.actionID === NULL_UUID) { - this.actionID = null; - } - this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - - if (this.actionID !== null) { - this.callEntityMethodOnGrabbed("startDistanceGrab"); - } - - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.turnOffVisualizations(); - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.distanceHolding = function(deltaTime, timestamp) { - - if (!this.triggerClicked) { - this.callEntityMethodOnGrabbed("releaseGrab"); - this.ensureDynamic(); - this.setState(STATE_OFF, "trigger released"); - if (this.getOtherHandController().state === STATE_DISTANCE_ROTATING) { - this.getOtherHandController().setState(STATE_SEARCHING, "trigger released on holding controller"); - // Can't set state of other controller to STATE_DISTANCE_HOLDING because then either: - // (a) The entity would jump to line up with the formerly rotating controller's orientation, or - // (b) The grab beam would need an orientation offset to the controller's true orientation. - // Neither of these options is good, so instead set STATE_SEARCHING and subsequently let the formerly distance - // rotating controller start distance holding the entity if it happens to be pointing at the entity. - } - return; - } - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - 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, GRABBABLE_PROPERTIES); - 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); - - this.callEntityMethodOnGrabbed("continueDistantGrab"); - - var defaultMoveWithHeadData = { - disableMoveWithHead: false - }; - - // 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); - var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position); - var handControllerData = getEntityCustomData('handControllerKey', this.grabbedThingID, defaultMoveWithHeadData); - if (handControllerData.disableMoveWithHead !== true) { - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after) * (PICK_WITH_HAND_RAY ? 0.0 : 1.0); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); - } - } - - this.maybeScale(grabbedProperties); - - // visualizations - this.updateLaserPointer(); - - var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); - - var candidateHotSpotEntities = - Entities.findEntities(controllerLocation.position,MAX_FAR_TO_NEAR_EQUIP_HOTSPOT_RADIUS); - entityPropertiesCache.addEntities(candidateHotSpotEntities); - - var potentialEquipHotspot = this.chooseBestEquipHotspotForFarToNearEquip(candidateHotSpotEntities); - if (potentialEquipHotspot && (potentialEquipHotspot.entityID == this.grabbedThingID)) { - if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && holdEnabled) { - this.grabbedHotspot = potentialEquipHotspot; - this.grabbedThingID = potentialEquipHotspot.entityID; - this.grabbedIsOverlay = false; - - Entities.deleteAction(this.grabbedThingID, this.actionID); - this.actionID = null; - - this.setState(STATE_HOLD, "equipping '" + entityPropertiesCache.getProps(this.grabbedThingID).name + "'"); - return; - } - } - var rayPositionOnEntity = Vec3.subtract(grabbedProperties.position, this.offsetPosition); - //Far to Near Grab: If object is draw by user inside FAR_TO_NEAR_GRAB_MAX_DISTANCE, grab it - if (this.entityIsFarToNearGrabbable(rayPositionOnEntity, - controllerLocation.position, - FAR_TO_NEAR_GRAB_MAX_DISTANCE)) { - this.farToNearGrab = true; - - Entities.deleteAction(this.grabbedThingID, this.actionID); - this.actionID = null; - - this.setState(STATE_NEAR_GRABBING , "near grab entity '" + this.grabbedThingID + "'"); - return; - } - - 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) { - this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - } else { - print("continueDistanceHolding -- updateAction failed"); - } - - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.distanceRotatingEnter = function() { - this.clearEquipHaptics(); - this.grabPointSphereOff(); - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - this.currentObjectPosition = grabbedProperties.position; - this.grabRadius = this.grabbedDistance; - - // 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; - - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.turnOffVisualizations(); - }; - - this.distanceRotating = function(deltaTime, timestamp) { - - if (!this.triggerClicked) { - this.callEntityMethodOnGrabbed("releaseGrab"); - this.ensureDynamic(); - this.setState(STATE_OFF, "trigger released"); - return; - } - - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - - // Delta rotation of grabbing controller since last update. - 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. - this.getOtherHandController().currentObjectRotation = Quat.multiply(controllerRotationDelta, - this.getOtherHandController().currentObjectRotation); - - // Rotate about the translation controller's target position. - this.offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.offsetPosition); - this.getOtherHandController().offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, - this.getOtherHandController().offsetPosition); - - this.updateLaserPointer(); - - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.setupHoldAction = function() { - 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 false; - } - var now = Date.now(); - this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - return true; - }; - - this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { - var aPrime = Vec3.subtract(position, axisStart); - var bPrime = Vec3.subtract(axisEnd, axisStart); - var bPrimeMagnitude = Vec3.length(bPrime); - var dotProduct = Vec3.dot(aPrime, bPrime); - var scalar = dotProduct / bPrimeMagnitude; - if (scalar < 0) { - scalar = 0; - } - if (scalar > 1) { - scalar = 1; - } - var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); - return projection; - }; - - 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.nearGrabbingEnter = function() { - this.grabPointSphereOff(); - this.laserPointerOff(); - - this.dropGestureReset(); - this.clearEquipHaptics(); - - this.shouldScale = false; - - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - - var grabbedProperties; - if (this.grabbedIsOverlay) { - 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" - }; - this.ignoreIK = true; - } else { - grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedThingID, DEFAULT_GRABBABLE_DATA); - if (FORCE_IGNORE_IK) { - this.ignoreIK = true; - } else { - this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; - } - - this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; - } - - var handRotation; - var handPosition; - if (this.ignoreIK) { - var controllerLocation = getControllerWorldLocation(this.handToController(), false); - handRotation = controllerLocation.orientation; - handPosition = controllerLocation.position; - } else { - handRotation = this.getHandRotation(); - handPosition = this.getHandPosition(); - } - - var hasPresetPosition = false; - if (this.state == STATE_HOLD && this.grabbedHotspot) { - // if an object is "equipped" and has a predefined offset, use it. - var offsets = USE_ATTACH_POINT_SETTINGS && getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); - if (offsets) { - this.offsetPosition = offsets[0]; - this.offsetRotation = offsets[1]; - hasPresetPosition = true; - } 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]; - hasPresetPosition = true; - } - } - } else { - 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); - } - - // This boolean is used to check if the object that is grabbed has just been cloned - // It is only set true, if the object that is grabbed creates a new clone. - var isClone = false; - var isPhysical = propsArePhysical(grabbedProperties) || - (!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID)); - if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) { - // grab entity via action - if (!this.setupHoldAction()) { - return; - } - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'grab', - grabbedEntity: this.grabbedThingID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - } else { - // grab entity via parenting - this.actionID = null; - 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} - }; - if (hasPresetPosition) { - reparentProps.localPosition = this.offsetPosition; - reparentProps.localRotation = this.offsetRotation; - } - - if (this.grabbedIsOverlay) { - Overlays.editOverlay(this.grabbedThingID, reparentProps); - } else { - if (grabbedProperties.userData.length > 0) { - try{ - var userData = JSON.parse(grabbedProperties.userData); - var grabInfo = userData.grabbableKey; - if (grabInfo && grabInfo.cloneable) { - var worldEntities = Entities.findEntities(MyAvatar.position, 50); - var count = 0; - worldEntities.forEach(function(item) { - var itemWE = Entities.getEntityProperties(item, ["name"]); - if (itemWE.name.indexOf('-clone-' + grabbedProperties.id) !== -1) { - count++; - } - }); - - var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; - if (count >= limit && limit !== 0) { - return; - } - - var cloneableProps = Entities.getEntityProperties(grabbedProperties.id); - cloneableProps.name = cloneableProps.name + '-clone-' + grabbedProperties.id; - var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; - var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; - var cUserData = Object.assign({}, userData); - var cProperties = Object.assign({}, cloneableProps); - isClone = true; - - 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; - cUserData.grabbableKey.triggerable = true; - cUserData.grabbableKey.grabbable = true; - cProperties.lifetime = lifetime; - cProperties.userData = JSON.stringify(cUserData); - var cloneID = Entities.addEntity(cProperties); - this.grabbedThingID = cloneID; - grabbedProperties = Entities.getEntityProperties(cloneID); - } - }catch(e) {} - } - Entities.editEntity(this.grabbedThingID, reparentProps); - } - - 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; - } - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'equip', - grabbedEntity: this.grabbedThingID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - } - - if (!this.grabbedIsOverlay) { - Entities.editEntity(this.grabbedThingID, { - velocity: { x: 0, y: 0, z: 0 }, - angularVelocity: { x: 0, y: 0, z: 0 }, - // dynamic: false - }); - } - - var _this = this; - /* - * Setting context for function that is either called via timer or directly, depending if - * if the object in question is a clone. If it is a clone, we need to make sure that the intial equipment event - * is called correctly, as these just freshly created entity may not have completely initialized. - */ - var grabEquipCheck = function () { - if (_this.state == STATE_NEAR_GRABBING) { - _this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_HOLD - _this.callEntityMethodOnGrabbed("startEquip"); - } - - // don't block teleport raypick with equipped entity - Messages.sendMessage('Hifi-Teleport-Ignore-Add', _this.grabbedThingID); - - _this.currentHandControllerTipPosition = - (_this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - _this.currentObjectTime = Date.now(); - - _this.currentObjectPosition = grabbedProperties.position; - _this.currentObjectRotation = grabbedProperties.rotation; - _this.currentVelocity = ZERO_VEC; - _this.currentAngularVelocity = ZERO_VEC; - - _this.prevDropDetected = false; - }; - - 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); - } else { - grabEquipCheck(); - } - }; - - this.nearGrabbing = function(deltaTime, timestamp) { - this.grabPointSphereOff(); - - var ttl = ACTION_TTL; - - if (this.farToNearGrab) { - if(!this.triggerClicked){ - this.farToNearGrab = false; - } - } - - if (this.state == STATE_NEAR_GRABBING && (!this.triggerClicked && this.secondaryReleased())) { - this.callEntityMethodOnGrabbed("releaseGrab"); - this.setState(STATE_OFF, "trigger released"); - return; - } - - if (this.state == STATE_HOLD) { - - if (this.secondarySqueezed()) { - // 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 && this.secondaryReleased()) { - // we have an equipped object and the secondary trigger was released - // short-circuit the other checks and release it - this.preparingHoldRelease = false; - this.callEntityMethodOnGrabbed("releaseEquip"); - this.setState(STATE_OFF, "equipping ended via secondary press"); - return; - } - - 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. - if (dropDetected) { - entityPropertiesCache.addEntity(this.grabbedHotspot.entityID); - equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp); - equipHotspotBuddy.highlightHotspot(this.grabbedHotspot); - } - - if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { - // store the offset attach points into preferences. - if (USE_ATTACH_POINT_SETTINGS && 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); - } - } - - var grabbedEntity = this.grabbedThingID; - this.release(); - this.grabbedThingID = grabbedEntity; - this.setState(STATE_NEAR_GRABBING, "drop gesture detected"); - return; - } - this.prevDropDetected = dropDetected; - } - - var props; - if (this.grabbedIsOverlay) { - props = { - localPosition: Overlays.getProperty(this.grabbedThingID, "localPosition"), - parentID: Overlays.getProperty(this.grabbedThingID, "parentID"), - parentJointIndex: Overlays.getProperty(this.grabbedThingID, "parentJointIndex"), - position: Overlays.getProperty(this.grabbedThingID, "position"), - rotation: Overlays.getProperty(this.grabbedThingID, "rotation"), - dimensions: Overlays.getProperty(this.grabbedThingID, "dimensions"), - registrationPoint: { x: 0.5, y: 0.5, z: 0.5 } - }; - } else { - props = Entities.getEntityProperties(this.grabbedThingID, ["localPosition", "parentID", "parentJointIndex", - "position", "rotation", "dimensions", - "registrationPoint"]); - } - if (!props.position) { - // server may have reset, taking our equipped entity with it. move back to "off" state - this.callEntityMethodOnGrabbed("releaseGrab"); - this.setState(STATE_OFF, "entity has no position property"); - return; - } - - if (this.state == STATE_NEAR_GRABBING && this.actionID === null && !this.thisHandIsParent(props)) { - // someone took it from us or otherwise edited the parentID. end the grab. We don't do this - // for equipped things so that they can be adjusted while equipped. - this.callEntityMethodOnGrabbed("releaseGrab"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "someone took it"); - return; - } - - var now = Date.now(); - if (this.state == STATE_HOLD && now - this.lastUnequipCheckTime > MSECS_PER_SEC * CHECK_TOO_FAR_UNEQUIP_TIME) { - this.lastUnequipCheckTime = now; - - if (props.parentID == AVATAR_SELF_ID) { - var handPosition; - if (this.ignoreIK) { - handPosition = getControllerWorldLocation(this.handToController(), false).position; - } else { - handPosition = this.getHandPosition(); - } - - var TEAR_AWAY_DISTANCE = 0.1; - var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); - if (dist > TEAR_AWAY_DISTANCE) { - this.autoUnequipCounter += deltaTime; - } else { - this.autoUnequipCounter = 0; - } - - if (this.autoUnequipCounter > 0.25) { - // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. - print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + - props.parentID + ", dist = " + dist); - - if (this.state == STATE_NEAR_GRABBING) { - this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // this.state == STATE_HOLD - this.callEntityMethodOnGrabbed("releaseEquip"); - } - this.setState(STATE_OFF, "held object too far away"); - return; - } - } - } - - // Keep track of the fingertip velocity to impart when we release the object. - // Note that the idea of using a constant 'tip' velocity regardless of the - // object's actual held offset is an idea intended to make it easier to throw things: - // Because we might catch something or transfer it between hands without a good idea - // of it's actual offset, let's try imparting a velocity which is at a fixed radius - // from the palm. - - var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - - var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - - if (deltaObjectTime > 0.0) { - var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition); - - var previousEulers = Quat.safeEulerAngles(this.currentObjectRotation); - var newEulers = Quat.safeEulerAngles(props.rotation); - var worldDeltaRotation = Vec3.subtract(newEulers, previousEulers); - - this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaObjectTime); - this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaObjectTime * 180.0)); - - this.currentObjectPosition = props.position; - this.currentObjectRotation = props.rotation; - } - - this.currentHandControllerTipPosition = handControllerPosition; - this.currentObjectTime = now; - - if (this.state === STATE_HOLD) { - this.callEntityMethodOnGrabbed("continueEquip"); - } - if (this.state == STATE_NEAR_GRABBING) { - this.callEntityMethodOnGrabbed("continueNearGrab"); - } - - if (this.state == STATE_NEAR_GRABBING) { - this.maybeScale(props); - } - - 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: ttl, - kinematic: this.kinematicGrab, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (success) { - this.actionTimeout = now + (ttl * MSECS_PER_SEC); - } else { - print("continueNearGrabbing -- updateAction failed"); - Entities.deleteAction(this.grabbedThingID, this.actionID); - this.setupHoldAction(); - } - } - }; - - this.maybeScale = function(props) { - if (!objectScalingEnabled || this.isTablet(this.grabbedThingID) || this.grabbedIsOverlay) { - return; - } - - if (!this.shouldScale) { - // If both secondary triggers squeezed, and the non-holding hand is empty, start scaling - if (this.secondarySqueezed() && - this.getOtherHandController().secondarySqueezed() && - this.grabbedThingID && this.getOtherHandController().grabbedThingID && - this.grabbedThingID == this.getOtherHandController().grabbedThingID) { - this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), - this.getOtherHandController().getHandPosition())); - this.scalingStartDimensions = props.dimensions; - this.shouldScale = true; - } - } else if (!this.secondarySqueezed() || !this.getOtherHandController().secondarySqueezed()) { - this.shouldScale = false; - } - if (this.shouldScale) { - var scalingCurrentDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), - this.getOtherHandController().getHandPosition())); - var currentRescale = scalingCurrentDistance / this.scalingStartDistance; - var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions); - Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions }); - } - }; - - this.maybeScaleMyAvatar = function() { - if (!myAvatarScalingEnabled || this.shouldScale || this.hand === LEFT_HAND) { - // If scaling disabled, or if we are currently scaling an entity, don't scale avatar - // and only rescale avatar for one hand (so we're not doing it twice) - return; - } - - // Only scale avatar if both triggers and grips are squeezed - var tryingToScale = this.secondarySqueezed() && this.getOtherHandController().secondarySqueezed() && - this.triggerSmoothedSqueezed() && this.getOtherHandController().triggerSmoothedSqueezed(); - - - if (!this.isScalingAvatar) { - // If both secondary triggers squeezed, start scaling - if (tryingToScale) { - this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), - this.getOtherHandController().getHandPosition())); - this.scalingStartAvatarScale = MyAvatar.scale; - this.isScalingAvatar = true; - } - } else if (!tryingToScale) { - this.isScalingAvatar = false; - } - if (this.isScalingAvatar) { - var scalingCurrentDistance = Vec3.length(Vec3.subtract(this.getHandPosition(), - this.getOtherHandController().getHandPosition())); - var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale; - MyAvatar.scale = newAvatarScale; - } - }; - - this.nearTriggerEnter = function() { - this.clearEquipHaptics(); - this.grabPointSphereOff(); - Controller.triggerShortHapticPulse(1.0, this.hand); - this.callEntityMethodOnGrabbed("startNearTrigger"); - }; - - this.farTriggerEnter = function() { - this.clearEquipHaptics(); - this.grabPointSphereOff(); - this.callEntityMethodOnGrabbed("startFarTrigger"); - }; - - this.nearTrigger = function(deltaTime, timestamp) { - if (this.triggerSmoothedReleased()) { - this.callEntityMethodOnGrabbed("stopNearTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "trigger released"); - return; - } - this.callEntityMethodOnGrabbed("continueNearTrigger"); - }; - - this.farTrigger = function(deltaTime, timestamp) { - if (this.triggerSmoothedReleased()) { - this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "trigger released"); - return; - } - - var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; - var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); - if (intersection.type != RayPick.INTERSECTED_NONE) { - if (intersection.objectID != this.grabbedThingID) { - this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "laser moved off of entity"); - return; - } - this.intersectionDistance = intersection.distance; - if (farGrabEnabled) { - this.updateLaserPointer(); - } - } - - this.callEntityMethodOnGrabbed("continueFarTrigger"); - }; - - this.offEnter = function() { - var existingSearchDistance = this.searchSphereDistance; - this.release(); - - if (hoveredEntityID) { - Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent); - hoveredEntityID = false; - } - if (entityWithContextOverlay) { - ContextOverlay.destroyContextOverlay(entityWithContextOverlay); - entityWithContextOverlay = false; - } - - if (isInEditMode()) { - this.searchSphereDistance = existingSearchDistance; - } - }; - - this.entityLaserTouchingEnter = function() { - // test for intersection between controller laser and web entity plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); - if (intersectInfo) { - var pointerEvent = { - type: "Press", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), - pos3D: intersectInfo.point, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "Primary", - isPrimaryHeld: true - }; - - Entities.sendMousePressOnEntity(this.grabbedThingID, pointerEvent); - Entities.sendClickDownOnEntity(this.grabbedThingID, pointerEvent); - - this.touchingEnterTimer = 0; - this.touchingEnterPointerEvent = pointerEvent; - this.touchingEnterPointerEvent.button = "None"; - this.deadspotExpired = false; - - var LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.026; // radians ~ 1.2 degrees - this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE) * intersectInfo.distance; // dead spot radius in meters - } - - Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); - }; - - this.entityLaserTouchingExit = function() { - // test for intersection between controller laser and web entity plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); - if (intersectInfo) { - var pointerEvent; - if (this.deadspotExpired) { - pointerEvent = { - type: "Release", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), - pos3D: intersectInfo.point, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "Primary" - }; - } else { - pointerEvent = this.touchingEnterPointerEvent; - pointerEvent.type = "Release"; - pointerEvent.button = "Primary"; - pointerEvent.isPrimaryHeld = false; - } - - Entities.sendMouseReleaseOnEntity(this.grabbedThingID, pointerEvent); - Entities.sendClickReleaseOnEntity(this.grabbedThingID, pointerEvent); - Entities.sendHoverLeaveEntity(this.grabbedThingID, pointerEvent); - } - this.grabbedThingID = null; - this.grabbedOverlay = null; - }; - - this.entityLaserTouching = function(dt) { - - this.touchingEnterTimer += dt; - - entityPropertiesCache.addEntity(this.grabbedThingID); - - if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { // AJT: - this.setState(STATE_OFF, "released trigger"); - return; - } - - // test for intersection between controller laser and web entity plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); - if (intersectInfo) { - - if (Entities.keyboardFocusEntity != this.grabbedThingID) { - Overlays.keyboardFocusOverlay = 0; - Entities.keyboardFocusEntity = this.grabbedThingID; - } - - var pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(this.grabbedThingID, intersectInfo.point), - pos3D: intersectInfo.point, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "NoButtons", - isPrimaryHeld: true - }; - - var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { - Entities.sendMouseMoveOnEntity(this.grabbedThingID, pointerEvent); - Entities.sendHoldingClickOnEntity(this.grabbedThingID, pointerEvent); - this.deadspotExpired = true; - } - - this.intersectionDistance = intersectInfo.distance; - if (this.state == STATE_ENTITY_LASER_TOUCHING) { - this.updateLaserPointer(); - } - Reticle.setVisible(false); - } else { - this.grabbedThingID = null; - this.setState(STATE_OFF, "grabbed entity was destroyed"); - return; - } - }; - - this.overlayLaserTouchingEnter = function () { - // Test for intersection between controller laser and overlay plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); - if (intersectInfo) { - var pointerEvent = { - type: "Press", - id: this.hand + 1, - pos2D: projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point), - pos3D: intersectInfo.point, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "Primary", - isPrimaryHeld: true - }; - - Overlays.sendMousePressOnOverlay(this.grabbedOverlay, pointerEvent); - - this.touchingEnterTimer = 0; - this.touchingEnterPointerEvent = pointerEvent; - this.touchingEnterPointerEvent.button = "None"; - this.deadspotExpired = false; - - var LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.026; // radians ~ 1.2 degrees - this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE) * intersectInfo.distance; // dead spot radius in meters - } - - Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); - }; - - this.overlayLaserTouchingExit = function () { - // Test for intersection between controller laser and Web overlay plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); - if (intersectInfo) { - var pointerEvent; - - var pos2D; - var pos3D; - if (this.tabletStabbed) { - // Some people like to jam the stylus a long ways into the tablet when clicking on a button. - // They almost always move out of the deadzone when they do this. We detect if the stylus - // has gone far through the tablet and suppress any further faux mouse events until the - // stylus is withdrawn. Once it has withdrawn, we do a release click wherever the stylus was - // when it was pushed into the tablet. - this.tabletStabbed = false; - pos2D = this.tabletStabbedPos2D; - pos3D = this.tabletStabbedPos3D; - } else { - pos2D = projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point); - pos3D = intersectInfo.point; - } - - if (this.deadspotExpired) { - pointerEvent = { - type: "Release", - id: this.hand + 1, - pos2D: pos2D, - pos3D: pos3D, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "Primary" - }; - } else { - pointerEvent = this.touchingEnterPointerEvent; - pointerEvent.type = "Release"; - pointerEvent.button = "Primary"; - pointerEvent.isPrimaryHeld = false; - } - - Overlays.sendMouseReleaseOnOverlay(this.grabbedOverlay, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.grabbedOverlay, pointerEvent); - } - this.grabbedThingID = null; - this.grabbedOverlay = null; - }; - - this.overlayLaserTouching = function (dt) { - this.touchingEnterTimer += dt; - - if (this.state == STATE_OVERLAY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { - this.setState(STATE_OFF, "released trigger"); - return; - } - - // Test for intersection between controller laser and Web overlay plane. - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); - if (intersectInfo) { - - var pos2D = projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point); - var pos3D = intersectInfo.point; - - if (Overlays.keyboardFocusOverlay != this.grabbedOverlay) { - Entities.keyboardFocusEntity = null; - Overlays.keyboardFocusOverlay = this.grabbedOverlay; - } - - var pointerEvent = { - type: "Move", - id: this.hand + 1, - pos2D: pos2D, - pos3D: pos3D, - normal: intersectInfo.normal, - direction: intersectInfo.searchRay.direction, - button: "NoButtons", - isPrimaryHeld: true - }; - - var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - Vec3.distance(intersectInfo.point, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { - Overlays.sendMouseMoveOnOverlay(this.grabbedOverlay, pointerEvent); - this.deadspotExpired = true; - } - - this.intersectionDistance = intersectInfo.distance; - if (this.state == STATE_OVERLAY_LASER_TOUCHING) { - this.updateLaserPointer(); - } - Reticle.setVisible(false); - } else { - this.grabbedThingID = null; - this.setState(STATE_OFF, "grabbed overlay was destroyed"); - return; - } - }; - - 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 (dt) { - - this.touchingEnterTimer += dt; - - if (this.stylusTarget.entityID) { - entityPropertiesCache.addEntity(this.stylusTarget.entityID); - this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityID); - } 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.setState(STATE_OFF, "hand moved away from touch surface"); - } - } else { - this.setState(STATE_OFF, "touch surface was destroyed"); - } - }; - - this.release = function() { - this.turnOffVisualizations(); - - if (this.grabbedThingID !== null) { - - Messages.sendMessage('Hifi-Teleport-Ignore-Remove', this.grabbedThingID); - - if (this.state === STATE_HOLD) { - this.callEntityMethodOnGrabbed("releaseEquip"); - } - - // Make a small release haptic pulse if we really were holding something - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - if (this.actionID !== null) { - Entities.deleteAction(this.grabbedThingID, this.actionID); - } else { - // no action, so it's a parenting grab - if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { - if (this.grabbedIsOverlay) { - Overlays.editOverlay(this.grabbedThingID, { - parentID: NULL_UUID, - parentJointIndex: -1 - }); - } else { - Entities.editEntity(this.grabbedThingID, { - parentID: this.previousParentID[this.grabbedThingID], - parentJointIndex: this.previousParentJointIndex[this.grabbedThingID] - }); - this.ensureDynamic(); - } - } else { - if (this.grabbedIsOverlay) { - Overlays.editOverlay(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} - }); - } - } - } - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.grabbedThingID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - } - - this.actionID = null; - this.grabbedThingID = null; - this.grabbedOverlay = null; - this.grabbedHotspot = null; - - if (this.triggerSmoothedGrab() || this.secondarySqueezed()) { - this.waitForTriggerRelease = true; - } - }; - - this.cleanup = function() { - this.release(); - this.grabPointSphereOff(); - this.hideStylus(); - LaserPointers.removeLaserPointer(this.laserPointer); - LaserPointers.removeLaserPointer(this.headLaserPointer); - }; - - 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.checkForUnexpectedChildren = function() { - var _this = this; - // sometimes things can get parented to a hand and this script is unaware. Search for such entities and - // unhook them. - - // find children of avatar's hand joint - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); - children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex)); - - // find children of faux controller joint - var controllerJointIndex = this.controllerJointIndex; - children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); - children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex)); - - // find children of faux camera-relative controller joint - var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerCRJointIndex)); - children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex)); - - children.forEach(function(childID) { - if (childID !== _this.stylus) { - // we appear to be holding something and this script isn't in a state that would be holding something. - // unhook it. if we previously took note of this entity's parent, put it back where it was. This - // works around some problems that happen when more than one hand or avatar is passing something around. - var childType = Entities.getNestableType(childID); - if (_this.previousParentID[childID]) { - var previousParentID = _this.previousParentID[childID]; - var previousParentJointIndex = _this.previousParentJointIndex[childID]; - - // The main flaw with keeping track of previous parantage in individual scripts is: - // (1) A grabs something (2) B takes it from A (3) A takes it from B (4) A releases it - // now A and B will take turns passing it back to the other. Detect this and stop the loop here... - var UNHOOK_LOOP_DETECT_MS = 200; - var now = Date.now(); - if (_this.previouslyUnhooked[childID]) { - if (now - _this.previouslyUnhooked[childID] < UNHOOK_LOOP_DETECT_MS) { - previousParentID = NULL_UUID; - previousParentJointIndex = -1; - } - } - _this.previouslyUnhooked[childID] = now; - - if (childType == "overlay" && Overlays.getProperty(childID, "grabbable")) { - // only auto-unhook overlays that were flagged as grabbable. this avoids unhooking overlays - // used in tutorial. - Overlays.editOverlay(childID, { - parentID: previousParentID, - parentJointIndex: previousParentJointIndex - }); - } - if (childType == "entity") { - Entities.editEntity(childID, { - parentID: previousParentID, - parentJointIndex: previousParentJointIndex - }); - } - - } else { - if (childType == "entity") { - Entities.editEntity(childID, { parentID: NULL_UUID }); - } else if (childType == "overlay") { - if (Overlays.getProperty(childID, "grabbable")) { - Overlays.editOverlay(childID, { parentID: NULL_UUID }); - } - } - } - } - }); - }; - - this.getOtherHandController = function() { - return (this.hand === RIGHT_HAND) ? leftController : rightController; - }; -} - -var rightController = new MyController(RIGHT_HAND); -var leftController = new MyController(LEFT_HAND); - -var MAPPING_NAME = "com.highfidelity.handControllerGrab"; - -var mapping = Controller.newMapping(MAPPING_NAME); -mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); -mapping.from([Controller.Standard.RTClick]).peek().to(rightController.triggerClick); - -mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); -mapping.from([Controller.Standard.LTClick]).peek().to(leftController.triggerClick); - -mapping.from([Controller.Standard.RB]).peek().to(rightController.secondaryPress); -mapping.from([Controller.Standard.LB]).peek().to(leftController.secondaryPress); -mapping.from([Controller.Standard.LeftGrip]).peek().to(leftController.secondaryPress); -mapping.from([Controller.Standard.RightGrip]).peek().to(rightController.secondaryPress); - -mapping.from([Controller.Standard.LeftPrimaryThumb]).peek().to(leftController.thumbPress); -mapping.from([Controller.Standard.RightPrimaryThumb]).peek().to(rightController.thumbPress); - -Controller.enableMapping(MAPPING_NAME); - -function handleMenuEvent(menuItem) { - if (menuItem === "Show Grab Sphere") { - SHOW_GRAB_POINT_SPHERE = Menu.isOptionChecked("Show Grab Sphere"); - } -} - -Menu.addMenuItem({ menuName: "Developer", menuItemName: "Show Grab Sphere", isCheckable: true, isChecked: false }); -Menu.menuItemEvent.connect(handleMenuEvent); - -// the section below allows the grab script to listen for messages -// that disable either one or both hands. useful for two handed items -var handToDisable = 'none'; - -function update(deltaTime) { - var timestamp = Date.now(); - - if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(deltaTime, timestamp); - } else { - leftController.release(); - } - if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(deltaTime, timestamp); - } else { - rightController.release(); - } - equipHotspotBuddy.update(deltaTime, timestamp); - entityPropertiesCache.update(); -} - -Messages.subscribe('Hifi-Grab-Disable'); -Messages.subscribe('Hifi-Hand-Disabler'); -Messages.subscribe('Hifi-Hand-Grab'); -Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); -Messages.subscribe('Hifi-Object-Manipulation'); -Messages.subscribe('Hifi-Hand-Drop'); - -var setBlacklist = function() { - if (USE_BLACKLIST) { - LaserPointers.setIgnoreEntities(leftController.laserPointer, blacklist); - LaserPointers.setIgnoreEntities(leftController.headLaserPointer, blacklist); - LaserPointers.setIgnoreEntities(rightController.laserPointer, blacklist); - LaserPointers.setIgnoreEntities(rightController.headLaserPointer, blacklist); - } -} - -var handleHandMessages = function(channel, message, sender) { - var data; - if (sender === MyAvatar.sessionUUID) { - if (channel === 'Hifi-Hand-Disabler') { - if (message === 'left') { - handToDisable = LEFT_HAND; - leftController.turnOffVisualizations(); - } - if (message === 'right') { - handToDisable = RIGHT_HAND; - rightController.turnOffVisualizations(); - } - if (message === 'both' || message === 'none') { - if (message === 'both') { - rightController.turnOffVisualizations(); - leftController.turnOffVisualizations(); - - } - handToDisable = message; - } - } else if (channel === 'Hifi-Grab-Disable') { - data = JSON.parse(message); - if (data.holdEnabled !== undefined) { - print("holdEnabled: ", data.holdEnabled); - holdEnabled = data.holdEnabled; - } - if (data.nearGrabEnabled !== undefined) { - print("nearGrabEnabled: ", data.nearGrabEnabled); - nearGrabEnabled = data.nearGrabEnabled; - } - if (data.farGrabEnabled !== undefined) { - print("farGrabEnabled: ", data.farGrabEnabled); - farGrabEnabled = data.farGrabEnabled; - } - if (data.myAvatarScalingEnabled !== undefined) { - print("myAvatarScalingEnabled: ", data.myAvatarScalingEnabled); - myAvatarScalingEnabled = data.myAvatarScalingEnabled; - } - if (data.objectScalingEnabled !== undefined) { - print("objectScalingEnabled: ", data.objectScalingEnabled); - objectScalingEnabled = data.objectScalingEnabled; - } - } else if (channel === 'Hifi-Hand-Grab') { - try { - data = JSON.parse(message); - var selectedController = (data.hand === 'left') ? leftController : rightController; - var hotspotIndex = data.hotspotIndex !== undefined ? parseInt(data.hotspotIndex) : 0; - selectedController.release(); - var wearableEntity = data.entityID; - entityPropertiesCache.addEntity(wearableEntity); - selectedController.grabbedThingID = wearableEntity; - var hotspots = selectedController.collectEquipHotspots(selectedController.grabbedThingID); - if (hotspots.length > 0) { - if (hotspotIndex >= hotspots.length) { - hotspotIndex = 0; - } - selectedController.grabbedHotspot = hotspots[hotspotIndex]; - } - selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received"); - selectedController.nearGrabbingEnter(); - - } catch (e) { - print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-Grab message: " + message); - } - - } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { - try { - data = JSON.parse(message); - var action = data.action; - var id = data.id; - var index = blacklist.indexOf(id); - - if (action === 'add' && index === -1) { - blacklist.push(id); - setBlacklist(); - } - if (action === 'remove') { - if (index > -1) { - blacklist.splice(index, 1); - setBlacklist(); - } - } - - } catch (e) { - print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message); - } - } else if (channel === 'Hifi-Hand-Drop') { - if (message === 'left') { - leftController.release(); - } else if (message === 'right') { - rightController.release(); - } else if (message === 'both') { - leftController.release(); - rightController.release(); - } - } - } -}; - -Messages.messageReceived.connect(handleHandMessages); - -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; -var updateTotalWork = 0; - -var UPDATE_PERFORMANCE_DEBUGGING = false; - -var updateWrapper = 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++; - } - - // will call update for both hands - var preWork = Date.now(); - update(deltaTime); - var postWork = Date.now(); - var workDelta = postWork - preWork; - updateTotalWork += workDelta; - - if (intervalCount == 100) { - - if (UPDATE_PERFORMANCE_DEBUGGING) { - print("handControllerGrab.js -- For " + intervalCount + " samples average= " + - totalDelta/intervalCount + " ms" + - " average variance:" + totalVariance/intervalCount + " ms" + - " high variance count:" + highVarianceCount + " [ " + (highVarianceCount/intervalCount) * 100 + "% ] " + - " VERY high variance count:" + veryhighVarianceCount + - " [ " + (veryhighVarianceCount/intervalCount) * 100 + "% ] " + - " average work:" + updateTotalWork/intervalCount + " ms"); - } - - intervalCount = 0; - totalDelta = 0; - totalVariance = 0; - highVarianceCount = 0; - veryhighVarianceCount = 0; - updateTotalWork = 0; - } - - Script.setTimeout(updateWrapper, UPDATE_SLEEP_MS); -}; - -Script.setTimeout(updateWrapper, UPDATE_SLEEP_MS); -function cleanup() { - Menu.removeMenuItem("Developer", "Show Grab Sphere"); - rightController.cleanup(); - leftController.cleanup(); - Controller.disableMapping(MAPPING_NAME); - Reticle.setVisible(true); -} - -Script.scriptEnding.connect(cleanup); - -}()); // END LOCAL_SCOPE From f4d8216501356cfd253b5555fad6c770233039e6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 13 Sep 2017 18:06:21 -0700 Subject: [PATCH 64/65] Fix race condition accessing QString sub-properties in zones --- .../src/RenderableZoneEntityItem.cpp | 34 ++++++++------ .../src/RenderableZoneEntityItem.h | 10 ++-- libraries/entities/src/ZoneEntityItem.cpp | 47 ++++++++++++++----- libraries/entities/src/ZoneEntityItem.h | 4 +- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index fc15a8539b..caf9cae341 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -176,6 +176,10 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen _lastRotation = entity->getRotation(); _lastDimensions = entity->getDimensions(); + _keyLightProperties = entity->getKeyLightProperties(); + _stageProperties = entity->getStageProperties(); + _skyboxProperties = entity->getSkyboxProperties(); + #if 0 if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) { @@ -196,14 +200,14 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen } #endif - updateKeyZoneItemFromEntity(entity); + updateKeyZoneItemFromEntity(); if (sunChanged) { - updateKeySunFromEntity(entity); + updateKeySunFromEntity(); } if (sunChanged || skyboxChanged) { - updateKeyAmbientFromEntity(entity); + updateKeyAmbientFromEntity(); } if (backgroundChanged || skyboxChanged) { updateKeyBackgroundFromEntity(entity); @@ -265,19 +269,19 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return false; } -void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity) { +void ZoneEntityRenderer::updateKeySunFromEntity() { const auto& sunLight = editSunLight(); sunLight->setType(model::Light::SUN); sunLight->setPosition(_lastPosition); sunLight->setOrientation(_lastRotation); // Set the keylight - sunLight->setColor(ColorUtils::toVec3(entity->getKeyLightProperties().getColor())); - sunLight->setIntensity(entity->getKeyLightProperties().getIntensity()); - sunLight->setDirection(entity->getKeyLightProperties().getDirection()); + sunLight->setColor(ColorUtils::toVec3(_keyLightProperties.getColor())); + sunLight->setIntensity(_keyLightProperties.getIntensity()); + sunLight->setDirection(_keyLightProperties.getDirection()); } -void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& entity) { +void ZoneEntityRenderer::updateKeyAmbientFromEntity() { const auto& ambientLight = editAmbientLight(); ambientLight->setType(model::Light::AMBIENT); ambientLight->setPosition(_lastPosition); @@ -285,24 +289,24 @@ void ZoneEntityRenderer::updateKeyAmbientFromEntity(const TypedEntityPointer& en // Set the keylight - ambientLight->setAmbientIntensity(entity->getKeyLightProperties().getAmbientIntensity()); + ambientLight->setAmbientIntensity(_keyLightProperties.getAmbientIntensity()); - if (entity->getKeyLightProperties().getAmbientURL().isEmpty()) { - setAmbientURL(entity->getSkyboxProperties().getURL()); + if (_keyLightProperties.getAmbientURL().isEmpty()) { + setAmbientURL(_skyboxProperties.getURL()); } else { - setAmbientURL(entity->getKeyLightProperties().getAmbientURL()); + setAmbientURL(_keyLightProperties.getAmbientURL()); } } void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& entity) { editBackground(); setBackgroundMode(entity->getBackgroundMode()); - setSkyboxColor(entity->getSkyboxProperties().getColorVec3()); + setSkyboxColor(_skyboxProperties.getColorVec3()); setProceduralUserData(entity->getUserData()); - setSkyboxURL(entity->getSkyboxProperties().getURL()); + setSkyboxURL(_skyboxProperties.getURL()); } -void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& entity) { +void ZoneEntityRenderer::updateKeyZoneItemFromEntity() { /* TODO: Implement the sun model behavior / Keep this code here for reference, this is how we { // Set the stage diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index babd35c0d6..80fe393f48 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -41,9 +41,9 @@ protected: virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; private: - void updateKeyZoneItemFromEntity(const TypedEntityPointer& entity); - void updateKeySunFromEntity(const TypedEntityPointer& entity); - void updateKeyAmbientFromEntity(const TypedEntityPointer& entity); + void updateKeyZoneItemFromEntity(); + void updateKeySunFromEntity(); + void updateKeyAmbientFromEntity(); void updateKeyBackgroundFromEntity(const TypedEntityPointer& entity); void updateAmbientMap(); void updateSkyboxMap(); @@ -89,6 +89,10 @@ private: bool _needAmbientUpdate{ true }; bool _needBackgroundUpdate{ true }; + KeyLightPropertyGroup _keyLightProperties; + StagePropertyGroup _stageProperties; + SkyboxPropertyGroup _skyboxProperties; + // More attributes used for rendering: QString _ambientTextureURL; NetworkTexturePointer _ambientTexture; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 1b220565cd..88e4f3c9e6 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -49,8 +49,10 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - - _keyLightProperties.getProperties(properties); + // Contains a QString property, must be synchronized + withReadLock([&] { + _keyLightProperties.getProperties(properties); + }); _stageProperties.getProperties(properties); @@ -58,7 +60,10 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundMode, getBackgroundMode); - _skyboxProperties.getProperties(properties); + // Contains a QString property, must be synchronized + withReadLock([&] { + _skyboxProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); @@ -88,8 +93,10 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class - - _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); + // Contains a QString property, must be synchronized + withWriteLock([&] { + _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); + }); _stagePropertiesChanged = _stageProperties.setProperties(properties); @@ -101,11 +108,13 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties); + // Contains a QString property, must be synchronized + withWriteLock([&] { + _skyboxPropertiesChanged = _skyboxProperties.setProperties(properties); + }); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged; - return somethingChanged; } @@ -116,8 +125,12 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; - int bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _keyLightPropertiesChanged); + int bytesFromKeylight; + withWriteLock([&] { + bytesFromKeylight = _keyLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _keyLightPropertiesChanged); + }); + somethingChanged = somethingChanged || _keyLightPropertiesChanged; bytesRead += bytesFromKeylight; dataAt += bytesFromKeylight; @@ -132,8 +145,11 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); - int bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _skyboxPropertiesChanged); + int bytesFromSkybox; + withWriteLock([&] { + bytesFromSkybox = _skyboxProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _skyboxPropertiesChanged); + }); somethingChanged = somethingChanged || _skyboxPropertiesChanged; bytesRead += bytesFromSkybox; dataAt += bytesFromSkybox; @@ -150,13 +166,18 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += _keyLightProperties.getEntityProperties(params); + withReadLock([&] { + requestedProperties += _keyLightProperties.getEntityProperties(params); + }); requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_BACKGROUND_MODE; requestedProperties += _stageProperties.getEntityProperties(params); - requestedProperties += _skyboxProperties.getEntityProperties(params); + + withReadLock([&] { + requestedProperties += _skyboxProperties.getEntityProperties(params); + }); requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 093c2edb64..14e7cd2f40 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -63,12 +63,12 @@ public: QString getCompoundShapeURL() const; virtual void setCompoundShapeURL(const QString& url); - const KeyLightPropertyGroup& getKeyLightProperties() const { return _keyLightProperties; } + KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; } BackgroundMode getBackgroundMode() const { return _backgroundMode; } - const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } + SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const StagePropertyGroup& getStageProperties() const { return _stageProperties; } bool getFlyingAllowed() const { return _flyingAllowed; } From 61c5395f44f372e1d39472aa7baaacff81f50ee0 Mon Sep 17 00:00:00 2001 From: beholder Date: Thu, 14 Sep 2017 18:48:43 +0300 Subject: [PATCH 65/65] add missing comma --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index bc5de1d65d..54ff0a1268 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,7 +57,7 @@ module.exports = { "print": false, "RayPick": false, "LaserPointers": false, - "ContextOverlay": false + "ContextOverlay": false, "module": false }, "rules": {