port action-near-grab to dispatcher

This commit is contained in:
Seth Alves 2017-08-09 17:48:35 -07:00
parent 5e3b573d19
commit 0d0f147056
6 changed files with 321 additions and 59 deletions

View file

@ -20,11 +20,11 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/bubble.js", "system/bubble.js",
"system/snapshot.js", "system/snapshot.js",
"system/help.js", "system/help.js",
// "system/pal.js", // "system/mod.js", // older UX, if you prefer "system/pal.js", // "system/mod.js", // older UX, if you prefer
"system/makeUserConnection.js", "system/makeUserConnection.js",
"system/tablet-goto.js", "system/tablet-goto.js",
"system/marketplaces/marketplaces.js", "system/marketplaces/marketplaces.js",
// "system/edit.js", "system/edit.js",
"system/notifications.js", "system/notifications.js",
"system/dialTone.js", "system/dialTone.js",
"system/firstPersonHMD.js", "system/firstPersonHMD.js",

View file

@ -68,9 +68,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
getControllerWorldLocation(Controller.Standard.RightHand, true)]; getControllerWorldLocation(Controller.Standard.RightHand, true)];
var nearbyEntityProperties = [[], []]; var nearbyEntityProperties = [[], []];
for (var i = LEFT_HAND; i <= RIGHT_HAND; i ++) { for (var h = LEFT_HAND; h <= RIGHT_HAND; h ++) {
// todo: check controllerLocations[i].valid // todo: check controllerLocations[h].valid
var controllerPosition = controllerLocations[i].position; var controllerPosition = controllerLocations[h].position;
var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS); var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS);
for (var j = 0; j < nearbyEntityIDs.length; j++) { for (var j = 0; j < nearbyEntityIDs.length; j++) {
var entityID = nearbyEntityIDs[j]; var entityID = nearbyEntityIDs[j];
@ -78,9 +78,15 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
props.id = entityID; props.id = entityID;
props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position)); props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position));
if (props.distanceFromController < NEAR_MAX_RADIUS) { if (props.distanceFromController < NEAR_MAX_RADIUS) {
nearbyEntityProperties[i].push(props); nearbyEntityProperties[h].push(props);
} }
} }
// sort by distance from each hand
nearbyEntityProperties[h].sort(function (a, b) {
var aDistance = Vec3.distance(a.position, controllerLocations[h]);
var bDistance = Vec3.distance(b.position, controllerLocations[h]);
return aDistance - bDistance;
});
} }
var controllerData = { var controllerData = {

View file

@ -6,8 +6,18 @@
// 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, getControllerJointIndex, /* global Camera, HMD, MyAvatar, controllerDispatcherPlugins,
LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, getGrabbableData */ MSECS_PER_SEC, LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, FORBIDDEN_GRAB_TYPES,
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
enableDispatcherModule,
disableDispatcherModule,
getGrabbableData,
entityIsGrabbable,
getControllerJointIndex,
propsArePhysical
*/
MSECS_PER_SEC = 1000.0;
LEFT_HAND = 0; LEFT_HAND = 0;
RIGHT_HAND = 1; RIGHT_HAND = 1;
@ -15,6 +25,23 @@ RIGHT_HAND = 1;
NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}";
FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"];
HAPTIC_PULSE_STRENGTH = 1.0;
HAPTIC_PULSE_DURATION = 13.0;
enableDispatcherModule = function (moduleName, module, priority) {
if (!controllerDispatcherPlugins) {
controllerDispatcherPlugins = {};
}
controllerDispatcherPlugins[moduleName] = module;
};
disableDispatcherModule = function (moduleName) {
delete controllerDispatcherPlugins[moduleName];
};
getGrabbableData = function (props) { getGrabbableData = function (props) {
// look in userData for a "grabbable" key, return the value or some defaults // look in userData for a "grabbable" key, return the value or some defaults
var grabbableData = {}; var grabbableData = {};
@ -29,10 +56,25 @@ getGrabbableData = function (props) {
if (!grabbableData.hasOwnProperty("grabbable")) { if (!grabbableData.hasOwnProperty("grabbable")) {
grabbableData.grabbable = true; grabbableData.grabbable = true;
} }
if (!grabbableData.hasOwnProperty("ignoreIK")) {
grabbableData.ignoreIK = true;
}
if (!grabbableData.hasOwnProperty("kinematicGrab")) {
grabbableData.kinematicGrab = false;
}
return grabbableData; return grabbableData;
}; };
entityIsGrabbable = function (props) {
var grabbable = getGrabbableData(props).grabbable;
if (!grabbable ||
props.locked ||
FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) {
return false;
}
return true;
};
getControllerJointIndex = function (hand) { getControllerJointIndex = function (hand) {
if (HMD.isHandControllerAvailable()) { if (HMD.isHandControllerAvailable()) {
@ -52,3 +94,11 @@ getControllerJointIndex = function (hand) {
return MyAvatar.getJointIndex("Head"); return MyAvatar.getJointIndex("Head");
}; };
propsArePhysical = function (props) {
if (!props.dynamic) {
return false;
}
var isPhysical = (props.shapeType && props.shapeType != 'none');
return isPhysical;
};

View file

@ -0,0 +1,203 @@
"use strict";
// nearActionGrabEntity.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule,
propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable,
Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation
*/
Script.include("/~/system/controllers/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
function NearActionGrabEntity(hand) {
this.hand = hand;
this.grabbedThingID = null;
this.actionID = null; // action this script created...
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
var ACTION_TTL = 15; // seconds
var ACTION_TTL_REFRESH = 5;
// XXX does handJointIndex change if the avatar changes?
this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
this.controllerJointIndex = getControllerJointIndex(this.hand);
// handPosition is where the avatar's hand appears to be, in-world.
this.getHandPosition = function () {
if (this.hand === RIGHT_HAND) {
return MyAvatar.getRightPalmPosition();
} else {
return MyAvatar.getLeftPalmPosition();
}
};
this.getHandRotation = function () {
if (this.hand === RIGHT_HAND) {
return MyAvatar.getRightPalmRotation();
} else {
return MyAvatar.getLeftPalmRotation();
}
};
this.startNearGrabAction = function (controllerData, grabbedProperties) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
var grabbableData = getGrabbableData(grabbedProperties);
this.ignoreIK = grabbableData.ignoreIK;
this.kinematicGrab = grabbableData.kinematicGrab;
var handRotation;
var handPosition;
if (this.ignoreIK) {
var controllerID =
(this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
var controllerLocation = getControllerWorldLocation(controllerID, false);
handRotation = controllerLocation.orientation;
handPosition = controllerLocation.position;
} else {
handRotation = this.getHandRotation();
handPosition = this.getHandPosition();
}
var objectRotation = grabbedProperties.rotation;
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
var currentObjectPosition = grabbedProperties.position;
var offset = Vec3.subtract(currentObjectPosition, handPosition);
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
var now = Date.now();
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
if (this.actionID) {
Entities.deleteAction(this.grabbedThingID, this.actionID);
}
this.actionID = Entities.addAction("hold", this.grabbedThingID, {
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition,
relativeRotation: this.offsetRotation,
ttl: ACTION_TTL,
kinematic: this.kinematicGrab,
kinematicSetVelocity: true,
ignoreIK: this.ignoreIK
});
if (this.actionID === NULL_UUID) {
this.actionID = null;
return;
}
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'grab',
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
};
// this is for when the action creation failed, before
this.restartNearGrabAction = function (controllerData) {
var props = Entities.getEntityProperties(this.grabbedThingID, ["position", "rotation", "userData"]);
if (props && entityIsGrabbable(props)) {
this.startNearGrabAction(controllerData, props);
}
};
// this is for when the action is going to time-out
this.refreshNearGrabAction = function (controllerData) {
var now = Date.now();
if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) {
// if less than a 5 seconds left, refresh the actions ttl
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: this.offsetPosition,
relativeRotation: this.offsetRotation,
ttl: ACTION_TTL,
kinematic: this.kinematicGrab,
kinematicSetVelocity: true,
ignoreIK: this.ignoreIK
});
if (success) {
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
} else {
print("continueNearGrabbing -- updateAction failed");
this.restartNearGrabAction(controllerData);
}
}
};
this.endNearGrabAction = function (controllerData) {
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args);
Entities.deleteAction(this.grabbedThingID, this.actionID);
this.actionID = null;
this.grabbedThingID = null;
};
this.isReady = function (controllerData) {
if (controllerData.triggerClicks[this.hand] == 0) {
return false;
}
var grabbedProperties = null;
// nearbyEntityProperties is already sorted by length from controller
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
for (var i = 0; i < nearbyEntityProperties.length; i++) {
var props = nearbyEntityProperties[i];
if (entityIsGrabbable(props)) {
grabbedProperties = props;
break;
}
}
if (grabbedProperties) {
if (!propsArePhysical(grabbedProperties)) {
return false; // let nearParentGrabEntity handle it
}
this.grabbedThingID = grabbedProperties.id;
this.startNearGrabAction(controllerData, grabbedProperties);
return true;
} else {
return false;
}
};
this.run = function (controllerData) {
if (controllerData.triggerClicks[this.hand] == 0) {
this.endNearGrabAction(controllerData);
return false;
}
if (!this.actionID) {
this.restartNearGrabAction(controllerData);
}
this.refreshNearGrabAction(controllerData);
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args);
return true;
};
}
enableDispatcherModule("LeftNearActionGrabEntity", new NearActionGrabEntity(LEFT_HAND), 500);
enableDispatcherModule("RightNearActionGrabEntity", new NearActionGrabEntity(RIGHT_HAND), 500);
this.cleanup = function () {
disableDispatcherModule("LeftNearActionGrabEntity");
disableDispatcherModule("RightNearActionGrabEntity");
};
Script.scriptEnding.connect(this.cleanup);
}());

View file

@ -1,38 +1,24 @@
"use strict"; "use strict";
// nearGrab.js // nearParentGrabEntity.js
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// 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 Script, Entities, MyAvatar, Controller, controllerDispatcherPlugins, /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID,
RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, getGrabbableData, NULL_UUID */ getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule,
FORBIDDEN_GRAB_TYPES, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION
*/
Script.include("/~/system/controllers/controllerDispatcherUtils.js"); Script.include("/~/system/controllers/controllerDispatcherUtils.js");
(function() { (function() {
var HAPTIC_PULSE_STRENGTH = 1.0; // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
var HAPTIC_PULSE_DURATION = 13.0; // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"];
var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"];
function entityIsParentingGrabbable(props) {
var grabbable = getGrabbableData(props).grabbable;
if (!grabbable ||
props.locked ||
FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0 ||
FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) {
return false;
}
return true;
}
function NearGrabParenting(hand) {
this.priority = 5;
function NearParentingGrabEntity(hand) {
this.hand = hand; this.hand = hand;
this.grabbedThingID = null; this.grabbedThingID = null;
this.previousParentID = {}; this.previousParentID = {};
@ -40,8 +26,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
this.previouslyUnhooked = {}; this.previouslyUnhooked = {};
// todo: does this change if the avatar changes? // XXX does handJointIndex change if the avatar changes?
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.thisHandIsParent = function(props) { this.thisHandIsParent = function(props) {
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) { if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) {
@ -68,12 +55,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
return false; return false;
}; };
this.startNearGrabParenting = function (controllerData, grabbedProperties) { this.startNearParentingGrabEntity = function (controllerData, grabbedProperties) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
var handJointIndex;
// if (this.ignoreIK) {
// handJointIndex = this.controllerJointIndex;
// } else {
// handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
// }
handJointIndex = this.controllerJointIndex;
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "startNearGrab", args);
var reparentProps = { var reparentProps = {
parentID: AVATAR_SELF_ID, parentID: AVATAR_SELF_ID,
parentJointIndex: getControllerJointIndex(this.hand), parentJointIndex: handJointIndex,
velocity: {x: 0, y: 0, z: 0}, velocity: {x: 0, y: 0, z: 0},
angularVelocity: {x: 0, y: 0, z: 0} angularVelocity: {x: 0, y: 0, z: 0}
}; };
@ -87,9 +85,15 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex; this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
} }
Entities.editEntity(this.grabbedThingID, reparentProps); Entities.editEntity(this.grabbedThingID, reparentProps);
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'grab',
grabbedEntity: this.grabbedThingID,
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
}));
}; };
this.endNearGrabParenting = function (controllerData) { this.endNearParentingGrabEntity = function (controllerData) {
if (this.previousParentID[this.grabbedThingID] === NULL_UUID) { if (this.previousParentID[this.grabbedThingID] === NULL_UUID) {
Entities.editEntity(this.grabbedThingID, { Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID], parentID: this.previousParentID[this.grabbedThingID],
@ -104,6 +108,10 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
angularVelocity: {x: 0, y: 0, z: 0} angularVelocity: {x: 0, y: 0, z: 0}
}); });
} }
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args);
this.grabbedThingID = null; this.grabbedThingID = null;
}; };
@ -113,22 +121,22 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
} }
var grabbedProperties = null; var grabbedProperties = null;
var bestDistance = 1000; // nearbyEntityProperties is already sorted by length from controller
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
for (var i = 0; i < nearbyEntityProperties.length; i++) {
for (var i = 0; i < nearbyEntityProperties.length; i ++) {
var props = nearbyEntityProperties[i]; var props = nearbyEntityProperties[i];
if (entityIsParentingGrabbable(props)) { if (entityIsGrabbable(props)) {
if (props.distanceFromController < bestDistance) { grabbedProperties = props;
bestDistance = props.distanceFromController; break;
grabbedProperties = props;
}
} }
} }
if (grabbedProperties) { if (grabbedProperties) {
if (propsArePhysical(grabbedProperties)) {
return false; // let nearActionGrabEntity handle it
}
this.grabbedThingID = grabbedProperties.id; this.grabbedThingID = grabbedProperties.id;
this.startNearGrabParenting(controllerData, grabbedProperties); this.startNearParentingGrabEntity(controllerData, grabbedProperties);
return true; return true;
} else { } else {
return false; return false;
@ -137,29 +145,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
this.run = function (controllerData) { this.run = function (controllerData) {
if (controllerData.triggerClicks[this.hand] == 0) { if (controllerData.triggerClicks[this.hand] == 0) {
this.endNearGrabParenting(controllerData); this.endNearParentingGrabEntity(controllerData);
return false; return false;
} }
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args);
return true; return true;
}; };
} }
var leftNearGrabParenting = new NearGrabParenting(LEFT_HAND); enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND), 500);
leftNearGrabParenting.name = "leftNearGrabParenting"; enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND), 500);
var rightNearGrabParenting = new NearGrabParenting(RIGHT_HAND);
rightNearGrabParenting.name = "rightNearGrabParenting";
if (!controllerDispatcherPlugins) {
controllerDispatcherPlugins = {};
}
controllerDispatcherPlugins.leftNearGrabParenting = leftNearGrabParenting;
controllerDispatcherPlugins.rightNearGrabParenting = rightNearGrabParenting;
this.cleanup = function () { this.cleanup = function () {
delete controllerDispatcherPlugins.leftNearGrabParenting; disableDispatcherModule("LeftNearParentingGrabEntity");
delete controllerDispatcherPlugins.rightNearGrabParenting; disableDispatcherModule("RightNearParentingGrabEntity");
}; };
Script.scriptEnding.connect(this.cleanup); Script.scriptEnding.connect(this.cleanup);
}()); }());

View file

@ -19,7 +19,8 @@ var CONTOLLER_SCRIPTS = [
"toggleAdvancedMovementForHandControllers.js", "toggleAdvancedMovementForHandControllers.js",
"ControllerDispatcher.js", "ControllerDispatcher.js",
"nearGrab.js" "controllerModules/nearParentGrabEntity.js",
"controllerModules/nearActionGrabEntity.js"
]; ];
var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; var DEBUG_MENU_ITEM = "Debug defaultScripts.js";