Merge pull request #6402 from sethalves/grab-equip

grab script bumper is an equip toggle
This commit is contained in:
Philip Rosedale 2015-11-17 20:15:26 -08:00
commit 5b853ddf57
3 changed files with 210 additions and 29 deletions

View file

@ -28,6 +28,8 @@ var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value
var TRIGGER_ON_VALUE = 0.4;
var TRIGGER_OFF_VALUE = 0.15;
var BUMPER_ON_VALUE = 0.5;
//
// distant manipulation
//
@ -45,7 +47,7 @@ var PICK_MAX_DISTANCE = 500; // max length of pick-ray
// near grabbing
//
var GRAB_RADIUS = 0.3; // if the ray misses but an object is this close, it will still be selected
var GRAB_RADIUS = 0.03; // if the ray misses but an object is this close, it will still be selected
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
var NEAR_GRABBING_VELOCITY_SMOOTH_RATIO = 1.0; // adjust time-averaging of held object's velocity. 1.0 to disable.
var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected
@ -53,6 +55,13 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things
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
//
// equip
//
var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05;
var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position
//
// other constants
//
@ -68,7 +77,7 @@ var ZERO_VEC = {
var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}";
var MSEC_PER_SEC = 1000.0;
// these control how long an abandoned pointer line will hang around
// these control how long an abandoned pointer line or action will hang around
var LIFETIME = 10;
var ACTION_TTL = 15; // seconds
var ACTION_TTL_REFRESH = 5;
@ -106,6 +115,12 @@ var STATE_CONTINUE_NEAR_TRIGGER = 7;
var STATE_FAR_TRIGGER = 8;
var STATE_CONTINUE_FAR_TRIGGER = 9;
var STATE_RELEASE = 10;
var STATE_EQUIP_SEARCHING = 11;
var STATE_EQUIP = 12
var STATE_CONTINUE_EQUIP_BD = 13; // equip while bumper is still held down
var STATE_CONTINUE_EQUIP = 14;
var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
var STATE_EQUIP_SPRING = 16;
function stateToName(state) {
@ -132,6 +147,18 @@ function stateToName(state) {
return "continue_far_trigger";
case STATE_RELEASE:
return "release";
case STATE_EQUIP_SEARCHING:
return "equip_searching";
case STATE_EQUIP:
return "equip";
case STATE_CONTINUE_EQUIP_BD:
return "continue_equip_bd";
case STATE_CONTINUE_EQUIP:
return "continue_equip";
case STATE_WAITING_FOR_BUMPER_RELEASE:
return "waiting_for_bumper_release";
case STATE_EQUIP_SPRING:
return "state_equip_spring";
}
return "unknown";
@ -182,6 +209,7 @@ function MyController(hand) {
this.pointer = null; // entity-id of line object
this.triggerValue = 0; // rolling average of trigger value
this.rawTriggerValue = 0;
this.rawBumperValue = 0;
this.offsetPosition = { x: 0.0, y: 0.0, z: 0.0 };
this.offsetRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
@ -200,6 +228,9 @@ function MyController(hand) {
case STATE_SEARCHING:
this.search();
break;
case STATE_EQUIP_SEARCHING:
this.search();
break;
case STATE_DISTANCE_HOLDING:
this.distanceHolding();
break;
@ -207,9 +238,18 @@ function MyController(hand) {
this.continueDistanceHolding();
break;
case STATE_NEAR_GRABBING:
case STATE_EQUIP:
this.nearGrabbing();
break;
case STATE_WAITING_FOR_BUMPER_RELEASE:
this.waitingForBumperRelease();
break;
case STATE_EQUIP_SPRING:
this.pullTowardEquipPosition()
break;
case STATE_CONTINUE_NEAR_GRABBING:
case STATE_CONTINUE_EQUIP_BD:
case STATE_CONTINUE_EQUIP:
this.continueNearGrabbing();
break;
case STATE_NEAR_TRIGGER:
@ -281,10 +321,15 @@ function MyController(hand) {
this.pointer = null;
};
this.eitherTrigger = function (value) {
this.triggerPress = function (value) {
_this.rawTriggerValue = value;
};
this.bumperPress = function (value) {
_this.rawBumperValue = value;
};
this.updateSmoothedTrigger = function () {
var triggerValue = this.rawTriggerValue;
// smooth out trigger value
@ -305,23 +350,37 @@ function MyController(hand) {
return triggerValue > TRIGGER_ON_VALUE;
};
this.bumperSqueezed = function() {
return _this.rawBumperValue > BUMPER_ON_VALUE;
}
this.bumperReleased = function() {
return _this.rawBumperValue < BUMPER_ON_VALUE;
}
this.off = function() {
if (this.triggerSmoothedSqueezed()) {
this.lastPickTime = 0;
this.setState(STATE_SEARCHING);
return;
}
if (this.bumperSqueezed()) {
this.lastPickTime = 0;
this.setState(STATE_EQUIP_SEARCHING);
return;
}
}
this.search = function() {
this.grabbedEntity = null;
//if this hand is the one that's disabled, we don't want to search for anything at all
// if this hand is the one that's disabled, we don't want to search for anything at all
if (this.hand === disabledHand) {
return;
}
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
this.setState(STATE_RELEASE);
return;
}
@ -334,8 +393,6 @@ function MyController(hand) {
length: PICK_MAX_DISTANCE
};
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
// don't pick 60x per second.
var pickRays = [];
var now = Date.now();
@ -398,7 +455,15 @@ function MyController(hand) {
return;
} else if (!intersection.properties.locked) {
this.grabbedEntity = intersection.entityID;
this.setState(STATE_NEAR_GRABBING);
if (this.state == STATE_SEARCHING) {
this.setState(STATE_NEAR_GRABBING);
} else { // equipping
if (typeof grabbableData.spatialKey !== 'undefined') {
this.setState(STATE_EQUIP_SPRING);
} else {
this.setState(STATE_EQUIP);
}
}
return;
}
} else if (! entityIsGrabbedByOther(intersection.entityID)) {
@ -407,8 +472,14 @@ function MyController(hand) {
&& !intersection.properties.locked) {
// the hand is far from the intersected object. go into distance-holding mode
this.grabbedEntity = intersection.entityID;
this.setState(STATE_DISTANCE_HOLDING);
return;
if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) {
// if a distance pick in equip mode hits something with a spatialKey, equip it
this.setState(STATE_EQUIP_SPRING);
return;
} else if (this.state == STATE_SEARCHING) {
this.setState(STATE_DISTANCE_HOLDING);
return;
}
} else if (grabbableData.wantsTrigger) {
this.grabbedEntity = intersection.entityID;
this.setState(STATE_FAR_TRIGGER);
@ -434,6 +505,7 @@ function MyController(hand) {
var nearbyEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = PICK_MAX_DISTANCE;
var i, props, distance, grabbableData;
this.grabbedEntity = null;
for (i = 0; i < nearbyEntities.length; i++) {
var grabbableDataForCandidate =
getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA);
@ -483,16 +555,17 @@ function MyController(hand) {
grabbableData = grabbableDataForCandidate;
}
}
if (this.grabbedEntity === null) {
return;
}
if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_TRIGGER);
return;
} else if (!props.locked && props.collisionsWillMove) {
this.setState(STATE_NEAR_GRABBING);
return;
if (this.grabbedEntity !== null) {
if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_TRIGGER);
return;
} else if (!props.locked && props.collisionsWillMove) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP)
return;
}
}
this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
};
this.distanceHolding = function() {
@ -551,6 +624,16 @@ function MyController(hand) {
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (this.state == STATE_CONTINUE_DISTANCE_HOLDING && this.bumperSqueezed() &&
typeof grabbableData.spatialKey !== 'undefined') {
var saveGrabbedID = this.grabbedEntity;
this.release();
this.setState(STATE_EQUIP);
this.grabbedEntity = saveGrabbedID;
return;
}
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
@ -634,13 +717,12 @@ function MyController(hand) {
return;
}
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
return;
}
this.lineOff();
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
@ -656,7 +738,8 @@ function MyController(hand) {
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
if (grabbableData.spatialKey) {
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
// if an object is "equipped" and has a spatialKey, use it.
if (grabbableData.spatialKey.relativePosition) {
this.offsetPosition = grabbableData.spatialKey.relativePosition;
}
@ -686,7 +769,13 @@ function MyController(hand) {
this.actionID = null;
} else {
this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC);
this.setState(STATE_CONTINUE_NEAR_GRABBING);
if (this.state == STATE_NEAR_GRABBING) {
this.setState(STATE_CONTINUE_NEAR_GRABBING);
} else {
// equipping
this.setState(STATE_CONTINUE_EQUIP_BD);
}
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else {
@ -696,17 +785,30 @@ function MyController(hand) {
}
this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;;
this.currentHandControllerTipPosition =
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
this.currentObjectTime = Date.now();
};
this.continueNearGrabbing = function() {
if (this.triggerSmoothedReleased()) {
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
return;
}
if (this.state == STATE_CONTINUE_EQUIP_BD && this.bumperReleased()) {
this.setState(STATE_CONTINUE_EQUIP);
return;
}
if (this.state == STATE_CONTINUE_EQUIP && this.bumperSqueezed()) {
this.setState(STATE_WAITING_FOR_BUMPER_RELEASE);
return;
}
if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.bumperSqueezed()) {
this.setState(STATE_CONTINUE_EQUIP_BD);
return;
}
// Keep track of the fingertip velocity to impart when we release the object.
// Note that the idea of using a constant 'tip' velocity regardless of the
@ -740,6 +842,66 @@ function MyController(hand) {
}
};
this.waitingForBumperRelease = function() {
if (this.bumperReleased()) {
this.setState(STATE_RELEASE);
Entities.callEntityMethod(this.grabbedEntity, "releaseGrab");
}
};
this.pullTowardEquipPosition = function() {
this.lineOff();
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
// use a spring to pull the object to where it will be when equipped
var relativeRotation = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
var relativePosition = { x: 0.0, y: 0.0, z: 0.0 };
if (grabbableData.spatialKey.relativePosition) {
relativePosition = grabbableData.spatialKey.relativePosition;
}
if (grabbableData.spatialKey.relativeRotation) {
relativeRotation = grabbableData.spatialKey.relativeRotation;
}
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
var targetRotation = Quat.multiply(handRotation, relativeRotation);
var offset = Vec3.multiplyQbyV(targetRotation, relativePosition);
var targetPosition = Vec3.sum(handPosition, offset);
if (typeof this.equipSpringID === 'undefined' ||
this.equipSpringID === null ||
this.equipSpringID === NULL_ACTION_ID) {
this.equipSpringID = Entities.addAction("spring", this.grabbedEntity, {
targetPosition: targetPosition,
linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL
});
if (this.equipSpringID === NULL_ACTION_ID) {
this.equipSpringID = null;
this.setState(STATE_OFF);
return;
}
} else {
Entities.updateAction(this.grabbedEntity, this.equipSpringID, {
targetPosition: targetPosition,
linearTimeScale: EQUIP_SPRING_TIMEFRAME,
targetRotation: targetRotation,
angularTimeScale: EQUIP_SPRING_TIMEFRAME,
ttl: ACTION_TTL
});
}
if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) {
Entities.deleteAction(this.grabbedEntity, this.equipSpringID);
this.equipSpringID = null;
this.setState(STATE_EQUIP);
}
};
this.nearTrigger = function() {
if (this.triggerSmoothedReleased()) {
this.setState(STATE_RELEASE);
@ -919,6 +1081,7 @@ function MyController(hand) {
}
Entities.editEntity(entityID, whileHeldProperties);
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
return data;
};
@ -948,8 +1111,12 @@ var leftController = new MyController(LEFT_HAND);
var MAPPING_NAME = "com.highfidelity.handControllerGrab";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from([Controller.Standard.RB, Controller.Standard.RT]).peek().to(rightController.eitherTrigger);
mapping.from([Controller.Standard.LB, Controller.Standard.LT]).peek().to(leftController.eitherTrigger);
mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress);
mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress);
mapping.from([Controller.Standard.RB]).peek().to(rightController.bumperPress);
mapping.from([Controller.Standard.LB]).peek().to(leftController.bumperPress);
Controller.enableMapping(MAPPING_NAME);

