diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 13789a4a8e..9295f50518 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -10,7 +10,7 @@ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, - getGrabPointSphereOffset, HMD, MyAvatar, Messages + getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities */ controllerDispatcherPlugins = {}; @@ -27,6 +27,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var PROFILE = false; + var DEBUG = true; if (typeof Test !== "undefined") { PROFILE = true; @@ -195,7 +196,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var h; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { - var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); + var nearbyOverlays = + Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); nearbyOverlays.sort(function (a, b) { var aPosition = Overlays.getProperty(a, "position"); var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); @@ -265,6 +267,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }); } + // sometimes, during a HMD snap-turn, an equipped or held item wont be near + // the hand when the findEntities is done. Gather up any hand-children here. + for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { + var handChildrenIDs = findHandChildEntities(h); + handChildrenIDs.forEach(function (handChildID) { + if (handChildID in nearbyEntityPropertiesByID) { + return; + } + var props = Entities.getEntityProperties(handChildID, DISPATCHER_PROPERTIES); + props.id = handChildID; + nearbyEntityPropertiesByID[handChildID] = props; + }); + } + // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], @@ -300,6 +316,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // activity-slots which this plugin consumes as "in use" _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); + if (DEBUG) { + print("controllerDispatcher running " + orderedPluginName); + } } if (PROFILE) { Script.endProfileRange("dispatch.isReady." + orderedPluginName); @@ -332,6 +351,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // of running plugins and mark its activity-slots as "not in use" delete _this.runningPluginNames[runningPluginName]; _this.markSlots(plugin, false); + if (DEBUG) { + print("controllerDispatcher stopping " + runningPluginName); + } } if (PROFILE) { Script.endProfileRange("dispatch.run." + runningPluginName); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index fe868493f4..29db02c6de 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -254,6 +254,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.triggerValue = 0; this.messageGrabEntity = false; this.grabEntityProps = null; + this.shouldSendStart = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -498,6 +499,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var cloneID = this.cloneHotspot(grabbedProperties, controllerData); this.targetEntityID = cloneID; Entities.editEntity(this.targetEntityID, reparentProps); + controllerData.nearbyEntityPropertiesByID[this.targetEntityID] = grabbedProperties; isClone = true; } else if (!grabbedProperties.locked) { Entities.editEntity(this.targetEntityID, reparentProps); @@ -507,8 +509,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa return; } - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "startEquip", args); + // we don't want to send startEquip message until the trigger is released. otherwise, + // guns etc will fire right as they are equipped. + this.shouldSendStart = true; Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'equip', @@ -588,22 +591,21 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // 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.messageGrabEntity) { - this.grabbedHotspot = potentialEquipHotspot; - this.targetEntityID = this.grabbedHotspot.entityID; - this.startEquipEntity(controllerData); - this.messageGrabEnity = false; - } + if (potentialEquipHotspot && + ((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 { return makeRunningValues(false, [], []); } }; - this.isTargetIDValid = function() { - var entityProperties = Entities.getEntityProperties(this.targetEntityID, ["type"]); - return "type" in entityProperties; + this.isTargetIDValid = function(controllerData) { + var entityProperties = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + return entityProperties && "type" in entityProperties; }; this.isReady = function (controllerData, deltaTime) { @@ -616,7 +618,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.isTargetIDValid()) { + if (!this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -643,6 +645,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var dropDetected = this.dropGestureProcess(deltaTime); if (this.triggerSmoothedReleased()) { + if (this.shouldSendStart) { + // we don't want to send startEquip message until the trigger is released. otherwise, + // guns etc will fire right as they are equipped. + var startArgs = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "startEquip", startArgs); + this.shouldSendStart = false; + } this.waitForTriggerRelease = false; } @@ -674,8 +683,10 @@ 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); + if (!this.shouldSendStart) { + 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/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index bd7a64572a..2484067655 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -182,7 +182,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); } if (targetProps) { - if (!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) { + if ((!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) || + targetProps.parentID != NULL_UUID) { return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it } else { this.targetEntityID = targetProps.id; @@ -216,7 +217,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (targetProps) { - if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { + 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 39e9371931..e0bb596253 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -8,8 +8,10 @@ /* 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, findGroupParent, - Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE + TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, + findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, + HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, + TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -28,6 +30,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.previousParentJointIndex = {}; this.previouslyUnhooked = {}; this.hapticTargetID = null; + this.lastUnequipCheckTime = 0; + this.autoUnequipCounter = 0; + this.lastUnexpectedChildrenCheckTime = 0; this.parameters = makeDispatcherModuleParameters( 500, @@ -40,11 +45,11 @@ 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.thisHandIsParent = function(props) { + if (!props) { + return false; + } + if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { return false; } @@ -93,8 +98,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (this.thisHandIsParent(targetProps)) { // 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; + this.previousParentID[targetProps.id] = null; + this.previousParentJointIndex[targetProps.id] = -1; } else { this.previousParentID[targetProps.id] = targetProps.parentID; this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex; @@ -111,20 +116,24 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.grabbing = true; }; - this.endNearParentingGrabEntity = function () { - if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) { - 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.targetEntityID, { - parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID], - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }); + this.endNearParentingGrabEntity = function (controllerData) { + this.hapticTargetID = null; + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + if (this.thisHandIsParent(props)) { + if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) { + 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.targetEntityID, { + parentID: this.previousParentID[this.targetEntityID], + parentJointIndex: this.previousParentJointIndex[this.targetEntityID], + localVelocity: {x: 0, y: 0, z: 0}, + localAngularVelocity: {x: 0, y: 0, z: 0} + }); + } } var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; @@ -133,6 +142,71 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.targetEntityID = null; }; + this.checkForChildTooFarAway = function (controllerData) { + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + var now = Date.now(); + if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * TEAR_AWAY_CHECK_TIME) { + this.lastUnequipCheckTime = now; + if (props.parentID == AVATAR_SELF_ID) { + var handPosition = controllerData.controllerLocations[this.hand].position; + var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); + if (dist > TEAR_AWAY_DISTANCE) { + this.autoUnequipCounter++; + } else { + this.autoUnequipCounter = 0; + } + if (this.autoUnequipCounter >= TEAR_AWAY_COUNT) { + return true; + } + } + } + return false; + }; + + + this.checkForUnexpectedChildren = function (controllerData) { + // sometimes things can get parented to a hand and this script is unaware. Search for such entities and + // unhook them. + + var now = Date.now(); + var UNEXPECTED_CHILDREN_CHECK_TIME = 0.1; // seconds + if (now - this.lastUnexpectedChildrenCheckTime > MSECS_PER_SEC * UNEXPECTED_CHILDREN_CHECK_TIME) { + this.lastUnexpectedChildrenCheckTime = now; + + var children = findHandChildEntities(this.hand); + var _this = this; + + children.forEach(function(childID) { + // 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. + 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; + if (_this.previouslyUnhooked[childID]) { + if (now - _this.previouslyUnhooked[childID] < UNHOOK_LOOP_DETECT_MS) { + previousParentID = NULL_UUID; + previousParentJointIndex = -1; + } + } + _this.previouslyUnhooked[childID] = now; + + Entities.editEntity(childID, { + parentID: previousParentID, + parentJointIndex: previousParentJointIndex + }); + } else { + Entities.editEntity(childID, { parentID: NULL_UUID }); + } + }); + } + }; + this.getTargetProps = function (controllerData) { // nearbyEntityProperties is already sorted by length from controller var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; @@ -168,11 +242,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); var targetProps = this.getTargetProps(controllerData); if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + this.checkForUnexpectedChildren(controllerData); return makeRunningValues(false, [], []); } if (targetProps) { - if (propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) { + if ((propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) && + targetProps.parentID == NULL_UUID) { return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it } else { this.targetEntityID = targetProps.id; @@ -188,16 +264,23 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (this.grabbing) { if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { - this.endNearParentingGrabEntity(); + this.endNearParentingGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + if (!props) { + // entity was deleted + this.grabbing = false; + this.targetEntityID = null; this.hapticTargetID = null; return makeRunningValues(false, [], []); } - var props = Entities.getEntityProperties(this.targetEntityID); - if (!this.thisHandIsParent(props)) { - this.grabbing = false; - this.targetEntityID = null; - this.hapticTargetID = null; + if (this.checkForChildTooFarAway(controllerData)) { + // if the held entity moves too far from the hand, release it + print("nearParentGrabEntity -- autoreleasing held item because it is far from hand"); + this.endNearParentingGrabEntity(controllerData); return makeRunningValues(false, [], []); } @@ -205,7 +288,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); } else { // still searching / highlighting - var readiness = this.isReady (controllerData); + var readiness = this.isReady(controllerData); if (!readiness.active) { return readiness; } @@ -218,7 +301,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); 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); diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index a6bc97fcca..e9e25b058b 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/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:true, Quat, Vec3, Overlays, +/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, 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, @@ -40,7 +40,12 @@ entityHasActions:true, ensureDynamic:true, findGroupParent:true, - BUMPER_ON_VALUE:true + BUMPER_ON_VALUE:true, + findHandChildEntities:true, + TEAR_AWAY_DISTANCE:true, + TEAR_AWAY_COUNT:true, + TEAR_AWAY_CHECK_TIME:true, + distanceBetweenPointAndEntityBoundingBox:true */ MSECS_PER_SEC = 1000.0; @@ -79,6 +84,10 @@ COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; NEAR_GRAB_RADIUS = 1.0; +TEAR_AWAY_DISTANCE = 0.1; // ungrab an entity if its bounding-box moves this far from the hand +TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away +TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks + DISPATCHER_PROPERTIES = [ "position", "registrationPoint", @@ -193,17 +202,6 @@ entityIsDistanceGrabbable = function(props) { 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; }; @@ -296,8 +294,9 @@ ensureDynamic = function (entityID) { }; findGroupParent = function (controllerData, targetProps) { - while (targetProps.parentID && targetProps.parentID !== NULL_UUID) { - // XXX use controllerData.nearbyEntityPropertiesByID ? + while (targetProps.parentID && + targetProps.parentID !== NULL_UUID && + Entities.getNestableType(targetProps.parentID) == "entity") { var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); if (!parentProps) { break; @@ -310,6 +309,50 @@ findGroupParent = function (controllerData, targetProps) { return targetProps; }; + +findHandChildEntities = function(hand) { + // find children of avatar's hand joint + var handJointIndex = MyAvatar.getJointIndex(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 = getControllerJointIndex(hand); + 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(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)); + + return children.filter(function (childID) { + var childType = Entities.getNestableType(childID); + return childType == "entity"; + }); +}; + +distanceBetweenPointAndEntityBoundingBox = function(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); +}; + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, @@ -319,6 +362,7 @@ if (typeof module !== 'undefined') { LEFT_HAND: LEFT_HAND, RIGHT_HAND: RIGHT_HAND, BUMPER_ON_VALUE: BUMPER_ON_VALUE, + TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE, propsArePhysical: propsArePhysical, entityIsGrabbable: entityIsGrabbable, NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS,