Newer
Older
reroad-test / 2020-ryusei / aframe-master / src / utils / tracked-controls.js
@ryusei ryusei on 22 Oct 2020 8 KB パノラマ表示
var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS;
var AXIS_LABELS = ['x', 'y', 'z', 'w'];
var NUM_HANDS = 2;  // Number of hands in a pair. Should always be 2.

/**
 * Called on controller component `.play` handlers.
 * Check if controller matches parameters and inject tracked-controls component.
 * Handle event listeners.
 * Generate controllerconnected or controllerdisconnected events.
 *
 * @param {object} component - Tracked controls component.
 * @param {object} idPrefix - Prefix to match in gamepad id if any.
 * @param {object} queryObject - Map of values to match.
 */
module.exports.checkControllerPresentAndSetup = function (component, idPrefix, queryObject) {
  var el = component.el;
  var controller;
  var hasWebXR = el.sceneEl.hasWebXR;
  var isControllerPresent = hasWebXR ? isControllerPresentWebXR : isControllerPresentWebVR;
  var isPresent;

  controller = isControllerPresent(component, idPrefix, queryObject);
  isPresent = !!controller;

  // If component was previously paused and now playing, re-add event listeners.
  // Handle the event listeners here since this helper method is control of calling
  // `.addEventListeners` and `.removeEventListeners`.
  if (component.controllerPresent && !component.controllerEventsActive && !hasWebXR) {
    component.addEventListeners();
  }

  // Nothing changed, no need to do anything.
  if (isPresent === component.controllerPresent) { return isPresent; }

  component.controllerPresent = isPresent;

  // Update controller presence.
  if (isPresent) {
    component.addEventListeners();
    component.injectTrackedControls();
    el.emit('controllerconnected', {name: component.name, component: component});
  } else {
    component.removeEventListeners();
    el.emit('controllerdisconnected', {name: component.name, component: component});
  }
};

/**
 * Enumerate controller (that have pose) and check if they match parameters for WebVR
 *
 * @param {object} component - Tracked controls component.
 * @param {object} idPrefix - Prefix to match in gamepad id if any.
 * @param {object} queryObject - Map of values to match.
 */
function isControllerPresentWebVR (component, idPrefix, queryObject) {
  var gamepads;
  var sceneEl = component.el.sceneEl;
  var trackedControlsSystem;
  var filterControllerIndex = queryObject.index || 0;

  if (!idPrefix) { return false; }

  trackedControlsSystem = sceneEl && sceneEl.systems['tracked-controls-webvr'];
  if (!trackedControlsSystem) { return false; }

  gamepads = trackedControlsSystem.controllers;
  if (!gamepads.length) { return false; }

  return !!findMatchingControllerWebVR(gamepads, null, idPrefix, queryObject.hand,
                                  filterControllerIndex);
}

/**
 *
 * @param {object} component - Tracked controls component.
 */
function isControllerPresentWebXR (component, id, queryObject) {
  var controllers;
  var sceneEl = component.el.sceneEl;
  var trackedControlsSystem = sceneEl && sceneEl.systems['tracked-controls-webxr'];
  if (!trackedControlsSystem) { return false; }

  controllers = trackedControlsSystem.controllers;
  if (!controllers || !controllers.length) { return false; }

  return findMatchingControllerWebXR(
    controllers, id,
    queryObject.hand, queryObject.index, queryObject.iterateControllerProfiles, queryObject.handTracking);
}

module.exports.isControllerPresentWebVR = isControllerPresentWebVR;
module.exports.isControllerPresentWebXR = isControllerPresentWebXR;

/**
 * Walk through the given controllers to find any where the device ID equals
 * filterIdExact, or startsWith filterIdPrefix.
 * A controller where this considered true is considered a 'match'.
 *
 * For each matching controller:
 *   If filterHand is set, and the controller:
 *     is handed, we further verify that controller.hand equals filterHand.
 *     is unhanded (controller.hand is ''), we skip until we have found a
 *     number of matching controllers that equals filterControllerIndex
 *   If filterHand is not set, we skip until we have found the nth matching
 *   controller, where n equals filterControllerIndex
 *
 * The method should be called with one of: [filterIdExact, filterIdPrefix] AND
 * one or both of: [filterHand, filterControllerIndex]
 *
 * @param {object} controllers - Array of gamepads to search
 * @param {string} filterIdExact - If set, used to find controllers with id === this value
 * @param {string} filterIdPrefix - If set, used to find controllers with id startsWith this value
 * @param {object} filterHand - If set, further filters controllers with matching 'hand' property
 * @param {object} filterControllerIndex - Find the nth matching controller,
 * where n equals filterControllerIndex. defaults to 0.
 */
