Merge pull request #11430 from sethalves/fix-parent-grab

Fix parent grab
This commit is contained in:
Seth Alves 2017-09-26 13:00:16 -07:00 committed by GitHub
commit a871a6db1d
5 changed files with 226 additions and 65 deletions

View file

@ -10,7 +10,7 @@
/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick, /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick,
controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true,
LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES,
getGrabPointSphereOffset, HMD, MyAvatar, Messages getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities
*/ */
controllerDispatcherPlugins = {}; controllerDispatcherPlugins = {};
@ -27,6 +27,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
var PROFILE = false; var PROFILE = false;
var DEBUG = true;
if (typeof Test !== "undefined") { if (typeof Test !== "undefined") {
PROFILE = true; PROFILE = true;
@ -195,7 +196,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
var h; var h;
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
if (controllerLocations[h].valid) { 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) { nearbyOverlays.sort(function (a, b) {
var aPosition = Overlays.getProperty(a, "position"); var aPosition = Overlays.getProperty(a, "position");
var aDistance = Vec3.distance(aPosition, controllerLocations[h].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 // bundle up all the data about the current situation
var controllerData = { var controllerData = {
triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue],
@ -300,6 +316,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
// activity-slots which this plugin consumes as "in use" // activity-slots which this plugin consumes as "in use"
_this.runningPluginNames[orderedPluginName] = true; _this.runningPluginNames[orderedPluginName] = true;
_this.markSlots(candidatePlugin, orderedPluginName); _this.markSlots(candidatePlugin, orderedPluginName);
if (DEBUG) {
print("controllerDispatcher running " + orderedPluginName);
}
} }
if (PROFILE) { if (PROFILE) {
Script.endProfileRange("dispatch.isReady." + orderedPluginName); 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" // of running plugins and mark its activity-slots as "not in use"
delete _this.runningPluginNames[runningPluginName]; delete _this.runningPluginNames[runningPluginName];
_this.markSlots(plugin, false); _this.markSlots(plugin, false);
if (DEBUG) {
print("controllerDispatcher stopping " + runningPluginName);
}
} }
if (PROFILE) { if (PROFILE) {
Script.endProfileRange("dispatch.run." + runningPluginName); Script.endProfileRange("dispatch.run." + runningPluginName);

View file

@ -254,6 +254,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.triggerValue = 0; this.triggerValue = 0;
this.messageGrabEntity = false; this.messageGrabEntity = false;
this.grabEntityProps = null; this.grabEntityProps = null;
this.shouldSendStart = false;
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
300, 300,
@ -498,6 +499,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var cloneID = this.cloneHotspot(grabbedProperties, controllerData); var cloneID = this.cloneHotspot(grabbedProperties, controllerData);
this.targetEntityID = cloneID; this.targetEntityID = cloneID;
Entities.editEntity(this.targetEntityID, reparentProps); Entities.editEntity(this.targetEntityID, reparentProps);
controllerData.nearbyEntityPropertiesByID[this.targetEntityID] = grabbedProperties;
isClone = true; isClone = true;
} else if (!grabbedProperties.locked) { } else if (!grabbedProperties.locked) {
Entities.editEntity(this.targetEntityID, reparentProps); Entities.editEntity(this.targetEntityID, reparentProps);
@ -507,8 +509,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
return; return;
} }
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; // we don't want to send startEquip message until the trigger is released. otherwise,
Entities.callEntityMethod(this.targetEntityID, "startEquip", args); // guns etc will fire right as they are equipped.
this.shouldSendStart = true;
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'equip', 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 is cloneable, clone it and return it
// if the potentialHotspot os not cloneable and locked return null // if the potentialHotspot os not cloneable and locked return null
if (potentialEquipHotspot) { if (potentialEquipHotspot &&
if ((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity) { ((this.triggerSmoothedSqueezed() && !this.waitForTriggerRelease) || this.messageGrabEntity)) {
this.grabbedHotspot = potentialEquipHotspot; this.grabbedHotspot = potentialEquipHotspot;
this.targetEntityID = this.grabbedHotspot.entityID; this.targetEntityID = this.grabbedHotspot.entityID;
this.startEquipEntity(controllerData); this.startEquipEntity(controllerData);
this.messageGrabEnity = false; this.messageGrabEnity = false;
}
return makeRunningValues(true, [potentialEquipHotspot.entityID], []); return makeRunningValues(true, [potentialEquipHotspot.entityID], []);
} else { } else {
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
} }
}; };
this.isTargetIDValid = function() { this.isTargetIDValid = function(controllerData) {
var entityProperties = Entities.getEntityProperties(this.targetEntityID, ["type"]); var entityProperties = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
return "type" in entityProperties; return entityProperties && "type" in entityProperties;
}; };
this.isReady = function (controllerData, deltaTime) { this.isReady = function (controllerData, deltaTime) {
@ -616,7 +618,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var timestamp = Date.now(); var timestamp = Date.now();
this.updateInputs(controllerData); this.updateInputs(controllerData);
if (!this.isTargetIDValid()) { if (!this.isTargetIDValid(controllerData)) {
this.endEquipEntity(); this.endEquipEntity();
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
} }
@ -643,6 +645,13 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
var dropDetected = this.dropGestureProcess(deltaTime); var dropDetected = this.dropGestureProcess(deltaTime);
if (this.triggerSmoothedReleased()) { 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; this.waitForTriggerRelease = false;
} }
@ -674,8 +683,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
equipHotspotBuddy.update(deltaTime, timestamp, controllerData); equipHotspotBuddy.update(deltaTime, timestamp, controllerData);
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; if (!this.shouldSendStart) {
Entities.callEntityMethod(this.targetEntityID, "continueEquip", args); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.targetEntityID, "continueEquip", args);
}
return makeRunningValues(true, [this.targetEntityID], []); return makeRunningValues(true, [this.targetEntityID], []);
}; };

View file

@ -182,7 +182,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
} }
if (targetProps) { if (targetProps) {
if (!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) { if ((!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) ||
targetProps.parentID != NULL_UUID) {
return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it
} else { } else {
this.targetEntityID = targetProps.id; this.targetEntityID = targetProps.id;
@ -216,7 +217,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var targetProps = this.getTargetProps(controllerData); var targetProps = this.getTargetProps(controllerData);
if (targetProps) { 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 // switch to grabbing
var targetCloneable = entityIsCloneable(targetProps); var targetCloneable = entityIsCloneable(targetProps);
if (targetCloneable) { if (targetCloneable) {

View file

@ -8,8 +8,10 @@
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, NULL_UUID, /* 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, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent, TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE 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"); Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -28,6 +30,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.previousParentJointIndex = {}; this.previousParentJointIndex = {};
this.previouslyUnhooked = {}; this.previouslyUnhooked = {};
this.hapticTargetID = null; this.hapticTargetID = null;
this.lastUnequipCheckTime = 0;
this.autoUnequipCounter = 0;
this.lastUnexpectedChildrenCheckTime = 0;
this.parameters = makeDispatcherModuleParameters( this.parameters = makeDispatcherModuleParameters(
500, 500,
@ -40,11 +45,11 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
this.controllerJointIndex = getControllerJointIndex(this.hand); this.controllerJointIndex = getControllerJointIndex(this.hand);
this.getOtherModule = function() {
return (this.hand === RIGHT_HAND) ? leftNearParentingGrabEntity : rightNearParentingGrabEntity;
};
this.thisHandIsParent = function(props) { this.thisHandIsParent = function(props) {
if (!props) {
return false;
}
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) {
return false; return false;
} }
@ -93,8 +98,8 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (this.thisHandIsParent(targetProps)) { if (this.thisHandIsParent(targetProps)) {
// this should never happen, but if it does, don't set previous parent to be this hand. // this should never happen, but if it does, don't set previous parent to be this hand.
// this.previousParentID[targetProps.id] = NULL; this.previousParentID[targetProps.id] = null;
// this.previousParentJointIndex[targetProps.id] = -1; this.previousParentJointIndex[targetProps.id] = -1;
} else { } else {
this.previousParentID[targetProps.id] = targetProps.parentID; this.previousParentID[targetProps.id] = targetProps.parentID;
this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex; this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex;
@ -111,20 +116,24 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.grabbing = true; this.grabbing = true;
}; };
this.endNearParentingGrabEntity = function () { this.endNearParentingGrabEntity = function (controllerData) {
if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) { this.hapticTargetID = null;
Entities.editEntity(this.targetEntityID, { var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
parentID: this.previousParentID[this.targetEntityID], if (this.thisHandIsParent(props)) {
parentJointIndex: this.previousParentJointIndex[this.targetEntityID] if (this.previousParentID[this.targetEntityID] === NULL_UUID || this.previousParentID === undefined) {
}); Entities.editEntity(this.targetEntityID, {
} else { parentID: this.previousParentID[this.targetEntityID],
// we're putting this back as a child of some other parent, so zero its velocity parentJointIndex: this.previousParentJointIndex[this.targetEntityID]
Entities.editEntity(this.targetEntityID, { });
parentID: this.previousParentID[this.targetEntityID], } else {
parentJointIndex: this.previousParentJointIndex[this.targetEntityID], // we're putting this back as a child of some other parent, so zero its velocity
localVelocity: {x: 0, y: 0, z: 0}, Entities.editEntity(this.targetEntityID, {
localAngularVelocity: {x: 0, y: 0, z: 0} 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]; var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
@ -133,6 +142,71 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.targetEntityID = null; 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) { this.getTargetProps = function (controllerData) {
// nearbyEntityProperties is already sorted by length from controller // nearbyEntityProperties is already sorted by length from controller
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
@ -168,11 +242,13 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var targetProps = this.getTargetProps(controllerData); var targetProps = this.getTargetProps(controllerData);
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE &&
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
this.checkForUnexpectedChildren(controllerData);
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
} }
if (targetProps) { if (targetProps) {
if (propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) { if ((propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) &&
targetProps.parentID == NULL_UUID) {
return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it
} else { } else {
this.targetEntityID = targetProps.id; this.targetEntityID = targetProps.id;
@ -188,16 +264,23 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (this.grabbing) { if (this.grabbing) {
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
controllerData.secondaryValues[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; this.hapticTargetID = null;
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
} }
var props = Entities.getEntityProperties(this.targetEntityID); if (this.checkForChildTooFarAway(controllerData)) {
if (!this.thisHandIsParent(props)) { // if the held entity moves too far from the hand, release it
this.grabbing = false; print("nearParentGrabEntity -- autoreleasing held item because it is far from hand");
this.targetEntityID = null; this.endNearParentingGrabEntity(controllerData);
this.hapticTargetID = null;
return makeRunningValues(false, [], []); return makeRunningValues(false, [], []);
} }
@ -205,7 +288,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
} else { } else {
// still searching / highlighting // still searching / highlighting
var readiness = this.isReady (controllerData); var readiness = this.isReady(controllerData);
if (!readiness.active) { if (!readiness.active) {
return readiness; return readiness;
} }
@ -218,7 +301,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand]; var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
var cloneID = cloneEntity(targetProps, worldEntityProps); var cloneID = cloneEntity(targetProps, worldEntityProps);
var cloneProps = Entities.getEntityProperties(cloneID); var cloneProps = Entities.getEntityProperties(cloneID);
this.grabbing = true; this.grabbing = true;
this.targetEntityID = cloneID; this.targetEntityID = cloneID;
this.startNearParentingGrabEntity(controllerData, cloneProps); this.startNearParentingGrabEntity(controllerData, cloneProps);

View file

@ -6,7 +6,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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, 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, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true,
@ -40,7 +40,12 @@
entityHasActions:true, entityHasActions:true,
ensureDynamic:true, ensureDynamic:true,
findGroupParent: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; MSECS_PER_SEC = 1000.0;
@ -79,6 +84,10 @@ COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 };
NEAR_GRAB_RADIUS = 1.0; 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 = [ DISPATCHER_PROPERTIES = [
"position", "position",
"registrationPoint", "registrationPoint",
@ -193,17 +202,6 @@ entityIsDistanceGrabbable = function(props) {
return false; 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; return true;
}; };
@ -296,8 +294,9 @@ ensureDynamic = function (entityID) {
}; };
findGroupParent = function (controllerData, targetProps) { findGroupParent = function (controllerData, targetProps) {
while (targetProps.parentID && targetProps.parentID !== NULL_UUID) { while (targetProps.parentID &&
// XXX use controllerData.nearbyEntityPropertiesByID ? targetProps.parentID !== NULL_UUID &&
Entities.getNestableType(targetProps.parentID) == "entity") {
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
if (!parentProps) { if (!parentProps) {
break; break;
@ -310,6 +309,50 @@ findGroupParent = function (controllerData, targetProps) {
return 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') { if (typeof module !== 'undefined') {
module.exports = { module.exports = {
makeDispatcherModuleParameters: makeDispatcherModuleParameters, makeDispatcherModuleParameters: makeDispatcherModuleParameters,
@ -319,6 +362,7 @@ if (typeof module !== 'undefined') {
LEFT_HAND: LEFT_HAND, LEFT_HAND: LEFT_HAND,
RIGHT_HAND: RIGHT_HAND, RIGHT_HAND: RIGHT_HAND,
BUMPER_ON_VALUE: BUMPER_ON_VALUE, BUMPER_ON_VALUE: BUMPER_ON_VALUE,
TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE,
propsArePhysical: propsArePhysical, propsArePhysical: propsArePhysical,
entityIsGrabbable: entityIsGrabbable, entityIsGrabbable: entityIsGrabbable,
NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS, NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS,