From 1f3cf9bce2d09b859bb4e47313c9e5fef66f4014 Mon Sep 17 00:00:00 2001 From: Cinders Date: Wed, 27 Nov 2013 20:09:34 -0700 Subject: [PATCH] =?UTF-8?q?Initial=20Leap=20Motion=20controller=20support,?= =?UTF-8?q?=20includes=20llleapmotioncontoller.*=20test=20code=20from=20Si?= =?UTF-8?q?mon=20Linden=20To=20build=20with=20Leap=20Motion=20support=20bu?= =?UTF-8?q?ild=20with=20=E2=80=94leapmotion=20(Very=20unstable!)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- autobuild.xml | 136 +++-- indra/cmake/CMakeLists.txt | 1 + indra/cmake/Copy3rdPartyLibs.cmake | 13 + indra/cmake/LeapMotion.cmake | 24 + indra/newview/CMakeLists.txt | 24 + indra/newview/app_settings/settings.xml | 11 + indra/newview/llappviewer.cpp | 16 + indra/newview/llleapmotioncontroller.cpp | 613 +++++++++++++++++++++++ indra/newview/llleapmotioncontroller.h | 45 ++ indra/newview/viewer_manifest.py | 15 + scripts/configure_firestorm.sh | 14 +- 11 files changed, 862 insertions(+), 50 deletions(-) create mode 100644 indra/cmake/LeapMotion.cmake create mode 100644 indra/newview/llleapmotioncontroller.cpp create mode 100644 indra/newview/llleapmotioncontroller.h diff --git a/autobuild.xml b/autobuild.xml index c78f476a60..336f747788 100755 --- a/autobuild.xml +++ b/autobuild.xml @@ -1175,6 +1175,54 @@ + leap-motion + + license + leap-motion + license_file + LICENSES/leap-motion.txt + name + leap-motion + platforms + + darwin + + archive + + hash + 94fa8329ff292e43a5f5527ed4a05291 + url + file:///opt/firestorm/leap_motion-1.0.9+8391-darwin-20131127.tar.bz2 + + name + darwin + + linux + + archive + + hash + c71a6149cda34b32103c17ed460cce66 + url + file:///opt/firestorm/leap_motion-1.0.9+8391-linux-x64-20131128.tar.bz2 + + name + linux + + windows + + archive + + hash + 12f025c0b3d76dd3f83d963615b7964b + url + file:///opt/firestorm/leap_motion-1.0.9+8391-windows-20131128.tar.bz2 + + name + windows + + + libhunspell license @@ -1885,6 +1933,26 @@ + slplugin_x86 + + license + OpenSSL/LGPL + license_file + LICENSES/slplugin.txt + name + slplugin_x86 + platforms + + windows + + archive + + + name + windows + + + slvoice license @@ -1957,6 +2025,26 @@ + wix + + license + MS-RL + license_file + LICENSES/wix.txt + name + wix + platforms + + windows + + archive + + + name + windows + + + xmlrpc-epi license @@ -2053,54 +2141,6 @@ - slplugin_x86 - - license - OpenSSL/LGPL - license_file - LICENSES/slplugin.txt - name - slplugin_x86 - platforms - - windows - - archive - - hash - - url - - - name - windows - - - - wix - - license - MS-RL - license_file - LICENSES/wix.txt - name - wix - platforms - - windows - - archive - - hash - - url - - - name - windows - - - package_description diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 86c86081ff..d1d41b7005 100755 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -80,6 +80,7 @@ set(cmake_SOURCE_FILES LLVFS.cmake LLWindow.cmake LLXML.cmake + LeapMotion.cmake # We'll be fine without you -> LScript.cmake Linking.cmake MediaPluginBase.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 286ff07e2c..f4a741daf9 100755 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -74,6 +74,11 @@ if(WINDOWS) endif( NOT ND_BUILD64BIT_ARCH ) endif (FMODEX) + if (LEAPMOTION) + set(debug_files ${debug_files} Leapd.dll) + set(release_files ${release_files} Leap.dll) + endif (LEAPMOTION) + # Copy pdb files for symbol generation too if( NOT ND_BUILD64BIT_ARCH ) set(debug_files ${debug_files} ssleay32.pdb libeay32.pdb apr-1.pdb aprutil-1.pdb growl.pdb growl++.pdb ) @@ -248,6 +253,10 @@ elseif(DARWIN) set(release_files ${release_files} libfmodex.dylib) endif (FMODEX) + if (LEAPMOTION) + set(release_files ${release_files} libLeap.dylib) + endif (LEAPMOTION) + elseif(LINUX) # linux is weird, multiple side by side configurations aren't supported # and we don't seem to have any debug shared libs built yet anyways... @@ -321,6 +330,10 @@ elseif(LINUX) set(release_file ${release_files} "libfmodex.so") endif (FMODEX) + if (LEAPMOTION) + set(release_file ${release_files} "libLeap.so") + endif (LEAPMOTION) + else(WINDOWS) message(STATUS "WARNING: unrecognized platform for staging 3rd party libs, skipping...") set(vivox_src_dir "${CMAKE_SOURCE_DIR}/newview/vivox-runtime/i686-linux") diff --git a/indra/cmake/LeapMotion.cmake b/indra/cmake/LeapMotion.cmake new file mode 100644 index 0000000000..20c7ed3dc4 --- /dev/null +++ b/indra/cmake/LeapMotion.cmake @@ -0,0 +1,24 @@ +# -*- cmake -*- + +include(Linking) + +if (INSTALL_PROPRIETARY) + set(LEAPMOTION ON CACHE BOOL "Building with Leap Motion Controller support.") +endif (INSTALL_PROPRIETARY) + +if (STANDALONE) + # *TODO: Standalone support +else (STANDALONE) + include(Prebuilt) + use_prebuilt_binary(leap-motion) +if (DARWIN) + set(LEAP_MOTION_LIBRARY libLeap.dylib) + elseif (WINDOWS) + set(LEAP_MOTION_LIBRARY + debug Leapd.dll + optimized Leap.dll) + elseif (LINUX) + set(LEAP_MOTION_LIBRARY libLeap.so) + endif() + set(LEAP_MOTION_INCLUDE_DIR ${LIBS_OPEN_DIR}/leap-motion) +endif (STANDALONE) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 34a451e288..599867bd78 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -32,6 +32,7 @@ include(LLUI) include(LLVFS) include(LLWindow) include(LLXML) +include(LeapMotion) # Nope -> include(LScript) include(Linking) include(NDOF) @@ -68,6 +69,11 @@ if(FMODEX) include_directories(${FMODEX_INCLUDE_DIR}) endif(FMODEX) +if(LEAPMOTION) + add_definitions(-DUSE_LEAPMOTION=1) + include_directories(${LEAP_MOTION_INCLUDE_DIR}) +endif(LEAPMOTION) + include_directories( ${DBUSGLIB_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIR} @@ -1529,6 +1535,11 @@ set_source_files_properties( # COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake ) +if (LEAPMOTION) + LIST(APPEND viewer_SOURCE_FILES llleapmotioncontroller.cpp) + LIST(APPEND viewer_HEADER_FILES llleapmotioncontroller.h) +endif (LEAPMOTION) + if (DARWIN) LIST(APPEND viewer_SOURCE_FILES llappviewermacosx.cpp) LIST(APPEND viewer_SOURCE_FILES llfilepicker_mac.mm) @@ -2054,6 +2065,13 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/Debug/fmodexL.dll ) endif (FMODEX) + + if (LEAPMOTION) + list(APPEND_COPY_INPUT_DEPENDENCIES + ${SHARED_LIB_STAGING_DIR}/Release/Leap.dll + ${SHARED_LIB_STAGING_DIR}/Debug/Leapd.dll + ) + endif (LEAPMOTION) add_custom_command( OUTPUT ${CMAKE_CFG_INTDIR}/copy_touched.bat @@ -2228,6 +2246,12 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${GROWL_LIBRARY} ) +if (LEAPMOTION) + target_link_libraries(${VIEWER_BINARY_NAME} + ${LEAP_MOTION_LIBRARY} + ) +endif (LEAPMOTION) + if (WINDOWS) target_link_libraries(${VIEWER_BINARY_NAME} ${GROWL_LIBRARY} diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 2a5760b58c..b5ae2daf7b 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7287,6 +7287,17 @@ Value 2 + LeapMotionTestMode + + Comment + Test mode for Leap Motion gesture controller + Persist + 1 + Type + S32 + Value + 0 + LeftClickShowMenu Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 5fedab09d3..771a4864e9 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -244,6 +244,10 @@ #include "llviewereventrecorder.h" +#ifdef USE_LEAPMOTION +#include "llleapmotioncontroller.h" +#endif + // *FIX: These extern globals should be cleaned up. // The globals either represent state/config/resource-storage of either @@ -1479,6 +1483,13 @@ bool LLAppViewer::mainLoop() mMainLoopInitialized = true; #endif } + +// [FS:CR] +#ifdef USE_LEAPMOTION + LLLeapMotionController gestureController; +#endif // USE_LEAPMOTION +// [/FS:CR] + // As we do not (yet) send data on the mainloop LLEventPump that varies // with each frame, no need to instantiate a new LLSD event object each // time. Obviously, if that changes, just instantiate the LLSD at the @@ -1661,6 +1672,11 @@ bool LLAppViewer::mainLoop() } } + +// [FS:CR] Run any LeapMotion devices +#ifdef USE_LEAPMOTION + gestureController.stepFrame(); +#endif //USE_LEAPMOTION pingMainloopTimeout("Main:Sleep"); diff --git a/indra/newview/llleapmotioncontroller.cpp b/indra/newview/llleapmotioncontroller.cpp new file mode 100644 index 0000000000..985ba507a0 --- /dev/null +++ b/indra/newview/llleapmotioncontroller.cpp @@ -0,0 +1,613 @@ +/** + * @file llleapmotioncontroller.cpp + * @brief LLLeapMotionController class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2013, Cinder Roxley + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llleapmotioncontroller.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llgesturemgr.h" +#include "llmath.h" +#include "llstartup.h" +#include "lltimer.h" +#include "llviewercontrol.h" +#include "fsnearbychathub.h" + +#include + +const F32 LM_DEAD_ZONE = 20.f; // Dead zone in the middle of the space +const F32 LM_ORBIT_RATE_FACTOR = 80.f; // Number for camera orbit magic factor + +class LLLMImpl : public Leap::Listener +{ +public: + LLLMImpl(); + ~LLLMImpl(); + + // LeapMotion callbacks + virtual void onInit(const Leap::Controller&); + virtual void onConnect(const Leap::Controller&); + virtual void onDisconnect(const Leap::Controller&); + virtual void onFrame(const Leap::Controller&); + + // Called from viewer main loop + void stepFrame(); + + Leap::Controller * mLMController; // Leapmotion's object + bool mLMConnected; // true if device is connected + bool mFrameAvailable; // true if there is a new frame of data available + int64_t mCurrentFrameID; // Id of the most recent frame of data + + LLTimer mYawTimer; // Avoid turning left / right too fast + + // Hacky demo code - send controller data to in-world objects via chat + LLTimer mChatMsgTimer; // Throttle sending LM controller data to region local chat + + LLTimer mGestureTimer; // Throttle invoking SL gestures + +private: + + // Various controller modes + void modeFlyingControlTest(Leap::HandList & hands); + void modeStreamDataToSL(Leap::HandList & hands); + void modeGestureDetection1(Leap::HandList & hands); + void modeMoveAndCamTest1(Leap::HandList & hands); + void modeDumpDebugInfo(Leap::HandList & hands); +}; + +const F32 LLLEAP_YAW_INTERVAL = 0.075f; + +// Time between spamming chat messages. Server-side throttle is 200 msgs in 10 seconds +const F32 LLLEAP_CHAT_MSG_INTERVAL = 0.200f; // Send 5/second + +const F32 LLLEAP_GESTURE_INTERVAL = 3.f; // 3 seconds in between SL gestures + + +LLLMImpl::LLLMImpl() : mLMController(NULL) +, mLMConnected(false) +, mFrameAvailable(false) +, mCurrentFrameID(0) +{ + mLMController = new Leap::Controller(*this); + mYawTimer.setTimerExpirySec(LLLEAP_YAW_INTERVAL); + mChatMsgTimer.setTimerExpirySec(LLLEAP_CHAT_MSG_INTERVAL); + mGestureTimer.setTimerExpirySec(LLLEAP_GESTURE_INTERVAL); +} + +LLLMImpl::~LLLMImpl() +{ + delete mLMController; +} + +void LLLMImpl::onInit(const Leap::Controller& controller) +{ + LL_INFOS("LeapMotion") << "Initialized" << LL_ENDL; +} + +void LLLMImpl::onConnect(const Leap::Controller& controller) +{ + LL_INFOS("LeapMotion") << "Connected" << LL_ENDL; + mLMConnected = true; + mCurrentFrameID = 0; +} + +void LLLMImpl::onDisconnect(const Leap::Controller& controller) +{ + LL_INFOS("LeapMotion") << "Disconnected" << LL_ENDL; + mLMConnected = false; +} + +// Callback from Leapmotion code when a new frame is available. It simply +// sets a flag so stepFrame() can pick up new controller data +void LLLMImpl::onFrame(const Leap::Controller& controller) +{ + if (mLMConnected) + { + // Get the most recent frame and report some basic information + const Leap::Frame frame = controller.frame(); + int64_t frame_id = frame.id(); + if (frame_id != mCurrentFrameID) + { // Just record the ID and set flag indicating data is available + mCurrentFrameID = frame_id; + mFrameAvailable = true; + } + } +} + + +// This is called every SL viewer frame +void LLLMImpl::stepFrame() +{ + if (mLMController + && mFrameAvailable + && mLMConnected) + { + mFrameAvailable = false; + + // Get the most recent frame and report some basic information + const Leap::Frame frame = mLMController->frame(); + Leap::HandList hands = frame.hands(); + + static LLCachedControl sControllerMode(gSavedSettings, "LeapMotionTestMode"); + + switch (sControllerMode) + { + case 1: + modeFlyingControlTest(hands); + break; + case 2: + modeStreamDataToSL(hands); + break; + case 3: + modeGestureDetection1(hands); + break; + case 4: + modeMoveAndCamTest1(hands); + break; + case 411: + modeDumpDebugInfo(hands); + break; + default: + // Do nothing + break; + } + } +} + +// This controller mode is used to fly the avatar, going up, down, forward and turning. +void LLLMImpl::modeFlyingControlTest(Leap::HandList & hands) +{ + static S32 sLMFlyingHysteresis = 0; + + S32 numHands = hands.count(); + BOOL agent_is_flying = gAgent.getFlying(); + + if (numHands == 0 + && agent_is_flying + && sLMFlyingHysteresis > 0) + { + sLMFlyingHysteresis--; + if (sLMFlyingHysteresis == 0) + { + LL_INFOS("LeapMotion") << "LM stop flying - look ma, no hands!" << LL_ENDL; + gAgent.setFlying(FALSE); + } + } + else if (numHands == 1) + { + // Get the first hand + Leap::Hand hand = hands[0]; + + // Check if the hand has any fingers + Leap::FingerList finger_list = hand.fingers(); + S32 num_fingers = finger_list.count(); + + Leap::Vector palm_pos = hand.palmPosition(); + Leap::Vector palm_normal = hand.palmNormal(); + + F32 ball_radius = (F32) hand.sphereRadius(); + Leap::Vector ball_center = hand.sphereCenter(); + + // Number of fingers controls flying on / off + if (num_fingers == 0 && // To do - add hysteresis or data smoothing? + agent_is_flying) + { + if (sLMFlyingHysteresis > 0) + { + sLMFlyingHysteresis--; + } + else + { + LL_INFOS("LeapMotion") << "LM stop flying" << LL_ENDL; + gAgent.setFlying(FALSE); + } + } + else if (num_fingers > 2 && + !agent_is_flying) + { + LL_INFOS("LeapMotion") << "LM start flying" << LL_ENDL; + gAgent.setFlying(TRUE); + sLMFlyingHysteresis = 5; + } + + // Radius of ball controls forward motion + if (agent_is_flying) + { + + if (ball_radius > 110.f) + { // Open hand, move fast + gAgent.setControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT); + } + else if (ball_radius > 85.f) + { // Partially open, move slow + gAgent.setControlFlags(AGENT_CONTROL_AT_POS); + } + else + { // Closed - stop + gAgent.clearControlFlags(AGENT_CONTROL_AT_POS); + } + + // Height of palm controls moving up and down + if (palm_pos.y > 260.f) + { // Go up fast + gAgent.setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); + } + else if (palm_pos.y > 200.f) + { // Go up + gAgent.setControlFlags(AGENT_CONTROL_UP_POS); + } + else if (palm_pos.y < 60.f) + { // Go down fast + gAgent.setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_NEG); + } + else if (palm_pos.y < 120.f) + { // Go down + gAgent.setControlFlags(AGENT_CONTROL_UP_NEG); + } + else + { // Clear up / down + gAgent.clearControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_POS | AGENT_CONTROL_UP_NEG); + } + + // Palm normal going left / right controls direction + if (mYawTimer.checkExpirationAndReset(LLLEAP_YAW_INTERVAL)) + { + if (palm_normal.x > 0.4) + { // Go left fast + gAgent.moveYaw(1.f); + } + else if (palm_normal.x < -0.4) + { // Go right fast + gAgent.moveYaw(-1.f); + } + } + + } // end flying controls + } +} + + +// This experimental mode sends chat messages into SL on a back channel for LSL scripts +// to intercept with a listen() event. This is experimental and not sustainable for +// a production feature ... many avatars using this would flood the chat system and +// hurt server performance. Depending on how useful this proves to be, a better +// mechanism should be designed to stream data from the viewer into SL scripts. +void LLLMImpl::modeStreamDataToSL(Leap::HandList & hands) +{ + S32 numHands = hands.count(); + if (numHands == 1 && + mChatMsgTimer.checkExpirationAndReset(LLLEAP_CHAT_MSG_INTERVAL)) + { + // Get the first (and only) hand + Leap::Hand hand = hands[0]; + + Leap::Vector palm_pos = hand.palmPosition(); + Leap::Vector palm_normal = hand.palmNormal(); + + F32 ball_radius = (F32) hand.sphereRadius(); + Leap::Vector ball_center = hand.sphereCenter(); + + // Chat message looks like "/2343 LM1,,,," + LLVector3 vec; + std::stringstream status_chat_msg; + status_chat_msg << "/2343 LM,"; + status_chat_msg << "<" << palm_pos.x << "," << palm_pos.y << "," << palm_pos.z << ">,"; + status_chat_msg << "<" << palm_normal.x << "," << palm_normal.y << "," << palm_normal.z << ">,"; + status_chat_msg << "<" << ball_center.x << "," << ball_center.y << "," << ball_center.z << ">," << ball_radius; + + FSNearbyChat::instance().sendChatFromViewer(status_chat_msg.str(), CHAT_TYPE_SHOUT, FALSE); + } +} + +// This mode tries to detect simple hand motion and either triggers an avatar gesture or +// sends a chat message into SL in response. It is very rough, hard-coded for detecting +// a hand wave (a SL gesture) or the wiggling-thumb gun trigger (a chat message sent to a +// special version of the popgun). +void LLLMImpl::modeGestureDetection1(Leap::HandList & hands) +{ + static S32 trigger_direction = -1; + + S32 numHands = hands.count(); + if (numHands == 1) + { + // Get the first hand + Leap::Hand hand = hands[0]; + + // Check if the hand has any fingers + Leap::FingerList finger_list = hand.fingers(); + S32 num_fingers = finger_list.count(); + static S32 last_num_fingers = 0; + + if (num_fingers == 1) + { // One finger ... possibly reset the + Leap::Finger finger = finger_list[0]; + Leap::Vector finger_dir = finger.direction(); + + // Negative Z is into the screen - check that it's the largest component + S32 abs_z_dir = llabs(finger_dir.z); + if (finger_dir.z < -0.5 && + abs_z_dir > llabs(finger_dir.x) && + abs_z_dir > llabs(finger_dir.y)) + { + Leap::Vector finger_pos = finger.tipPosition(); + Leap::Vector finger_vel = finger.tipVelocity(); + LL_INFOS("LeapMotion") << "finger direction is " << finger_dir.x << ", " << finger_dir.y << ", " << finger_dir.z + << ", position " << finger_pos.x << ", " << finger_pos.y << ", " << finger_pos.z + << ", velocity " << finger_vel.x << ", " << finger_vel.y << ", " << finger_vel.z + << LL_ENDL; + } + + if (trigger_direction != -1) + { + LL_INFOS("LeapMotion") << "Reset trigger_direction - one finger" << LL_ENDL; + trigger_direction = -1; + } + } + else if (num_fingers == 2) + { + Leap::Finger barrel_finger = finger_list[0]; + Leap::Vector barrel_finger_dir = barrel_finger.direction(); + + // Negative Z is into the screen - check that it's the largest component + F32 abs_z_dir = llabs(barrel_finger_dir.z); + if (barrel_finger_dir.z < -0.5f && + abs_z_dir > llabs(barrel_finger_dir.x) && + abs_z_dir > llabs(barrel_finger_dir.y)) + { + Leap::Finger thumb_finger = finger_list[1]; + Leap::Vector thumb_finger_dir = thumb_finger.direction(); + Leap::Vector thumb_finger_pos = thumb_finger.tipPosition(); + Leap::Vector thumb_finger_vel = thumb_finger.tipVelocity(); + + if ((thumb_finger_dir.x < barrel_finger_dir.x) ) + { // Trigger gunfire + if (trigger_direction < 0 && // Haven't fired + thumb_finger_vel.x > 50.f && // Moving into screen + thumb_finger_vel.z < -50.f && + mChatMsgTimer.checkExpirationAndReset(LLLEAP_CHAT_MSG_INTERVAL)) + { + // Chat message looks like "/2343 LM2 gunfire" + std::string gesture_chat_msg("/2343 LM2 gunfire"); + //LLNearbyChatBar::sendChatFromViewer(gesture_chat_msg, CHAT_TYPE_SHOUT, FALSE); + trigger_direction = 1; + LL_INFOS("LeapMotion") << "Sent gunfire chat" << LL_ENDL; + } + else if (trigger_direction > 0 && // Have fired, need to pull thumb back + thumb_finger_vel.x < -50.f && + thumb_finger_vel.z > 50.f) // Moving out of screen + { + trigger_direction = -1; + LL_INFOS("LeapMotion") << "Reset trigger_direction" << LL_ENDL; + } + } + } + else if (trigger_direction != -1) + { + LL_INFOS("LeapMotion") << "Reset trigger_direction - hand pos" << LL_ENDL; + trigger_direction = -1; + } + } + else if (num_fingers == 5 && + num_fingers == last_num_fingers) + { + if (mGestureTimer.checkExpirationAndReset(LLLEAP_GESTURE_INTERVAL)) + { + // figure out a gesture to trigger + std::string gestureString("/overhere"); + LLGestureMgr::instance().triggerAndReviseString( gestureString ); + } + } + + last_num_fingers = num_fingers; + } +} + + +// This mode tries to move the avatar and camera in Second Life. It's pretty rough and needs a lot of work +void LLLMImpl::modeMoveAndCamTest1(Leap::HandList & hands) +{ + S32 numHands = hands.count(); + if (numHands == 1) + { + // Get the first hand + Leap::Hand hand = hands[0]; + + // Check if the hand has any fingers + Leap::FingerList finger_list = hand.fingers(); + S32 num_fingers = finger_list.count(); + + F32 orbit_rate = 0.f; + + Leap::Vector pos(0, 0, 0); + for (size_t i = 0; i < num_fingers; ++i) + { + Leap::Finger finger = finger_list[i]; + pos += finger.tipPosition(); + } + pos = Leap::Vector(pos.x/num_fingers, pos.y/num_fingers, pos.z/num_fingers); + + if (num_fingers == 1) + { // 1 finger - move avatar + if (pos.x < -LM_DEAD_ZONE) + { // Move left + gAgent.moveLeftNudge(1.f); + } + else if (pos.x > LM_DEAD_ZONE) + { + gAgent.moveLeftNudge(-1.f); + } + + /* + if (pos.z < -LM_DEAD_ZONE) + { + gAgent.moveAtNudge(1.f); + } + else if (pos.z > LM_DEAD_ZONE) + { + gAgent.moveAtNudge(-1.f); + } */ + + if (pos.y < -LM_DEAD_ZONE) + { + gAgent.moveYaw(-1.f); + } + else if (pos.y > LM_DEAD_ZONE) + { + gAgent.moveYaw(1.f); + } + } // end 1 finger + else if (num_fingers == 2) + { // 2 fingers - move camera around + // X values run from about -170 to +170 + if (pos.x < -LM_DEAD_ZONE) + { // Camera rotate left + gAgentCamera.unlockView(); + orbit_rate = (llabs(pos.x) - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitLeftKey(orbit_rate); + } + else if (pos.x > LM_DEAD_ZONE) + { + gAgentCamera.unlockView(); + orbit_rate = (pos.x - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitRightKey(orbit_rate); + } + if (pos.z < -LM_DEAD_ZONE) + { // Camera zoom in + gAgentCamera.unlockView(); + orbit_rate = (llabs(pos.z) - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitInKey(orbit_rate); + } + else if (pos.z > LM_DEAD_ZONE) + { // Camera zoom out + gAgentCamera.unlockView(); + orbit_rate = (pos.z - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitOutKey(orbit_rate); + } + + if (pos.y < -LM_DEAD_ZONE) + { // Camera zoom in + gAgentCamera.unlockView(); + orbit_rate = (llabs(pos.y) - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitUpKey(orbit_rate); + } + else if (pos.y > LM_DEAD_ZONE) + { // Camera zoom out + gAgentCamera.unlockView(); + orbit_rate = (pos.y - LM_DEAD_ZONE) / LM_ORBIT_RATE_FACTOR; + gAgentCamera.setOrbitDownKey(orbit_rate); + } + } // end 2 finger + } +} + + +// This controller mode just dumps out a bunch of the Leap Motion device data, which can then be +// analyzed for other use. +void LLLMImpl::modeDumpDebugInfo(Leap::HandList & hands) +{ + S32 numHands = hands.count(); + if (numHands == 1) + { + // Get the first hand + Leap::Hand hand = hands[0]; + + // Check if the hand has any fingers + Leap::FingerList finger_list = hand.fingers(); + S32 num_fingers = finger_list.count(); + + if (num_fingers >= 1) + { // Calculate the hand's average finger tip position + Leap::Vector pos(0, 0, 0); + Leap::Vector direction(0, 0, 0); + for (size_t i = 0; i < num_fingers; ++i) + { + Leap::Finger finger = finger_list[i]; + pos += finger.tipPosition(); + direction += finger.direction(); + + // Lots of log spam + LL_INFOS("LeapMotion") << "Finger " << i << " string is " << finger.toString() << LL_ENDL; + } + pos = Leap::Vector(pos.x/num_fingers, pos.y/num_fingers, pos.z/num_fingers); + direction = Leap::Vector(direction.x/num_fingers, direction.y/num_fingers, direction.z/num_fingers); + + LL_INFOS("LeapMotion") << "Hand has " << num_fingers << " fingers with average tip position" + << " (" << pos.x << ", " << pos.y << ", " << pos.z << ")" + << " direction (" << direction.x << ", " << direction.y << ", " << direction.z << ")" + << LL_ENDL; + + } + + Leap::Vector palm_pos = hand.palmPosition(); + Leap::Vector palm_normal = hand.palmNormal(); + LL_INFOS("LeapMotion") << "Palm pos " << palm_pos.x + << ", " << palm_pos.y + << ", " << palm_pos.z + << ". Normal: " << palm_normal.x + << ", " << palm_normal.y + << ", " << palm_normal.z + << LL_ENDL; + + F32 ball_radius = (F32) hand.sphereRadius(); + Leap::Vector ball_center = hand.sphereCenter(); + LL_INFOS("LeapMotion") << "Ball pos " << ball_center.x + << ", " << ball_center.y + << ", " << ball_center.z + << ", radius " << ball_radius + << LL_ENDL; + } // dump_out_data +} + + +// -------------------------------------------------------------------------------- +// The LLLeapMotionController class is a thin public glue layer into the LLLMImpl +// class, which does all the interesting work. + +// One controller instance to rule them all +LLLeapMotionController::LLLeapMotionController() +{ + mController = new LLLMImpl(); +} + + +LLLeapMotionController::~LLLeapMotionController() +{ + delete mController; + mController = NULL; +} + + +// Called every viewer frame +void LLLeapMotionController::stepFrame() +{ + if (mController && + STATE_STARTED == LLStartUp::getStartupState()) + { + mController->stepFrame(); + } +} diff --git a/indra/newview/llleapmotioncontroller.h b/indra/newview/llleapmotioncontroller.h new file mode 100644 index 0000000000..b4ec6b9cdf --- /dev/null +++ b/indra/newview/llleapmotioncontroller.h @@ -0,0 +1,45 @@ +/** + * @file llleapmotioncontroller.h + * @brief LLLeapMotionController class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLEAPMOTIONCONTROLLER_H +#define LL_LLLEAPMOTIONCONTROLLER_H + +class LLLMImpl; + +class LLLeapMotionController +{ +public: + LLLeapMotionController(); + ~LLLeapMotionController(); + + // Called every viewer frame + void stepFrame(); + +protected: + LLLMImpl * mController; +}; + +#endif // LL_LLLEAPMOTIONCONTROLLER_H diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index a675091730..f8721a9ebe 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -470,6 +470,15 @@ class WindowsManifest(ViewerManifest): self.path("fmodex64.dll") except: print "Skipping fmodex audio library(assuming other audio engine)" + + # Get Leap Motion SDK + try: + if self.args['configuration'].lower() == 'debug': + self.path("Leapd.dll") + else: + self.path("Leap.dll") + except: + print "Leap Motion library was not found" # For textures if self.args['configuration'].lower() == 'debug': @@ -945,6 +954,7 @@ class DarwinManifest(ViewerManifest): "libfmodex.dylib", "libfmodexL.dylib", "libGLOD.dylib", + "libLeap.dylib", ): dylibs += path_optional(os.path.join(libdir, libfile), libfile) @@ -1403,6 +1413,11 @@ class Linux_i686Manifest(LinuxManifest): except: print "Skipping libfmodex.so - not found" pass + + try: + self.path("libLeap.so") + except: + print "Leap Motion library not found" self.end_prefix("lib") diff --git a/scripts/configure_firestorm.sh b/scripts/configure_firestorm.sh index 158a09cf9a..0f25cd0234 100755 --- a/scripts/configure_firestorm.sh +++ b/scripts/configure_firestorm.sh @@ -17,6 +17,7 @@ FALSE=1 # -DINSTALL_PROPRIETARY=FALSE # -DUSE_KDU=TRUE # -DFMODEX:BOOL=ON +# -DLEAPMOTION:BOOL=OFF # -DOPENSIM:BOOL=ON # -DUSE_AVX_OPTIMIZATION:BOOL=OFF # -DLL_TESTS:BOOL=OFF @@ -33,6 +34,7 @@ WANTS_PACKAGE=$FALSE WANTS_VERSION=$FALSE WANTS_KDU=$FALSE WANTS_FMODEX=$FALSE +WANTS_LEAPMOTION=$FALSE WANTS_OPENSIM=$TRUE WANTS_AVX=$FALSE WANTS_BUILD=$FALSE @@ -62,6 +64,7 @@ showUsage() echo " --package : Build installer" echo " --no-package : Build without installer (Overrides --package)" echo " --fmodex : Build with FMOD Ex" + echo " --leapmotion : Build with Leap Motion Controller support" echo " --opensim : Build with OpenSim support (Disables Havok features)" echo " --no-opensim : Build without OpenSim support (Overrides --opensim)" echo " --avx : Build with Advanced Vector Extensions (Windows only)" @@ -76,7 +79,7 @@ getArgs() # $* = the options passed in from main { if [ $# -gt 0 ]; then - while getoptex "clean build config version package no-package fmodex jobs: platform: kdu opensim no-opensim avx help chan: btype:" "$@" ; do + while getoptex "clean build config version package no-package fmodex jobs: platform: kdu leapmotion opensim no-opensim avx help chan: btype:" "$@" ; do #insure options are valid if [ -z "$OPTOPT" ] ; then @@ -95,6 +98,7 @@ getArgs() ;; kdu) WANTS_KDU=$TRUE;; fmodex) WANTS_FMODEX=$TRUE;; + leapmotion) WANTS_LEAPMOTION=$TRUE;; opensim) WANTS_OPENSIM=$TRUE;; no-opensim) WANTS_OPENSIM=$FALSE;; avx) WANTS_AVX=$TRUE;; @@ -271,6 +275,7 @@ echo -e "configure_firestorm.py" > $LOG echo -e " PLATFORM: '$PLATFORM'" | tee -a $LOG echo -e " KDU: `b2a $WANTS_KDU`" | tee -a $LOG echo -e " FMODEX: `b2a $WANTS_FMODEX`" | tee -a $LOG +echo -e " LEAPMOTION: `b2a $WANTS_LEAPMOTION`" | tee -a $LOG echo -e " OPENSIM: `b2a $WANTS_OPENSIM`" | tee -a $LOG echo -e " AVX: `b2a $WANTS_AVX` " | tee -a $LOG echo -e " PACKAGE: `b2a $WANTS_PACKAGE`" | tee -a $LOG @@ -356,6 +361,11 @@ if [ $WANTS_CONFIG -eq $TRUE ] ; then else FMODEX="-DFMODEX:BOOL=OFF" fi + if [ $WANTS_LEAPMOTION -eq $TRUE ] ; then + LEAPMOTION="-DLEAPMOTION:BOOL=ON" + else + LEAPMOTION="-DLEAPMOTION:BOOL=OFF" + fi if [ $WANTS_OPENSIM -eq $TRUE ] ; then OPENSIM="-DOPENSIM:BOOL=ON" else @@ -407,7 +417,7 @@ if [ $WANTS_CONFIG -eq $TRUE ] ; then UNATTENDED="-DUNATTENDED=ON" fi - cmake -G "$TARGET" ../indra $CHANNEL $FMODEX $KDU $OPENSIM $AVX_OPTIMIZATION $PACKAGE $UNATTENDED -DLL_TESTS:BOOL=OFF -DWORD_SIZE:STRING=32 -DCMAKE_BUILD_TYPE:STRING=$BTYPE \ + cmake -G "$TARGET" ../indra $CHANNEL $FMODEX $KDU $LEAPMOTION $OPENSIM $AVX_OPTIMIZATION $PACKAGE $UNATTENDED -DLL_TESTS:BOOL=OFF -DWORD_SIZE:STRING=32 -DCMAKE_BUILD_TYPE:STRING=$BTYPE \ -DNDTARGET_ARCH="${TARGET_ARCH}" -DROOT_PROJECT_NAME:STRING=Firestorm $LL_ARGS_PASSTHRU | tee $LOG if [ $PLATFORM == "win32" ] ; then