diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a2a832afe0..ddc034801b 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -137,8 +137,9 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY}) endif () - target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) - + if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE") + target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) + endif () endif () endforeach() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e0c03584bf..0e63c3dfb2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -182,7 +182,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()) { - // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -378,12 +377,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _particleEditSender.setPacketsPerSecond(3000); // super high!! _entityEditSender.setPacketsPerSecond(3000); // super high!! - // Set the sixense filtering - _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); - - // Set hand controller velocity filtering - _sixenseManager.setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter)); - checkVersion(); _overlays.init(_glWidget); // do this before scripts load @@ -1481,7 +1474,7 @@ void Application::setRenderVoxels(bool voxelRender) { } void Application::setLowVelocityFilter(bool lowVelocityFilter) { - getSixenseManager()->setLowVelocityFilter(lowVelocityFilter); + SixenseManager::getInstance().setLowVelocityFilter(lowVelocityFilter); } void Application::doKillLocalVoxels() { @@ -1802,6 +1795,17 @@ void Application::init() { if (urlIndex != -1) { AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1)); } + +#ifdef __APPLE__ + if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) { + // on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash + // if hydra support is temporarily not required + Menu::getInstance()->toggleSixense(true); + } +#else + // setup sixense + Menu::getInstance()->toggleSixense(true); +#endif // initialize our face trackers after loading the menu settings _faceshift.init(); @@ -2178,7 +2182,7 @@ void Application::update(float deltaTime) { DeviceTracker::updateAll(); updateFaceshift(); updateVisage(); - _sixenseManager.update(deltaTime); + SixenseManager::getInstance().update(deltaTime); JoystickScriptingInterface::getInstance().update(); _prioVR.update(deltaTime); diff --git a/interface/src/Application.h b/interface/src/Application.h index f8710bae7b..e50039a20e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -219,7 +219,6 @@ public: DdeFaceTracker* getDDE() { return &_dde; } CaraFaceTracker* getCara() { return &_cara; } FaceTracker* getActiveFaceTracker(); - SixenseManager* getSixenseManager() { return &_sixenseManager; } PrioVR* getPrioVR() { return &_prioVR; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } QUndoStack* getUndoStack() { return &_undoStack; } @@ -510,7 +509,6 @@ private: CaraFaceTracker _cara; DdeFaceTracker _dde; - SixenseManager _sixenseManager; PrioVR _prioVR; Camera _myCamera; // My view onto the world diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8f7b2be43b..1da85aad16 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -431,11 +431,18 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); +#ifdef __APPLE__ + addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, + MenuOption::SixenseEnabled, + 0, true, + this, + SLOT(toggleSixense(bool))); +#endif addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::FilterSixense, 0, true, - appInstance->getSixenseManager(), + &SixenseManager::getInstance(), SLOT(setFilter(bool))); addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::LowVelocityFilter, @@ -1134,6 +1141,18 @@ void Menu::editAnimations() { } } +void Menu::toggleSixense(bool shouldEnable) { + SixenseManager& sixenseManager = SixenseManager::getInstance(); + + if (shouldEnable && !sixenseManager.isInitialized()) { + sixenseManager.initialize(); + sixenseManager.setFilter(isOptionChecked(MenuOption::FilterSixense)); + sixenseManager.setLowVelocityFilter(isOptionChecked(MenuOption::LowVelocityFilter)); + } + + sixenseManager.setIsEnabled(shouldEnable); +} + void Menu::changePrivateKey() { // setup the dialog QInputDialog privateKeyDialog(Application::getInstance()->getWindow()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b267ab8b2c..c47c04e177 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -186,6 +186,7 @@ public slots: void pasteToVoxel(); void toggleLoginMenuItem(); + void toggleSixense(bool shouldEnable); QMenu* addMenu(const QString& menuName); void removeMenu(const QString& menuName); @@ -448,6 +449,7 @@ namespace MenuOption { const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; const QString SimpleShadows = "Simple"; + const QString SixenseEnabled = "Enable Hydra Support"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString SixenseLasers = "Enable Sixense UI Lasers"; const QString StandOnNearbyFloors = "Stand on nearby floors"; diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 417b0619f8..7823d6e840 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -30,23 +30,28 @@ const int CALIBRATION_STATE_COMPLETE = 4; const float NECK_X = 0.25f; // meters const float NECK_Y = 0.3f; // meters const float NECK_Z = 0.3f; // meters + +#ifdef __APPLE__ +typedef int (*SixenseBaseFunction)(); +typedef int (*SixenseTakeIntFunction)(int); +typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*); #endif -SixenseManager::SixenseManager() { -#ifdef HAVE_SIXENSE - _lastMovement = 0; - _amountMoved = glm::vec3(0.0f); - _lowVelocityFilter = false; - - _calibrationState = CALIBRATION_STATE_IDLE; - // By default we assume the _neckBase (in orb frame) is as high above the orb - // as the "torso" is below it. - _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z); - - sixenseInit(); - #endif - _hydrasConnected = false; + +SixenseManager& SixenseManager::getInstance() { + static SixenseManager sharedInstance; + return sharedInstance; +} + +SixenseManager::SixenseManager() : +#ifdef __APPLE__ + _sixenseLibrary(NULL), +#endif + _isInitialized(false), + _isEnabled(true), + _hydrasConnected(false) +{ _triggerPressed[0] = false; _bumperPressed[0] = false; _oldX[0] = -1; @@ -58,155 +63,227 @@ SixenseManager::SixenseManager() { } SixenseManager::~SixenseManager() { +#ifdef HAVE_SIXENSE_ + + if (_isInitialized) { +#ifdef __APPLE__ + SixenseBaseFunction sixenseExit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseExit"); +#endif + + sixenseExit(); + } + +#ifdef __APPLE__ + delete _sixenseLibrary; +#endif + +#endif +} + +void SixenseManager::initialize() { #ifdef HAVE_SIXENSE - sixenseExit(); + + if (!_isInitialized) { + _lastMovement = 0; + _amountMoved = glm::vec3(0.0f); + _lowVelocityFilter = false; + + _calibrationState = CALIBRATION_STATE_IDLE; + // By default we assume the _neckBase (in orb frame) is as high above the orb + // as the "torso" is below it. + _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z); + +#ifdef __APPLE__ + + if (!_sixenseLibrary) { + const QString SIXENSE_LIBRARY_NAME = "libsixense_x64.dylib"; + _sixenseLibrary = new QLibrary(SIXENSE_LIBRARY_NAME); + } + + SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit"); +#endif + + sixenseInit(); + + _isInitialized = true; + } + #endif } void SixenseManager::setFilter(bool filter) { #ifdef HAVE_SIXENSE - if (filter) { - sixenseSetFilterEnabled(1); - } else { - sixenseSetFilterEnabled(0); + + if (_isInitialized) { +#ifdef __APPLE__ + SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled"); +#endif + + if (filter) { + sixenseSetFilterEnabled(1); + } else { + sixenseSetFilterEnabled(0); + } } + #endif } void SixenseManager::update(float deltaTime) { #ifdef HAVE_SIXENSE - // if the controllers haven't been moved in a while, disable - const unsigned int MOVEMENT_DISABLE_SECONDS = 3; - if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) { - Hand* hand = Application::getInstance()->getAvatar()->getHand(); - for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) { - it->setActive(false); - } - _lastMovement = usecTimestampNow(); - } - - if (sixenseGetNumActiveControllers() == 0) { - _hydrasConnected = false; - return; - } - - PerformanceTimer perfTimer("sixense"); - if (!_hydrasConnected) { - _hydrasConnected = true; - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); - } - MyAvatar* avatar = Application::getInstance()->getAvatar(); - Hand* hand = avatar->getHand(); - - int maxControllers = sixenseGetMaxControllers(); - - // we only support two controllers - sixenseControllerData controllers[2]; - - int numActiveControllers = 0; - for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) { - if (!sixenseIsControllerEnabled(i)) { - continue; - } - sixenseControllerData* data = controllers + numActiveControllers; - ++numActiveControllers; - sixenseGetNewestData(i, data); - - // Set palm position and normal based on Hydra position/orientation - - // Either find a palm matching the sixense controller, or make a new one - PalmData* palm; - bool foundHand = false; - for (size_t j = 0; j < hand->getNumPalms(); j++) { - if (hand->getPalms()[j].getSixenseID() == data->controller_index) { - palm = &(hand->getPalms()[j]); - foundHand = true; + if (_isInitialized && _isEnabled) { + // if the controllers haven't been moved in a while, disable + const unsigned int MOVEMENT_DISABLE_SECONDS = 3; + if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) { + Hand* hand = Application::getInstance()->getAvatar()->getHand(); + for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) { + it->setActive(false); } - } - if (!foundHand) { - PalmData newPalm(hand); - hand->getPalms().push_back(newPalm); - palm = &(hand->getPalms()[hand->getNumPalms() - 1]); - palm->setSixenseID(data->controller_index); - qDebug("Found new Sixense controller, ID %i", data->controller_index); - } - - palm->setActive(true); - - // Read controller buttons and joystick into the hand - palm->setControllerButtons(data->buttons); - palm->setTrigger(data->trigger); - palm->setJoystick(data->joystick_x, data->joystick_y); - - - // Emulate the mouse so we can use scripts - if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { - emulateMouse(palm, numActiveControllers - 1); - } - - // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. - glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); - position *= METERS_PER_MILLIMETER; - - // Transform the measured position into body frame. - glm::vec3 neck = _neckBase; - // Zeroing y component of the "neck" effectively raises the measured position a little bit. - neck.y = 0.f; - position = _orbRotation * (position - neck); - - // Rotation of Palm - glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]); - rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation; - - // Compute current velocity from position change - glm::vec3 rawVelocity; - if (deltaTime > 0.f) { - rawVelocity = (position - palm->getRawPosition()) / deltaTime; - } else { - rawVelocity = glm::vec3(0.0f); - } - palm->setRawVelocity(rawVelocity); // meters/sec - - // adjustment for hydra controllers fit into hands - float sign = (i == 0) ? -1.0f : 1.0f; - rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f)); - - if (_lowVelocityFilter) { - // Use a velocity sensitive filter to damp small motions and preserve large ones with - // no latency. - float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); - position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter); - rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter); - palm->setRawPosition(position); - palm->setRawRotation(rotation); - } else { - palm->setRawPosition(position); - palm->setRawRotation(rotation); - } - - // use the velocity to determine whether there's any movement (if the hand isn't new) - const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f; - _amountMoved += rawVelocity * deltaTime; - if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) { _lastMovement = usecTimestampNow(); - _amountMoved = glm::vec3(0.0f); } - // Store the one fingertip in the palm structure so we can track velocity - const float FINGER_LENGTH = 0.3f; // meters - const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); - const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; - glm::vec3 oldTipPosition = palm->getTipRawPosition(); - if (deltaTime > 0.f) { - palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime); - } else { - palm->setTipVelocity(glm::vec3(0.f)); +#ifdef __APPLE__ + SixenseBaseFunction sixenseGetNumActiveControllers = + (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers"); +#endif + + if (sixenseGetNumActiveControllers() == 0) { + _hydrasConnected = false; + return; + } + + PerformanceTimer perfTimer("sixense"); + if (!_hydrasConnected) { + _hydrasConnected = true; + UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); + } + MyAvatar* avatar = Application::getInstance()->getAvatar(); + Hand* hand = avatar->getHand(); + +#ifdef __APPLE__ + SixenseBaseFunction sixenseGetMaxControllers = + (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers"); +#endif + + int maxControllers = sixenseGetMaxControllers(); + + // we only support two controllers + sixenseControllerData controllers[2]; + +#ifdef __APPLE__ + SixenseTakeIntFunction sixenseIsControllerEnabled = + (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled"); + + SixenseTakeIntAndSixenseControllerData sixenseGetNewestData = + (SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData"); +#endif + + int numActiveControllers = 0; + for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) { + if (!sixenseIsControllerEnabled(i)) { + continue; + } + sixenseControllerData* data = controllers + numActiveControllers; + ++numActiveControllers; + sixenseGetNewestData(i, data); + + // Set palm position and normal based on Hydra position/orientation + + // Either find a palm matching the sixense controller, or make a new one + PalmData* palm; + bool foundHand = false; + for (size_t j = 0; j < hand->getNumPalms(); j++) { + if (hand->getPalms()[j].getSixenseID() == data->controller_index) { + palm = &(hand->getPalms()[j]); + foundHand = true; + } + } + if (!foundHand) { + PalmData newPalm(hand); + hand->getPalms().push_back(newPalm); + palm = &(hand->getPalms()[hand->getNumPalms() - 1]); + palm->setSixenseID(data->controller_index); + qDebug("Found new Sixense controller, ID %i", data->controller_index); + } + + palm->setActive(true); + + // Read controller buttons and joystick into the hand + palm->setControllerButtons(data->buttons); + palm->setTrigger(data->trigger); + palm->setJoystick(data->joystick_x, data->joystick_y); + + + // Emulate the mouse so we can use scripts + if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { + emulateMouse(palm, numActiveControllers - 1); + } + + // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. + glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); + position *= METERS_PER_MILLIMETER; + + // Transform the measured position into body frame. + glm::vec3 neck = _neckBase; + // Zeroing y component of the "neck" effectively raises the measured position a little bit. + neck.y = 0.f; + position = _orbRotation * (position - neck); + + // Rotation of Palm + glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]); + rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation; + + // Compute current velocity from position change + glm::vec3 rawVelocity; + if (deltaTime > 0.f) { + rawVelocity = (position - palm->getRawPosition()) / deltaTime; + } else { + rawVelocity = glm::vec3(0.0f); + } + palm->setRawVelocity(rawVelocity); // meters/sec + + // adjustment for hydra controllers fit into hands + float sign = (i == 0) ? -1.0f : 1.0f; + rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f)); + + if (_lowVelocityFilter) { + // Use a velocity sensitive filter to damp small motions and preserve large ones with + // no latency. + float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); + position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter); + rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter); + palm->setRawPosition(position); + palm->setRawRotation(rotation); + } else { + palm->setRawPosition(position); + palm->setRawRotation(rotation); + } + + // use the velocity to determine whether there's any movement (if the hand isn't new) + const float MOVEMENT_DISTANCE_THRESHOLD = 0.003f; + _amountMoved += rawVelocity * deltaTime; + if (glm::length(_amountMoved) > MOVEMENT_DISTANCE_THRESHOLD && foundHand) { + _lastMovement = usecTimestampNow(); + _amountMoved = glm::vec3(0.0f); + } + + // Store the one fingertip in the palm structure so we can track velocity + const float FINGER_LENGTH = 0.3f; // meters + const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); + const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; + glm::vec3 oldTipPosition = palm->getTipRawPosition(); + if (deltaTime > 0.f) { + palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime); + } else { + palm->setTipVelocity(glm::vec3(0.f)); + } + palm->setTipPosition(newTipPosition); + } + + if (numActiveControllers == 2) { + updateCalibration(controllers); } - palm->setTipPosition(newTipPosition); - } - if (numActiveControllers == 2) { - updateCalibration(controllers); } #endif // HAVE_SIXENSE } diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index 664c102f76..1954ac91bc 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -18,6 +18,11 @@ #include #include #include "sixense.h" + +#ifdef __APPLE__ + #include +#endif + #endif const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2 @@ -38,9 +43,12 @@ const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; class SixenseManager : public QObject { Q_OBJECT public: + static SixenseManager& getInstance(); - SixenseManager(); - ~SixenseManager(); + void initialize(); + bool isInitialized() const { return _isInitialized; } + + void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; } void update(float deltaTime); float getCursorPixelRangeMult() const; @@ -51,6 +59,9 @@ public slots: void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; }; private: + SixenseManager(); + ~SixenseManager(); + #ifdef HAVE_SIXENSE void updateCalibration(const sixenseControllerData* controllers); void emulateMouse(PalmData* palm, int index); @@ -72,7 +83,13 @@ private: glm::vec3 _reachForward; float _lastDistance; +#ifdef __APPLE__ + QLibrary* _sixenseLibrary; #endif + +#endif + bool _isInitialized; + bool _isEnabled; bool _hydrasConnected; quint64 _lastMovement; glm::vec3 _amountMoved; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index df7c2d2289..d8d2213d8f 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -671,7 +671,7 @@ void ApplicationOverlay::renderControllerPointers() { float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult(); + float cursorRange = glWidget->width() * SixenseManager::getInstance().getCursorPixelRangeMult(); mouseX = (glWidget->width() / 2.0f + cursorRange * xAngle); mouseY = (glWidget->height() / 2.0f + cursorRange * yAngle);