fix some parent-grab bugs, re-enable adjusting equipped entities, don't trigger guns right when they are equipped

This commit is contained in:
Seth Alves 2017-09-21 11:07:47 -07:00
parent a4082dc7e9
commit 473db92a8e
5 changed files with 201 additions and 73 deletions

View file

@ -197,7 +197,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);

View file

@ -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,
@ -507,8 +508,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 +590,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 +617,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 +644,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 +682,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], []);
};

View file

@ -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;

View file

@ -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,15 +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.otherHandIsParent = function(props) {
return this.getOtherModule().thisHandIsParent(props);
};
this.thisHandIsParent = function(props) {
if (!props) {
return false;
}
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) {
return false;
}
@ -97,14 +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;
} 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();
this.previousParentID[targetProps.id] = null;
this.previousParentJointIndex[targetProps.id] = -1;
} else {
this.previousParentID[targetProps.id] = targetProps.parentID;
this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex;
@ -121,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];
@ -143,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];
@ -178,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;
@ -198,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, [], []);
}
@ -215,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;
}
@ -227,7 +300,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (targetCloneable) {
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
var cloneID = cloneEntity(targetProps, worldEntityProps);
var cloneProps = Entities.getEntityProperties(cloneID);
var cloneProps = controllerData.nearbyEntityPropertiesByID[cloneID];
this.grabbing = true;
this.targetEntityID = cloneID;

View file

@ -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,7 +294,7 @@ ensureDynamic = function (entityID) {
};
findGroupParent = function (controllerData, targetProps) {
while (targetProps.parentID && targetProps.parentID !== NULL_UUID) {
while (targetProps.parentID && targetProps.parentID !== NULL_UUID && targetProps.parentID !== AVATAR_SELF_ID) {
// XXX use controllerData.nearbyEntityPropertiesByID ?
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
if (!parentProps) {
@ -310,6 +308,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,
@ -318,6 +360,7 @@ if (typeof module !== 'undefined') {
makeRunningValues: makeRunningValues,
LEFT_HAND: LEFT_HAND,
RIGHT_HAND: RIGHT_HAND,
BUMPER_ON_VALUE: BUMPER_ON_VALUE
BUMPER_ON_VALUE: BUMPER_ON_VALUE,
TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE
};
}