function findMatchingControllerWebVR (controllers, filterIdExact, filterIdPrefix, filterHand,
                                 filterControllerIndex) {
  var controller;
  var i;
  var matchingControllerOccurence = 0;
  var targetControllerMatch = filterControllerIndex >= 0 ? filterControllerIndex : 0;

  for (i = 0; i < controllers.length; i++) {
    controller = controllers[i];

    // Determine if the controller ID matches our criteria.
    if (filterIdPrefix && !controller.id.startsWith(filterIdPrefix)) {
      continue;
    }

    if (!filterIdPrefix && controller.id !== filterIdExact) { continue; }

    // If the hand filter and controller handedness are defined we compare them.
    if (filterHand && controller.hand && filterHand !== controller.hand) { continue; }

    // If we have detected an unhanded controller and the component was asking
    // for a particular hand, we need to treat the controllers in the array as
    // pairs of controllers. This effectively means that we need to skip
    // NUM_HANDS matches for each controller number, instead of 1.
    if (filterHand && !controller.hand) {
      targetControllerMatch = NUM_HANDS * filterControllerIndex + ((filterHand === DEFAULT_HANDEDNESS) ? 0 : 1);
    } else {
      return controller;
    }

    // We are looking for the nth occurence of a matching controller
    // (n equals targetControllerMatch).
    if (matchingControllerOccurence === targetControllerMatch) { return controller; }
    ++matchingControllerOccurence;
  }
  return undefined;
}

function findMatchingControllerWebXR (controllers, idPrefix, handedness, index, iterateProfiles, handTracking) {
  var i;
  var j;
  var controller;
  var controllerMatch = false;
  var controllerHasHandedness;
  var profiles;
  for (i = 0; i < controllers.length; i++) {
    controller = controllers[i];
    profiles = controller.profiles;
    if (handTracking) {
      controllerMatch = controller.hand;
    } else {
      if (iterateProfiles) {
        for (j = 0; j < profiles.length; j++) {
          controllerMatch = profiles[j].startsWith(idPrefix);
          if (controllerMatch) { break; }
        }
      } else {
        controllerMatch = profiles.length > 0 && profiles[0].startsWith(idPrefix);
      }
    }
    if (!controllerMatch) { continue; }
    // Vive controllers are assigned handedness at runtime and it might not be always available.
    controllerHasHandedness = controller.handedness === 'right' || controller.handedness === 'left';
    if (controllerHasHandedness) {
      if (controller.handedness === handedness) { return controllers[i]; }
    } else { // Fallback to index if controller has no handedness.
      if ((i === index)) { return controllers[i]; }
    }
  }
  return undefined;
}

module.exports.findMatchingControllerWebVR = findMatchingControllerWebVR;
module.exports.findMatchingControllerWebXR = findMatchingControllerWebXR;

/**
 * Emit specific `moved` event(s) if axes changed based on original axismoved event.
 *
 * @param {object} component - Controller component in use.
 * @param {array} axesMapping - For example `{thumbstick: [0, 1]}`.
 * @param {object} evt - Event to process.
 */
module.exports.emitIfAxesChanged = function (component, axesMapping, evt) {
  var axes;
  var buttonType;
  var changed;
  var detail;
  var j;

  for (buttonType in axesMapping) {
    axes = axesMapping[buttonType];

    changed = false;
    for (j = 0; j < axes.length; j++) {
      if (evt.detail.changed[axes[j]]) { changed = true; }
    }

    if (!changed) { continue; }

    // Axis has changed. Emit the specific moved event with axis values in detail.
    detail = {};
    for (j = 0; j < axes.length; j++) {
      detail[AXIS_LABELS[j]] = evt.detail.axis[axes[j]];
    }
    component.el.emit(buttonType + 'moved', detail);
  }
};

/**
 * Handle a button event and reemits the events.
 *
 * @param {string} id - id of the button.
 * @param {string} evtName - name of the reemitted event
 * @param {object} component - reference to the component
 * @param {string} hand - handedness of the controller: left or right.
 */
module.exports.onButtonEvent = function (id, evtName, component, hand) {
  var mapping = hand ? component.mapping[hand] : component.mapping;
  var buttonName = mapping.buttons[id];
  component.el.emit(buttonName + evtName);
  if (component.updateModel) {
    component.updateModel(buttonName, evtName);
  }
};