From 2a82dddc2b019e53e2b6cfc1b9a7d9f2eb8932d7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 21 Jun 2016 17:36:36 -0700 Subject: [PATCH 1/3] Draw attach points as if they were equip-hotspots --- .../system/controllers/handControllerGrab.js | 17 ++++++-- scripts/system/libraries/Xform.js | 40 +++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 scripts/system/libraries/Xform.js diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 39d85f1224..31f8ebd73f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,9 +10,10 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ Script.include("/~/system/libraries/utils.js"); +Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -879,21 +880,29 @@ function MyController(hand) { var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); var i, l = entities.length; for (i = 0; i < l; i++) { - var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); if (wearableData && wearableData.joints) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; if (wearableData.joints[handJointName]) { + var handOffsetPos = wearableData.joints[handJointName][0]; + var handOffsetRot = wearableData.joints[handJointName][1]; + + var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); + var objectXform = new Xform(props.rotation, props.position); + var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + // draw the hotspot this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: grabProps.position, + rotation: overlayXform.rot, + position: overlayXform.pos, size: 0.2, color: { red: 90, green: 255, blue: 90 }, alpha: 0.7, solid: true, visible: true, - ignoreRayIntersection: false, + ignoreRayIntersection: true, drawInFront: false })); } diff --git a/scripts/system/libraries/Xform.js b/scripts/system/libraries/Xform.js new file mode 100644 index 0000000000..75051c4983 --- /dev/null +++ b/scripts/system/libraries/Xform.js @@ -0,0 +1,40 @@ +// +// Created by Anthony J. Thibault on 2016/06/21 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// ctor +Xform = function(rot, pos) { + this.rot = rot; + this.pos = pos; +} + +Xform.ident = function() { + return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0}); +}; + +Xform.mul = function(lhs, rhs) { + var rot = Quat.multiply(lhs.rot, rhs.rot); + var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); + return new Xform(rot, pos); +}; + +Xform.prototype.inv = function() { + var invRot = Quat.inverse(this.rot); + var invPos = Vec3.multiply(-1, this.pos); + return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos)); +}; + +Xform.prototype.mirrorX = function() { + return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w}, + {x: -this.pos.x, y: this.pos.y, z: this.pos.z}); +}; + +Xform.prototype.toString = function() { + var rot = this.rot; + var pos = this.pos; + return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")"; +}; From fc42a3aef52759e8f8cda7a665deeede5377345e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 15:36:47 -0700 Subject: [PATCH 2/3] Grab script hotspot work * Updated grab/equip logic to use sphere vs sphere tests, instead of sphere vs entity bounding box. * Added debug flag for visualizing grab spheres. * hotspot overlays are now updated as the objects they are attached to move. * You can now use the search beam to near grab large objects, as well as the sphere sphere test. * Optimized EntityPropertyCache to make a single call to Entities.getEntityProperties instead of three. * Moved grab script options from the "Developer > Hands" menu to the "Developer > Grab Script" menu. --- .../system/controllers/handControllerGrab.js | 312 +++++++++++------- 1 file changed, 200 insertions(+), 112 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 31f8ebd73f..1772e55015 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,10 +10,9 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr, Xform */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); -Script.include("../libraries/Xform.js"); // // add lines where the hand ray picking is happening @@ -38,6 +37,9 @@ var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; + +var DRAW_GRAB_SPHERES = false; +var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; // @@ -68,16 +70,20 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray + // // near grabbing // -var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected +var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. + var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position -var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected + +var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. +var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. + var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed -var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds // @@ -254,7 +260,8 @@ function restore2DMode() { } } -// constructor +// EntityPropertiesCache is a helper class that contains a cache of entity properties. +// the hope is to prevent excess calls to Entity.getEntityProperties() function EntityPropertiesCache() { this.cache = {}; } @@ -265,34 +272,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; entities.forEach(function (x) { - _this.addEntity(x); + _this.updateEntity(x); }); }; -EntityPropertiesCache.prototype.addEntity = function(entityID) { +EntityPropertiesCache.prototype.updateEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); - var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); - var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); - var wearableProps = getEntityCustomData("wearable", entityID, {}); - this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps }; + + // 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; + + this.cache[entityID] = props; }; EntityPropertiesCache.prototype.getEntities = function() { return Object.keys(this.cache); } EntityPropertiesCache.prototype.getProps = function(entityID) { var obj = this.cache[entityID] - return obj ? obj.props : undefined; + return obj ? obj : undefined; }; EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.grabbableProps : undefined; + 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 obj = this.cache[entityID] - return obj ? obj.grabProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.grabKey ? props.userData.grabKey : {}; + } else { + return undefined; + } }; EntityPropertiesCache.prototype.getWearableProps = function(entityID) { - var obj = this.cache[entityID] - return obj ? obj.wearableProps : undefined; + var props = this.cache[entityID]; + if (props) { + return props.userData.wearable ? props.userData.wearable : {}; + } else { + return undefined; + } }; function MyController(hand) { @@ -323,7 +351,6 @@ function MyController(hand) { //for visualizations this.overlayLine = null; this.particleBeamObject = null; - this.grabSphere = null; //for lights this.spotlight = null; @@ -384,7 +411,7 @@ function MyController(hand) { } this.setState = function(newState, reason) { - this.grabSphereOff(); + if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -491,39 +518,6 @@ function MyController(hand) { } } - this.grabSphereOn = function() { - var color = {red: 0, green: 255, blue: 0}; - if (this.grabSphere === null) { - var sphereProperties = { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, // Even when burried inside of something, show it. - visible: true - } - this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); - } else { - Overlays.editOverlay(this.grabSphere, { - position: this.getHandPosition(), - size: GRAB_RADIUS*2, - color: color, - alpha: 0.1, - solid: true, - visible: true - }); - } - } - - this.grabSphereOff = function() { - if (this.grabSphere !== null) { - Overlays.deleteOverlay(this.grabSphere); - this.grabSphere = null; - } - }; - this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { @@ -873,51 +867,123 @@ function MyController(hand) { } }; - this.searchEnter = function() { - this.equipHotspotOverlays = []; + this.createHotspots = function () { + + var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_SPHERE_ALPHA = 0.7; + var HAND_SPHERE_RADIUS = 0.01; + + var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var EQUIP_SPHERE_ALPHA = 0.3; + + var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var NEAR_SPHERE_ALPHA = 0.1; + + this.hotspotOverlays = []; + + if (DRAW_HAND_SPHERES) { + // add tiny spheres around the palm. + var handPosition = this.getHandPosition(); + var overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_SPHERE_RADIUS * 2, + color: HAND_SPHERE_COLOR, + alpha: HAND_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + } // find entities near the avatar that might be equipable. - var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var i, l = entities.length; - for (i = 0; i < l; i++) { - var props = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); - // does this entity have an attach point? - var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData && wearableData.joints) { - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData.joints[handJointName]) { - var handOffsetPos = wearableData.joints[handJointName][0]; - var handOffsetRot = wearableData.joints[handJointName][1]; + this.entityPropertyCache.clear(); + this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var handOffsetXform = new Xform(handOffsetRot, handOffsetPos); - var objectXform = new Xform(props.rotation, props.position); - var overlayXform = Xform.mul(objectXform, handOffsetXform.inv()); + var _this = this; + this.entityPropertyCache.getEntities().forEach(function (entityID) { + if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); - // draw the hotspot - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - rotation: overlayXform.rot, - position: overlayXform.pos, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - })); - } + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: EQUIP_RADIUS * 2, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "equip" + }); } - } + + if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + var props = _this.entityPropertyCache.getProps(entityID); + + overlay = Overlays.addOverlay("sphere", { + rotation: props.rotation, + position: props.position, + size: NEAR_GRAB_RADIUS * 2, + color: NEAR_SPHERE_COLOR, + alpha: NEAR_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + _this.hotspotOverlays.push({ + entityID: entityID, + overlay: overlay, + type: "near" + }); + } + }); + }; + + this.updateHotspots = function() { + var _this = this; + this.hotspotOverlays.forEach(function (overlayInfo) { + if (overlayInfo.type === "hand") { + Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() }); + } else if (overlayInfo.type === "equip") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } else if (overlayInfo.type === "near") { + _this.entityPropertyCache.updateEntity(overlayInfo.entityID); + var props = _this.entityPropertyCache.getProps(overlayInfo.entityID); + Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + } + }); + }; + + this.destroyHotspots = function() { + this.hotspotOverlays.forEach(function (overlayInfo) { + Overlays.deleteOverlay(overlayInfo.overlay); + }); + this.hotspotOverlays = []; + }; + + this.searchEnter = function() { + this.createHotspots(); }; this.searchExit = function() { - - // delete all equip hotspots - var i, l = this.equipHotspotOverlays.length; - for (i = 0; i < l; i++) { - Overlays.deleteOverlay(this.equipHotspotOverlays[i]); - } - this.equipHotspotOverlays = []; + this.destroyHotspots(); }; /// @@ -984,7 +1050,13 @@ function MyController(hand) { return grabbableProps && grabbableProps.wantsTrigger; }; - this.entityIsEquippable = function (entityID, handPosition) { + this.entityIsEquippableWithoutDistanceCheck = function (entityID) { + var props = this.entityPropertyCache.getProps(entityID); + var handPosition = props.position; + return this.entityIsEquippableWithDistanceCheck(entityID, handPosition); + }; + + this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition) { var props = this.entityPropertyCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var grabProps = this.entityPropertyCache.getGrabProps(entityID); @@ -998,9 +1070,9 @@ function MyController(hand) { return false; } - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > EQUIP_RADIUS) { if (debug) { - print("equip is skipping '" + props.name + "': too far away."); + print("equip is skipping '" + props.name + "': too far away, " + distance + " meters"); } return false; } @@ -1113,7 +1185,7 @@ function MyController(hand) { return true; }; - this.entityIsNearGrabbable = function(entityID, handPosition) { + this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { if (!this.entityIsGrabbable(entityID)) { return false; @@ -1123,7 +1195,7 @@ function MyController(hand) { var distance = Vec3.distance(props.position, handPosition); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); - if (distance > NEAR_PICK_MAX_DISTANCE) { + if (distance > maxDistance) { // too far away, don't grab if (debug) { print(" grab is skipping '" + props.name + "': too far away."); @@ -1138,6 +1210,8 @@ function MyController(hand) { var _this = this; var name; + this.updateHotspots(); + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; @@ -1151,16 +1225,12 @@ function MyController(hand) { var handPosition = this.getHandPosition(); - if (SHOW_GRAB_SPHERE) { - this.grabSphereOn(); - } - this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); + this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); var equippableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsEquippable(entity, handPosition); + return _this.entityIsEquippableWithDistanceCheck(entity, handPosition); }); var entity; @@ -1181,16 +1251,19 @@ function MyController(hand) { } } + var grabbableEntities = candidateEntities.filter(function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + }); + var rayPickInfo = this.calcRayPickInfo(this.hand); this.intersectionDistance = rayPickInfo.distance; - if (rayPickInfo.entityID) { - candidateEntities.push(rayPickInfo.entityID); - this.entityPropertyCache.addEntity(rayPickInfo.entityID); - } - var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition); - }); + if (rayPickInfo.entityID) { + this.entityPropertyCache.updateEntity(rayPickInfo.entityID); + if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { + grabbableEntities.push(rayPickInfo.entityID); + } + } if (grabbableEntities.length > 0) { // sort by distance @@ -1732,11 +1805,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // 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." + @@ -2182,17 +2255,32 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update); +if (!Menu.menuExists("Developer > Grab Script")) { + Menu.addMenu("Developer > Grab Script"); +} + Menu.addMenuItem({ - menuName: "Developer > Hands", + menuName: "Developer > Grab Script", menuItemName: "Drop Without Shake", isCheckable: true, isChecked: DROP_WITHOUT_SHAKE }); +Menu.addMenuItem({ + menuName: "Developer > Grab Script", + menuItemName: "Draw Grab Spheres", + isCheckable: true, + isChecked: DRAW_GRAB_SPHERES +}); + function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } + if (menuItem === "Draw Grab Spheres") { + DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); + DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + } } Menu.menuItemEvent.connect(handleMenuItemEvent); From 7bd553c09cea7784828354085901adf788f37a0c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 23 Jun 2016 17:03:15 -0700 Subject: [PATCH 3/3] near grab logic to uses sphere vs entity box instead of sphere vs sphere. Adjusted debug drawing accordingly. --- .../system/controllers/handControllerGrab.js | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 26f977f9b0..b1cf3927d9 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -38,7 +38,7 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only var PICK_WITH_HAND_RAY = true; -var DRAW_GRAB_SPHERES = false; +var DRAW_GRAB_BOXES = false; var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; @@ -80,6 +80,8 @@ var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRAB_RADIUS = 0.15; // 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 NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object @@ -870,26 +872,48 @@ function MyController(hand) { this.createHotspots = function () { var props, overlay; - var HAND_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; - var HAND_SPHERE_ALPHA = 0.7; - var HAND_SPHERE_RADIUS = 0.01; + var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; + var HAND_EQUIP_SPHERE_ALPHA = 0.7; + var HAND_EQUIP_SPHERE_RADIUS = 0.01; + + var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; + var HAND_GRAB_SPHERE_ALPHA = 0.3; + var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS; var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; var EQUIP_SPHERE_ALPHA = 0.3; - var NEAR_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; - var NEAR_SPHERE_ALPHA = 0.1; + var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 }; + var GRAB_BOX_ALPHA = 0.1; this.hotspotOverlays = []; if (DRAW_HAND_SPHERES) { - // add tiny spheres around the palm. + // add tiny green sphere around the palm. var handPosition = this.getHandPosition(); overlay = Overlays.addOverlay("sphere", { position: handPosition, - size: HAND_SPHERE_RADIUS * 2, - color: HAND_SPHERE_COLOR, - alpha: HAND_SPHERE_ALPHA, + size: HAND_EQUIP_SPHERE_RADIUS * 2, + color: HAND_EQUIP_SPHERE_COLOR, + alpha: HAND_EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + }); + + this.hotspotOverlays.push({ + entityID: undefined, + overlay: overlay, + type: "hand" + }); + + // add larger blue sphere around the palm. + overlay = Overlays.addOverlay("sphere", { + position: handPosition, + size: HAND_GRAB_SPHERE_RADIUS * 2, + color: HAND_GRAB_SPHERE_COLOR, + alpha: HAND_GRAB_SPHERE_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -931,15 +955,15 @@ function MyController(hand) { }); } - if (DRAW_GRAB_SPHERES && _this.entityIsGrabbable(entityID)) { + if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) { props = _this.entityPropertyCache.getProps(entityID); - overlay = Overlays.addOverlay("sphere", { + overlay = Overlays.addOverlay("cube", { rotation: props.rotation, position: props.position, - size: NEAR_GRAB_RADIUS * 2, - color: NEAR_SPHERE_COLOR, - alpha: NEAR_SPHERE_ALPHA, + size: props.dimensions, //{x: props.dimensions.x, y: props.dimensions.y, z: props.dimensions.z}, + color: GRAB_BOX_COLOR, + alpha: GRAB_BOX_ALPHA, solid: true, visible: true, ignoreRayIntersection: true, @@ -1254,7 +1278,7 @@ function MyController(hand) { } var grabbableEntities = candidateEntities.filter(function (entity) { - return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_RADIUS); + return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); var rayPickInfo = this.calcRayPickInfo(this.hand); @@ -1807,11 +1831,11 @@ function MyController(hand) { this.lastUnequipCheckTime = now; if (props.parentID == MyAvatar.sessionUUID && - Vec3.length(props.localPosition) > NEAR_GRAB_PICK_RADIUS * 2.0) { + Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - var nearPickedCandidateEntities = Entities.findEntities(handPosition, EQUIP_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // 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." + @@ -2270,18 +2294,18 @@ Menu.addMenuItem({ Menu.addMenuItem({ menuName: "Developer > Grab Script", - menuItemName: "Draw Grab Spheres", + menuItemName: "Draw Grab Boxes", isCheckable: true, - isChecked: DRAW_GRAB_SPHERES + isChecked: DRAW_GRAB_BOXES }); function handleMenuItemEvent(menuItem) { if (menuItem === "Drop Without Shake") { DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); } - if (menuItem === "Draw Grab Spheres") { - DRAW_GRAB_SPHERES = Menu.isOptionChecked("Draw Grab Spheres"); - DRAW_HAND_SPHERES = DRAW_GRAB_SPHERES; + if (menuItem === "Draw Grab Boxes") { + DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes"); + DRAW_HAND_SPHERES = DRAW_GRAB_BOXES; } }