"use strict"; // controllerDispatcher.js//// Created by Seth Alves, July 27th, 2017.// Copyright 2017 High Fidelity, Inc.// Copyright 2023, Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// /* jslint bitwise: true */ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, print, Keyboard*/ var controllerDispatcherPlugins = {};var controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js");Script.include("/~/system/libraries/controllers.js");Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { Script.include("/~/system/libraries/pointersUtils.js"); var controllerStandard = Controller.Standard; var NEAR_MAX_RADIUS = 0.1; var NEAR_TABLET_MAX_RADIUS = 0.05; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var PROFILE = false; var DEBUG = false; var SHOW_GRAB_SPHERE = false; if (typeof Test !== "undefined") { PROFILE = true; } function ControllerDispatcher() { var _this = this; this.lastInterval = Date.now(); this.intervalCount = 0; this.totalDelta = 0; this.totalVariance = 0; this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.orderedPluginNames = []; this.tabletID = null; this.blocklist = []; this.pointerManager = new PointerManager(); this.grabSphereOverlays = [null, null]; this.targetIDs = {}; this.debugPanelID = null; this.debugLines = []; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { head: false, leftHand: false, rightHand: false, rightHandTrigger: false, leftHandTrigger: false, rightHandEquip: false, leftHandEquip: false, mouse: false }; this.laserVisibleStatus = [false, false, false, false]; this.laserLockStatus = [false, false, false, false]; this.slotsAreAvailableForPlugin = function (plugin) { for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { return false; // something is already using a slot which _this plugin requires } } return true; }; this.markSlots = function (plugin, pluginName) { for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { _this.activitySlots[plugin.parameters.activitySlots[i]] = pluginName; } }; this.unmarkSlotsForPluginName = function (runningPluginName) { // this is used to free activity-slots when a plugin is deactivated while it's running. for (var activitySlot in _this.activitySlots) { if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) { _this.activitySlots[activitySlot] = false; } } }; this.runningPluginNames = {}; this.leftTriggerValue = 0; this.leftTriggerClicked = 0; this.leftTrackerClicked = false; // is leftTriggerClicked == 1 because a hand tracker set it? this.leftSecondaryValue = 0; this.rightTriggerValue = 0; this.rightTriggerClicked = 0; this.rightTrackerClicked = false; // is rightTriggerClicked == 1 because a hand tracker set it? this.rightSecondaryValue = 0; this.leftTriggerPress = function (value) { _this.leftTriggerValue = value; }; this.leftTriggerClick = function (value) { _this.leftTriggerClicked = value; }; this.rightTriggerPress = function (value) { _this.rightTriggerValue = value; }; this.rightTriggerClick = function (value) { _this.rightTriggerClicked = value; }; this.leftSecondaryPress = function (value) { _this.leftSecondaryValue = value; }; this.rightSecondaryPress = function (value) { _this.rightSecondaryValue = value; }; this.dataGatherers = {}; this.dataGatherers.leftControllerLocation = function () { return getControllerWorldLocation(controllerStandard.LeftHand, true); }; this.dataGatherers.rightControllerLocation = function () { return getControllerWorldLocation(controllerStandard.RightHand, true); }; this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); var deltaTimeMsec = thisInterval - _this.lastInterval; var deltaTime = deltaTimeMsec / 1000; _this.lastInterval = thisInterval; _this.totalDelta += deltaTimeMsec; var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); _this.totalVariance += variance; if (variance > 1) { _this.highVarianceCount++; } if (variance > 5) { _this.veryhighVarianceCount++; } return deltaTime; }; this.setIgnorePointerItems = function() { if (HMD.tabletID && HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; Pointers.setIgnoreItems(_this.leftPointer, _this.blocklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blocklist); } }; this.checkForHandTrackingClick = function() { var pinchOnBelowDistance = 0.016; var pinchOffAboveDistance = 0.035; var leftIndexPose = Controller.getPoseValue(controllerStandard.LeftHandIndex4); var leftThumbPose = Controller.getPoseValue(controllerStandard.LeftHandThumb4); var leftThumbToIndexDistance = Vec3.distance(leftIndexPose.translation, leftThumbPose.translation); if (leftIndexPose.valid && leftThumbPose.valid && leftThumbToIndexDistance < pinchOnBelowDistance) { _this.leftTriggerClicked = 1; _this.leftTriggerValue = 1; _this.leftTrackerClicked = true; } else if (_this.leftTrackerClicked && leftThumbToIndexDistance > pinchOffAboveDistance) { _this.leftTriggerClicked = 0; _this.leftTriggerValue = 0; _this.leftTrackerClicked = false; } var rightIndexPose = Controller.getPoseValue(controllerStandard.RightHandIndex4); var rightThumbPose = Controller.getPoseValue(controllerStandard.RightHandThumb4); var rightThumbToIndexDistance = Vec3.distance(rightIndexPose.translation, rightThumbPose.translation); if (rightIndexPose.valid && rightThumbPose.valid && rightThumbToIndexDistance < pinchOnBelowDistance) { _this.rightTriggerClicked = 1; _this.rightTriggerValue = 1; _this.rightTrackerClicked = true; } else if (_this.rightTrackerClicked && rightThumbToIndexDistance > pinchOffAboveDistance) { _this.rightTriggerClicked = 0; _this.rightTriggerValue = 0; _this.rightTrackerClicked = false; } }; this.update = function () { try { _this.updateInternal(); } catch (e) { print(e); } Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); }; this.addDebugLine = function(line) { if (this.debugLines.length > 8) { this.debugLines.shift(); } this.debugLines.push(line); var debugPanelText = ""; this.debugLines.forEach(function(debugLine) { debugPanelText += debugLine + "\n"; }); Entities.editEntity(this.debugPanelID, { text: debugPanelText }); }; this.updateInternal = function () { if (PROFILE) { Script.beginProfileRange("dispatch.pre"); } var sensorScaleFactor = MyAvatar.sensorToWorldScale; var deltaTime = _this.updateTimings(); _this.setIgnorePointerItems(); if (controllerDispatcherPluginsNeedSort) { _this.orderedPluginNames = []; for (var pluginName in controllerDispatcherPlugins) { if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { _this.orderedPluginNames.push(pluginName); } } _this.orderedPluginNames.sort(function (a, b) { return controllerDispatcherPlugins[a].parameters.priority - controllerDispatcherPlugins[b].parameters.priority; }); controllerDispatcherPluginsNeedSort = false; } if (PROFILE) { Script.endProfileRange("dispatch.pre"); } if (PROFILE) { Script.beginProfileRange("dispatch.gather"); } var controllerLocations = [ _this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation() ]; // find 3d overlays/Local Entities near each hand var nearbyOverlayIDs = []; var h;//V8TODO: Overlays.findOverlays might not work here for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); // Tablet and mini-tablet must be within NEAR_TABLET_MAX_RADIUS in order to be grabbed. // Mini tablet can only be grabbed the hand it's displayed on. var tabletIndex = nearbyOverlays.indexOf(HMD.tabletID); var miniTabletIndex = nearbyOverlays.indexOf(HMD.miniTabletID); if (tabletIndex !== -1 || miniTabletIndex !== -1) { var closebyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_TABLET_MAX_RADIUS * sensorScaleFactor); // Assumes that the tablet and mini-tablet are not displayed at the same time. if (tabletIndex !== -1 && closebyOverlays.indexOf(HMD.tabletID) === -1) { nearbyOverlays.splice(tabletIndex, 1); } if (miniTabletIndex !== -1 && ((closebyOverlays.indexOf(HMD.miniTabletID) === -1) || h !== HMD.miniTabletHand)) { nearbyOverlays.splice(miniTabletIndex, 1); } } nearbyOverlays.sort(function (a, b) { var aPosition = Entities.getEntityProperties(a, ["position"]).position; var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); var bPosition = Entities.getEntityProperties(b, ["position"]).position; var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); nearbyOverlayIDs.push(nearbyOverlays); } else { nearbyOverlayIDs.push([]); } } // find entities near each hand var nearbyEntityProperties = [[], []]; var nearbyEntityPropertiesByID = {}; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { var controllerPosition = controllerLocations[h].position; var findRadius = NEAR_MAX_RADIUS * sensorScaleFactor; if (SHOW_GRAB_SPHERE) { if (this.grabSphereOverlays[h]) { Entities.editEntity(this.grabSphereOverlays[h], { "position": controllerLocations[h].position }); } else { var grabSphereSize = findRadius * 2; this.grabSphereOverlays[h] = Entities.addEntity({ "type": "Shape", "shape": "Sphere", "position": controllerLocations[h].position, "dimensions": { "x": grabSphereSize, "y": grabSphereSize, "z": grabSphereSize }, "color": { "red": 30, "green": 30, "blue": 255 }, "alpha": 0.3, "primitiveMode": "solid", "visible": true, "renderLayer": "front", "grab": { "grabbable": false } }, "local"); } } var nearbyEntityIDs = Entities.findEntities(controllerPosition, findRadius); for (var j = 0; j < nearbyEntityIDs.length; j++) { var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; props.distance = Vec3.distance(props.position, controllerLocations[h].position); nearbyEntityPropertiesByID[entityID] = props; nearbyEntityProperties[h].push(props); } } } // Enable/disable controller raypicking depending on whether we are in HMD if (HMD.active) { Pointers.enablePointer(_this.leftPointer); Pointers.enablePointer(_this.rightPointer); Pointers.enablePointer(_this.leftHudPointer); Pointers.enablePointer(_this.rightHudPointer); Pointers.enablePointer(_this.mouseRayPointer); } else { Pointers.disablePointer(_this.leftPointer); Pointers.disablePointer(_this.rightPointer); Pointers.disablePointer(_this.leftHudPointer); Pointers.disablePointer(_this.rightHudPointer); Pointers.disablePointer(_this.mouseRayPointer); } // raypick for each controller var rayPicks = [ Pointers.getPrevPickResult(_this.leftPointer), Pointers.getPrevPickResult(_this.rightPointer) ]; var hudRayPicks = [ Pointers.getPrevPickResult(_this.leftHudPointer), Pointers.getPrevPickResult(_this.rightHudPointer) ]; var mouseRayPointer = Pointers.getPrevPickResult(_this.mouseRayPointer); // if the pickray hit something very nearby, put it into the nearby entities list for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // XXX find a way to extract searchRay from samuel's stuff if (controllerLocations[h].valid) { rayPicks[h].searchRay = { origin: controllerLocations[h].position, direction: Quat.getUp(controllerLocations[h].orientation), length: 1000 }; if (rayPicks[h].type === Picks.INTERSECTED_ENTITY) { // XXX check to make sure this one isn't already in nearbyEntityProperties? if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS * sensorScaleFactor) { var nearEntityID = rayPicks[h].objectID; var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); nearbyProps.id = nearEntityID; nearbyProps.distance = rayPicks[h].distance; nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; nearbyEntityProperties[h].push(nearbyProps); } } // sort by distance from each hand nearbyEntityProperties[h].sort(function (a, b) { return a.distance - b.distance; }); } } // sometimes, during a HMD snap-turn, an equipped or held item wont be near // the hand when the findEntities is done. Gather up any hand-children here. for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { var handChildrenIDs = findHandChildEntities(h); handChildrenIDs.forEach(function (handChildID) { if (handChildID in nearbyEntityPropertiesByID) { return; } var props = Entities.getEntityProperties(handChildID, DISPATCHER_PROPERTIES); props.id = handChildID; nearbyEntityPropertiesByID[handChildID] = props; }); } // also make sure we have the properties from the current module's target for (var tIDRunningPluginName in _this.runningPluginNames) { if (_this.runningPluginNames.hasOwnProperty(tIDRunningPluginName)) { var targetIDs = _this.targetIDs[tIDRunningPluginName]; if (targetIDs) { for (var k = 0; k < targetIDs.length; k++) { var targetID = targetIDs[k]; if (!nearbyEntityPropertiesByID[targetID]) { var targetProps = Entities.getEntityProperties(targetID, DISPATCHER_PROPERTIES); targetProps.id = targetID; nearbyEntityPropertiesByID[targetID] = targetProps; } } } } } // TODO: These are not used currently, but have severe impact on performace. They can be re-enabled when we have OpenXR support // check for hand-tracking "click" //_this.checkForHandTrackingClick(); // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, nearbyOverlayIDs: nearbyOverlayIDs, rayPicks: rayPicks, hudRayPicks: hudRayPicks, mouseRayPointer: mouseRayPointer }; if (PROFILE) { Script.endProfileRange("dispatch.gather"); } if (PROFILE) { Script.beginProfileRange("dispatch.isReady"); } // check for plugins that would like to start. ask in order of increasing priority value for (var pluginIndex = 0; pluginIndex < _this.orderedPluginNames.length; pluginIndex++) { var orderedPluginName = _this.orderedPluginNames[pluginIndex]; var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { if (PROFILE) { Script.beginProfileRange("dispatch.isReady." + orderedPluginName); } var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the // activity-slots which this plugin consumes as "in use" _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); if (DEBUG) { _this.addDebugLine("running " + orderedPluginName); } } if (PROFILE) { Script.endProfileRange("dispatch.isReady." + orderedPluginName); } } } if (PROFILE) { Script.endProfileRange("dispatch.isReady"); } if (PROFILE) { Script.beginProfileRange("dispatch.run"); } // give time to running pluginsLines 1–500 of 767. Load more
"use strict"; // controllerDispatcher.js // // Created by Seth Alves, July 27th, 2017. // Copyright 2017 High Fidelity, Inc. // Copyright 2023, Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // /* jslint bitwise: true */ /* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, controllerDispatcherPlugins:true, controllerDispatcherPluginsNeedSort:true, LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, print, Keyboard */ var controllerDispatcherPlugins = {}; var controllerDispatcherPluginsNeedSort = false; Script.include("/~/system/libraries/utils.js"); Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/controllerDispatcherUtils.js"); (function() { Script.include("/~/system/libraries/pointersUtils.js"); var controllerStandard = Controller.Standard; var NEAR_MAX_RADIUS = 0.1; var NEAR_TABLET_MAX_RADIUS = 0.05; var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ; var PROFILE = false; var DEBUG = false; var SHOW_GRAB_SPHERE = false; if (typeof Test !== "undefined") { PROFILE = true; } function ControllerDispatcher() { var _this = this; this.lastInterval = Date.now(); this.intervalCount = 0; this.totalDelta = 0; this.totalVariance = 0; this.highVarianceCount = 0; this.veryhighVarianceCount = 0; this.orderedPluginNames = []; this.tabletID = null; this.blocklist = []; this.pointerManager = new PointerManager(); this.grabSphereOverlays = [null, null]; this.targetIDs = {}; this.debugPanelID = null; this.debugLines = []; // a module can occupy one or more "activity" slots while it's running. If all the required slots for a module are // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { head: false, leftHand: false, rightHand: false, rightHandTrigger: false, leftHandTrigger: false, rightHandEquip: false, leftHandEquip: false, mouse: false }; this.laserVisibleStatus = [false, false, false, false]; this.laserLockStatus = [false, false, false, false]; this.slotsAreAvailableForPlugin = function (plugin) { for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { if (_this.activitySlots[plugin.parameters.activitySlots[i]]) { return false; // something is already using a slot which _this plugin requires } } return true; }; this.markSlots = function (plugin, pluginName) { for (var i = 0; i < plugin.parameters.activitySlots.length; i++) { _this.activitySlots[plugin.parameters.activitySlots[i]] = pluginName; } }; this.unmarkSlotsForPluginName = function (runningPluginName) { // this is used to free activity-slots when a plugin is deactivated while it's running. for (var activitySlot in _this.activitySlots) { if (activitySlot.hasOwnProperty(activitySlot) && _this.activitySlots[activitySlot] === runningPluginName) { _this.activitySlots[activitySlot] = false; } } }; this.runningPluginNames = {}; this.leftTriggerValue = 0; this.leftTriggerClicked = 0; this.leftTrackerClicked = false; // is leftTriggerClicked == 1 because a hand tracker set it? this.leftSecondaryValue = 0; this.rightTriggerValue = 0; this.rightTriggerClicked = 0; this.rightTrackerClicked = false; // is rightTriggerClicked == 1 because a hand tracker set it? this.rightSecondaryValue = 0; this.leftTriggerPress = function (value) { _this.leftTriggerValue = value; }; this.leftTriggerClick = function (value) { _this.leftTriggerClicked = value; }; this.rightTriggerPress = function (value) { _this.rightTriggerValue = value; }; this.rightTriggerClick = function (value) { _this.rightTriggerClicked = value; }; this.leftSecondaryPress = function (value) { _this.leftSecondaryValue = value; }; this.rightSecondaryPress = function (value) { _this.rightSecondaryValue = value; }; this.dataGatherers = {}; this.dataGatherers.leftControllerLocation = function () { return getControllerWorldLocation(controllerStandard.LeftHand, true); }; this.dataGatherers.rightControllerLocation = function () { return getControllerWorldLocation(controllerStandard.RightHand, true); }; this.updateTimings = function () { _this.intervalCount++; var thisInterval = Date.now(); var deltaTimeMsec = thisInterval - _this.lastInterval; var deltaTime = deltaTimeMsec / 1000; _this.lastInterval = thisInterval; _this.totalDelta += deltaTimeMsec; var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS); _this.totalVariance += variance; if (variance > 1) { _this.highVarianceCount++; } if (variance > 5) { _this.veryhighVarianceCount++; } return deltaTime; }; this.setIgnorePointerItems = function() { if (HMD.tabletID && HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; Pointers.setIgnoreItems(_this.leftPointer, _this.blocklist); Pointers.setIgnoreItems(_this.rightPointer, _this.blocklist); } }; this.checkForHandTrackingClick = function() { var pinchOnBelowDistance = 0.016; var pinchOffAboveDistance = 0.035; var leftIndexPose = Controller.getPoseValue(controllerStandard.LeftHandIndex4); var leftThumbPose = Controller.getPoseValue(controllerStandard.LeftHandThumb4); var leftThumbToIndexDistance = Vec3.distance(leftIndexPose.translation, leftThumbPose.translation); if (leftIndexPose.valid && leftThumbPose.valid && leftThumbToIndexDistance < pinchOnBelowDistance) { _this.leftTriggerClicked = 1; _this.leftTriggerValue = 1; _this.leftTrackerClicked = true; } else if (_this.leftTrackerClicked && leftThumbToIndexDistance > pinchOffAboveDistance) { _this.leftTriggerClicked = 0; _this.leftTriggerValue = 0; _this.leftTrackerClicked = false; } var rightIndexPose = Controller.getPoseValue(controllerStandard.RightHandIndex4); var rightThumbPose = Controller.getPoseValue(controllerStandard.RightHandThumb4); var rightThumbToIndexDistance = Vec3.distance(rightIndexPose.translation, rightThumbPose.translation); if (rightIndexPose.valid && rightThumbPose.valid && rightThumbToIndexDistance < pinchOnBelowDistance) { _this.rightTriggerClicked = 1; _this.rightTriggerValue = 1; _this.rightTrackerClicked = true; } else if (_this.rightTrackerClicked && rightThumbToIndexDistance > pinchOffAboveDistance) { _this.rightTriggerClicked = 0; _this.rightTriggerValue = 0; _this.rightTrackerClicked = false; } }; this.update = function () { try { _this.updateInternal(); } catch (e) { print(e); } Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); }; this.addDebugLine = function(line) { if (this.debugLines.length > 8) { this.debugLines.shift(); } this.debugLines.push(line); var debugPanelText = ""; this.debugLines.forEach(function(debugLine) { debugPanelText += debugLine + "\n"; }); Entities.editEntity(this.debugPanelID, { text: debugPanelText }); }; this.updateInternal = function () { if (PROFILE) { Script.beginProfileRange("dispatch.pre"); } var sensorScaleFactor = MyAvatar.sensorToWorldScale; var deltaTime = _this.updateTimings(); _this.setIgnorePointerItems(); if (controllerDispatcherPluginsNeedSort) { _this.orderedPluginNames = []; for (var pluginName in controllerDispatcherPlugins) { if (controllerDispatcherPlugins.hasOwnProperty(pluginName)) { _this.orderedPluginNames.push(pluginName); } } _this.orderedPluginNames.sort(function (a, b) { return controllerDispatcherPlugins[a].parameters.priority - controllerDispatcherPlugins[b].parameters.priority; }); controllerDispatcherPluginsNeedSort = false; } if (PROFILE) { Script.endProfileRange("dispatch.pre"); } if (PROFILE) { Script.beginProfileRange("dispatch.gather"); } var controllerLocations = [ _this.dataGatherers.leftControllerLocation(), _this.dataGatherers.rightControllerLocation() ]; // find 3d overlays/Local Entities near each hand var nearbyOverlayIDs = []; var h; //V8TODO: Overlays.findOverlays might not work here for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); // Tablet and mini-tablet must be within NEAR_TABLET_MAX_RADIUS in order to be grabbed. // Mini tablet can only be grabbed the hand it's displayed on. var tabletIndex = nearbyOverlays.indexOf(HMD.tabletID); var miniTabletIndex = nearbyOverlays.indexOf(HMD.miniTabletID); if (tabletIndex !== -1 || miniTabletIndex !== -1) { var closebyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_TABLET_MAX_RADIUS * sensorScaleFactor); // Assumes that the tablet and mini-tablet are not displayed at the same time. if (tabletIndex !== -1 && closebyOverlays.indexOf(HMD.tabletID) === -1) { nearbyOverlays.splice(tabletIndex, 1); } if (miniTabletIndex !== -1 && ((closebyOverlays.indexOf(HMD.miniTabletID) === -1) || h !== HMD.miniTabletHand)) { nearbyOverlays.splice(miniTabletIndex, 1); } } nearbyOverlays.sort(function (a, b) { var aPosition = Entities.getEntityProperties(a, ["position"]).position; var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); var bPosition = Entities.getEntityProperties(b, ["position"]).position; var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); nearbyOverlayIDs.push(nearbyOverlays); } else { nearbyOverlayIDs.push([]); } } // find entities near each hand var nearbyEntityProperties = [[], []]; var nearbyEntityPropertiesByID = {}; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { var controllerPosition = controllerLocations[h].position; var findRadius = NEAR_MAX_RADIUS * sensorScaleFactor; if (SHOW_GRAB_SPHERE) { if (this.grabSphereOverlays[h]) { Entities.editEntity(this.grabSphereOverlays[h], { "position": controllerLocations[h].position }); } else { var grabSphereSize = findRadius * 2; this.grabSphereOverlays[h] = Entities.addEntity({ "type": "Shape", "shape": "Sphere", "position": controllerLocations[h].position, "dimensions": { "x": grabSphereSize, "y": grabSphereSize, "z": grabSphereSize }, "color": { "red": 30, "green": 30, "blue": 255 }, "alpha": 0.3, "primitiveMode": "solid", "visible": true, "renderLayer": "front", "grab": { "grabbable": false } }, "local"); } } var nearbyEntityIDs = Entities.findEntities(controllerPosition, findRadius); for (var j = 0; j < nearbyEntityIDs.length; j++) { var entityID = nearbyEntityIDs[j]; var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); props.id = entityID; props.distance = Vec3.distance(props.position, controllerLocations[h].position); nearbyEntityPropertiesByID[entityID] = props; nearbyEntityProperties[h].push(props); } } } // Enable/disable controller raypicking depending on whether we are in HMD if (HMD.active) { Pointers.enablePointer(_this.leftPointer); Pointers.enablePointer(_this.rightPointer); Pointers.enablePointer(_this.leftHudPointer); Pointers.enablePointer(_this.rightHudPointer); Pointers.enablePointer(_this.mouseRayPointer); } else { Pointers.disablePointer(_this.leftPointer); Pointers.disablePointer(_this.rightPointer); Pointers.disablePointer(_this.leftHudPointer); Pointers.disablePointer(_this.rightHudPointer); Pointers.disablePointer(_this.mouseRayPointer); } // raypick for each controller var rayPicks = [ Pointers.getPrevPickResult(_this.leftPointer), Pointers.getPrevPickResult(_this.rightPointer) ]; var hudRayPicks = [ Pointers.getPrevPickResult(_this.leftHudPointer), Pointers.getPrevPickResult(_this.rightHudPointer) ]; var mouseRayPointer = Pointers.getPrevPickResult(_this.mouseRayPointer); // if the pickray hit something very nearby, put it into the nearby entities list for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { // XXX find a way to extract searchRay from samuel's stuff if (controllerLocations[h].valid) { rayPicks[h].searchRay = { origin: controllerLocations[h].position, direction: Quat.getUp(controllerLocations[h].orientation), length: 1000 }; if (rayPicks[h].type === Picks.INTERSECTED_ENTITY) { // XXX check to make sure this one isn't already in nearbyEntityProperties? if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS * sensorScaleFactor) { var nearEntityID = rayPicks[h].objectID; var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES); nearbyProps.id = nearEntityID; nearbyProps.distance = rayPicks[h].distance; nearbyEntityPropertiesByID[nearEntityID] = nearbyProps; nearbyEntityProperties[h].push(nearbyProps); } } // sort by distance from each hand nearbyEntityProperties[h].sort(function (a, b) { return a.distance - b.distance; }); } } // sometimes, during a HMD snap-turn, an equipped or held item wont be near // the hand when the findEntities is done. Gather up any hand-children here. for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { var handChildrenIDs = findHandChildEntities(h); handChildrenIDs.forEach(function (handChildID) { if (handChildID in nearbyEntityPropertiesByID) { return; } var props = Entities.getEntityProperties(handChildID, DISPATCHER_PROPERTIES); props.id = handChildID; nearbyEntityPropertiesByID[handChildID] = props; }); } // also make sure we have the properties from the current module's target for (var tIDRunningPluginName in _this.runningPluginNames) { if (_this.runningPluginNames.hasOwnProperty(tIDRunningPluginName)) { var targetIDs = _this.targetIDs[tIDRunningPluginName]; if (targetIDs) { for (var k = 0; k < targetIDs.length; k++) { var targetID = targetIDs[k]; if (!nearbyEntityPropertiesByID[targetID]) { var targetProps = Entities.getEntityProperties(targetID, DISPATCHER_PROPERTIES); targetProps.id = targetID; nearbyEntityPropertiesByID[targetID] = targetProps; } } } } } // TODO: These are not used currently, but have severe impact on performace. They can be re-enabled when we have OpenXR support // check for hand-tracking "click" //_this.checkForHandTrackingClick(); // bundle up all the data about the current situation var controllerData = { triggerValues: [_this.leftTriggerValue, _this.rightTriggerValue], triggerClicks: [_this.leftTriggerClicked, _this.rightTriggerClicked], secondaryValues: [_this.leftSecondaryValue, _this.rightSecondaryValue], controllerLocations: controllerLocations, nearbyEntityProperties: nearbyEntityProperties, nearbyEntityPropertiesByID: nearbyEntityPropertiesByID, nearbyOverlayIDs: nearbyOverlayIDs, rayPicks: rayPicks, hudRayPicks: hudRayPicks, mouseRayPointer: mouseRayPointer }; if (PROFILE) { Script.endProfileRange("dispatch.gather"); } if (PROFILE) { Script.beginProfileRange("dispatch.isReady"); } // check for plugins that would like to start. ask in order of increasing priority value for (var pluginIndex = 0; pluginIndex < _this.orderedPluginNames.length; pluginIndex++) { var orderedPluginName = _this.orderedPluginNames[pluginIndex]; var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { if (PROFILE) { Script.beginProfileRange("dispatch.isReady." + orderedPluginName); } var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the // activity-slots which this plugin consumes as "in use" _this.runningPluginNames[orderedPluginName] = true; _this.markSlots(candidatePlugin, orderedPluginName); _this.pointerManager.makePointerVisible(candidatePlugin.parameters.handLaser); if (DEBUG) { _this.addDebugLine("running " + orderedPluginName); } } if (PROFILE) { Script.endProfileRange("dispatch.isReady." + orderedPluginName); } } } if (PROFILE) { Script.endProfileRange("dispatch.isReady"); } if (PROFILE) { Script.beginProfileRange("dispatch.run"); } // give time to running plugins