View file

@ -11,6 +11,12 @@ vec3toStr = function(v, digits) {
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
}
quatToStr = function(q, digits) {
if (!digits) { digits = 3; }
return "{ " + q.w.toFixed(digits) + ", " + q.x.toFixed(digits) + ", " +
q.y.toFixed(digits) + ", " + q.z.toFixed(digits)+ " }";
}
vec3equal = function(v0, v1) {
return (v0.x == v1.x) && (v0.y == v1.y) && (v0.z == v1.z);
}
@ -51,7 +57,7 @@ addLine = function(origin, vector, color) {
// FIXME fetch from a subkey of user data to support non-destructive modifications
setEntityUserData = function(id, data) {
var json = JSON.stringify(data)
Entities.editEntity(id, { userData: json });
Entities.editEntity(id, { userData: json });
}
// FIXME do non-destructive modification of the existing user data
@ -60,7 +66,7 @@ getEntityUserData = function(id) {
var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) {
try {
results = JSON.parse(properties.userData);
results = JSON.parse(properties.userData);
} catch(err) {
logDebug(err);
logDebug(properties.userData);

View file

@ -704,6 +704,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
changedProperties[index] = QString("locked:") + changeHint;
}
}
if (properties.userDataChanged()) {
int index = changedProperties.indexOf("userData");
if (index >= 0) {
QString changeHint = properties.getUserData();
changedProperties[index] = QString("userData:") + changeHint;
}
}
}
int EntityTree::processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength,