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/snapshot.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/tablet-goto.js",
"system/marketplaces/marketplaces.js",
// "system/edit.js",
"system/edit.js",
"system/notifications.js",
"system/dialTone.js",
"system/firstPersonHMD.js",

View File

@ -68,9 +68,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
getControllerWorldLocation(Controller.Standard.RightHand, true)];
var nearbyEntityProperties = [[], []];
for (var i = LEFT_HAND; i <= RIGHT_HAND; i ++) {
// todo: check controllerLocations[i].valid
var controllerPosition = controllerLocations[i].position;
for (var h = LEFT_HAND; h <= RIGHT_HAND; h ++) {
// todo: check controllerLocations[h].valid
var controllerPosition = controllerLocations[h].position;
var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MIN_RADIUS);
for (var j = 0; j < nearbyEntityIDs.length; j++) {
var entityID = nearbyEntityIDs[j];
@ -78,9 +78,15 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
props.id = entityID;
props.distanceFromController = Vec3.length(Vec3.subtract(controllerPosition, props.position));
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 = {

View File

@ -6,8 +6,18 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Camera, HMD, MyAvatar, getControllerJointIndex,
LEFT_HAND, RIGHT_HAND, NULL_UUID, AVATAR_SELF_ID, getGrabbableData */
/* global Camera, HMD, MyAvatar, controllerDispatcherPlugins,
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;
RIGHT_HAND = 1;
@ -15,6 +25,23 @@ RIGHT_HAND = 1;
NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
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) {
// look in userData for a "grabbable" key, return the value or some defaults
var grabbableData = {};
@ -29,10 +56,25 @@ getGrabbableData = function (props) {
if (!grabbableData.hasOwnProperty("grabbable")) {
grabbableData.grabbable = true;
}
if (!grabbableData.hasOwnProperty("ignoreIK")) {
grabbableData.ignoreIK = true;
}
if (!grabbableData.hasOwnProperty("kinematicGrab")) {
grabbableData.kinematicGrab = false;
}
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) {
if (HMD.isHandControllerAvailable()) {
@ -52,3 +94,11 @@ getControllerJointIndex = function (hand) {
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";
// nearGrab.js
// nearParentGrabEntity.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, controllerDispatcherPlugins,
RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID, getControllerJointIndex, getGrabbableData, NULL_UUID */
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, AVATAR_SELF_ID,
getControllerJointIndex, getGrabbableData, NULL_UUID, enableDispatcherModule, disableDispatcherModule,
FORBIDDEN_GRAB_TYPES, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION
*/
Script.include("/~/system/controllers/controllerDispatcherUtils.js");
(function() {
var HAPTIC_PULSE_STRENGTH = 1.0;
var HAPTIC_PULSE_DURATION = 13.0;
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;
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
function NearParentingGrabEntity(hand) {
this.hand = hand;
this.grabbedThingID = null;
this.previousParentID = {};
@ -40,8 +26,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
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.controllerJointIndex = getControllerJointIndex(this.hand);
this.thisHandIsParent = function(props) {
if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== AVATAR_SELF_ID) {
@ -68,12 +55,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
return false;
};
this.startNearGrabParenting = function (controllerData, grabbedProperties) {
this.startNearParentingGrabEntity = function (controllerData, grabbedProperties) {
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 = {
parentID: AVATAR_SELF_ID,
parentJointIndex: getControllerJointIndex(this.hand),
parentJointIndex: handJointIndex,
velocity: {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;
}
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) {
Entities.editEntity(this.grabbedThingID, {
parentID: this.previousParentID[this.grabbedThingID],
@ -104,6 +108,10 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
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;
};
@ -113,22 +121,22 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
}
var grabbedProperties = null;
var bestDistance = 1000;
// nearbyEntityProperties is already sorted by length from controller
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];
if (entityIsParentingGrabbable(props)) {
if (props.distanceFromController < bestDistance) {
bestDistance = props.distanceFromController;
grabbedProperties = props;
}
if (entityIsGrabbable(props)) {
grabbedProperties = props;
break;
}
}
if (grabbedProperties) {
if (propsArePhysical(grabbedProperties)) {
return false; // let nearActionGrabEntity handle it
}
this.grabbedThingID = grabbedProperties.id;
this.startNearGrabParenting(controllerData, grabbedProperties);
this.startNearParentingGrabEntity(controllerData, grabbedProperties);
return true;
} else {
return false;
@ -137,29 +145,23 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js");
this.run = function (controllerData) {
if (controllerData.triggerClicks[this.hand] == 0) {
this.endNearGrabParenting(controllerData);
this.endNearParentingGrabEntity(controllerData);
return false;
}
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
Entities.callEntityMethod(this.grabbedThingID, "continueNearGrab", args);
return true;
};
}
var leftNearGrabParenting = new NearGrabParenting(LEFT_HAND);
leftNearGrabParenting.name = "leftNearGrabParenting";
var rightNearGrabParenting = new NearGrabParenting(RIGHT_HAND);
rightNearGrabParenting.name = "rightNearGrabParenting";
if (!controllerDispatcherPlugins) {
controllerDispatcherPlugins = {};
}
controllerDispatcherPlugins.leftNearGrabParenting = leftNearGrabParenting;
controllerDispatcherPlugins.rightNearGrabParenting = rightNearGrabParenting;
enableDispatcherModule("LeftNearParentingGrabEntity", new NearParentingGrabEntity(LEFT_HAND), 500);
enableDispatcherModule("RightNearParentingGrabEntity", new NearParentingGrabEntity(RIGHT_HAND), 500);
this.cleanup = function () {
delete controllerDispatcherPlugins.leftNearGrabParenting;
delete controllerDispatcherPlugins.rightNearGrabParenting;
disableDispatcherModule("LeftNearParentingGrabEntity");
disableDispatcherModule("RightNearParentingGrabEntity");
};
Script.scriptEnding.connect(this.cleanup);
}());

View File

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