summaryrefslogtreecommitdiff
path: root/indra/newview/llviewerjoystick.cpp
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
committerAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
commit1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch)
treeab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llviewerjoystick.cpp
parent6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff)
parente1623bb276f83a43ce7a197e388720c05bdefe61 (diff)
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts: # autobuild.xml # indra/cmake/CMakeLists.txt # indra/cmake/GoogleMock.cmake # indra/llaudio/llaudioengine_fmodstudio.cpp # indra/llaudio/llaudioengine_fmodstudio.h # indra/llaudio/lllistener_fmodstudio.cpp # indra/llaudio/lllistener_fmodstudio.h # indra/llaudio/llstreamingaudio_fmodstudio.cpp # indra/llaudio/llstreamingaudio_fmodstudio.h # indra/llcharacter/llmultigesture.cpp # indra/llcharacter/llmultigesture.h # indra/llimage/llimage.cpp # indra/llimage/llimagepng.cpp # indra/llimage/llimageworker.cpp # indra/llimage/tests/llimageworker_test.cpp # indra/llmessage/tests/llmockhttpclient.h # indra/llprimitive/llgltfmaterial.h # indra/llrender/llfontfreetype.cpp # indra/llui/llcombobox.cpp # indra/llui/llfolderview.cpp # indra/llui/llfolderviewmodel.h # indra/llui/lllineeditor.cpp # indra/llui/lllineeditor.h # indra/llui/lltextbase.cpp # indra/llui/lltextbase.h # indra/llui/lltexteditor.cpp # indra/llui/lltextvalidate.cpp # indra/llui/lltextvalidate.h # indra/llui/lluictrl.h # indra/llui/llview.cpp # indra/llwindow/llwindowmacosx.cpp # indra/newview/app_settings/settings.xml # indra/newview/llappearancemgr.cpp # indra/newview/llappearancemgr.h # indra/newview/llavatarpropertiesprocessor.cpp # indra/newview/llavatarpropertiesprocessor.h # indra/newview/llbreadcrumbview.cpp # indra/newview/llbreadcrumbview.h # indra/newview/llbreastmotion.cpp # indra/newview/llbreastmotion.h # indra/newview/llconversationmodel.h # indra/newview/lldensityctrl.cpp # indra/newview/lldensityctrl.h # indra/newview/llface.inl # indra/newview/llfloatereditsky.cpp # indra/newview/llfloatereditwater.cpp # indra/newview/llfloateremojipicker.h # indra/newview/llfloaterimsessiontab.cpp # indra/newview/llfloaterprofiletexture.cpp # indra/newview/llfloaterprofiletexture.h # indra/newview/llgesturemgr.cpp # indra/newview/llgesturemgr.h # indra/newview/llimpanel.cpp # indra/newview/llimpanel.h # indra/newview/llinventorybridge.cpp # indra/newview/llinventorybridge.h # indra/newview/llinventoryclipboard.cpp # indra/newview/llinventoryclipboard.h # indra/newview/llinventoryfunctions.cpp # indra/newview/llinventoryfunctions.h # indra/newview/llinventorygallery.cpp # indra/newview/lllistbrowser.cpp # indra/newview/lllistbrowser.h # indra/newview/llpanelobjectinventory.cpp # indra/newview/llpanelprofile.cpp # indra/newview/llpanelprofile.h # indra/newview/llpreviewgesture.cpp # indra/newview/llsavedsettingsglue.cpp # indra/newview/llsavedsettingsglue.h # indra/newview/lltooldraganddrop.cpp # indra/newview/llurllineeditorctrl.cpp # indra/newview/llvectorperfoptions.cpp # indra/newview/llvectorperfoptions.h # indra/newview/llviewerparceloverlay.cpp # indra/newview/llviewertexlayer.cpp # indra/newview/llviewertexturelist.cpp # indra/newview/macmain.h # indra/test/test.cpp
Diffstat (limited to 'indra/newview/llviewerjoystick.cpp')
-rw-r--r--indra/newview/llviewerjoystick.cpp3084
1 files changed, 1601 insertions, 1483 deletions
diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp
index 0aba982ba9..a1d5f4c53e 100644
--- a/indra/newview/llviewerjoystick.cpp
+++ b/indra/newview/llviewerjoystick.cpp
@@ -1,1483 +1,1601 @@
-/**
- * @file llviewerjoystick.cpp
- * @brief Joystick / NDOF device functionality.
- *
- * $LicenseInfo:firstyear=2002&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$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llviewerjoystick.h"
-
-#include "llviewercontrol.h"
-#include "llviewerwindow.h"
-#include "llviewercamera.h"
-#include "llappviewer.h"
-#include "llkeyboard.h"
-#include "lltoolmgr.h"
-#include "llselectmgr.h"
-#include "llviewermenu.h"
-#include "llviewerwindow.h"
-#include "llwindow.h"
-#include "llagent.h"
-#include "llagentcamera.h"
-#include "llfocusmgr.h"
-
-#if LL_WINDOWS && !LL_MESA_HEADLESS
-// Require DirectInput version 8
-#define DIRECTINPUT_VERSION 0x0800
-
-#include <dinput.h>
-#endif
-
-
-// ----------------------------------------------------------------------------
-// Constants
-
-#define X_I 1
-#define Y_I 2
-#define Z_I 0
-#define RX_I 4
-#define RY_I 5
-#define RZ_I 3
-
-F32 LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0};
-F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
-
-// These constants specify the maximum absolute value coming in from the device.
-// HACK ALERT! the value of MAX_JOYSTICK_INPUT_VALUE is not arbitrary as it
-// should be. It has to be equal to 3000 because the SpaceNavigator on Windows
-// refuses to respond to the DirectInput SetProperty call; it always returns
-// values in the [-3000, 3000] range.
-#define MAX_SPACENAVIGATOR_INPUT 3000.0f
-#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT
-
-
-#if LIB_NDOF
-std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr)
-{
- if (! ptr)
- {
- return out << "nullptr";
- }
- out << "NDOF_Device{ ";
- out << "axes [";
- const char* delim = "";
- for (short axis = 0; axis < ptr->axes_count; ++axis)
- {
- out << delim << ptr->axes[axis];
- delim = ", ";
- }
- out << "]";
- out << ", buttons [";
- delim = "";
- for (short button = 0; button < ptr->btn_count; ++button)
- {
- out << delim << ptr->buttons[button];
- delim = ", ";
- }
- out << "]";
- out << ", range " << ptr->axes_min << ':' << ptr->axes_max;
- // If we don't coerce these to unsigned, they're streamed as characters,
- // e.g. ctrl-A or nul.
- out << ", absolute " << unsigned(ptr->absolute);
- out << ", valid " << unsigned(ptr->valid);
- out << ", manufacturer '" << ptr->manufacturer << "'";
- out << ", product '" << ptr->product << "'";
- out << ", private " << ptr->private_data;
- out << " }";
- return out;
-}
-#endif // LIB_NDOF
-
-
-#if LL_WINDOWS && !LL_MESA_HEADLESS
-// this should reflect ndof and set axises, see ndofdev_win.cpp from ndof package
-BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* inst, VOID* user_data)
-{
- if (inst->dwType & DIDFT_AXIS)
- {
- LPDIRECTINPUTDEVICE8 device = *((LPDIRECTINPUTDEVICE8 *)user_data);
- DIPROPRANGE diprg;
- diprg.diph.dwSize = sizeof(DIPROPRANGE);
- diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
- diprg.diph.dwHow = DIPH_BYID;
- diprg.diph.dwObj = inst->dwType; // specify the enumerated axis
-
- // Set the range for the axis
- diprg.lMin = (long)-MAX_JOYSTICK_INPUT_VALUE;
- diprg.lMax = (long)+MAX_JOYSTICK_INPUT_VALUE;
- HRESULT hr = device->SetProperty(DIPROP_RANGE, &diprg.diph);
-
- if (FAILED(hr))
- {
- return DIENUM_STOP;
- }
- }
-
- return DIENUM_CONTINUE;
-}
-
-BOOL CALLBACK di8_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef)
-{
- // Note: If a single device can function as more than one DirectInput
- // device type, it is enumerated as each device type that it supports.
- // Capable of detecting devices like Oculus Rift
- if (device_instance_ptr)
- {
- std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName));
-
- LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID();
-
- bool init_device = false;
- if (guid.isBinary())
- {
- std::vector<U8> bin_bucket = guid.asBinary();
- init_device = memcmp(&bin_bucket[0], &device_instance_ptr->guidInstance, sizeof(GUID)) == 0;
- }
- else
- {
- // It might be better to init space navigator here, but if system doesn't has one,
- // ndof will pick a random device, it is simpler to pick first device now to have an id
- init_device = true;
- }
-
- if (init_device)
- {
- LL_DEBUGS("Joystick") << "Found and attempting to use device: " << product_name << LL_ENDL;
- LPDIRECTINPUT8 di8_interface = *((LPDIRECTINPUT8 *)gViewerWindow->getWindow()->getDirectInput8());
- LPDIRECTINPUTDEVICE8 device = NULL;
-
- HRESULT status = di8_interface->CreateDevice(
- device_instance_ptr->guidInstance, // REFGUID rguid,
- &device, // LPDIRECTINPUTDEVICE * lplpDirectInputDevice,
- NULL // LPUNKNOWN pUnkOuter
- );
-
- if (status == DI_OK)
- {
- // prerequisite for aquire()
- LL_DEBUGS("Joystick") << "Device created" << LL_ENDL;
- status = device->SetDataFormat(&c_dfDIJoystick); // c_dfDIJoystick2
- }
-
- if (status == DI_OK)
- {
- // set properties
- LL_DEBUGS("Joystick") << "Format set" << LL_ENDL;
- status = device->EnumObjects(EnumObjectsCallback, &device, DIDFT_ALL);
- }
-
- if (status == DI_OK)
- {
- LL_DEBUGS("Joystick") << "Properties updated" << LL_ENDL;
-
- S32 size = sizeof(GUID);
- LLSD::Binary data; //just an std::vector
- data.resize(size);
- memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size);
- LLViewerJoystick::getInstance()->initDevice(&device, product_name, LLSD(data));
- return DIENUM_STOP;
- }
- }
- else
- {
- LL_DEBUGS("Joystick") << "Found device: " << product_name << LL_ENDL;
- }
- }
- return DIENUM_CONTINUE;
-}
-
-// Windows guids
-// This is GUID2 so teoretically it can be memcpy copied into LLUUID
-void guid_from_string(GUID &guid, const std::string &input)
-{
- CLSIDFromString(utf8str_to_utf16str(input).c_str(), &guid);
-}
-
-std::string string_from_guid(const GUID &guid)
-{
- OLECHAR* guidString; //wchat
- StringFromCLSID(guid, &guidString);
-
- // use guidString...
-
- std::string res = utf16str_to_utf8str(llutf16string(guidString));
- // ensure memory is freed
- ::CoTaskMemFree(guidString);
-
- return res;
-}
-#endif
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::updateEnabled(bool autoenable)
-{
- if (mDriverState == JDS_UNINITIALIZED)
- {
- gSavedSettings.setBOOL("JoystickEnabled", false);
- }
- else
- {
- // autoenable if user specifically chose this device
- if (autoenable && (isLikeSpaceNavigator() || isDeviceUUIDSet()))
- {
- gSavedSettings.setBOOL("JoystickEnabled", true );
- }
- }
- if (!gSavedSettings.getBOOL("JoystickEnabled"))
- {
- mOverrideCamera = false;
- }
-}
-
-void LLViewerJoystick::setOverrideCamera(bool val)
-{
- if (!gSavedSettings.getBOOL("JoystickEnabled"))
- {
- mOverrideCamera = false;
- }
- else
- {
- mOverrideCamera = val;
- }
-
- if (mOverrideCamera)
- {
- gAgentCamera.changeCameraToDefault();
- }
-}
-
-// -----------------------------------------------------------------------------
-#if LIB_NDOF
-NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev)
-{
- NDOF_HotPlugResult res = NDOF_DISCARD_HOTPLUGGED;
- LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
- if (joystick->mDriverState == JDS_UNINITIALIZED)
- {
- LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL;
- ndof_dump(stderr, dev);
- joystick->mNdofDev = dev;
- joystick->mDriverState = JDS_INITIALIZED;
- res = NDOF_KEEP_HOTPLUGGED;
- }
- joystick->updateEnabled(true);
- return res;
-}
-#endif
-
-// -----------------------------------------------------------------------------
-#if LIB_NDOF
-void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev)
-{
- LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
- if (joystick->mNdofDev == dev)
- {
- LL_INFOS("Joystick") << "HotPlugRemovalCallback: joystick->mNdofDev="
- << joystick->mNdofDev << "; removed device:" << LL_ENDL;
- ndof_dump(stderr, dev);
- joystick->mDriverState = JDS_UNINITIALIZED;
- }
- joystick->updateEnabled(true);
-}
-#endif
-
-// -----------------------------------------------------------------------------
-LLViewerJoystick::LLViewerJoystick()
-: mDriverState(JDS_UNINITIALIZED),
- mNdofDev(NULL),
- mResetFlag(false),
- mCameraUpdated(true),
- mOverrideCamera(false),
- mJoystickRun(0)
-{
- for (int i = 0; i < 6; i++)
- {
- mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f;
- }
-
- memset(mBtn, 0, sizeof(mBtn));
-
- // factor in bandwidth? bandwidth = gViewerStats->mKBitStat
- mPerfScale = 4000.f / gSysCPU.getMHz(); // hmm. why?
-
- mLastDeviceUUID = LLSD::Integer(1);
-}
-
-// -----------------------------------------------------------------------------
-LLViewerJoystick::~LLViewerJoystick()
-{
- if (mDriverState == JDS_INITIALIZED)
- {
- terminate();
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::init(bool autoenable)
-{
-#if LIB_NDOF
- static bool libinit = false;
- mDriverState = JDS_INITIALIZING;
-
- loadDeviceIdFromSettings();
-
- if (!libinit)
- {
- // Note: The HotPlug callbacks are not actually getting called on Windows
- if (ndof_libinit(HotPlugAddCallback,
- HotPlugRemovalCallback,
- gViewerWindow->getWindow()->getDirectInput8()))
- {
- mDriverState = JDS_UNINITIALIZED;
- }
- else
- {
- // NB: ndof_libinit succeeds when there's no device
- libinit = true;
-
- // allocate memory once for an eventual device
- mNdofDev = ndof_create();
- }
- }
-
- if (libinit)
- {
- if (mNdofDev)
- {
- // di8_devices_callback callback is immediate and happens in scope of getInputDevices()
-#if LL_WINDOWS && !LL_MESA_HEADLESS
- // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib
- U32 device_type = DI8DEVCLASS_GAMECTRL;
- void* callback = &di8_devices_callback;
-#else
- // MAC doesn't support device search yet
- // On MAC there is an ndof_idsearch and it is possible to specify product
- // and manufacturer in NDOF_Device for ndof_init_first to pick specific one
- U32 device_type = 0;
- void* callback = NULL;
-#endif
- if (!gViewerWindow->getWindow()->getInputDevices(device_type, callback, NULL))
- {
- LL_INFOS("Joystick") << "Failed to gather devices from window. Falling back to ndof's init" << LL_ENDL;
- // Failed to gather devices from windows, init first suitable one
- mLastDeviceUUID = LLSD();
- void *preffered_device = NULL;
- initDevice(preffered_device);
- }
-
- if (mDriverState == JDS_INITIALIZING)
- {
- LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL;
- mDriverState = JDS_UNINITIALIZED;
- }
- }
- else
- {
- mDriverState = JDS_UNINITIALIZED;
- }
- }
-
- // Autoenable the joystick for recognized devices if nothing was connected previously
- if (!autoenable)
- {
- autoenable = gSavedSettings.getString("JoystickInitialized").empty();
- }
- updateEnabled(autoenable);
-
- if (mDriverState == JDS_INITIALIZED)
- {
- // A Joystick device is plugged in
- if (isLikeSpaceNavigator())
- {
- // It's a space navigator, we have defaults for it.
- if (gSavedSettings.getString("JoystickInitialized") != "SpaceNavigator")
- {
- // Only set the defaults if we haven't already (in case they were overridden)
- setSNDefaults();
- gSavedSettings.setString("JoystickInitialized", "SpaceNavigator");
- }
- }
- else
- {
- // It's not a Space Navigator
- gSavedSettings.setString("JoystickInitialized", "UnknownDevice");
- }
- }
- else
- {
- // No device connected, don't change any settings
- }
-
- LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
- << mNdofDev << "; libinit=" << libinit << LL_ENDL;
-#endif
-}
-
-void LLViewerJoystick::initDevice(LLSD &guid)
-{
-#if LIB_NDOF
- mLastDeviceUUID = guid;
-
-#if LL_WINDOWS && !LL_MESA_HEADLESS
- // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib
- U32 device_type = DI8DEVCLASS_GAMECTRL;
- void* callback = &di8_devices_callback;
-#else
- // MAC doesn't support device search yet
- // On MAC there is an ndof_idsearch and it is possible to specify product
- // and manufacturer in NDOF_Device for ndof_init_first to pick specific one
- U32 device_type = 0;
- void* callback = NULL;
-#endif
-
- mDriverState = JDS_INITIALIZING;
- if (!gViewerWindow->getWindow()->getInputDevices(device_type, callback, NULL))
- {
- LL_INFOS("Joystick") << "Failed to gather devices from window. Falling back to ndof's init" << LL_ENDL;
- // Failed to gather devices from windows, init first suitable one
- void *preffered_device = NULL;
- mLastDeviceUUID = LLSD();
- initDevice(preffered_device);
- }
-
- if (mDriverState == JDS_INITIALIZING)
- {
- LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL;
- mDriverState = JDS_UNINITIALIZED;
- }
-#endif
-}
-
-void LLViewerJoystick::initDevice(void * preffered_device /*LPDIRECTINPUTDEVICE8*/, std::string &name, LLSD &guid)
-{
-#if LIB_NDOF
- mLastDeviceUUID = guid;
-
- strncpy(mNdofDev->product, name.c_str(), sizeof(mNdofDev->product));
- mNdofDev->manufacturer[0] = '\0';
-
- initDevice(preffered_device);
-#endif
-}
-
-void LLViewerJoystick::initDevice(void * preffered_device /* LPDIRECTINPUTDEVICE8* */)
-{
-#if LIB_NDOF
- // Different joysticks will return different ranges of raw values.
- // Since we want to handle every device in the same uniform way,
- // we initialize the mNdofDev struct and we set the range
- // of values we would like to receive.
- //
- // HACK: On Windows, libndofdev passes our range to DI with a
- // SetProperty call. This works but with one notable exception, the
- // SpaceNavigator, who doesn't seem to care about the SetProperty
- // call. In theory, we should handle this case inside libndofdev.
- // However, the range we're setting here is arbitrary anyway,
- // so let's just use the SpaceNavigator range for our purposes.
- mNdofDev->axes_min = (long)-MAX_JOYSTICK_INPUT_VALUE;
- mNdofDev->axes_max = (long)+MAX_JOYSTICK_INPUT_VALUE;
-
- // libndofdev could be used to return deltas. Here we choose to
- // just have the absolute values instead.
- mNdofDev->absolute = 1;
- // init & use the first suitable NDOF device found on the USB chain
- // On windows preffered_device needs to be a pointer to LPDIRECTINPUTDEVICE8
- if (ndof_init_first(mNdofDev, preffered_device))
- {
- mDriverState = JDS_UNINITIALIZED;
- LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL;
- }
- else
- {
- mDriverState = JDS_INITIALIZED;
- }
-#endif
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::terminate()
-{
-#if LIB_NDOF
- if (mNdofDev != NULL)
- {
- ndof_libcleanup(); // frees alocated memory in mNdofDev
- mDriverState = JDS_UNINITIALIZED;
- mNdofDev = NULL;
- LL_INFOS("Joystick") << "Terminated connection with NDOF device." << LL_ENDL;
- }
-#endif
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::updateStatus()
-{
-#if LIB_NDOF
-
- ndof_update(mNdofDev);
-
- for (int i=0; i<6; i++)
- {
- mAxes[i] = (F32) mNdofDev->axes[i] / mNdofDev->axes_max;
- }
-
- for (int i=0; i<16; i++)
- {
- mBtn[i] = mNdofDev->buttons[i];
- }
-
-#endif
-}
-
-// -----------------------------------------------------------------------------
-F32 LLViewerJoystick::getJoystickAxis(U32 axis) const
-{
- if (axis < 6)
- {
- return mAxes[axis];
- }
- return 0.f;
-}
-
-// -----------------------------------------------------------------------------
-U32 LLViewerJoystick::getJoystickButton(U32 button) const
-{
- if (button < 16)
- {
- return mBtn[button];
- }
- return 0;
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::handleRun(F32 inc)
-{
- // Decide whether to walk or run by applying a threshold, with slight
- // hysteresis to avoid oscillating between the two with input spikes.
- // Analog speed control would be better, but not likely any time soon.
- if (inc > gSavedSettings.getF32("JoystickRunThreshold"))
- {
- if (1 == mJoystickRun)
- {
- ++mJoystickRun;
- gAgent.setRunning();
- gAgent.sendWalkRun(gAgent.getRunning());
- }
- else if (0 == mJoystickRun)
- {
- // hysteresis - respond NEXT frame
- ++mJoystickRun;
- }
- }
- else
- {
- if (mJoystickRun > 0)
- {
- --mJoystickRun;
- if (0 == mJoystickRun)
- {
- gAgent.clearRunning();
- gAgent.sendWalkRun(gAgent.getRunning());
- }
- }
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentJump()
-{
- gAgent.moveUp(1);
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentSlide(F32 inc)
-{
- if (inc < 0.f)
- {
- gAgent.moveLeft(1);
- }
- else if (inc > 0.f)
- {
- gAgent.moveLeft(-1);
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentPush(F32 inc)
-{
- if (inc < 0.f) // forward
- {
- gAgent.moveAt(1, false);
- }
- else if (inc > 0.f) // backward
- {
- gAgent.moveAt(-1, false);
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentFly(F32 inc)
-{
- if (inc < 0.f)
- {
- if (! (gAgent.getFlying() ||
- !gAgent.canFly() ||
- gAgent.upGrabbed() ||
- !gSavedSettings.getBOOL("AutomaticFly")) )
- {
- gAgent.setFlying(true);
- }
- gAgent.moveUp(1);
- }
- else if (inc > 0.f)
- {
- // crouch
- gAgent.moveUp(-1);
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentPitch(F32 pitch_inc)
-{
- if (pitch_inc < 0)
- {
- gAgent.setControlFlags(AGENT_CONTROL_PITCH_POS);
- }
- else if (pitch_inc > 0)
- {
- gAgent.setControlFlags(AGENT_CONTROL_PITCH_NEG);
- }
-
- gAgent.pitch(-pitch_inc);
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::agentYaw(F32 yaw_inc)
-{
- // Cannot steer some vehicles in mouselook if the script grabs the controls
- if (gAgentCamera.cameraMouselook() && !gSavedSettings.getBOOL("JoystickMouselookYaw"))
- {
- gAgent.rotate(-yaw_inc, gAgent.getReferenceUpVector());
- }
- else
- {
- if (yaw_inc < 0)
- {
- gAgent.setControlFlags(AGENT_CONTROL_YAW_POS);
- }
- else if (yaw_inc > 0)
- {
- gAgent.setControlFlags(AGENT_CONTROL_YAW_NEG);
- }
-
- gAgent.yaw(-yaw_inc);
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::resetDeltas(S32 axis[])
-{
- for (U32 i = 0; i < 6; i++)
- {
- sLastDelta[i] = -mAxes[axis[i]];
- sDelta[i] = 0.f;
- }
-
- sLastDelta[6] = sDelta[6] = 0.f;
- mResetFlag = false;
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::moveObjects(bool reset)
-{
- static bool toggle_send_to_sim = false;
-
- if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
- || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickBuildEnabled"))
- {
- return;
- }
-
- S32 axis[] =
- {
- gSavedSettings.getS32("JoystickAxis0"),
- gSavedSettings.getS32("JoystickAxis1"),
- gSavedSettings.getS32("JoystickAxis2"),
- gSavedSettings.getS32("JoystickAxis3"),
- gSavedSettings.getS32("JoystickAxis4"),
- gSavedSettings.getS32("JoystickAxis5"),
- };
-
- if (reset || mResetFlag)
- {
- resetDeltas(axis);
- return;
- }
-
- F32 axis_scale[] =
- {
- gSavedSettings.getF32("BuildAxisScale0"),
- gSavedSettings.getF32("BuildAxisScale1"),
- gSavedSettings.getF32("BuildAxisScale2"),
- gSavedSettings.getF32("BuildAxisScale3"),
- gSavedSettings.getF32("BuildAxisScale4"),
- gSavedSettings.getF32("BuildAxisScale5"),
- };
-
- F32 dead_zone[] =
- {
- gSavedSettings.getF32("BuildAxisDeadZone0"),
- gSavedSettings.getF32("BuildAxisDeadZone1"),
- gSavedSettings.getF32("BuildAxisDeadZone2"),
- gSavedSettings.getF32("BuildAxisDeadZone3"),
- gSavedSettings.getF32("BuildAxisDeadZone4"),
- gSavedSettings.getF32("BuildAxisDeadZone5"),
- };
-
- F32 cur_delta[6];
- F32 time = gFrameIntervalSeconds.value();
-
- // avoid making ridicously big movements if there's a big drop in fps
- if (time > .2f)
- {
- time = .2f;
- }
-
- // max feather is 32
- F32 feather = gSavedSettings.getF32("BuildFeathering");
- bool is_zero = true, absolute = gSavedSettings.getBOOL("Cursor3D");
-
- for (U32 i = 0; i < 6; i++)
- {
- cur_delta[i] = -mAxes[axis[i]];
- F32 tmp = cur_delta[i];
- if (absolute)
- {
- cur_delta[i] = cur_delta[i] - sLastDelta[i];
- }
- sLastDelta[i] = tmp;
- is_zero = is_zero && (cur_delta[i] == 0.f);
-
- if (cur_delta[i] > 0)
- {
- cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
- }
- else
- {
- cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
- }
- cur_delta[i] *= axis_scale[i];
-
- if (!absolute)
- {
- cur_delta[i] *= time;
- }
-
- sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
- }
-
- U32 upd_type = UPD_NONE;
- LLVector3 v;
-
- if (!is_zero)
- {
- // Clear AFK state if moved beyond the deadzone
- if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
- {
- gAgent.clearAFK();
- }
-
- if (sDelta[0] || sDelta[1] || sDelta[2])
- {
- upd_type |= UPD_POSITION;
- v.setVec(sDelta[0], sDelta[1], sDelta[2]);
- }
-
- if (sDelta[3] || sDelta[4] || sDelta[5])
- {
- upd_type |= UPD_ROTATION;
- }
-
- // the selection update could fail, so we won't send
- if (LLSelectMgr::getInstance()->selectionMove(v, sDelta[3],sDelta[4],sDelta[5], upd_type))
- {
- toggle_send_to_sim = true;
- }
- }
- else if (toggle_send_to_sim)
- {
- LLSelectMgr::getInstance()->sendSelectionMove();
- toggle_send_to_sim = false;
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::moveAvatar(bool reset)
-{
- if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
- || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickAvatarEnabled"))
- {
- return;
- }
-
- S32 axis[] =
- {
- // [1 0 2 4 3 5]
- // [Z X Y RZ RX RY]
- gSavedSettings.getS32("JoystickAxis0"),
- gSavedSettings.getS32("JoystickAxis1"),
- gSavedSettings.getS32("JoystickAxis2"),
- gSavedSettings.getS32("JoystickAxis3"),
- gSavedSettings.getS32("JoystickAxis4"),
- gSavedSettings.getS32("JoystickAxis5")
- };
-
- if (reset || mResetFlag)
- {
- resetDeltas(axis);
- if (reset)
- {
- // Note: moving the agent triggers agent camera mode;
- // don't do this every time we set mResetFlag (e.g. because we gained focus)
- gAgent.moveAt(0, true);
- }
- return;
- }
-
- bool is_zero = true;
- static bool button_held = false;
-
- if (mBtn[1] == 1)
- {
- // If AutomaticFly is enabled, then button1 merely causes a
- // jump (as the up/down axis already controls flying) if on the
- // ground, or cease flight if already flying.
- // If AutomaticFly is disabled, then button1 toggles flying.
- if (gSavedSettings.getBOOL("AutomaticFly"))
- {
- if (!gAgent.getFlying())
- {
- gAgent.moveUp(1);
- }
- else if (!button_held)
- {
- button_held = true;
- gAgent.setFlying(false);
- }
- }
- else if (!button_held)
- {
- button_held = true;
- gAgent.setFlying(!gAgent.getFlying());
- }
-
- is_zero = false;
- }
- else
- {
- button_held = false;
- }
-
- F32 axis_scale[] =
- {
- gSavedSettings.getF32("AvatarAxisScale0"),
- gSavedSettings.getF32("AvatarAxisScale1"),
- gSavedSettings.getF32("AvatarAxisScale2"),
- gSavedSettings.getF32("AvatarAxisScale3"),
- gSavedSettings.getF32("AvatarAxisScale4"),
- gSavedSettings.getF32("AvatarAxisScale5")
- };
-
- F32 dead_zone[] =
- {
- gSavedSettings.getF32("AvatarAxisDeadZone0"),
- gSavedSettings.getF32("AvatarAxisDeadZone1"),
- gSavedSettings.getF32("AvatarAxisDeadZone2"),
- gSavedSettings.getF32("AvatarAxisDeadZone3"),
- gSavedSettings.getF32("AvatarAxisDeadZone4"),
- gSavedSettings.getF32("AvatarAxisDeadZone5")
- };
-
- // time interval in seconds between this frame and the previous
- F32 time = gFrameIntervalSeconds.value();
-
- // avoid making ridicously big movements if there's a big drop in fps
- if (time > .2f)
- {
- time = .2f;
- }
-
- // note: max feather is 32.0
- F32 feather = gSavedSettings.getF32("AvatarFeathering");
-
- F32 cur_delta[6];
- F32 val, dom_mov = 0.f;
- U32 dom_axis = Z_I;
-#if LIB_NDOF
- bool absolute = (gSavedSettings.getBOOL("Cursor3D") && mNdofDev->absolute);
-#else
- bool absolute = false;
-#endif
- // remove dead zones and determine biggest movement on the joystick
- for (U32 i = 0; i < 6; i++)
- {
- cur_delta[i] = -mAxes[axis[i]];
- if (absolute)
- {
- F32 tmp = cur_delta[i];
- cur_delta[i] = cur_delta[i] - sLastDelta[i];
- sLastDelta[i] = tmp;
- }
-
- if (cur_delta[i] > 0)
- {
- cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
- }
- else
- {
- cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
- }
-
- // we don't care about Roll (RZ) and Z is calculated after the loop
- if (i != Z_I && i != RZ_I)
- {
- // find out the axis with the biggest joystick motion
- val = fabs(cur_delta[i]);
- if (val > dom_mov)
- {
- dom_axis = i;
- dom_mov = val;
- }
- }
-
- is_zero = is_zero && (cur_delta[i] == 0.f);
- }
-
- if (!is_zero)
- {
- // Clear AFK state if moved beyond the deadzone
- if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
- {
- gAgent.clearAFK();
- }
-
- setCameraNeedsUpdate(true);
- }
-
- // forward|backward movements overrule the real dominant movement if
- // they're bigger than its 20%. This is what you want 'cos moving forward
- // is what you do most. We also added a special (even more lenient) case
- // for RX|RY to allow walking while pitching and turning
- if (fabs(cur_delta[Z_I]) > .2f * dom_mov
- || ((dom_axis == RX_I || dom_axis == RY_I)
- && fabs(cur_delta[Z_I]) > .05f * dom_mov))
- {
- dom_axis = Z_I;
- }
-
- sDelta[X_I] = -cur_delta[X_I] * axis_scale[X_I];
- sDelta[Y_I] = -cur_delta[Y_I] * axis_scale[Y_I];
- sDelta[Z_I] = -cur_delta[Z_I] * axis_scale[Z_I];
- cur_delta[RX_I] *= -axis_scale[RX_I] * mPerfScale;
- cur_delta[RY_I] *= -axis_scale[RY_I] * mPerfScale;
-
- if (!absolute)
- {
- cur_delta[RX_I] *= time;
- cur_delta[RY_I] *= time;
- }
- sDelta[RX_I] += (cur_delta[RX_I] - sDelta[RX_I]) * time * feather;
- sDelta[RY_I] += (cur_delta[RY_I] - sDelta[RY_I]) * time * feather;
-
- handleRun((F32) sqrt(sDelta[Z_I]*sDelta[Z_I] + sDelta[X_I]*sDelta[X_I]));
-
- // Allow forward/backward movement some priority
- if (dom_axis == Z_I)
- {
- agentPush(sDelta[Z_I]); // forward/back
-
- if (fabs(sDelta[X_I]) > .1f)
- {
- agentSlide(sDelta[X_I]); // move sideways
- }
-
- if (fabs(sDelta[Y_I]) > .1f)
- {
- agentFly(sDelta[Y_I]); // up/down & crouch
- }
-
- // too many rotations during walking can be confusing, so apply
- // the deadzones one more time (quick & dirty), at 50%|30% power
- F32 eff_rx = .3f * dead_zone[RX_I];
- F32 eff_ry = .3f * dead_zone[RY_I];
-
- if (sDelta[RX_I] > 0)
- {
- eff_rx = llmax(sDelta[RX_I] - eff_rx, 0.f);
- }
- else
- {
- eff_rx = llmin(sDelta[RX_I] + eff_rx, 0.f);
- }
-
- if (sDelta[RY_I] > 0)
- {
- eff_ry = llmax(sDelta[RY_I] - eff_ry, 0.f);
- }
- else
- {
- eff_ry = llmin(sDelta[RY_I] + eff_ry, 0.f);
- }
-
-
- if (fabs(eff_rx) > 0.f || fabs(eff_ry) > 0.f)
- {
- if (gAgent.getFlying())
- {
- agentPitch(eff_rx);
- agentYaw(eff_ry);
- }
- else
- {
- agentPitch(eff_rx);
- agentYaw(2.f * eff_ry);
- }
- }
- }
- else
- {
- agentSlide(sDelta[X_I]); // move sideways
- agentFly(sDelta[Y_I]); // up/down & crouch
- agentPush(sDelta[Z_I]); // forward/back
- agentPitch(sDelta[RX_I]); // pitch
- agentYaw(sDelta[RY_I]); // turn
- }
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::moveFlycam(bool reset)
-{
- static LLQuaternion sFlycamRotation;
- static LLVector3 sFlycamPosition;
- static F32 sFlycamZoom;
-
- if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
- || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled"))
- {
- return;
- }
-
- S32 axis[] =
- {
- gSavedSettings.getS32("JoystickAxis0"),
- gSavedSettings.getS32("JoystickAxis1"),
- gSavedSettings.getS32("JoystickAxis2"),
- gSavedSettings.getS32("JoystickAxis3"),
- gSavedSettings.getS32("JoystickAxis4"),
- gSavedSettings.getS32("JoystickAxis5"),
- gSavedSettings.getS32("JoystickAxis6")
- };
-
- bool in_build_mode = LLToolMgr::getInstance()->inBuildMode();
- if (reset || mResetFlag)
- {
- sFlycamPosition = LLViewerCamera::getInstance()->getOrigin();
- sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion();
- sFlycamZoom = LLViewerCamera::getInstance()->getView();
-
- resetDeltas(axis);
-
- return;
- }
-
- F32 axis_scale[] =
- {
- gSavedSettings.getF32("FlycamAxisScale0"),
- gSavedSettings.getF32("FlycamAxisScale1"),
- gSavedSettings.getF32("FlycamAxisScale2"),
- gSavedSettings.getF32("FlycamAxisScale3"),
- gSavedSettings.getF32("FlycamAxisScale4"),
- gSavedSettings.getF32("FlycamAxisScale5"),
- gSavedSettings.getF32("FlycamAxisScale6")
- };
-
- F32 dead_zone[] =
- {
- gSavedSettings.getF32("FlycamAxisDeadZone0"),
- gSavedSettings.getF32("FlycamAxisDeadZone1"),
- gSavedSettings.getF32("FlycamAxisDeadZone2"),
- gSavedSettings.getF32("FlycamAxisDeadZone3"),
- gSavedSettings.getF32("FlycamAxisDeadZone4"),
- gSavedSettings.getF32("FlycamAxisDeadZone5"),
- gSavedSettings.getF32("FlycamAxisDeadZone6")
- };
-
- F32 time = gFrameIntervalSeconds.value();
-
- // avoid making ridiculously big movements if there's a big drop in fps
- if (time > .2f)
- {
- time = .2f;
- }
-
- F32 cur_delta[7];
- F32 feather = gSavedSettings.getF32("FlycamFeathering");
- bool absolute = gSavedSettings.getBOOL("Cursor3D");
- bool is_zero = true;
-
- for (U32 i = 0; i < 7; i++)
- {
- cur_delta[i] = -getJoystickAxis(axis[i]);
-
-
- F32 tmp = cur_delta[i];
- if (absolute)
- {
- cur_delta[i] = cur_delta[i] - sLastDelta[i];
- }
- sLastDelta[i] = tmp;
-
- if (cur_delta[i] > 0)
- {
- cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
- }
- else
- {
- cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
- }
-
- // We may want to scale camera movements up or down in build mode.
- // NOTE: this needs to remain after the deadzone calculation, otherwise
- // we have issues with flycam "jumping" when the build dialog is opened/closed -Nyx
- if (in_build_mode)
- {
- if (i == X_I || i == Y_I || i == Z_I)
- {
- static LLCachedControl<F32> build_mode_scale(gSavedSettings,"FlycamBuildModeScale", 1.0);
- cur_delta[i] *= build_mode_scale;
- }
- }
-
- cur_delta[i] *= axis_scale[i];
-
- if (!absolute)
- {
- cur_delta[i] *= time;
- }
-
- sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
-
- is_zero = is_zero && (cur_delta[i] == 0.f);
-
- }
-
- // Clear AFK state if moved beyond the deadzone
- if (!is_zero && gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
- {
- gAgent.clearAFK();
- }
-
- sFlycamPosition += LLVector3(sDelta) * sFlycamRotation;
-
- LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]);
- sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation;
-
- if (gSavedSettings.getBOOL("AutoLeveling"))
- {
- LLMatrix3 level(sFlycamRotation);
-
- LLVector3 x = LLVector3(level.mMatrix[0]);
- LLVector3 y = LLVector3(level.mMatrix[1]);
- LLVector3 z = LLVector3(level.mMatrix[2]);
-
- y.mV[2] = 0.f;
- y.normVec();
-
- level.setRows(x,y,z);
- level.orthogonalize();
-
- LLQuaternion quat(level);
- sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat);
- }
-
- if (gSavedSettings.getBOOL("ZoomDirect"))
- {
- sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6];
- }
- else
- {
- sFlycamZoom += sDelta[6];
- }
-
- LLMatrix3 mat(sFlycamRotation);
-
- LLViewerCamera::getInstance()->setView(sFlycamZoom);
- LLViewerCamera::getInstance()->setOrigin(sFlycamPosition);
- LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]);
- LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]);
- LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]);
-}
-
-// -----------------------------------------------------------------------------
-bool LLViewerJoystick::toggleFlycam()
-{
- if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled"))
- {
- mOverrideCamera = false;
- return false;
- }
-
- if (!mOverrideCamera)
- {
- gAgentCamera.changeCameraToDefault();
- }
-
- if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
- {
- gAgent.clearAFK();
- }
-
- mOverrideCamera = !mOverrideCamera;
- if (mOverrideCamera)
- {
- moveFlycam(true);
-
- }
- else
- {
- // Exiting from the flycam mode: since we are going to keep the flycam POV for
- // the main camera until the avatar moves, we need to track this situation.
- setCameraNeedsUpdate(false);
- setNeedsReset(true);
- }
- return true;
-}
-
-void LLViewerJoystick::scanJoystick()
-{
- if (mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled"))
- {
- return;
- }
-
-#if LL_WINDOWS
- // On windows, the flycam is updated syncronously with a timer, so there is
- // no need to update the status of the joystick here.
- if (!mOverrideCamera)
-#endif
- updateStatus();
-
- // App focus check Needs to happen AFTER updateStatus in case the joystick
- // is not centred when the app loses focus.
- if (!gFocusMgr.getAppHasFocus())
- {
- return;
- }
-
- static long toggle_flycam = 0;
-
- if (mBtn[0] == 1)
- {
- if (mBtn[0] != toggle_flycam)
- {
- toggle_flycam = toggleFlycam() ? 1 : 0;
- }
- }
- else
- {
- toggle_flycam = 0;
- }
-
- if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled")))
- {
- moveAvatar();
- }
-}
-
-// -----------------------------------------------------------------------------
-bool LLViewerJoystick::isDeviceUUIDSet()
-{
-#if LL_WINDOWS && !LL_MESA_HEADLESS
- // for ease of comparison and to dial less with platform specific variables, we store id as LLSD binary
- return mLastDeviceUUID.isBinary();
-#else
- return false;
-#endif
-}
-
-LLSD LLViewerJoystick::getDeviceUUID()
-{
- return mLastDeviceUUID;
-}
-
-std::string LLViewerJoystick::getDeviceUUIDString()
-{
-#if LL_WINDOWS && !LL_MESA_HEADLESS
- // Might be simpler to just convert _GUID into string everywhere, store and compare as string
- if (mLastDeviceUUID.isBinary())
- {
- S32 size = sizeof(GUID);
- LLSD::Binary data = mLastDeviceUUID.asBinary();
- GUID guid;
- memcpy(&guid, &data[0], size);
- return string_from_guid(guid);
- }
- else
- {
- return std::string();
- }
-#else
- return std::string();
- // return mLastDeviceUUID;
-#endif
-}
-
-void LLViewerJoystick::loadDeviceIdFromSettings()
-{
-#if LL_WINDOWS && !LL_MESA_HEADLESS
- // We can't save binary data to gSavedSettings, somebody editing the file will corrupt it,
- // so _GUID data gets converted to string (we probably can convert it to LLUUID with memcpy)
- // and here we need to convert it back to binary from string
- std::string device_string = gSavedSettings.getString("JoystickDeviceUUID");
- if (device_string.empty())
- {
- mLastDeviceUUID = LLSD();
- }
- else
- {
- LL_DEBUGS("Joystick") << "Looking for device by id: " << device_string << LL_ENDL;
- GUID guid;
- guid_from_string(guid, device_string);
- S32 size = sizeof(GUID);
- LLSD::Binary data; //just an std::vector
- data.resize(size);
- memcpy(&data[0], &guid /*POD _GUID*/, size);
- // We store this data in LLSD since LLSD is versatile and will be able to handle both GUID2
- // and any data MAC will need for device selection
- mLastDeviceUUID = LLSD(data);
- }
-#else
- mLastDeviceUUID = LLSD();
- //mLastDeviceUUID = gSavedSettings.getLLSD("JoystickDeviceUUID");
-#endif
-}
-
-// -----------------------------------------------------------------------------
-std::string LLViewerJoystick::getDescription()
-{
- std::string res;
-#if LIB_NDOF
- if (mDriverState == JDS_INITIALIZED && mNdofDev)
- {
- res = ll_safe_string(mNdofDev->product);
- }
-#endif
- return res;
-}
-
-bool LLViewerJoystick::isLikeSpaceNavigator() const
-{
-#if LIB_NDOF
- return (isJoystickInitialized()
- && (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0
- || strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0
- || strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0
- || strncmp(mNdofDev->product, "SpacePilot", 10) == 0));
-#else
- return false;
-#endif
-}
-
-// -----------------------------------------------------------------------------
-void LLViewerJoystick::setSNDefaults()
-{
-#if LL_DARWIN || LL_LINUX
- const float platformScale = 20.f;
- const float platformScaleAvXZ = 1.f;
- // The SpaceNavigator doesn't act as a 3D cursor on macOS / Linux.
- const bool is_3d_cursor = false;
-#else
- const float platformScale = 1.f;
- const float platformScaleAvXZ = 2.f;
- const bool is_3d_cursor = true;
-#endif
-
- //gViewerWindow->alertXml("CacheWillClear");
- LL_INFOS("Joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL;
-
- gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
- gSavedSettings.setS32("JoystickAxis1", 0); // x (slide)
- gSavedSettings.setS32("JoystickAxis2", 2); // y (up)
- gSavedSettings.setS32("JoystickAxis3", 4); // pitch
- gSavedSettings.setS32("JoystickAxis4", 3); // roll
- gSavedSettings.setS32("JoystickAxis5", 5); // yaw
- gSavedSettings.setS32("JoystickAxis6", -1);
-
- gSavedSettings.setBOOL("Cursor3D", is_3d_cursor);
- gSavedSettings.setBOOL("AutoLeveling", true);
- gSavedSettings.setBOOL("ZoomDirect", false);
-
- gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ);
- gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ);
- gSavedSettings.setF32("AvatarAxisScale2", 1.f);
- gSavedSettings.setF32("AvatarAxisScale4", .1f * platformScale);
- gSavedSettings.setF32("AvatarAxisScale5", .1f * platformScale);
- gSavedSettings.setF32("AvatarAxisScale3", 0.f * platformScale);
- gSavedSettings.setF32("BuildAxisScale1", .3f * platformScale);
- gSavedSettings.setF32("BuildAxisScale2", .3f * platformScale);
- gSavedSettings.setF32("BuildAxisScale0", .3f * platformScale);
- gSavedSettings.setF32("BuildAxisScale4", .3f * platformScale);
- gSavedSettings.setF32("BuildAxisScale5", .3f * platformScale);
- gSavedSettings.setF32("BuildAxisScale3", .3f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale1", 2.f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale2", 2.f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale0", 2.1f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale4", .1f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale5", .15f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale3", 0.f * platformScale);
- gSavedSettings.setF32("FlycamAxisScale6", 0.f * platformScale);
-
- gSavedSettings.setF32("AvatarAxisDeadZone0", .1f);
- gSavedSettings.setF32("AvatarAxisDeadZone1", .1f);
- gSavedSettings.setF32("AvatarAxisDeadZone2", .1f);
- gSavedSettings.setF32("AvatarAxisDeadZone3", 1.f);
- gSavedSettings.setF32("AvatarAxisDeadZone4", .02f);
- gSavedSettings.setF32("AvatarAxisDeadZone5", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone0", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone1", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone2", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone3", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone4", .01f);
- gSavedSettings.setF32("BuildAxisDeadZone5", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone0", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone1", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone2", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone3", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone4", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone5", .01f);
- gSavedSettings.setF32("FlycamAxisDeadZone6", 1.f);
-
- gSavedSettings.setF32("AvatarFeathering", 6.f);
- gSavedSettings.setF32("BuildFeathering", 12.f);
- gSavedSettings.setF32("FlycamFeathering", 5.f);
-}
+/**
+ * @file llviewerjoystick.cpp
+ * @brief Joystick / NDOF device functionality.
+ *
+ * $LicenseInfo:firstyear=2002&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$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llviewerjoystick.h"
+
+#include "llviewercontrol.h"
+#include "llviewerwindow.h"
+#include "llviewercamera.h"
+#include "llappviewer.h"
+#include "llkeyboard.h"
+#include "lltoolmgr.h"
+#include "llselectmgr.h"
+#include "llviewermenu.h"
+#include "llviewerwindow.h"
+#include "llwindow.h"
+#include "llagent.h"
+#include "llagentcamera.h"
+#include "llfocusmgr.h"
+
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+// Require DirectInput version 8
+#define DIRECTINPUT_VERSION 0x0800
+
+#include <dinput.h>
+#endif
+
+
+// ----------------------------------------------------------------------------
+// Constants
+
+#define X_I 1
+#define Y_I 2
+#define Z_I 0
+#define RX_I 4
+#define RY_I 5
+#define RZ_I 3
+
+F32 LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0};
+F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
+
+// These constants specify the maximum absolute value coming in from the device.
+// HACK ALERT! the value of MAX_JOYSTICK_INPUT_VALUE is not arbitrary as it
+// should be. It has to be equal to 3000 because the SpaceNavigator on Windows
+// refuses to respond to the DirectInput SetProperty call; it always returns
+// values in the [-3000, 3000] range.
+#define MAX_SPACENAVIGATOR_INPUT 3000.0f
+#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT
+
+
+#if LIB_NDOF
+std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr)
+{
+ if (! ptr)
+ {
+ return out << "nullptr";
+ }
+ out << "NDOF_Device{ ";
+ out << "axes [";
+ const char* delim = "";
+ for (short axis = 0; axis < ptr->axes_count; ++axis)
+ {
+ out << delim << ptr->axes[axis];
+ delim = ", ";
+ }
+ out << "]";
+ out << ", buttons [";
+ delim = "";
+ for (short button = 0; button < ptr->btn_count; ++button)
+ {
+ out << delim << ptr->buttons[button];
+ delim = ", ";
+ }
+ out << "]";
+ out << ", range " << ptr->axes_min << ':' << ptr->axes_max;
+ // If we don't coerce these to unsigned, they're streamed as characters,
+ // e.g. ctrl-A or nul.
+ out << ", absolute " << unsigned(ptr->absolute);
+ out << ", valid " << unsigned(ptr->valid);
+ out << ", manufacturer '" << ptr->manufacturer << "'";
+ out << ", product '" << ptr->product << "'";
+ out << ", private " << ptr->private_data;
+ out << " }";
+ return out;
+}
+#endif // LIB_NDOF
+
+
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+// this should reflect ndof and set axises, see ndofdev_win.cpp from ndof package
+BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* inst, VOID* user_data)
+{
+ if (inst->dwType & DIDFT_AXIS)
+ {
+ LPDIRECTINPUTDEVICE8 device = *((LPDIRECTINPUTDEVICE8 *)user_data);
+ DIPROPRANGE diprg;
+ diprg.diph.dwSize = sizeof(DIPROPRANGE);
+ diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
+ diprg.diph.dwHow = DIPH_BYID;
+ diprg.diph.dwObj = inst->dwType; // specify the enumerated axis
+
+ // Set the range for the axis
+ diprg.lMin = (long)-MAX_JOYSTICK_INPUT_VALUE;
+ diprg.lMax = (long)+MAX_JOYSTICK_INPUT_VALUE;
+ HRESULT hr = device->SetProperty(DIPROP_RANGE, &diprg.diph);
+
+ if (FAILED(hr))
+ {
+ return DIENUM_STOP;
+ }
+ }
+
+ return DIENUM_CONTINUE;
+}
+
+BOOL CALLBACK di8_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef)
+{
+ // Note: If a single device can function as more than one DirectInput
+ // device type, it is enumerated as each device type that it supports.
+ // Capable of detecting devices like Oculus Rift
+ if (device_instance_ptr)
+ {
+ std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName));
+
+ LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID();
+
+ bool init_device = false;
+ if (guid.isBinary())
+ {
+ std::vector<U8> bin_bucket = guid.asBinary();
+ init_device = memcmp(&bin_bucket[0], &device_instance_ptr->guidInstance, sizeof(GUID)) == 0;
+ }
+ else
+ {
+ // It might be better to init space navigator here, but if system doesn't has one,
+ // ndof will pick a random device, it is simpler to pick first device now to have an id
+ init_device = true;
+ }
+
+ if (init_device)
+ {
+ LL_DEBUGS("Joystick") << "Found and attempting to use device: " << product_name << LL_ENDL;
+ LPDIRECTINPUT8 di8_interface = *((LPDIRECTINPUT8 *)gViewerWindow->getWindow()->getDirectInput8());
+ LPDIRECTINPUTDEVICE8 device = NULL;
+
+ HRESULT status = di8_interface->CreateDevice(
+ device_instance_ptr->guidInstance, // REFGUID rguid,
+ &device, // LPDIRECTINPUTDEVICE * lplpDirectInputDevice,
+ NULL // LPUNKNOWN pUnkOuter
+ );
+
+ if (status == DI_OK)
+ {
+ // prerequisite for aquire()
+ LL_DEBUGS("Joystick") << "Device created" << LL_ENDL;
+ status = device->SetDataFormat(&c_dfDIJoystick); // c_dfDIJoystick2
+ }
+
+ if (status == DI_OK)
+ {
+ // set properties
+ LL_DEBUGS("Joystick") << "Format set" << LL_ENDL;
+ status = device->EnumObjects(EnumObjectsCallback, &device, DIDFT_ALL);
+ }
+
+ if (status == DI_OK)
+ {
+ LL_DEBUGS("Joystick") << "Properties updated" << LL_ENDL;
+
+ S32 size = sizeof(GUID);
+ LLSD::Binary data; //just an std::vector
+ data.resize(size);
+ memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size);
+ LLViewerJoystick::getInstance()->initDevice(&device, product_name, LLSD(data));
+ return DIENUM_STOP;
+ }
+ }
+ else
+ {
+ LL_DEBUGS("Joystick") << "Found device: " << product_name << LL_ENDL;
+ }
+ }
+ return DIENUM_CONTINUE;
+}
+
+// Windows guids
+// This is GUID2 so teoretically it can be memcpy copied into LLUUID
+void guid_from_string(GUID &guid, const std::string &input)
+{
+ CLSIDFromString(utf8str_to_utf16str(input).c_str(), &guid);
+}
+
+std::string string_from_guid(const GUID &guid)
+{
+ OLECHAR* guidString; //wchat
+ StringFromCLSID(guid, &guidString);
+
+ // use guidString...
+
+ std::string res = utf16str_to_utf8str(llutf16string(guidString));
+ // ensure memory is freed
+ ::CoTaskMemFree(guidString);
+
+ return res;
+}
+#elif LL_DARWIN
+
+bool macos_devices_callback(std::string &product_name, LLSD &data, void* userdata)
+{
+ std::string product = data["product"].asString();
+
+ return LLViewerJoystick::getInstance()->initDevice(nullptr, product, data);
+}
+
+#endif
+
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::updateEnabled(bool autoenable)
+{
+ if (mDriverState == JDS_UNINITIALIZED)
+ {
+ gSavedSettings.setBOOL("JoystickEnabled", false);
+ }
+ else
+ {
+ // autoenable if user specifically chose this device
+ if (autoenable && (isLikeSpaceNavigator() || isDeviceUUIDSet()))
+ {
+ gSavedSettings.setBOOL("JoystickEnabled", true );
+ }
+ }
+ if (!gSavedSettings.getBOOL("JoystickEnabled"))
+ {
+ mOverrideCamera = false;
+ }
+}
+
+void LLViewerJoystick::setOverrideCamera(bool val)
+{
+ if (!gSavedSettings.getBOOL("JoystickEnabled"))
+ {
+ mOverrideCamera = false;
+ }
+ else
+ {
+ mOverrideCamera = val;
+ }
+
+ if (mOverrideCamera)
+ {
+ gAgentCamera.changeCameraToDefault();
+ }
+}
+
+// -----------------------------------------------------------------------------
+#if LIB_NDOF
+NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev)
+{
+ NDOF_HotPlugResult res = NDOF_DISCARD_HOTPLUGGED;
+ LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+ if (joystick->mDriverState == JDS_UNINITIALIZED)
+ {
+ LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL;
+ ndof_dump(stderr, dev);
+ joystick->mNdofDev = dev;
+ joystick->mDriverState = JDS_INITIALIZED;
+ res = NDOF_KEEP_HOTPLUGGED;
+ }
+ joystick->updateEnabled(true);
+ return res;
+}
+#endif
+
+// -----------------------------------------------------------------------------
+#if LIB_NDOF
+void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev)
+{
+ LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
+ if (joystick->mNdofDev == dev)
+ {
+ LL_INFOS("Joystick") << "HotPlugRemovalCallback: joystick->mNdofDev="
+ << joystick->mNdofDev << "; removed device:" << LL_ENDL;
+ ndof_dump(stderr, dev);
+ joystick->mDriverState = JDS_UNINITIALIZED;
+ }
+ joystick->updateEnabled(true);
+}
+#endif
+
+// -----------------------------------------------------------------------------
+LLViewerJoystick::LLViewerJoystick()
+: mDriverState(JDS_UNINITIALIZED),
+ mNdofDev(NULL),
+ mResetFlag(false),
+ mCameraUpdated(true),
+ mOverrideCamera(false),
+ mJoystickRun(0)
+{
+ for (int i = 0; i < 6; i++)
+ {
+ mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f;
+ }
+
+ memset(mBtn, 0, sizeof(mBtn));
+
+ // factor in bandwidth? bandwidth = gViewerStats->mKBitStat
+ mPerfScale = 4000.f / gSysCPU.getMHz(); // hmm. why?
+
+ mLastDeviceUUID = LLSD::Integer(1);
+}
+
+// -----------------------------------------------------------------------------
+LLViewerJoystick::~LLViewerJoystick()
+{
+ if (mDriverState == JDS_INITIALIZED)
+ {
+ terminate();
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::init(bool autoenable)
+{
+#if LIB_NDOF
+ static bool libinit = false;
+ mDriverState = JDS_INITIALIZING;
+
+ loadDeviceIdFromSettings();
+
+ if (!libinit)
+ {
+ // Note: The HotPlug callbacks are not actually getting called on Windows
+ if (ndof_libinit(HotPlugAddCallback,
+ HotPlugRemovalCallback,
+ gViewerWindow->getWindow()->getDirectInput8()))
+ {
+ mDriverState = JDS_UNINITIALIZED;
+ }
+ else
+ {
+ // NB: ndof_libinit succeeds when there's no device
+ libinit = true;
+
+ // allocate memory once for an eventual device
+ mNdofDev = ndof_create();
+ }
+ }
+
+ if (libinit)
+ {
+ if (mNdofDev)
+ {
+ U32 device_type = 0;
+ void* win_callback = nullptr;
+ std::function<bool(std::string&, LLSD&, void*)> osx_callback;
+ // di8_devices_callback callback is immediate and happens in scope of getInputDevices()
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib
+ device_type = DI8DEVCLASS_GAMECTRL;
+ win_callback = &di8_devices_callback;
+#elif LL_DARWIN
+ osx_callback = macos_devices_callback;
+
+ if (mLastDeviceUUID.isMap())
+ {
+ std::string manufacturer = mLastDeviceUUID["manufacturer"].asString();
+ std::string product = mLastDeviceUUID["product"].asString();
+
+ strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer));
+ strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product));
+
+ if (ndof_init_first(mNdofDev, nullptr))
+ {
+ mDriverState = JDS_INITIALIZING;
+ // Saved device no longer exist
+ // No device found
+ LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL;
+ }
+ else
+ {
+ mDriverState = JDS_INITIALIZED;
+ }
+ }
+#endif
+ if (mDriverState != JDS_INITIALIZED)
+ {
+ if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL))
+ {
+ LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL;
+ // Failed to gather devices, init first suitable one
+ mLastDeviceUUID = LLSD();
+ void *preffered_device = NULL;
+ initDevice(preffered_device);
+ }
+ }
+
+ if (mDriverState == JDS_INITIALIZING)
+ {
+ LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL;
+ mDriverState = JDS_UNINITIALIZED;
+ }
+ }
+ else
+ {
+ mDriverState = JDS_UNINITIALIZED;
+ }
+ }
+
+ // Autoenable the joystick for recognized devices if nothing was connected previously
+ if (!autoenable)
+ {
+ autoenable = gSavedSettings.getString("JoystickInitialized").empty();
+ }
+ updateEnabled(autoenable);
+
+ if (mDriverState == JDS_INITIALIZED)
+ {
+ // A Joystick device is plugged in
+ if (isLikeSpaceNavigator())
+ {
+ // It's a space navigator, we have defaults for it.
+ if (gSavedSettings.getString("JoystickInitialized") != "SpaceNavigator")
+ {
+ // Only set the defaults if we haven't already (in case they were overridden)
+ setSNDefaults();
+ gSavedSettings.setString("JoystickInitialized", "SpaceNavigator");
+ }
+ }
+ else
+ {
+ // It's not a Space Navigator
+ gSavedSettings.setString("JoystickInitialized", "UnknownDevice");
+ }
+ }
+ else
+ {
+ // No device connected, don't change any settings
+ }
+
+ LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
+ << mNdofDev << "; libinit=" << libinit << LL_ENDL;
+#endif
+}
+
+void LLViewerJoystick::initDevice(LLSD &guid)
+{
+#if LIB_NDOF
+ mLastDeviceUUID = guid;
+ U32 device_type = 0;
+ void* win_callback = nullptr;
+ std::function<bool(std::string&, LLSD&, void*)> osx_callback;
+ mDriverState = JDS_INITIALIZING;
+
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib
+ device_type = DI8DEVCLASS_GAMECTRL;
+ win_callback = &di8_devices_callback;
+#elif LL_DARWIN
+ osx_callback = macos_devices_callback;
+ if (mLastDeviceUUID.isMap())
+ {
+ std::string manufacturer = mLastDeviceUUID["manufacturer"].asString();
+ std::string product = mLastDeviceUUID["product"].asString();
+
+ strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer));
+ strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product));
+
+ if (ndof_init_first(mNdofDev, nullptr))
+ {
+ mDriverState = JDS_INITIALIZING;
+ // Saved device no longer exist
+ // Np other device present
+ LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL;
+ }
+ else
+ {
+ mDriverState = JDS_INITIALIZED;
+ }
+ }
+#endif
+
+ if (mDriverState != JDS_INITIALIZED)
+ {
+ if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL))
+ {
+ LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL;
+ // Failed to gather devices from window, init first suitable one
+ void *preffered_device = NULL;
+ mLastDeviceUUID = LLSD();
+ initDevice(preffered_device);
+ }
+ }
+
+ if (mDriverState == JDS_INITIALIZING)
+ {
+ LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL;
+ mDriverState = JDS_UNINITIALIZED;
+ }
+#endif
+}
+
+bool LLViewerJoystick::initDevice(void * preffered_device /*LPDIRECTINPUTDEVICE8*/, std::string &name, LLSD &guid)
+{
+#if LIB_NDOF
+ mLastDeviceUUID = guid;
+
+#if LL_DARWIN
+ if (guid.isMap())
+ {
+ std::string manufacturer = mLastDeviceUUID["manufacturer"].asString();
+ std::string product = mLastDeviceUUID["product"].asString();
+
+ strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer));
+ strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product));
+ }
+ else
+ {
+ mNdofDev->product[0] = '\0';
+ mNdofDev->manufacturer[0] = '\0';
+ }
+#else
+ strncpy(mNdofDev->product, name.c_str(), sizeof(mNdofDev->product));
+ mNdofDev->manufacturer[0] = '\0';
+#endif
+
+ return initDevice(preffered_device);
+#else
+ return false;
+#endif
+}
+
+bool LLViewerJoystick::initDevice(void * preffered_device /* LPDIRECTINPUTDEVICE8* */)
+{
+#if LIB_NDOF
+ // Different joysticks will return different ranges of raw values.
+ // Since we want to handle every device in the same uniform way,
+ // we initialize the mNdofDev struct and we set the range
+ // of values we would like to receive.
+ //
+ // HACK: On Windows, libndofdev passes our range to DI with a
+ // SetProperty call. This works but with one notable exception, the
+ // SpaceNavigator, who doesn't seem to care about the SetProperty
+ // call. In theory, we should handle this case inside libndofdev.
+ // However, the range we're setting here is arbitrary anyway,
+ // so let's just use the SpaceNavigator range for our purposes.
+ mNdofDev->axes_min = (long)-MAX_JOYSTICK_INPUT_VALUE;
+ mNdofDev->axes_max = (long)+MAX_JOYSTICK_INPUT_VALUE;
+
+ // libndofdev could be used to return deltas. Here we choose to
+ // just have the absolute values instead.
+ mNdofDev->absolute = 1;
+ // init & use the first suitable NDOF device found on the USB chain
+ // On windows preffered_device needs to be a pointer to LPDIRECTINPUTDEVICE8
+ if (ndof_init_first(mNdofDev, preffered_device))
+ {
+ mDriverState = JDS_UNINITIALIZED;
+ LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL;
+ }
+ else
+ {
+ mDriverState = JDS_INITIALIZED;
+ return true;
+ }
+#endif
+ return false;
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::terminate()
+{
+#if LIB_NDOF
+ if (mNdofDev != NULL)
+ {
+ ndof_libcleanup(); // frees alocated memory in mNdofDev
+ mDriverState = JDS_UNINITIALIZED;
+ mNdofDev = NULL;
+ LL_INFOS("Joystick") << "Terminated connection with NDOF device." << LL_ENDL;
+ }
+#endif
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::updateStatus()
+{
+#if LIB_NDOF
+
+ ndof_update(mNdofDev);
+
+ for (int i=0; i<6; i++)
+ {
+ mAxes[i] = (F32) mNdofDev->axes[i] / mNdofDev->axes_max;
+ }
+
+ for (int i=0; i<16; i++)
+ {
+ mBtn[i] = mNdofDev->buttons[i];
+ }
+
+#endif
+}
+
+// -----------------------------------------------------------------------------
+F32 LLViewerJoystick::getJoystickAxis(U32 axis) const
+{
+ if (axis < 6)
+ {
+ return mAxes[axis];
+ }
+ return 0.f;
+}
+
+// -----------------------------------------------------------------------------
+U32 LLViewerJoystick::getJoystickButton(U32 button) const
+{
+ if (button < 16)
+ {
+ return mBtn[button];
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::handleRun(F32 inc)
+{
+ // Decide whether to walk or run by applying a threshold, with slight
+ // hysteresis to avoid oscillating between the two with input spikes.
+ // Analog speed control would be better, but not likely any time soon.
+ if (inc > gSavedSettings.getF32("JoystickRunThreshold"))
+ {
+ if (1 == mJoystickRun)
+ {
+ ++mJoystickRun;
+ gAgent.setRunning();
+ gAgent.sendWalkRun(gAgent.getRunning());
+ }
+ else if (0 == mJoystickRun)
+ {
+ // hysteresis - respond NEXT frame
+ ++mJoystickRun;
+ }
+ }
+ else
+ {
+ if (mJoystickRun > 0)
+ {
+ --mJoystickRun;
+ if (0 == mJoystickRun)
+ {
+ gAgent.clearRunning();
+ gAgent.sendWalkRun(gAgent.getRunning());
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentJump()
+{
+ gAgent.moveUp(1);
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentSlide(F32 inc)
+{
+ if (inc < 0.f)
+ {
+ gAgent.moveLeft(1);
+ }
+ else if (inc > 0.f)
+ {
+ gAgent.moveLeft(-1);
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentPush(F32 inc)
+{
+ if (inc < 0.f) // forward
+ {
+ gAgent.moveAt(1, false);
+ }
+ else if (inc > 0.f) // backward
+ {
+ gAgent.moveAt(-1, false);
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentFly(F32 inc)
+{
+ if (inc < 0.f)
+ {
+ if (! (gAgent.getFlying() ||
+ !gAgent.canFly() ||
+ gAgent.upGrabbed() ||
+ !gSavedSettings.getBOOL("AutomaticFly")) )
+ {
+ gAgent.setFlying(true);
+ }
+ gAgent.moveUp(1);
+ }
+ else if (inc > 0.f)
+ {
+ // crouch
+ gAgent.moveUp(-1);
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentPitch(F32 pitch_inc)
+{
+ if (pitch_inc < 0)
+ {
+ gAgent.setControlFlags(AGENT_CONTROL_PITCH_POS);
+ }
+ else if (pitch_inc > 0)
+ {
+ gAgent.setControlFlags(AGENT_CONTROL_PITCH_NEG);
+ }
+
+ gAgent.pitch(-pitch_inc);
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::agentYaw(F32 yaw_inc)
+{
+ // Cannot steer some vehicles in mouselook if the script grabs the controls
+ if (gAgentCamera.cameraMouselook() && !gSavedSettings.getBOOL("JoystickMouselookYaw"))
+ {
+ gAgent.rotate(-yaw_inc, gAgent.getReferenceUpVector());
+ }
+ else
+ {
+ if (yaw_inc < 0)
+ {
+ gAgent.setControlFlags(AGENT_CONTROL_YAW_POS);
+ }
+ else if (yaw_inc > 0)
+ {
+ gAgent.setControlFlags(AGENT_CONTROL_YAW_NEG);
+ }
+
+ gAgent.yaw(-yaw_inc);
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::resetDeltas(S32 axis[])
+{
+ for (U32 i = 0; i < 6; i++)
+ {
+ sLastDelta[i] = -mAxes[axis[i]];
+ sDelta[i] = 0.f;
+ }
+
+ sLastDelta[6] = sDelta[6] = 0.f;
+ mResetFlag = false;
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveObjects(bool reset)
+{
+ static bool toggle_send_to_sim = false;
+
+ if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
+ || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickBuildEnabled"))
+ {
+ return;
+ }
+
+ S32 axis[] =
+ {
+ gSavedSettings.getS32("JoystickAxis0"),
+ gSavedSettings.getS32("JoystickAxis1"),
+ gSavedSettings.getS32("JoystickAxis2"),
+ gSavedSettings.getS32("JoystickAxis3"),
+ gSavedSettings.getS32("JoystickAxis4"),
+ gSavedSettings.getS32("JoystickAxis5"),
+ };
+
+ if (reset || mResetFlag)
+ {
+ resetDeltas(axis);
+ return;
+ }
+
+ F32 axis_scale[] =
+ {
+ gSavedSettings.getF32("BuildAxisScale0"),
+ gSavedSettings.getF32("BuildAxisScale1"),
+ gSavedSettings.getF32("BuildAxisScale2"),
+ gSavedSettings.getF32("BuildAxisScale3"),
+ gSavedSettings.getF32("BuildAxisScale4"),
+ gSavedSettings.getF32("BuildAxisScale5"),
+ };
+
+ F32 dead_zone[] =
+ {
+ gSavedSettings.getF32("BuildAxisDeadZone0"),
+ gSavedSettings.getF32("BuildAxisDeadZone1"),
+ gSavedSettings.getF32("BuildAxisDeadZone2"),
+ gSavedSettings.getF32("BuildAxisDeadZone3"),
+ gSavedSettings.getF32("BuildAxisDeadZone4"),
+ gSavedSettings.getF32("BuildAxisDeadZone5"),
+ };
+
+ F32 cur_delta[6];
+ F32 time = gFrameIntervalSeconds.value();
+
+ // avoid making ridicously big movements if there's a big drop in fps
+ if (time > .2f)
+ {
+ time = .2f;
+ }
+
+ // max feather is 32
+ F32 feather = gSavedSettings.getF32("BuildFeathering");
+ bool is_zero = true, absolute = gSavedSettings.getBOOL("Cursor3D");
+
+ for (U32 i = 0; i < 6; i++)
+ {
+ cur_delta[i] = -mAxes[axis[i]];
+ F32 tmp = cur_delta[i];
+ if (absolute)
+ {
+ cur_delta[i] = cur_delta[i] - sLastDelta[i];
+ }
+ sLastDelta[i] = tmp;
+ is_zero = is_zero && (cur_delta[i] == 0.f);
+
+ if (cur_delta[i] > 0)
+ {
+ cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
+ }
+ else
+ {
+ cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
+ }
+ cur_delta[i] *= axis_scale[i];
+
+ if (!absolute)
+ {
+ cur_delta[i] *= time;
+ }
+
+ sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
+ }
+
+ U32 upd_type = UPD_NONE;
+ LLVector3 v;
+
+ if (!is_zero)
+ {
+ // Clear AFK state if moved beyond the deadzone
+ if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
+ {
+ gAgent.clearAFK();
+ }
+
+ if (sDelta[0] || sDelta[1] || sDelta[2])
+ {
+ upd_type |= UPD_POSITION;
+ v.setVec(sDelta[0], sDelta[1], sDelta[2]);
+ }
+
+ if (sDelta[3] || sDelta[4] || sDelta[5])
+ {
+ upd_type |= UPD_ROTATION;
+ }
+
+ // the selection update could fail, so we won't send
+ if (LLSelectMgr::getInstance()->selectionMove(v, sDelta[3],sDelta[4],sDelta[5], upd_type))
+ {
+ toggle_send_to_sim = true;
+ }
+ }
+ else if (toggle_send_to_sim)
+ {
+ LLSelectMgr::getInstance()->sendSelectionMove();
+ toggle_send_to_sim = false;
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveAvatar(bool reset)
+{
+ if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
+ || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickAvatarEnabled"))
+ {
+ return;
+ }
+
+ S32 axis[] =
+ {
+ // [1 0 2 4 3 5]
+ // [Z X Y RZ RX RY]
+ gSavedSettings.getS32("JoystickAxis0"),
+ gSavedSettings.getS32("JoystickAxis1"),
+ gSavedSettings.getS32("JoystickAxis2"),
+ gSavedSettings.getS32("JoystickAxis3"),
+ gSavedSettings.getS32("JoystickAxis4"),
+ gSavedSettings.getS32("JoystickAxis5")
+ };
+
+ if (reset || mResetFlag)
+ {
+ resetDeltas(axis);
+ if (reset)
+ {
+ // Note: moving the agent triggers agent camera mode;
+ // don't do this every time we set mResetFlag (e.g. because we gained focus)
+ gAgent.moveAt(0, true);
+ }
+ return;
+ }
+
+ bool is_zero = true;
+ static bool button_held = false;
+
+ if (mBtn[1] == 1)
+ {
+ // If AutomaticFly is enabled, then button1 merely causes a
+ // jump (as the up/down axis already controls flying) if on the
+ // ground, or cease flight if already flying.
+ // If AutomaticFly is disabled, then button1 toggles flying.
+ if (gSavedSettings.getBOOL("AutomaticFly"))
+ {
+ if (!gAgent.getFlying())
+ {
+ gAgent.moveUp(1);
+ }
+ else if (!button_held)
+ {
+ button_held = true;
+ gAgent.setFlying(false);
+ }
+ }
+ else if (!button_held)
+ {
+ button_held = true;
+ gAgent.setFlying(!gAgent.getFlying());
+ }
+
+ is_zero = false;
+ }
+ else
+ {
+ button_held = false;
+ }
+
+ F32 axis_scale[] =
+ {
+ gSavedSettings.getF32("AvatarAxisScale0"),
+ gSavedSettings.getF32("AvatarAxisScale1"),
+ gSavedSettings.getF32("AvatarAxisScale2"),
+ gSavedSettings.getF32("AvatarAxisScale3"),
+ gSavedSettings.getF32("AvatarAxisScale4"),
+ gSavedSettings.getF32("AvatarAxisScale5")
+ };
+
+ F32 dead_zone[] =
+ {
+ gSavedSettings.getF32("AvatarAxisDeadZone0"),
+ gSavedSettings.getF32("AvatarAxisDeadZone1"),
+ gSavedSettings.getF32("AvatarAxisDeadZone2"),
+ gSavedSettings.getF32("AvatarAxisDeadZone3"),
+ gSavedSettings.getF32("AvatarAxisDeadZone4"),
+ gSavedSettings.getF32("AvatarAxisDeadZone5")
+ };
+
+ // time interval in seconds between this frame and the previous
+ F32 time = gFrameIntervalSeconds.value();
+
+ // avoid making ridicously big movements if there's a big drop in fps
+ if (time > .2f)
+ {
+ time = .2f;
+ }
+
+ // note: max feather is 32.0
+ F32 feather = gSavedSettings.getF32("AvatarFeathering");
+
+ F32 cur_delta[6];
+ F32 val, dom_mov = 0.f;
+ U32 dom_axis = Z_I;
+#if LIB_NDOF
+ bool absolute = (gSavedSettings.getBOOL("Cursor3D") && mNdofDev->absolute);
+#else
+ bool absolute = false;
+#endif
+ // remove dead zones and determine biggest movement on the joystick
+ for (U32 i = 0; i < 6; i++)
+ {
+ cur_delta[i] = -mAxes[axis[i]];
+ if (absolute)
+ {
+ F32 tmp = cur_delta[i];
+ cur_delta[i] = cur_delta[i] - sLastDelta[i];
+ sLastDelta[i] = tmp;
+ }
+
+ if (cur_delta[i] > 0)
+ {
+ cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
+ }
+ else
+ {
+ cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
+ }
+
+ // we don't care about Roll (RZ) and Z is calculated after the loop
+ if (i != Z_I && i != RZ_I)
+ {
+ // find out the axis with the biggest joystick motion
+ val = fabs(cur_delta[i]);
+ if (val > dom_mov)
+ {
+ dom_axis = i;
+ dom_mov = val;
+ }
+ }
+
+ is_zero = is_zero && (cur_delta[i] == 0.f);
+ }
+
+ if (!is_zero)
+ {
+ // Clear AFK state if moved beyond the deadzone
+ if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
+ {
+ gAgent.clearAFK();
+ }
+
+ setCameraNeedsUpdate(true);
+ }
+
+ // forward|backward movements overrule the real dominant movement if
+ // they're bigger than its 20%. This is what you want 'cos moving forward
+ // is what you do most. We also added a special (even more lenient) case
+ // for RX|RY to allow walking while pitching and turning
+ if (fabs(cur_delta[Z_I]) > .2f * dom_mov
+ || ((dom_axis == RX_I || dom_axis == RY_I)
+ && fabs(cur_delta[Z_I]) > .05f * dom_mov))
+ {
+ dom_axis = Z_I;
+ }
+
+ sDelta[X_I] = -cur_delta[X_I] * axis_scale[X_I];
+ sDelta[Y_I] = -cur_delta[Y_I] * axis_scale[Y_I];
+ sDelta[Z_I] = -cur_delta[Z_I] * axis_scale[Z_I];
+ cur_delta[RX_I] *= -axis_scale[RX_I] * mPerfScale;
+ cur_delta[RY_I] *= -axis_scale[RY_I] * mPerfScale;
+
+ if (!absolute)
+ {
+ cur_delta[RX_I] *= time;
+ cur_delta[RY_I] *= time;
+ }
+ sDelta[RX_I] += (cur_delta[RX_I] - sDelta[RX_I]) * time * feather;
+ sDelta[RY_I] += (cur_delta[RY_I] - sDelta[RY_I]) * time * feather;
+
+ handleRun((F32) sqrt(sDelta[Z_I]*sDelta[Z_I] + sDelta[X_I]*sDelta[X_I]));
+
+ // Allow forward/backward movement some priority
+ if (dom_axis == Z_I)
+ {
+ agentPush(sDelta[Z_I]); // forward/back
+
+ if (fabs(sDelta[X_I]) > .1f)
+ {
+ agentSlide(sDelta[X_I]); // move sideways
+ }
+
+ if (fabs(sDelta[Y_I]) > .1f)
+ {
+ agentFly(sDelta[Y_I]); // up/down & crouch
+ }
+
+ // too many rotations during walking can be confusing, so apply
+ // the deadzones one more time (quick & dirty), at 50%|30% power
+ F32 eff_rx = .3f * dead_zone[RX_I];
+ F32 eff_ry = .3f * dead_zone[RY_I];
+
+ if (sDelta[RX_I] > 0)
+ {
+ eff_rx = llmax(sDelta[RX_I] - eff_rx, 0.f);
+ }
+ else
+ {
+ eff_rx = llmin(sDelta[RX_I] + eff_rx, 0.f);
+ }
+
+ if (sDelta[RY_I] > 0)
+ {
+ eff_ry = llmax(sDelta[RY_I] - eff_ry, 0.f);
+ }
+ else
+ {
+ eff_ry = llmin(sDelta[RY_I] + eff_ry, 0.f);
+ }
+
+
+ if (fabs(eff_rx) > 0.f || fabs(eff_ry) > 0.f)
+ {
+ if (gAgent.getFlying())
+ {
+ agentPitch(eff_rx);
+ agentYaw(eff_ry);
+ }
+ else
+ {
+ agentPitch(eff_rx);
+ agentYaw(2.f * eff_ry);
+ }
+ }
+ }
+ else
+ {
+ agentSlide(sDelta[X_I]); // move sideways
+ agentFly(sDelta[Y_I]); // up/down & crouch
+ agentPush(sDelta[Z_I]); // forward/back
+ agentPitch(sDelta[RX_I]); // pitch
+ agentYaw(sDelta[RY_I]); // turn
+ }
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::moveFlycam(bool reset)
+{
+ static LLQuaternion sFlycamRotation;
+ static LLVector3 sFlycamPosition;
+ static F32 sFlycamZoom;
+
+ if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED
+ || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled"))
+ {
+ return;
+ }
+
+ S32 axis[] =
+ {
+ gSavedSettings.getS32("JoystickAxis0"),
+ gSavedSettings.getS32("JoystickAxis1"),
+ gSavedSettings.getS32("JoystickAxis2"),
+ gSavedSettings.getS32("JoystickAxis3"),
+ gSavedSettings.getS32("JoystickAxis4"),
+ gSavedSettings.getS32("JoystickAxis5"),
+ gSavedSettings.getS32("JoystickAxis6")
+ };
+
+ bool in_build_mode = LLToolMgr::getInstance()->inBuildMode();
+ if (reset || mResetFlag)
+ {
+ sFlycamPosition = LLViewerCamera::getInstance()->getOrigin();
+ sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion();
+ sFlycamZoom = LLViewerCamera::getInstance()->getView();
+
+ resetDeltas(axis);
+
+ return;
+ }
+
+ F32 axis_scale[] =
+ {
+ gSavedSettings.getF32("FlycamAxisScale0"),
+ gSavedSettings.getF32("FlycamAxisScale1"),
+ gSavedSettings.getF32("FlycamAxisScale2"),
+ gSavedSettings.getF32("FlycamAxisScale3"),
+ gSavedSettings.getF32("FlycamAxisScale4"),
+ gSavedSettings.getF32("FlycamAxisScale5"),
+ gSavedSettings.getF32("FlycamAxisScale6")
+ };
+
+ F32 dead_zone[] =
+ {
+ gSavedSettings.getF32("FlycamAxisDeadZone0"),
+ gSavedSettings.getF32("FlycamAxisDeadZone1"),
+ gSavedSettings.getF32("FlycamAxisDeadZone2"),
+ gSavedSettings.getF32("FlycamAxisDeadZone3"),
+ gSavedSettings.getF32("FlycamAxisDeadZone4"),
+ gSavedSettings.getF32("FlycamAxisDeadZone5"),
+ gSavedSettings.getF32("FlycamAxisDeadZone6")
+ };
+
+ F32 time = gFrameIntervalSeconds.value();
+
+ // avoid making ridiculously big movements if there's a big drop in fps
+ if (time > .2f)
+ {
+ time = .2f;
+ }
+
+ F32 cur_delta[7];
+ F32 feather = gSavedSettings.getF32("FlycamFeathering");
+ bool absolute = gSavedSettings.getBOOL("Cursor3D");
+ bool is_zero = true;
+
+ for (U32 i = 0; i < 7; i++)
+ {
+ cur_delta[i] = -getJoystickAxis(axis[i]);
+
+
+ F32 tmp = cur_delta[i];
+ if (absolute)
+ {
+ cur_delta[i] = cur_delta[i] - sLastDelta[i];
+ }
+ sLastDelta[i] = tmp;
+
+ if (cur_delta[i] > 0)
+ {
+ cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f);
+ }
+ else
+ {
+ cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f);
+ }
+
+ // We may want to scale camera movements up or down in build mode.
+ // NOTE: this needs to remain after the deadzone calculation, otherwise
+ // we have issues with flycam "jumping" when the build dialog is opened/closed -Nyx
+ if (in_build_mode)
+ {
+ if (i == X_I || i == Y_I || i == Z_I)
+ {
+ static LLCachedControl<F32> build_mode_scale(gSavedSettings,"FlycamBuildModeScale", 1.0);
+ cur_delta[i] *= build_mode_scale;
+ }
+ }
+
+ cur_delta[i] *= axis_scale[i];
+
+ if (!absolute)
+ {
+ cur_delta[i] *= time;
+ }
+
+ sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather;
+
+ is_zero = is_zero && (cur_delta[i] == 0.f);
+
+ }
+
+ // Clear AFK state if moved beyond the deadzone
+ if (!is_zero && gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
+ {
+ gAgent.clearAFK();
+ }
+
+ sFlycamPosition += LLVector3(sDelta) * sFlycamRotation;
+
+ LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]);
+ sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation;
+
+ if (gSavedSettings.getBOOL("AutoLeveling"))
+ {
+ LLMatrix3 level(sFlycamRotation);
+
+ LLVector3 x = LLVector3(level.mMatrix[0]);
+ LLVector3 y = LLVector3(level.mMatrix[1]);
+ LLVector3 z = LLVector3(level.mMatrix[2]);
+
+ y.mV[2] = 0.f;
+ y.normVec();
+
+ level.setRows(x,y,z);
+ level.orthogonalize();
+
+ LLQuaternion quat(level);
+ sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat);
+ }
+
+ if (gSavedSettings.getBOOL("ZoomDirect"))
+ {
+ sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6];
+ }
+ else
+ {
+ sFlycamZoom += sDelta[6];
+ }
+
+ LLMatrix3 mat(sFlycamRotation);
+
+ LLViewerCamera::getInstance()->setView(sFlycamZoom);
+ LLViewerCamera::getInstance()->setOrigin(sFlycamPosition);
+ LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]);
+ LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]);
+ LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]);
+}
+
+// -----------------------------------------------------------------------------
+bool LLViewerJoystick::toggleFlycam()
+{
+ if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled"))
+ {
+ mOverrideCamera = false;
+ return false;
+ }
+
+ if (!mOverrideCamera)
+ {
+ gAgentCamera.changeCameraToDefault();
+ }
+
+ if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME)
+ {
+ gAgent.clearAFK();
+ }
+
+ mOverrideCamera = !mOverrideCamera;
+ if (mOverrideCamera)
+ {
+ moveFlycam(true);
+
+ }
+ else
+ {
+ // Exiting from the flycam mode: since we are going to keep the flycam POV for
+ // the main camera until the avatar moves, we need to track this situation.
+ setCameraNeedsUpdate(false);
+ setNeedsReset(true);
+ }
+ return true;
+}
+
+void LLViewerJoystick::scanJoystick()
+{
+ if (mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled"))
+ {
+ return;
+ }
+
+#if LL_WINDOWS
+ // On windows, the flycam is updated syncronously with a timer, so there is
+ // no need to update the status of the joystick here.
+ if (!mOverrideCamera)
+#endif
+ updateStatus();
+
+ // App focus check Needs to happen AFTER updateStatus in case the joystick
+ // is not centred when the app loses focus.
+ if (!gFocusMgr.getAppHasFocus())
+ {
+ return;
+ }
+
+ static long toggle_flycam = 0;
+
+ if (mBtn[0] == 1)
+ {
+ if (mBtn[0] != toggle_flycam)
+ {
+ toggle_flycam = toggleFlycam() ? 1 : 0;
+ }
+ }
+ else
+ {
+ toggle_flycam = 0;
+ }
+
+ if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled")))
+ {
+ moveAvatar();
+ }
+}
+
+// -----------------------------------------------------------------------------
+bool LLViewerJoystick::isDeviceUUIDSet()
+{
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // for ease of comparison and to dial less with platform specific variables, we store id as LLSD binary
+ return mLastDeviceUUID.isBinary();
+#elif LL_DARWIN
+ return mLastDeviceUUID.isMap();
+#else
+ return false;
+#endif
+}
+
+LLSD LLViewerJoystick::getDeviceUUID()
+{
+ return mLastDeviceUUID;
+}
+
+std::string LLViewerJoystick::getDeviceUUIDString()
+{
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // Might be simpler to just convert _GUID into string everywhere, store and compare as string
+ if (mLastDeviceUUID.isBinary())
+ {
+ S32 size = sizeof(GUID);
+ LLSD::Binary data = mLastDeviceUUID.asBinary();
+ GUID guid;
+ memcpy(&guid, &data[0], size);
+ return string_from_guid(guid);
+ }
+ else
+ {
+ return std::string();
+ }
+#elif LL_DARWIN
+ if (mLastDeviceUUID.isMap())
+ {
+ std::string manufacturer = mLastDeviceUUID["manufacturer"].asString();
+ std::string product = mLastDeviceUUID["product"].asString();
+ return manufacturer + ":" + product;
+ }
+ else
+ {
+ return std::string();
+ }
+#else
+ return std::string();
+#endif
+}
+
+void LLViewerJoystick::saveDeviceIdToSettings()
+{
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // can't save as binary directly,
+ // someone editing the xml will corrupt it
+ // so convert to string first
+ std::string device_string = getDeviceUUIDString();
+ gSavedSettings.setLLSD("JoystickDeviceUUID", LLSD(device_string));
+#else
+ LLSD device_id = getDeviceUUID();
+ gSavedSettings.setLLSD("JoystickDeviceUUID", device_id);
+#endif
+}
+
+void LLViewerJoystick::loadDeviceIdFromSettings()
+{
+ LLSD dev_id = gSavedSettings.getLLSD("JoystickDeviceUUID");
+#if LL_WINDOWS && !LL_MESA_HEADLESS
+ // We can't save binary data to gSavedSettings, somebody editing the file will corrupt it,
+ // so _GUID data gets converted to string (we probably can convert it to LLUUID with memcpy)
+ // and here we need to convert it back to binary from string
+ std::string device_string;
+ if (dev_id.isString())
+ {
+ device_string = dev_id.asString();
+ }
+ if (device_string.empty())
+ {
+ mLastDeviceUUID = LLSD();
+ }
+ else
+ {
+ LL_DEBUGS("Joystick") << "Looking for device by id: " << device_string << LL_ENDL;
+ GUID guid;
+ guid_from_string(guid, device_string);
+ S32 size = sizeof(GUID);
+ LLSD::Binary data; //just an std::vector
+ data.resize(size);
+ memcpy(&data[0], &guid /*POD _GUID*/, size);
+ // We store this data in LLSD since it can handle both GUID2 and long
+ mLastDeviceUUID = LLSD(data);
+ }
+#elif LL_DARWIN
+ if (!dev_id.isMap())
+ {
+ mLastDeviceUUID = LLSD();
+ }
+ else
+ {
+ std::string manufacturer = mLastDeviceUUID["manufacturer"].asString();
+ std::string product = mLastDeviceUUID["product"].asString();
+ LL_DEBUGS("Joystick") << "Looking for device by manufacturer: " << manufacturer << " and product: " << product << LL_ENDL;
+ // We store this data in LLSD since it can handle both GUID2 and long
+ mLastDeviceUUID = dev_id;
+ }
+#else
+ mLastDeviceUUID = LLSD();
+ //mLastDeviceUUID = gSavedSettings.getLLSD("JoystickDeviceUUID");
+#endif
+}
+
+// -----------------------------------------------------------------------------
+std::string LLViewerJoystick::getDescription()
+{
+ std::string res;
+#if LIB_NDOF
+ if (mDriverState == JDS_INITIALIZED && mNdofDev)
+ {
+ res = ll_safe_string(mNdofDev->product);
+ }
+#endif
+ return res;
+}
+
+bool LLViewerJoystick::isLikeSpaceNavigator() const
+{
+#if LIB_NDOF
+ return (isJoystickInitialized()
+ && (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0
+ || strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0
+ || strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0
+ || strncmp(mNdofDev->product, "SpacePilot", 10) == 0));
+#else
+ return false;
+#endif
+}
+
+// -----------------------------------------------------------------------------
+void LLViewerJoystick::setSNDefaults()
+{
+#if LL_DARWIN || LL_LINUX
+ const float platformScale = 20.f;
+ const float platformScaleAvXZ = 1.f;
+ // The SpaceNavigator doesn't act as a 3D cursor on macOS / Linux.
+ const bool is_3d_cursor = false;
+#else
+ const float platformScale = 1.f;
+ const float platformScaleAvXZ = 2.f;
+ const bool is_3d_cursor = true;
+#endif
+
+ //gViewerWindow->alertXml("CacheWillClear");
+ LL_INFOS("Joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL;
+
+ gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
+ gSavedSettings.setS32("JoystickAxis1", 0); // x (slide)
+ gSavedSettings.setS32("JoystickAxis2", 2); // y (up)
+ gSavedSettings.setS32("JoystickAxis3", 4); // pitch
+ gSavedSettings.setS32("JoystickAxis4", 3); // roll
+ gSavedSettings.setS32("JoystickAxis5", 5); // yaw
+ gSavedSettings.setS32("JoystickAxis6", -1);
+
+ gSavedSettings.setBOOL("Cursor3D", is_3d_cursor);
+ gSavedSettings.setBOOL("AutoLeveling", true);
+ gSavedSettings.setBOOL("ZoomDirect", false);
+
+ gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ);
+ gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ);
+ gSavedSettings.setF32("AvatarAxisScale2", 1.f);
+ gSavedSettings.setF32("AvatarAxisScale4", .1f * platformScale);
+ gSavedSettings.setF32("AvatarAxisScale5", .1f * platformScale);
+ gSavedSettings.setF32("AvatarAxisScale3", 0.f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale1", .3f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale2", .3f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale0", .3f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale4", .3f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale5", .3f * platformScale);
+ gSavedSettings.setF32("BuildAxisScale3", .3f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale1", 2.f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale2", 2.f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale0", 2.1f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale4", .1f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale5", .15f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale3", 0.f * platformScale);
+ gSavedSettings.setF32("FlycamAxisScale6", 0.f * platformScale);
+
+ gSavedSettings.setF32("AvatarAxisDeadZone0", .1f);
+ gSavedSettings.setF32("AvatarAxisDeadZone1", .1f);
+ gSavedSettings.setF32("AvatarAxisDeadZone2", .1f);
+ gSavedSettings.setF32("AvatarAxisDeadZone3", 1.f);
+ gSavedSettings.setF32("AvatarAxisDeadZone4", .02f);
+ gSavedSettings.setF32("AvatarAxisDeadZone5", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone0", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone1", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone2", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone3", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone4", .01f);
+ gSavedSettings.setF32("BuildAxisDeadZone5", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone0", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone1", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone2", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone3", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone4", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone5", .01f);
+ gSavedSettings.setF32("FlycamAxisDeadZone6", 1.f);
+
+ gSavedSettings.setF32("AvatarFeathering", 6.f);
+ gSavedSettings.setF32("BuildFeathering", 12.f);
+ gSavedSettings.setF32("FlycamFeathering", 5.f);
+}