Files
SingularityViewer/indra/newview/llviewerjoystick.cpp
Inusaito Sayori 8583a2f4f7 Idea!: Set to defaults flyout button with options for all styles of joystick controller
The button portion does nothing if there is an unknown or no controller connected, otherwise it sets defaults for the controller's identified type.

Joystick buttons are still configured based on identification, however.
DS3 users will likely find that using Motion in Joy switchboards is much more effective, however.
2014-07-10 22:38:30 -04:00

1465 lines
38 KiB
C++

/**
* @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 "llvoavatarself.h" // Singu Note: For toggle sit.
#include "llagent.h"
#include "llagentcamera.h"
#include "llfocusmgr.h"
#include <boost/regex.hpp>
// ----------------------------------------------------------------------------
// 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
// minimum time after setting away state before coming back
const F32 MIN_AFK_TIME = 2.f;
F32 LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0};
F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
// <Singu> Note: Save the type of controller
enum EControllerType { NONE, SPACE_NAV, XBOX, DS3, UNKNOWN };
static EControllerType sType = NONE;
// Control cursor instead of avatar?
bool sControlCursor = false;
enum XBoxKeys
{
XBOX_A_KEY = 0,
XBOX_B_KEY,
XBOX_X_KEY,
XBOX_Y_KEY,
XBOX_L_BUMP_KEY,
XBOX_R_BUMP_KEY,
XBOX_BACK_KEY,
XBOX_START_KEY,
XBOX_L_STICK_CLICK,
XBOX_R_STICK_CLICK
};
bool isOUYA(const std::string& desc) { return desc.find("OUYA") != std::string::npos; }
bool isXboxLike(const std::string& desc)
{
return desc.find("Xbox") != std::string::npos
|| isOUYA(desc);
}
bool isDS3Like(const std::string& desc)
{
return desc.find("MotioninJoy") != std::string::npos;
}
enum DS3Keys
{
DS3_TRIANGLE_KEY = 0,
DS3_CIRCLE_KEY,
DS3_X_KEY,
DS3_SQUARE_KEY,
DS3_L1_KEY,
DS3_R1_KEY,
DS3_L2_KEY,
DS3_R2_KEY,
DS3_SELECT_KEY,
DS3_L_STICK_CLICK,
DS3_R_STICK_CLICK,
DS3_START_KEY,
DS3_LOGO_KEY
};
S32 get_joystick_type()
{
if (sType == SPACE_NAV) return 0;
if (sType == XBOX) return isOUYA(LLViewerJoystick::getInstance()->getDescription()) ? 1 : 2;
if (sType == DS3) return 3;
return -1; // sType == NONE || sType == UNKNOWN
}
// </Singu>
// 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
// -----------------------------------------------------------------------------
void LLViewerJoystick::updateEnabled(bool autoenable)
{
if (mDriverState == JDS_UNINITIALIZED)
{
gSavedSettings.setBOOL("JoystickEnabled", FALSE );
}
else
{
if (isLikeSpaceNavigator() && autoenable)
{
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)
{
llinfos << "HotPlugAddCallback: will use device:" << llendl;
ndof_dump(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)
{
llinfos << "HotPlugRemovalCallback: joystick->mNdofDev="
<< joystick->mNdofDev << "; removed device:" << llendl;
ndof_dump(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?
}
// -----------------------------------------------------------------------------
LLViewerJoystick::~LLViewerJoystick()
{
if (mDriverState == JDS_INITIALIZED)
{
terminate();
}
}
// -----------------------------------------------------------------------------
void LLViewerJoystick::init(bool autoenable)
{
#if LIB_NDOF
static bool libinit = false;
mDriverState = JDS_INITIALIZING;
if (libinit == false)
{
// Note: The HotPlug callbacks are not actually getting called on Windows
if (ndof_libinit(HotPlugAddCallback,
HotPlugRemovalCallback,
NULL))
{
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)
{
// 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
if (ndof_init_first(mNdofDev, NULL))
{
mDriverState = JDS_UNINITIALIZED;
llwarns << "ndof_init_first FAILED" << llendl;
}
else
{
mDriverState = JDS_INITIALIZED;
}
}
else
{
mDriverState = JDS_UNINITIALIZED;
}
}
// Autoenable the joystick for recognized devices if nothing was connected previously
if (!autoenable)
{
autoenable = gSavedSettings.getString("JoystickInitialized").empty() ? true : false;
}
updateEnabled(autoenable);
const std::string desc(getDescription());
if (mDriverState == JDS_INITIALIZED)
{
sControlCursor = false;
// A Joystick device is plugged in
if (isLikeSpaceNavigator())
{
sType = SPACE_NAV;
// 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 if (isXboxLike(desc))
{
sType = XBOX;
// It's an Xbox controller, we have defaults for it.
bool ouya(isOUYA(desc));
std::string controller = ouya ? "OUYA" : "XboxController";
if (gSavedSettings.getString("JoystickInitialized") != controller)
{
// Only set the defaults if we haven't already (in case they were overridden)
setSNDefaults(ouya ? 1 : 2);
gSavedSettings.setString("JoystickInitialized", controller);
}
}
else if (isDS3Like(desc))
{
sType = DS3;
// It's a DS3 controller, we have defaults for it.
if (gSavedSettings.getString("JoystickInitialized") != "DualShock3")
{
// Only set the defaults if we haven't already (in case they were overridden)
setSNDefaults(3);
gSavedSettings.setString("JoystickInitialized", "DualShock3");
}
}
else
{
// It's not a Space Navigator, 360 controller, or DualShock 3
sType = UNKNOWN;
gSavedSettings.setString("JoystickInitialized", "UnknownDevice");
}
}
else
{
// No device connected, don't change any settings
sType = NONE;
}
llinfos << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
<< mNdofDev << "; libinit=" << libinit << llendl;
// <CV:David>
if (mDriverState == JDS_INITIALIZED)
{
llinfos << "Joystick = " << desc << llendl;
}
// </CV:David>
#endif
}
// -----------------------------------------------------------------------------
void LLViewerJoystick::terminate()
{
#if LIB_NDOF
ndof_libcleanup();
llinfos << "Terminated connection with NDOF device." << llendl;
mDriverState = JDS_UNINITIALIZED;
#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());
// [RLVa:KB] - Checked: 2011-05-11 (RLVa-1.3.0i) | Added: RLVa-1.3.0i
gAgent.setTempRun();
// [/RLVa:KB]
}
else if (0 == mJoystickRun)
{
// hysteresis - respond NEXT frame
++mJoystickRun;
}
}
else
{
if (mJoystickRun > 0)
{
--mJoystickRun;
if (0 == mJoystickRun)
{
// gAgent.clearRunning();
// gAgent.sendWalkRun(gAgent.getRunning());
// [RLVa:KB] - Checked: 2011-05-11 (RLVa-1.3.0i) | Added: RLVa-1.3.0i
gAgent.clearTempRun();
// [/RLVa:KB]
}
}
}
}
// -----------------------------------------------------------------------------
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);
}
}
S32 linear_ramp(const F32& inc, const F32& prev_inc)
{
// Start out linear for fine control but then ramp up more quickly for faster movement.
F32 nudge = inc > F_APPROXIMATELY_ZERO ? 1.f : -1.f;
F32 linear = inc + prev_inc;
F32 square = 0.f;
if (abs(linear) > 0.2f)
{
square = linear + (0.2f * -nudge);
square *= abs(square);
}
return nudge + linear * 25.f + square * 300.f;
}
void LLViewerJoystick::cursorSlide(F32 inc)
{
static F32 prev_inc = 0.f; // Smooth a little.
if (!is_approx_zero(inc))
{
S32 x, y;
LLUI::getMousePositionScreen(&x, &y);
x = llclamp(x + linear_ramp(inc, prev_inc), 0, gViewerWindow->getWindowWidthRaw());
LLUI::setMousePositionScreen(x, y);
}
prev_inc = inc;
}
void LLViewerJoystick::cursorPush(F32 inc)
{
static F32 prev_inc = 0.f; // Smooth a little.
if (!is_approx_zero(0.001))
{
S32 x, y;
LLUI::getMousePositionScreen(&x, &y);
y = llclamp(y + linear_ramp(inc, prev_inc), 0, gViewerWindow->getWindowHeightRaw());
LLUI::setMousePositionScreen(x, y);
}
prev_inc = inc;
}
void LLViewerJoystick::cursorZoom(F32 inc)
{
if (!is_approx_zero(inc))
{
static U8 count = 0;
++count;
if (count == 3) // Slow down the zoom in/out.
{
gViewerWindow->handleScrollWheel(inc > F_APPROXIMATELY_ZERO ? 1 : -1);
count = 0;
}
}
}
// -----------------------------------------------------------------------------
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;
// 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() > 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 (!sControlCursor)
{
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[sType == XBOX ? XBOX_L_STICK_CLICK : sType == DS3 ? DS3_L_STICK_CLICK : 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;
// 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() > 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;
if (sControlCursor)
{
cursorSlide(sDelta[X_I]); // left / right
cursorPush(-sDelta[Z_I]); // up / down
cursorZoom(sDelta[RX_I]); // mousewheel
return;
}
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;
// 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() > 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() > MIN_AFK_TIME)
{
gAgent.clearAFK();
}
mOverrideCamera = !mOverrideCamera;
if (mOverrideCamera)
{
moveFlycam(true);
}
else if (!LLToolMgr::getInstance()->inBuildMode())
{
moveAvatar(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;
}
bool toggleCursor() { sControlCursor = !sControlCursor; 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();
static long toggle_flycam = 0;
static bool toggle_cursor = false;
// <Singu> Xbox 360 support
if (sType == XBOX || sType == DS3)
{
bool ds3 = sType == DS3;
// Special command keys ...
// - Back = toggle flycam
U8 key = ds3 ? DS3_SELECT_KEY : XBOX_BACK_KEY;
if (mBtn[key] == 1)
{
if (!toggle_flycam) toggle_flycam = toggleFlycam();
}
else
{
toggle_flycam = false;
}
// - Start = toggle cursor/camera control
key = ds3 ? DS3_START_KEY : XBOX_START_KEY;
if (mBtn[key] == 1)
{
if (!toggle_cursor) toggle_cursor = toggleCursor();
}
else
{
toggle_cursor = false;
}
// Toggle mouselook ...
static bool right_stick_click_down = false;
key = ds3 ? DS3_R_STICK_CLICK : XBOX_R_STICK_CLICK;
if (!!mBtn[key] != right_stick_click_down)
{
if (right_stick_click_down = mBtn[key]) // Note: Setting, not comparing.
gAgentCamera.cameraMouselook() ? gAgentCamera.changeCameraToDefault() : gAgentCamera.changeCameraToMouselook();
}
MASK mask = gKeyboard->currentMask(TRUE);
// Esc
static bool esc_down = false;
key = ds3 ? DS3_TRIANGLE_KEY : XBOX_Y_KEY;
if (!!mBtn[key] != esc_down)
{
esc_down = mBtn[key];
(gKeyboard->*(esc_down ? &LLKeyboard::handleTranslatedKeyDown : &LLKeyboard::handleTranslatedKeyDown))(KEY_ESCAPE, mask);
}
// Alt
static bool alt_down = false;
key = ds3 ? DS3_X_KEY : XBOX_A_KEY;
if (!!mBtn[key] != alt_down)
{
gKeyboard->setControllerKey(KEY_ALT, alt_down = mBtn[key]);
}
// Ctrl
static bool ctrl_down = false;
key = ds3 ? DS3_SQUARE_KEY : XBOX_X_KEY;
if (!!mBtn[key] != ctrl_down)
{
gKeyboard->setControllerKey(KEY_CONTROL, ctrl_down = mBtn[key]);
}
// Shift
static bool shift_down = false;
key = ds3 ? DS3_CIRCLE_KEY : XBOX_B_KEY;
if (!!mBtn[key] != shift_down)
{
gKeyboard->setControllerKey(KEY_SHIFT, shift_down = mBtn[key]);
}
// Mouse clicks ...
LLCoordGL coord;
LLUI::getMousePositionScreen(&coord.mX, &coord.mY);
static bool m1_down = false;
static F32 last_m1 = 0;
key = ds3 ? DS3_L1_KEY : XBOX_L_BUMP_KEY;
if (!!mBtn[key] != m1_down)
{
m1_down = mBtn[key];
(gViewerWindow->*(m1_down ? &LLViewerWindow::handleMouseDown : &LLViewerWindow::handleMouseUp))(gViewerWindow->getWindow(), coord, mask);
if (m1_down && gFrameTimeSeconds-last_m1 == 0.5f)
gViewerWindow->handleDoubleClick(gViewerWindow->getWindow(), coord, mask);
last_m1 = gFrameTimeSeconds;
}
static bool m2_down = false;
key = ds3 ? DS3_R1_KEY : XBOX_R_BUMP_KEY;
if (!!mBtn[key] != m2_down)
{
m2_down = mBtn[key];
(gViewerWindow->*(m2_down ? &LLViewerWindow::handleRightMouseDown : &LLViewerWindow::handleRightMouseUp))(gViewerWindow->getWindow(), coord, mask);
}
if (ds3) // Yay bonus keys~
{
static bool sit_down = false;
if (!!mBtn[DS3_LOGO_KEY] != sit_down)
{
if (sit_down = mBtn[DS3_LOGO_KEY])
(gAgentAvatarp && gAgentAvatarp->isSitting()) ? gAgent.standUp() : gAgent.sitDown();
}
/* Singu TODO: What should these be?
DS3_L2_KEY
DS3_R2_KEY
*/
}
}
else
// </Singu>
{
if (mBtn[0] == 1)
{
if (mBtn[0] != toggle_flycam)
{
toggle_flycam = toggleFlycam() ? 1 : 0;
}
}
else
{
toggle_flycam = 0;
}
}
if (sControlCursor || (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))))
{
moveAvatar();
}
}
// -----------------------------------------------------------------------------
std::string LLViewerJoystick::getDescription()
{
std::string res;
#if LIB_NDOF
if (mDriverState == JDS_INITIALIZED && mNdofDev)
{
res = ll_safe_string(mNdofDev->product);
}
// <CV:David>
// Tidy up description of Xbox controllers.
res = boost::regex_replace(res, boost::regex("^Controller \\((.*)\\)$", boost::regex::perl), "$1");
// </CV:David>
#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(S32 type)
{
#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 OS X / 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");
const bool ouya = type == 1;
const bool xbox = ouya || type == 2;
const bool ds3 = type == 3;
llinfos << "restoring " << (xbox ? ouya ? "OUYA Game Controller" : "Xbox Controller" : ds3 ? "Dual Shock 3" : "SpaceNavigator") << " defaults..." << llendl;
/*
Axis 0: Left Thumbstick Horizontal
Axis 1: Left Thumbstick Vertical
Axis 2: Left and Right triggers (Analog)
Axis 3: Right Thumbstick Horizontal
Axis 4: Left Thumbstick Vertical
Axis 5: Unused
Syntax/Format:
Debug setting InternalMapping,Jostick Axis (see above) */
gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
gSavedSettings.setS32("JoystickAxis1", ouya ? 3 : 0); // x (slide)
gSavedSettings.setS32("JoystickAxis2", ouya ? 4 : ds3 ? 3 : 2); // y (up)
gSavedSettings.setS32("JoystickAxis3", xbox ? ouya ? 3 : -1 : 4); // roll
gSavedSettings.setS32("JoystickAxis4", xbox ? 4 : ds3 ? 5 : 3); // pitch
gSavedSettings.setS32("JoystickAxis5", xbox ? ouya ? 0 : 3 : ds3 ? 2 : 5); // yaw
gSavedSettings.setS32("JoystickAxis6", ouya ? 5 : -1);
const bool game = xbox || ds3; // All game controllers are relatively the same
gSavedSettings.setBOOL("Cursor3D", !game && is_3d_cursor);
gSavedSettings.setBOOL("AutoLeveling", true);
gSavedSettings.setBOOL("ZoomDirect", false);
gSavedSettings.setF32("AvatarAxisScale0", (xbox ? 0.43f : ds3 ? 0.215f : 1.f) * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale1", (xbox ? 0.43f : ds3 ? 0.215f : 1.f) * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale2", xbox ? 0.43f : ds3 ? -0.43f : 1.f);
gSavedSettings.setF32("AvatarAxisScale4", ds3 ? 0.215f * platformScaleAvXZ : ((xbox ? 4.f : .1f) * platformScale));
gSavedSettings.setF32("AvatarAxisScale5", ds3 ? 0.215f * platformScaleAvXZ : ((xbox ? 4.f : .1f) * platformScale));
gSavedSettings.setF32("AvatarAxisScale3", (game ? 4.f : 0.f) * platformScale);
gSavedSettings.setF32("BuildAxisScale1", (game ? ouya ? 20.f : 0.8f : .3f) * platformScale);
gSavedSettings.setF32("BuildAxisScale2", (xbox ? ouya ? 20.f : 0.8f : ds3 ? -0.8f : .3f) * platformScale);
gSavedSettings.setF32("BuildAxisScale0", (game ? ouya ? 50.f : 1.6f : .3f) * platformScale);
gSavedSettings.setF32("BuildAxisScale4", (game ? ouya ? 1.8f : 1.f : .3f) * platformScale);
gSavedSettings.setF32("BuildAxisScale5", (game ? 2.f : .3f) * platformScale);
gSavedSettings.setF32("BuildAxisScale3", (game ? ouya ? -6.f : 1.f : .3f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale1", (game ? ouya ? 20.f : 16.f : 2.f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale2", (game ? ouya ? 20.f : 16.f : ds3 ? -16.f : 2.f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale0", (game ? ouya ? 50.f : 25.f : 2.1f) * platformScale); // Z Scale
gSavedSettings.setF32("FlycamAxisScale4", (game ? ouya ? 1.80 : -4.f : .1f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale5", (game ? 4.f : .15f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale3", (xbox ? 4.f : ds3 ? 6.f : 0.f) * platformScale);
gSavedSettings.setF32("FlycamAxisScale6", (game ? 4.f : 0.f) * platformScale);
gSavedSettings.setF32("AvatarAxisDeadZone0", game ? .2f : .1f);
gSavedSettings.setF32("AvatarAxisDeadZone1", game ? .2f : .1f);
gSavedSettings.setF32("AvatarAxisDeadZone2", game ? .2f : .1f);
gSavedSettings.setF32("AvatarAxisDeadZone3", game ? .2f : 1.f);
gSavedSettings.setF32("AvatarAxisDeadZone4", game ? .2f : .02f);
gSavedSettings.setF32("AvatarAxisDeadZone5", game ? .2f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone0", game ? .02f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone1", game ? .02f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone2", game ? .02f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone3", game ? .02f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone4", game ? .02f : .01f);
gSavedSettings.setF32("BuildAxisDeadZone5", game ? .02f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone0", game ? .2f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone1", game ? .2f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone2", game ? .2f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone3", game ? .1f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone4", game ? .25f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone5", game ? .25f : .01f);
gSavedSettings.setF32("FlycamAxisDeadZone6", game ? .2f : 1.f);
gSavedSettings.setF32("AvatarFeathering", game ? 3.f : 6.f);
gSavedSettings.setF32("BuildFeathering", 12.f);
gSavedSettings.setF32("FlycamFeathering", game ? 1.f : 5.f);
}