"use strict";// handControllerGrab.js//// Created by Eric Levin on 9/2/15// Additions by James B. Pollack @imgntn on 9/24/2015// Additions By Seth Alves on 10/20/2015// Copyright 2015 High Fidelity, Inc.//// Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); //// add lines where the hand ray picking is happening//var WANT_DEBUG = false;var WANT_DEBUG_STATE = false;var WANT_DEBUG_SEARCH_NAME = null; //// these tune time-averaging and "on" value for analog trigger// var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothingvar TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grabvar TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grabvar TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; var DRAW_GRAB_BOXES = false;var DRAW_HAND_SPHERES = false;var DROP_WITHOUT_SHAKE = false; //// distant manipulation// var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and objectvar DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new positionvar DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodifiedvar DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodifiedvar MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects var NO_INTERSECT_COLOR = { red: 10, green: 10, blue: 255}; // line color when pick missesvar INTERSECT_COLOR = { red: 250, green: 10, blue: 10}; // line color when pick hitsvar LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000, z: 1000}; var LINE_LENGTH = 500;var PICK_MAX_DISTANCE = 500; // max length of pick-ray //// near grabbing// var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing.var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble objectvar NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbedvar CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds //// other constants// var HOTSPOT_DRAW_DISTANCE = 10;var RIGHT_HAND = 1;var LEFT_HAND = 0; var ZERO_VEC = { x: 0, y: 0, z: 0}; var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; // these control how long an abandoned pointer line or action will hang aroundvar LIFETIME = 10;var ACTION_TTL = 15; // secondsvar ACTION_TTL_REFRESH = 5;var PICKS_PER_SECOND_PER_HAND = 60;var MSECS_PER_SEC = 1000.0;var GRABBABLE_PROPERTIES = [ "position", "rotation", "gravity", "collidesWith", "dynamic", "collisionless", "locked", "name", "shapeType", "parentID", "parentJointIndex", "density", "dimensions", "userData"]; var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.jsvar GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js var DEFAULT_GRABBABLE_DATA = { disableReleaseVelocity: false}; // sometimes we want to exclude objects from being pickedvar USE_BLACKLIST = true;var blacklist = []; //we've created various ways of visualizing looking for and moving distant objectsvar USE_ENTITY_LINES_FOR_SEARCHING = false;var USE_OVERLAY_LINES_FOR_SEARCHING = true; var USE_ENTITY_LINES_FOR_MOVING = false;var USE_OVERLAY_LINES_FOR_MOVING = false;var USE_PARTICLE_BEAM_FOR_MOVING = true; var USE_SPOTLIGHT = false;var USE_POINTLIGHT = false; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"];var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; // states for the state machinevar STATE_OFF = 0;var STATE_SEARCHING = 1;var STATE_DISTANCE_HOLDING = 2;var STATE_NEAR_GRABBING = 3;var STATE_NEAR_TRIGGER = 4;var STATE_FAR_TRIGGER = 5;var STATE_HOLD = 6; // "collidesWith" is specified by comma-separated list of group names// the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatarvar COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar";var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC;var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; var CONTROLLER_STATE_MACHINE = {}; CONTROLLER_STATE_MACHINE[STATE_OFF] = { name: "off", enterMethod: "offEnter", updateMethod: "off"};CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { name: "searching", updateMethod: "search", enterMethod: "searchEnter", exitMethod: "searchExit"};CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding"};CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing"};CONTROLLER_STATE_MACHINE[STATE_HOLD] = { name: "hold", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing"};CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { name: "trigger", enterMethod: "nearTriggerEnter", updateMethod: "nearTrigger"};CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { name: "far_trigger", enterMethod: "farTriggerEnter", updateMethod: "farTrigger"}; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???";} function getTag() { return "grab-" + MyAvatar.sessionUUID;} function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0;} function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); var tag = actionArguments["tag"]; if (tag == getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; } if (tag.slice(0, 5) == "grab-") { // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. return true; } } return false;} function propsArePhysical(props) { if (!props.dynamic) { return false; } var isPhysical = (props.shapeType && props.shapeType != 'none'); return isPhysical;} // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here,// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode.var EXTERNALLY_MANAGED_2D_MINOR_MODE = true;function isIn2DMode() { // In this version, we make our own determination of whether we're aimed a HUD element, // because other scripts (such as handControllerPointer) might be using some other visualization // instead of setting Reticle.visible. return EXTERNALLY_MANAGED_2D_MINOR_MODE && (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position));}function restore2DMode() { if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { Reticle.setVisible(true); }} // EntityPropertiesCache is a helper class that contains a cache of entity properties.// the hope is to prevent excess calls to Entity.getEntityProperties()function EntityPropertiesCache() { this.cache = {};}EntityPropertiesCache.prototype.clear = function() { this.cache = {};};EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; entities.forEach(function (x) { _this.updateEntity(x); });};EntityPropertiesCache.prototype.updateEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); // convert props.userData from a string to an object. var userData = {}; if (props.userData) { try { userData = JSON.parse(props.userData); } catch(err) { print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); } } props.userData = userData; this.cache[entityID] = props;};EntityPropertiesCache.prototype.getEntities = function() { return Object.keys(this.cache);}EntityPropertiesCache.prototype.getProps = function(entityID) { var obj = this.cache[entityID] return obj ? obj : undefined;};EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; } else { return undefined; }};EntityPropertiesCache.prototype.getGrabProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabKey ? props.userData.grabKey : {}; } else { return undefined; }};EntityPropertiesCache.prototype.getWearableProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.wearable ? props.userData.wearable : {}; } else { return undefined; }}; function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { this.getHandPosition = MyAvatar.getRightPalmPosition; // this.getHandRotation = MyAvatar.getRightPalmRotation; } else { this.getHandPosition = MyAvatar.getLeftPalmPosition; // this.getHandRotation = MyAvatar.getLeftPalmRotation; } this.getHandRotation = function() { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); } this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. this.state = STATE_OFF; this.pointer = null; // entity-id of line object this.entityActivated = false; this.triggerValue = 0; // rolling average of trigger value this.rawTriggerValue = 0; this.rawSecondaryValue = 0; this.rawThumbValue = 0; //for visualizations this.overlayLine = null; this.particleBeamObject = null; //for lights this.spotlight = null; this.pointlight = null; this.overlayLine = null; this.searchSphere = null; this.waitForTriggerRelease = false; // how far from camera to search intersection? var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; this.lastPickTime = 0; this.lastUnequipCheckTime = 0; this.entityPropertyCache = new EntityPropertiesCache(); var _this = this; var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; this.ignoreInput = function () { // We've made the decision to use 'this' for new code, even though it is fragile, // in order to keep/ the code uniform without making any no-op line changes. return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; this.update = function(deltaTime) { this.updateSmoothedTrigger(); if (this.ignoreInput()) { this.turnOffVisualizations(); return; } if (CONTROLLER_STATE_MACHINE[this.state]) { var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; var updateMethod = this[updateMethodName]; if (updateMethod) { updateMethod.call(this, deltaTime); } else { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } } else { print("WARNING: could not find state " + this.state + " in state machine"); } }; this.callEntityMethodOnGrabbed = function(entityMethodName) { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); } this.setState = function(newState, reason) { if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName + ", reason = " + reason); } // exit the old state if (CONTROLLER_STATE_MACHINE[this.state]) { var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; var exitMethod = this[exitMethodName]; if (exitMethod) { exitMethod.call(this); } } else { print("WARNING: could not find state " + this.state + " in state machine"); } this.state = newState; // enter the new state if (CONTROLLER_STATE_MACHINE[newState]) { var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; var enterMethod = this[enterMethodName]; if (enterMethod) { enterMethod.call(this); } } else { print("WARNING: could not find newState " + newState + " in state machine"); } }; this.debugLine = function(closePoint, farPoint, color) { Entities.addEntity({ type: "Line", name: "Grab Debug Entity", dimensions: LINE_ENTITY_DIMENSIONS, visible: true, position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: 0.1, dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { grabbable: false } }) }); }; this.lineOn = function(closePoint, farPoint, color) { // draw a line if (this.pointer === null) { this.pointer = Entities.addEntity({ type: "Line", name: "grab pointer", dimensions: LINE_ENTITY_DIMENSIONS, visible: true, position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: LIFETIME, dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { grabbable: false } }) }); } else { var age = Entities.getEntityProperties(this.pointer, "age").age; this.pointer = Entities.editEntity(this.pointer, { position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: age + LIFETIME }); } }; var SEARCH_SPHERE_ALPHA = 0.5; this.searchSphereOn = function(location, size, color) {Lines 1–500 of 2314. Load more
"use strict"; // handControllerGrab.js // // Created by Eric Levin on 9/2/15 // Additions by James B. Pollack @imgntn on 9/24/2015 // Additions By Seth Alves on 10/20/2015 // Copyright 2015 High Fidelity, Inc. // // Grabs physically moveable entities with hydra-like controllers; it works for either near or far objects. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); // // add lines where the hand ray picking is happening // var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // // these tune time-averaging and "on" value for analog trigger // var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; var DRAW_GRAB_BOXES = false; var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; // // distant manipulation // var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects var NO_INTERSECT_COLOR = { red: 10, green: 10, blue: 255 }; // line color when pick misses var INTERSECT_COLOR = { red: 250, green: 10, blue: 10 }; // line color when pick hits var LINE_ENTITY_DIMENSIONS = { x: 1000, y: 1000, z: 1000 }; var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray // // near grabbing // var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing. var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. 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 var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds // // other constants // var HOTSPOT_DRAW_DISTANCE = 10; var RIGHT_HAND = 1; var LEFT_HAND = 0; var ZERO_VEC = { x: 0, y: 0, z: 0 }; var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; // 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; var PICKS_PER_SECOND_PER_HAND = 60; var MSECS_PER_SEC = 1000.0; var GRABBABLE_PROPERTIES = [ "position", "rotation", "gravity", "collidesWith", "dynamic", "collisionless", "locked", "name", "shapeType", "parentID", "parentJointIndex", "density", "dimensions", "userData" ]; var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js var DEFAULT_GRABBABLE_DATA = { disableReleaseVelocity: false }; // sometimes we want to exclude objects from being picked var USE_BLACKLIST = true; var blacklist = []; //we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; var USE_OVERLAY_LINES_FOR_SEARCHING = true; var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; // states for the state machine var STATE_OFF = 0; var STATE_SEARCHING = 1; var STATE_DISTANCE_HOLDING = 2; var STATE_NEAR_GRABBING = 3; var STATE_NEAR_TRIGGER = 4; var STATE_FAR_TRIGGER = 5; var STATE_HOLD = 6; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; var CONTROLLER_STATE_MACHINE = {}; CONTROLLER_STATE_MACHINE[STATE_OFF] = { name: "off", enterMethod: "offEnter", updateMethod: "off" }; CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { name: "searching", updateMethod: "search", enterMethod: "searchEnter", exitMethod: "searchExit" }; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { name: "hold", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { name: "trigger", enterMethod: "nearTriggerEnter", updateMethod: "nearTrigger" }; CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { name: "far_trigger", enterMethod: "farTriggerEnter", updateMethod: "farTrigger" }; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; } function getTag() { return "grab-" + MyAvatar.sessionUUID; } function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { var actionID = actionIDs[actionIndex]; var actionArguments = Entities.getActionArguments(entityID, actionID); var tag = actionArguments["tag"]; if (tag == getTag()) { // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. continue; } if (tag.slice(0, 5) == "grab-") { // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. return true; } } return false; } function propsArePhysical(props) { if (!props.dynamic) { return false; } var isPhysical = (props.shapeType && props.shapeType != 'none'); return isPhysical; } // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; function isIn2DMode() { // In this version, we make our own determination of whether we're aimed a HUD element, // because other scripts (such as handControllerPointer) might be using some other visualization // instead of setting Reticle.visible. return EXTERNALLY_MANAGED_2D_MINOR_MODE && (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)); } function restore2DMode() { if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { Reticle.setVisible(true); } } // EntityPropertiesCache is a helper class that contains a cache of entity properties. // the hope is to prevent excess calls to Entity.getEntityProperties() function EntityPropertiesCache() { this.cache = {}; } EntityPropertiesCache.prototype.clear = function() { this.cache = {}; }; EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; entities.forEach(function (x) { _this.updateEntity(x); }); }; EntityPropertiesCache.prototype.updateEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); // convert props.userData from a string to an object. var userData = {}; if (props.userData) { try { userData = JSON.parse(props.userData); } catch(err) { print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err); } } props.userData = userData; this.cache[entityID] = props; }; EntityPropertiesCache.prototype.getEntities = function() { return Object.keys(this.cache); } EntityPropertiesCache.prototype.getProps = function(entityID) { var obj = this.cache[entityID] return obj ? obj : undefined; }; EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; } else { return undefined; } }; EntityPropertiesCache.prototype.getGrabProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabKey ? props.userData.grabKey : {}; } else { return undefined; } }; EntityPropertiesCache.prototype.getWearableProps = function(entityID) { var props = this.cache[entityID]; if (props) { return props.userData.wearable ? props.userData.wearable : {}; } else { return undefined; } }; function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { this.getHandPosition = MyAvatar.getRightPalmPosition; // this.getHandRotation = MyAvatar.getRightPalmRotation; } else { this.getHandPosition = MyAvatar.getLeftPalmPosition; // this.getHandRotation = MyAvatar.getLeftPalmRotation; } this.getHandRotation = function() { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); } this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. this.state = STATE_OFF; this.pointer = null; // entity-id of line object this.entityActivated = false; this.triggerValue = 0; // rolling average of trigger value this.rawTriggerValue = 0; this.rawSecondaryValue = 0; this.rawThumbValue = 0; //for visualizations this.overlayLine = null; this.particleBeamObject = null; //for lights this.spotlight = null; this.pointlight = null; this.overlayLine = null; this.searchSphere = null; this.waitForTriggerRelease = false; // how far from camera to search intersection? var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; this.lastPickTime = 0; this.lastUnequipCheckTime = 0; this.entityPropertyCache = new EntityPropertiesCache(); var _this = this; var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; this.ignoreInput = function () { // We've made the decision to use 'this' for new code, even though it is fragile, // in order to keep/ the code uniform without making any no-op line changes. return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; this.update = function(deltaTime) { this.updateSmoothedTrigger(); if (this.ignoreInput()) { this.turnOffVisualizations(); return; } if (CONTROLLER_STATE_MACHINE[this.state]) { var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; var updateMethod = this[updateMethodName]; if (updateMethod) { updateMethod.call(this, deltaTime); } else { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } } else { print("WARNING: could not find state " + this.state + " in state machine"); } }; this.callEntityMethodOnGrabbed = function(entityMethodName) { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); } this.setState = function(newState, reason) { if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName + ", reason = " + reason); } // exit the old state if (CONTROLLER_STATE_MACHINE[this.state]) { var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; var exitMethod = this[exitMethodName]; if (exitMethod) { exitMethod.call(this); } } else { print("WARNING: could not find state " + this.state + " in state machine"); } this.state = newState; // enter the new state if (CONTROLLER_STATE_MACHINE[newState]) { var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; var enterMethod = this[enterMethodName]; if (enterMethod) { enterMethod.call(this); } } else { print("WARNING: could not find newState " + newState + " in state machine"); } }; this.debugLine = function(closePoint, farPoint, color) { Entities.addEntity({ type: "Line", name: "Grab Debug Entity", dimensions: LINE_ENTITY_DIMENSIONS, visible: true, position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: 0.1, dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { grabbable: false } }) }); }; this.lineOn = function(closePoint, farPoint, color) { // draw a line if (this.pointer === null) { this.pointer = Entities.addEntity({ type: "Line", name: "grab pointer", dimensions: LINE_ENTITY_DIMENSIONS, visible: true, position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: LIFETIME, dynamic: false, ignoreForCollisions: true, userData: JSON.stringify({ grabbableKey: { grabbable: false } }) }); } else { var age = Entities.getEntityProperties(this.pointer, "age").age; this.pointer = Entities.editEntity(this.pointer, { position: closePoint, linePoints: [ZERO_VEC, farPoint], color: color, lifetime: age + LIFETIME }); } }; var SEARCH_SPHERE_ALPHA = 0.5; this.searchSphereOn = function(location, size, color) {