mirror of
https://github.com/overte-org/overte.git
synced 2025-04-26 09:56:15 +02:00
341 lines
14 KiB
JavaScript
341 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
// controllerDispatcher.js
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
|
|
/*jslint bitwise: true */
|
|
|
|
/* global Script, Entities, Overlays, Controller, Vec3, Quat, getControllerWorldLocation, RayPick,
|
|
controllerDispatcherPlugins, controllerDispatcherPluginsNeedSort, entityIsGrabbable,
|
|
LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE
|
|
*/
|
|
|
|
controllerDispatcherPlugins = {};
|
|
controllerDispatcherPluginsNeedSort = false;
|
|
|
|
Script.include("/~/system/libraries/utils.js");
|
|
Script.include("/~/system/libraries/controllers.js");
|
|
Script.include("/~/system/controllers/controllerDispatcherUtils.js");
|
|
|
|
(function() {
|
|
var _this = this;
|
|
|
|
var NEAR_MIN_RADIUS = 0.1;
|
|
var NEAR_MAX_RADIUS = 1.0;
|
|
|
|
var DISPATCHER_PROPERTIES = [
|
|
"position",
|
|
"registrationPoint",
|
|
"rotation",
|
|
"gravity",
|
|
"collidesWith",
|
|
"dynamic",
|
|
"collisionless",
|
|
"locked",
|
|
"name",
|
|
"shapeType",
|
|
"parentID",
|
|
"parentJointIndex",
|
|
"density",
|
|
"dimensions",
|
|
"userData"
|
|
];
|
|
|
|
|
|
var TARGET_UPDATE_HZ = 60; // 50hz good enough, but we're using update
|
|
var BASIC_TIMER_INTERVAL_MS = 1000 / TARGET_UPDATE_HZ;
|
|
var lastInterval = Date.now();
|
|
var intervalCount = 0;
|
|
var totalDelta = 0;
|
|
var totalVariance = 0;
|
|
var highVarianceCount = 0;
|
|
var veryhighVarianceCount = 0;
|
|
|
|
// 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 = {
|
|
leftHand: false,
|
|
rightHand: 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, used) {
|
|
for (var i = 0; i < plugin.parameters.activitySlots.length; i++) {
|
|
_this.activitySlots[plugin.parameters.activitySlots[i]] = used;
|
|
}
|
|
};
|
|
|
|
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.rightTriggerValue = 0;
|
|
this.rightTriggerClicked = 0;
|
|
this.leftSecondaryValue = 0;
|
|
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(Controller.Standard.LeftHand, true);
|
|
};
|
|
this.dataGatherers.rightControllerLocation = function () {
|
|
return getControllerWorldLocation(Controller.Standard.RightHand, true);
|
|
};
|
|
|
|
this.updateTimings = function () {
|
|
intervalCount++;
|
|
var thisInterval = Date.now();
|
|
var deltaTimeMsec = thisInterval - lastInterval;
|
|
var deltaTime = deltaTimeMsec / 1000;
|
|
lastInterval = thisInterval;
|
|
|
|
totalDelta += deltaTimeMsec;
|
|
|
|
var variance = Math.abs(deltaTimeMsec - BASIC_TIMER_INTERVAL_MS);
|
|
totalVariance += variance;
|
|
|
|
if (variance > 1) {
|
|
highVarianceCount++;
|
|
}
|
|
|
|
if (variance > 5) {
|
|
veryhighVarianceCount++;
|
|
}
|
|
|
|
return deltaTime;
|
|
};
|
|
|
|
this.update = function () {
|
|
var deltaTime = this.updateTimings();
|
|
|
|
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;
|
|
});
|
|
|
|
// print("controllerDispatcher -- new plugin order: " + JSON.stringify(this.orderedPluginNames));
|
|
var output = "controllerDispatcher -- new plugin order: ";
|
|
for (var k = 0; k < this.orderedPluginNames.length; k++) {
|
|
var dbgPluginName = this.orderedPluginNames[k];
|
|
var priority = controllerDispatcherPlugins[dbgPluginName].parameters.priority;
|
|
output += dbgPluginName + ":" + priority;
|
|
if (k + 1 < this.orderedPluginNames.length) {
|
|
output += ", ";
|
|
}
|
|
}
|
|
print(output);
|
|
|
|
controllerDispatcherPluginsNeedSort = false;
|
|
}
|
|
|
|
var controllerLocations = [_this.dataGatherers.leftControllerLocation(),
|
|
_this.dataGatherers.rightControllerLocation()];
|
|
|
|
// find 3d overlays near each hand
|
|
var nearbyOverlayIDs = [];
|
|
var h;
|
|
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
|
// todo: check controllerLocations[h].valid
|
|
var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS);
|
|
nearbyOverlays.sort(function (a, b) {
|
|
var aPosition = Overlays.getProperty(a, "position");
|
|
var aDistance = Vec3.distance(aPosition, controllerLocations[h].position);
|
|
var bPosition = Overlays.getProperty(b, "position");
|
|
var bDistance = Vec3.distance(bPosition, controllerLocations[h].position);
|
|
return aDistance - bDistance;
|
|
});
|
|
nearbyOverlayIDs.push(nearbyOverlays);
|
|
}
|
|
|
|
// find entities near each hand
|
|
var nearbyEntityProperties = [[], []];
|
|
var nearbyEntityPropertiesByID = {};
|
|
for (h = LEFT_HAND; h <= RIGHT_HAND; h++) {
|
|
// todo: check controllerLocations[h].valid
|
|
var controllerPosition = controllerLocations[h].position;
|
|
var nearbyEntityIDs = Entities.findEntities(controllerPosition, NEAR_MAX_RADIUS);
|
|
for (var j = 0; j < nearbyEntityIDs.length; j++) {
|
|
var entityID = nearbyEntityIDs[j];
|
|
var props = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
|
props.id = entityID;
|
|
nearbyEntityPropertiesByID[entityID] = props;
|
|
nearbyEntityProperties[h].push(props);
|
|
}
|
|
}
|
|
|
|
// raypick for each controller
|
|
var rayPicks = [
|
|
RayPick.getPrevRayPickResult(_this.leftControllerRayPick),
|
|
RayPick.getPrevRayPickResult(_this.rightControllerRayPick)
|
|
];
|
|
// 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
|
|
rayPicks[h].searchRay = {
|
|
origin: controllerLocations[h].position,
|
|
direction: Quat.getUp(controllerLocations[h].orientation),
|
|
length: 1000
|
|
};
|
|
|
|
if (rayPicks[h].type == RayPick.INTERSECTED_ENTITY) {
|
|
// XXX check to make sure this one isn't already in nearbyEntityProperties?
|
|
if (rayPicks[h].distance < NEAR_GRAB_PICK_RADIUS) {
|
|
var nearEntityID = rayPicks[h].objectID;
|
|
var nearbyProps = Entities.getEntityProperties(nearEntityID, DISPATCHER_PROPERTIES);
|
|
nearbyProps.id = nearEntityID;
|
|
nearbyEntityPropertiesByID[nearEntityID] = nearbyProps;
|
|
nearbyEntityProperties[h].push(nearbyProps);
|
|
}
|
|
}
|
|
|
|
// sort by distance from each hand
|
|
nearbyEntityProperties[h].sort(function (a, b) {
|
|
var aDistance = Vec3.distance(a.position, controllerLocations[h].position);
|
|
var bDistance = Vec3.distance(b.position, controllerLocations[h].position);
|
|
return aDistance - bDistance;
|
|
});
|
|
}
|
|
|
|
// 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
|
|
};
|
|
|
|
// 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)) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// give time to running plugins
|
|
for (var runningPluginName in _this.runningPluginNames) {
|
|
if (_this.runningPluginNames.hasOwnProperty(runningPluginName)) {
|
|
var plugin = controllerDispatcherPlugins[runningPluginName];
|
|
if (!plugin) {
|
|
// plugin was deactivated while running. find the activity-slots it was using and make
|
|
// them available.
|
|
delete _this.runningPluginNames[runningPluginName];
|
|
_this.unmarkSlotsForPluginName(runningPluginName);
|
|
} else {
|
|
var runningness = plugin.run(controllerData, deltaTime);
|
|
if (!runningness.active) {
|
|
// plugin is finished running, for now. remove it from the list
|
|
// of running plugins and mark its activity-slots as "not in use"
|
|
delete _this.runningPluginNames[runningPluginName];
|
|
_this.markSlots(plugin, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var MAPPING_NAME = "com.highfidelity.controllerDispatcher";
|
|
var mapping = Controller.newMapping(MAPPING_NAME);
|
|
mapping.from([Controller.Standard.RT]).peek().to(_this.rightTriggerPress);
|
|
mapping.from([Controller.Standard.RTClick]).peek().to(_this.rightTriggerClick);
|
|
mapping.from([Controller.Standard.LT]).peek().to(_this.leftTriggerPress);
|
|
mapping.from([Controller.Standard.LTClick]).peek().to(_this.leftTriggerClick);
|
|
|
|
mapping.from([Controller.Standard.RB]).peek().to(_this.rightSecondaryPress);
|
|
mapping.from([Controller.Standard.LB]).peek().to(_this.leftSecondaryPress);
|
|
mapping.from([Controller.Standard.LeftGrip]).peek().to(_this.leftSecondaryPress);
|
|
mapping.from([Controller.Standard.RightGrip]).peek().to(_this.rightSecondaryPress);
|
|
|
|
Controller.enableMapping(MAPPING_NAME);
|
|
|
|
|
|
this.mouseRayPick = RayPick.createRayPick({
|
|
joint: "Mouse",
|
|
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
|
|
enabled: true
|
|
});
|
|
|
|
this.leftControllerRayPick = RayPick.createRayPick({
|
|
joint: "_CONTROLLER_LEFTHAND",
|
|
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
|
|
enabled: true,
|
|
maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE
|
|
});
|
|
this.rightControllerRayPick = RayPick.createRayPick({
|
|
joint: "_CONTROLLER_RIGHTHAND",
|
|
filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS,
|
|
enabled: true,
|
|
maxDistance: DEFAULT_SEARCH_SPHERE_DISTANCE
|
|
});
|
|
|
|
|
|
this.cleanup = function () {
|
|
Script.update.disconnect(_this.update);
|
|
Controller.disableMapping(MAPPING_NAME);
|
|
// RayPick.removeRayPick(_this.mouseRayPick);
|
|
RayPick.removeRayPick(_this.leftControllerRayPick);
|
|
RayPick.removeRayPick(_this.rightControllerRayPick);
|
|
};
|
|
|
|
Script.scriptEnding.connect(this.cleanup);
|
|
Script.update.connect(this.update);
|
|
}());
|