summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeviathan Linden <leviathan@lindenlab.com>2023-11-16 13:53:37 -0800
committerAndrew Meadows <andrew.l.meadows@gmail.com>2024-10-03 09:01:12 -0700
commited6ecca2a45e52d9be1d91107b9643b5ecdfb8bf (patch)
tree2b9d241bf5cb0520de13ee10f729d2b58278ce33
parent13221f67c465017f44ca46aeca23b0d820935825 (diff)
avatar_motion-->GameControl translation and flycam
-rw-r--r--autobuild.xml24
-rw-r--r--indra/llcommon/llinitparam.h1
-rw-r--r--indra/llwindow/CMakeLists.txt2
-rw-r--r--indra/llwindow/llgamecontrol.cpp687
-rw-r--r--indra/llwindow/llgamecontrol.h156
-rw-r--r--indra/llwindow/llgamecontroltranslator.cpp344
-rw-r--r--indra/llwindow/llgamecontroltranslator.h83
-rw-r--r--indra/llwindow/llkeyboard.cpp3
-rw-r--r--indra/llwindow/llkeyboardheadless.cpp1
-rw-r--r--indra/llwindow/llkeyboardmacosx.cpp1
-rw-r--r--indra/llwindow/llkeyboardsdl.cpp1
-rw-r--r--indra/llwindow/llkeyboardwin32.cpp1
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/app_settings/settings.xml19
-rw-r--r--indra/newview/llagent.cpp271
-rw-r--r--indra/newview/llagent.h32
-rw-r--r--indra/newview/llappviewer.cpp109
-rw-r--r--indra/newview/llfloaterjoystick.cpp26
-rw-r--r--indra/newview/llfloaterpreference.cpp365
-rw-r--r--indra/newview/llfloaterpreference.h104
-rw-r--r--indra/newview/llflycam.cpp117
-rw-r--r--indra/newview/llflycam.h55
-rw-r--r--indra/newview/llviewerinput.cpp9
-rw-r--r--indra/newview/llviewerjoystick.cpp27
-rw-r--r--indra/newview/llviewerjoystick.h2
-rw-r--r--indra/newview/skins/default/textures/bottomtray/Dpad.pngbin0 -> 765 bytes
-rw-r--r--indra/newview/skins/default/xui/en/floater_joystick.xml36
-rw-r--r--indra/newview/skins/default/xui/en/floater_preferences.xml10
-rw-r--r--indra/newview/skins/default/xui/en/game_control_table_camera_rows.xml106
-rw-r--r--indra/newview/skins/default/xui/en/game_control_table_columns.xml15
-rw-r--r--indra/newview/skins/default/xui/en/game_control_table_rows.xml117
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_game_control.xml244
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml32
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_move.xml2
-rw-r--r--indra/newview/tests/llgamecontrol_stub.cpp43
-rw-r--r--indra/newview/tests/llversioninfo_test.cpp2
36 files changed, 2687 insertions, 362 deletions
diff --git a/autobuild.xml b/autobuild.xml
index 4ec43be8de..2d06a159df 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" ?>
<llsd>
-<map>
+ <map>
<key>version</key>
<string>1.3</string>
<key>type</key>
@@ -1207,18 +1207,6 @@
<key>name</key>
<string>darwin64</string>
</map>
- <key>linux64</key>
- <map>
- <key>archive</key>
- <map>
- <key>hash</key>
- <string>ffbdd109356d66ddfefd8a5d57f63f1f</string>
- <key>url</key>
- <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/533/1144/libhunspell-1.3.2.500526-linux64-500526.tar.bz2</string>
- </map>
- <key>name</key>
- <string>linux64</string>
- </map>
<key>windows64</key>
<map>
<key>archive</key>
@@ -1550,7 +1538,7 @@
<key>name</key>
<string>linux64</string>
</map>
- <key>windows64</key>
+ <key>windows</key>
<map>
<key>archive</key>
<map>
@@ -1560,7 +1548,7 @@
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/60043/564063/llphysicsextensions_stub-1.0.542456-windows-542456.tar.bz2</string>
</map>
<key>name</key>
- <string>windows64</string>
+ <string>windows</string>
</map>
</map>
<key>license</key>
@@ -1612,7 +1600,7 @@
<string>https://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/84731/788139/llphysicsextensions_tpv-1.0.561752-windows64-561752.tar.bz2</string>
</map>
<key>name</key>
- <string>windows64</string>
+ <string>windows</string>
</map>
</map>
<key>license</key>
@@ -3292,7 +3280,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>name</key>
<string>linux64</string>
</map>
- <key>windows64</key>
+ <key>windows</key>
<map>
<key>configurations</key>
<map>
@@ -3449,7 +3437,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>build_directory</key>
<string>build-vc${AUTOBUILD_VSVER|170}-$AUTOBUILD_ADDRSIZE</string>
<key>name</key>
- <string>windows64</string>
+ <string>windows</string>
</map>
</map>
<key>license</key>
diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h
index 32d7b17034..c6a8dd737e 100644
--- a/indra/llcommon/llinitparam.h
+++ b/indra/llcommon/llinitparam.h
@@ -2836,3 +2836,4 @@ namespace LLInitParam
#endif // LL_LLPARAM_H
+
diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt
index e251af3e6c..ebac55cb9c 100644
--- a/indra/llwindow/CMakeLists.txt
+++ b/indra/llwindow/CMakeLists.txt
@@ -23,6 +23,7 @@ include(SDL2)
set(llwindow_SOURCE_FILES
llcursortypes.cpp
llgamecontrol.cpp
+ llgamecontroltranslator.cpp
llkeyboard.cpp
llkeyboardheadless.cpp
llwindowheadless.cpp
@@ -35,6 +36,7 @@ set(llwindow_HEADER_FILES
llcursortypes.h
llgamecontrol.h
+ llgamecontroltranslator.h
llkeyboard.h
llkeyboardheadless.h
llwindowheadless.h
diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp
index 5dc01c5e54..9853eb763a 100644
--- a/indra/llwindow/llgamecontrol.cpp
+++ b/indra/llwindow/llgamecontrol.cpp
@@ -28,40 +28,198 @@
#include <algorithm>
#include <chrono>
-#include <map>
+#include <unordered_map>
#include "SDL2/SDL.h"
#include "SDL2/SDL_gamecontroller.h"
#include "SDL2/SDL_joystick.h"
+#include "indra_constants.h"
+#include "llgamecontroltranslator.h"
+
constexpr size_t NUM_AXES = 6;
+std::string LLGameControl::InputChannel::getLocalName() const
+{
+ // HACK: we hard-code English channel names, but
+ // they should be loaded from localized XML config files.
+ std::string name = " ";
+ if (mType == LLGameControl::InputChannel::TYPE_AXIS)
+ {
+ if (mIndex < (U8)(NUM_AXES))
+ {
+ name = "AXIS_";
+ name.append(std::to_string((S32)(mIndex)));
+ if (mSign < 0)
+ {
+ name.append("-");
+ }
+ else if (mSign > 0)
+ {
+ name.append("+");
+ }
+ }
+ }
+ else if (mType == LLGameControl::InputChannel::TYPE_BUTTON)
+ {
+ constexpr U8 NUM_BUTTONS = 32;
+ if (mIndex < NUM_BUTTONS)
+ {
+ name = "BUTTON_";
+ name.append(std::to_string((S32)(mIndex)));
+ }
+ }
+ return name;
+}
+
+std::string LLGameControl::InputChannel::getRemoteName() const
+{
+ // HACK: we hard-code English channel names, but
+ // they should be loaded from localized XML config files.
+ std::string name = " ";
+ // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc
+ if (mType == LLGameControl::InputChannel::TYPE_AXIS)
+ {
+ switch(mIndex)
+ {
+ case 0:
+ name = "GAME_CONTROL_AXIS_LEFTX";
+ break;
+ case 1:
+ name = "GAME_CONTROL_AXIS_LEFTY";
+ break;
+ case 2:
+ name = "GAME_CONTROL_AXIS_RIGHTX";
+ break;
+ case 3:
+ name = "GAME_CONTROL_AXIS_RIGHTY";
+ break;
+ case 4:
+ name = "GAME_CONTROL_AXIS_PADDLELEFT";
+ break;
+ case 5:
+ name = "GAME_CONTROL_AXIS_PADDLERIGHT";
+ break;
+ default:
+ break;
+ }
+ }
+ else if (mType == LLGameControl::InputChannel::TYPE_BUTTON)
+ {
+ switch(mIndex)
+ {
+ case 0:
+ name = "GAME_CONTROL_BUTTON_A";
+ break;
+ case 1:
+ name = "GAME_CONTROL_BUTTON_B";
+ break;
+ case 2:
+ name = "GAME_CONTROL_BUTTON_X";
+ break;
+ case 3:
+ name = "GAME_CONTROL_BUTTON_Y";
+ break;
+ case 4:
+ name = "GAME_CONTROL_BUTTON_BACK";
+ break;
+ case 5:
+ name = "GAME_CONTROL_BUTTON_GUIDE";
+ break;
+ case 6:
+ name = "GAME_CONTROL_BUTTON_START";
+ break;
+ case 7:
+ name = "GAME_CONTROL_BUTTON_LEFTSTICK";
+ break;
+ case 8:
+ name = "GAME_CONTROL_BUTTON_RIGHTSTICK";
+ break;
+ case 9:
+ name = "GAME_CONTROL_BUTTON_LEFTSHOULDER";
+ break;
+ case 10:
+ name = "GAME_CONTROL_BUTTON_RIGHTSHOULDER";
+ break;
+ case 11:
+ name = "GAME_CONTROL_BUTTON_DPAD_UP";
+ break;
+ case 12:
+ name = "GAME_CONTROL_BUTTON_DPAD_DOWN";
+ break;
+ case 13:
+ name = "GAME_CONTROL_BUTTON_DPAD_LEFT";
+ break;
+ case 14:
+ name = "GAME_CONTROL_BUTTON_DPAD_RIGHT";
+ break;
+ case 15:
+ name = "GAME_CONTROL_BUTTON_MISC1";
+ break;
+ case 16:
+ name = "GAME_CONTROL_BUTTON_PADDLE1";
+ break;
+ case 17:
+ name = "GAME_CONTROL_BUTTON_PADDLE2";
+ break;
+ case 18:
+ name = "GAME_CONTROL_BUTTON_PADDLE3";
+ break;
+ case 19:
+ name = "GAME_CONTROL_BUTTON_PADDLE4";
+ break;
+ case 20:
+ name = "GAME_CONTROL_BUTTON_TOUCHPAD";
+ break;
+ default:
+ break;
+ }
+ }
+ return name;
+}
+
+
// internal class for managing list of controllers and per-controller state
class LLGameControllerManager
{
public:
+ using ActionToChannelMap = std::map< std::string, LLGameControl::InputChannel >;
+ LLGameControllerManager();
+
void addController(SDL_JoystickID id, SDL_GameController* controller);
void removeController(SDL_JoystickID id);
void onAxis(SDL_JoystickID id, U8 axis, S16 value);
void onButton(SDL_JoystickID id, U8 button, bool pressed);
- void onKeyButton(U8 button, bool pressed);
- void onKeyAxis(U8 axis, U16 value);
-
- void clearAllInput();
- void clearAllKeys();
+ void clearAllState();
size_t getControllerIndex(SDL_JoystickID id) const;
+ void accumulateInternalState();
void computeFinalState(LLGameControl::State& state);
+ LLGameControl::InputChannel getChannelByName(const std::string& name) const;
+ LLGameControl::InputChannel getChannelByActionName(const std::string& action_name) const;
+
+ bool updateActionMap(const std::string& name, LLGameControl::InputChannel channel);
+ U32 computeInternalActionFlags();
+ void getCameraInputs(std::vector<F32>& inputs_out);
+ void setExternalActionFlags(U32 action_flags);
+
void clear();
private:
std::vector<SDL_JoystickID> mControllerIDs;
std::vector<SDL_GameController*> mControllers;
- std::vector<LLGameControl::State> mStates;
- LLGameControl::State mKeyboardState;
+ std::vector<LLGameControl::State> mStates; // one state per device
+
+ LLGameControl::State mExternalState;
+ LLGameControlTranslator mActionTranslator;
+ ActionToChannelMap mCameraChannelMap;
+ std::vector<S32> mAxesAccumulator;
+ U32 mButtonAccumulator { 0 };
+ U32 mLastActionFlags { 0 };
+ U32 mLastCameraActionFlags { 0 };
};
// local globals
@@ -83,15 +241,16 @@ namespace
constexpr U64 MSEC_PER_NSEC = 1e6;
constexpr U64 FIRST_RESEND_PERIOD = 100 * MSEC_PER_NSEC;
constexpr U64 RESEND_EXPANSION_RATE = 10;
- LLGameControl::State g_gameControlState;
+ LLGameControl::State g_outerState; // from controller devices
+ LLGameControl::State g_innerState; // state from gAgent
+ LLGameControl::State g_finalState; // sum of inner and outer
U64 g_lastSend = 0;
U64 g_nextResendPeriod = FIRST_RESEND_PERIOD;
- std::map<U16, U8> g_keyButtonMap;
- std::map<U16, U8> g_keyAxisMapPositive;
- std::map<U16, U8> g_keyAxisMapNegative;
-
- bool g_includeKeyboardButtons = false;
+ bool g_sendToServer = false;
+ bool g_controlAgent = false;
+ bool g_translateAgentActions = false;
+ LLGameControl::AgentControlMode g_agentControlMode = LLGameControl::CONTROL_MODE_AVATAR;
constexpr U8 MAX_AXIS = 5;
constexpr U8 MAX_BUTTON = 31;
@@ -108,6 +267,16 @@ LLGameControl::State::State() : mButtons(0)
mPrevAxes.resize(NUM_AXES, 0);
}
+void LLGameControl::State::clear()
+{
+ std::fill(mAxes.begin(), mAxes.end(), 0);
+
+ // DO NOT clear mPrevAxes because those are managed by external logic.
+ //std::fill(mPrevAxes.begin(), mPrevAxes.end(), 0);
+
+ mButtons = 0;
+}
+
bool LLGameControl::State::onButton(U8 button, bool pressed)
{
U32 old_buttons = mButtons;
@@ -126,6 +295,71 @@ bool LLGameControl::State::onButton(U8 button, bool pressed)
return changed;
}
+LLGameControllerManager::LLGameControllerManager()
+{
+ mAxesAccumulator.resize(NUM_AXES, 0);
+
+ // Here we build an invarient map between the named agent actions
+ // and control bit sent to the server. This map will be used,
+ // in combination with the action->InputChannel map below,
+ // to maintain an inverse map from control bit masks to GameControl data.
+ LLGameControlTranslator::ActionToMaskMap actions;
+ actions["push+"] = AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT;
+ actions["push-"] = AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT;
+ actions["slide+"] = AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT;
+ actions["slide-"] = AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT;
+ actions["jump+"] = AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP;
+ actions["jump-"] = AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP;
+ actions["turn+"] = AGENT_CONTROL_YAW_POS;
+ actions["turn-"] = AGENT_CONTROL_YAW_NEG;
+ actions["look+"] = AGENT_CONTROL_PITCH_POS;
+ actions["look-"] = AGENT_CONTROL_PITCH_NEG;
+ actions["stop"] = AGENT_CONTROL_STOP;
+ // These are HACKs. We borrow some AGENT_CONTROL bits for "unrelated" features.
+ // Not a problem because these bits are only used internally.
+ actions["toggle_run"] = AGENT_CONTROL_NUDGE_AT_POS; // HACK
+ actions["toggle_fly"] = AGENT_CONTROL_FLY; // HACK
+ actions["toggle_sit"] = AGENT_CONTROL_SIT_ON_GROUND; // HACK
+ actions["toggle_flycam"] = AGENT_CONTROL_NUDGE_AT_NEG; // HACK
+ mActionTranslator.setAvailableActions(actions);
+
+ // Here we build a list of pairs between named agent actions and
+ // GameControl channels. Note: we only supply the non-signed names
+ // (e.g. "push" instead of "push+" and "push-") because mActionTranator
+ // automatially expands action names as necessary.
+ using type = LLGameControl::InputChannel::Type;
+ std::vector< std::pair< std::string, LLGameControl::InputChannel> > agent_defaults =
+ {
+ { "push", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 } },
+ { "slide", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 } },
+ { "jump", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT), 1 } },
+ { "turn", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 } },
+ { "look", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), 1 } },
+ { "toggle_run", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSHOULDER) } },
+ { "toggle_fly", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_UP) } },
+ { "toggle_sit", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_DPAD_DOWN) } },
+ { "toggle_flycam", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_RIGHTSHOULDER) } },
+ { "stop", { type::TYPE_BUTTON, (U8)(LLGameControl::BUTTON_LEFTSTICK) } }
+ };
+ mActionTranslator.setMappings(agent_defaults);
+
+ // Camera actions don't need bitwise translation, so we maintain the map in
+ // here directly rather than using an LLGameControlTranslator.
+ // Note: there must NOT be duplicate names between avatar and camera actions
+ mCameraChannelMap =
+ {
+ { "move", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), -1 } },
+ { "pan", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), -1 } },
+ { "rise", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT), -1 } },
+ { "pitch", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), -1 } },
+ { "yaw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), -1 } },
+ { "zoom", { type::TYPE_NONE, 0 } },
+ // TODO?: allow flycam to roll
+ //{ "roll_ccw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT) } },
+ //{ "roll_cw", { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERLEFT) } }
+ };
+}
+
void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller)
{
if (controller)
@@ -198,6 +432,25 @@ void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value)
size_t index = getControllerIndex(id);
if (index < mControllers.size())
{
+ // Note: the RAW analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD
+ // whereas those directions are actually POSITIVE in SL's local right-handed
+ // reference frame. Therefore we implicitly negate those axes here where
+ // they are extracted from SDL, before being used anywhere.
+ if (axis < SDL_CONTROLLER_AXIS_TRIGGERLEFT)
+ {
+ // Note: S16 value is in range [-32768, 32767] which means
+ // the negative range has an extra possible value. We need
+ // to add (or subtract) one during negation.
+ if (value < 0)
+ {
+ value = - (value + 1);
+ }
+ else if (value > 0)
+ {
+ value = (-value) - 1;
+ }
+ }
+
LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
<< " axis=" << (S32)(axis)
<< " value=" << (S32)(value) << LL_ENDL;
@@ -219,83 +472,145 @@ void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool presse
}
}
-void LLGameControllerManager::onKeyButton(U8 button, bool pressed)
+void LLGameControllerManager::clearAllState()
{
- if (mKeyboardState.onButton(button, pressed))
+ for (auto& state : mStates)
{
- LL_DEBUGS("SDL2") << " keyboard button i=" << (S32)(button) << " pressed=" << pressed << LL_ENDL;
+ state.clear();
}
+ mExternalState.clear();
+ mLastActionFlags = 0;
+ mLastCameraActionFlags = 0;
}
-void LLGameControllerManager::onKeyAxis(U8 axis, U16 value)
+void LLGameControllerManager::accumulateInternalState()
{
- if (mKeyboardState.mAxes[axis] != value)
+ // clear the old state
+ std::fill(mAxesAccumulator.begin(), mAxesAccumulator.end(), 0);
+ mButtonAccumulator = 0;
+
+ // accumulate the controllers
+ for (const auto& state : mStates)
{
- mKeyboardState.mAxes[axis] = value;
- LL_DEBUGS("SDL2") << " keyboard axis i=" << (S32)(axis) << " value=" << (S32)(value) << LL_ENDL;
+ mButtonAccumulator |= state.mButtons;
+ for (size_t i = 0; i < NUM_AXES; ++i)
+ {
+ // Note: we don't bother to clamp the axes yet
+ // because at this stage we haven't yet accumulated the "inner" state.
+ mAxesAccumulator[i] += (S32)(state.mAxes[i]);
+ }
}
}
-void LLGameControllerManager::clearAllInput()
+void LLGameControllerManager::computeFinalState(LLGameControl::State& final_state)
{
- for (auto& state : mStates)
+ // We assume accumulateInternalState() has already been called and we will
+ // finish by accumulating "external" state (if enabled)
+ U32 old_buttons = final_state.mButtons;
+ final_state.mButtons = mButtonAccumulator;
+ if (g_translateAgentActions)
+ {
+ // accumulate from mExternalState
+ final_state.mButtons |= mExternalState.mButtons;
+ for (size_t i = 0; i < NUM_AXES; ++i)
+ {
+ mAxesAccumulator[i] += (S32)(mExternalState.mAxes[i]);
+ }
+ }
+ if (old_buttons != final_state.mButtons)
{
- state.mButtons = 0;
- std::fill(state.mAxes.begin(), state.mAxes.end(), 0);
+ g_nextResendPeriod = 0; // packet needs to go out ASAP
+ }
+
+ // clamp the accumulated axes
+ for (size_t i = 0; i < NUM_AXES; ++i)
+ {
+ S32 new_axis = (S16)(std::min(std::max(mAxesAccumulator[i], -32768), 32767));
+ // check for change
+ if (final_state.mAxes[i] != new_axis)
+ {
+ // When axis changes we explicitly update the corresponding prevAxis
+ // prior to storing new_axis. The only other place where prevAxis
+ // is updated in updateResendPeriod() which is explicitly called after
+ // a packet is sent. The result is: unchanged axes are included in
+ // first resend but not later ones.
+ final_state.mPrevAxes[i] = final_state.mAxes[i];
+ final_state.mAxes[i] = new_axis;
+ g_nextResendPeriod = 0; // packet needs to go out ASAP
+ }
}
- mKeyboardState.mButtons = 0;
- std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0);
}
-void LLGameControllerManager::clearAllKeys()
+LLGameControl::InputChannel LLGameControllerManager::getChannelByActionName(const std::string& name) const
{
- mKeyboardState.mButtons = 0;
- std::fill(mKeyboardState.mAxes.begin(), mKeyboardState.mAxes.end(), 0);
+ LLGameControl::InputChannel channel = mActionTranslator.getChannelByAction(name);
+ if (channel.isNone())
+ {
+ //maybe we're looking for a camera action
+ ActionToChannelMap::const_iterator itr = mCameraChannelMap.find(name);
+ if (itr != mCameraChannelMap.end())
+ {
+ channel = itr->second;
+ }
+ }
+ return channel;
}
-void LLGameControllerManager::computeFinalState(LLGameControl::State& state)
+bool LLGameControllerManager::updateActionMap(const std::string& action, LLGameControl::InputChannel channel)
{
- // clear the slate
- std::vector<S32> axes_accumulator;
- axes_accumulator.resize(NUM_AXES, 0);
- U32 old_buttons = state.mButtons;
- state.mButtons = 0;
-
- // accumulate the controllers
- for (const auto& s : mStates)
+ bool success = mActionTranslator.updateMap(action, channel);
+ if (success)
{
- state.mButtons |= s.mButtons;
- for (size_t i = 0; i < NUM_AXES; ++i)
+ mLastActionFlags = 0;
+ }
+ else
+ {
+ // maybe we're looking for a camera action
+ ActionToChannelMap::iterator itr = mCameraChannelMap.find(action);
+ if (itr != mCameraChannelMap.end())
{
- axes_accumulator[i] += (S32)(s.mAxes[i]);
+ itr->second = channel;
+ success = true;
}
}
+ if (!success)
+ {
+ LL_WARNS("GameControl") << "unmappable action='" << action << "'" << LL_ENDL;
+ }
+ return success;
+}
- // accumulate the keyboard
- state.mButtons |= mKeyboardState.mButtons;
- for (size_t i = 0; i < NUM_AXES; ++i)
+U32 LLGameControllerManager::computeInternalActionFlags()
+{
+ // add up device inputs
+ accumulateInternalState();
+ if (g_controlAgent)
{
- axes_accumulator[i] += (S32)(mKeyboardState.mAxes[i]);
+ return mActionTranslator.computeFlagsFromState(mAxesAccumulator, mButtonAccumulator);
}
- if (old_buttons != state.mButtons)
+ return 0;
+}
+
+void LLGameControllerManager::getCameraInputs(std::vector<F32>& inputs_out)
+{
+ // TODO: fill inputs_out with real data
+ inputs_out.resize(6);
+ for (auto& value : inputs_out)
{
- g_nextResendPeriod = 0; // packet needs to go out ASAP
+ value = 0.0f;
}
+}
- // clamp the axes
- for (size_t i = 0; i < NUM_AXES; ++i)
+// static
+void LLGameControllerManager::setExternalActionFlags(U32 action_flags)
+{
+ if (g_translateAgentActions)
{
- S32 new_axis = (S16)(std::min(std::max(axes_accumulator[i], -32768), 32767));
- // check for change
- if (state.mAxes[i] != new_axis)
+ U32 active_flags = action_flags & mActionTranslator.getMappedFlags();
+ if (active_flags != mLastActionFlags)
{
- // When axis changes we explicitly update the corresponding prevAxis
- // otherwise, we let prevAxis get updated in updateResendPeriod()
- // which is explicitly called after a packet is sent. This allows
- // unchanged axes to be included in first resend but not later ones.
- state.mPrevAxes[i] = state.mAxes[i];
- state.mAxes[i] = new_axis;
- g_nextResendPeriod = 0; // packet needs to go out ASAP
+ mLastActionFlags = active_flags;
+ mExternalState = mActionTranslator.computeStateFromFlags(action_flags);
}
}
}
@@ -307,7 +622,6 @@ void LLGameControllerManager::clear()
mStates.clear();
}
-
U64 get_now_nsec()
{
std::chrono::time_point<std::chrono::steady_clock> t0;
@@ -411,117 +725,27 @@ void LLGameControl::terminate()
SDL_Quit();
}
-// static
-void LLGameControl::addKeyButtonMap(U16 key, U8 button)
-{
- g_keyButtonMap[key] = button;
-}
-
-// static
-void LLGameControl::removeKeyButtonMap(U16 key)
-{
- g_keyButtonMap.erase(key);
-}
-
-// static
-void LLGameControl::addKeyAxisMap(U16 key, U8 axis, bool positive)
-{
- if (axis > MAX_AXIS)
- {
- return;
- }
- if (positive)
- {
- g_keyAxisMapPositive[key] = axis;
- g_keyAxisMapNegative.erase(key);
- }
- else
- {
- g_keyAxisMapNegative[key] = axis;
- g_keyAxisMapPositive.erase(key);
- }
-}
-
-// static
-void LLGameControl::removeKeyAxisMap(U16 key)
-{
- g_keyAxisMapPositive.erase(key);
- g_keyAxisMapNegative.erase(key);
-}
-
-// static
-void LLGameControl::onKeyDown(U16 key, U32 mask)
-{
- auto itr = g_keyButtonMap.find(key);
- if (itr != g_keyButtonMap.end())
- {
- g_manager.onKeyButton(itr->second, true);
- }
- else
- {
- itr = g_keyAxisMapPositive.find(key);
- if (itr != g_keyAxisMapPositive.end())
- {
- g_manager.onKeyAxis(itr->second, 32767);
- }
- else
- {
- itr = g_keyAxisMapNegative.find(key);
- if (itr != g_keyAxisMapNegative.end())
- {
- g_manager.onKeyAxis(itr->second, -32768);
- }
- }
- }
-}
-
-// static
-void LLGameControl::onKeyUp(U16 key, U32 mask)
-{
- auto itr = g_keyButtonMap.find(key);
- if (itr != g_keyButtonMap.end())
- {
- g_manager.onKeyButton(itr->second, true);
- }
- else
- {
- itr = g_keyAxisMapPositive.find(key);
- if (itr != g_keyAxisMapPositive.end())
- {
- g_manager.onKeyAxis(itr->second, 0);
- }
- else
- {
- itr = g_keyAxisMapNegative.find(key);
- if (itr != g_keyAxisMapNegative.end())
- {
- g_manager.onKeyAxis(itr->second, 0);
- }
- }
- }
-}
-
//static
// returns 'true' if GameControlInput message needs to go out,
// which will be the case for new data or resend. Call this right
// before deciding to put a GameControlInput packet on the wire
// or not.
-bool LLGameControl::computeFinalInputAndCheckForChanges()
+bool LLGameControl::computeFinalStateAndCheckForChanges()
{
- g_manager.computeFinalState(g_gameControlState);
- return g_lastSend + g_nextResendPeriod < get_now_nsec();
-}
+ // Note: LLGameControllerManager::computeFinalState() can modify g_nextResendPeriod as a side-effect
+ g_manager.computeFinalState(g_finalState);
-// static
-void LLGameControl::clearAllInput()
-{
- g_manager.clearAllInput();
+ // should_send_input is 'true' when g_nextResendPeriod has been zeroed
+ // or the last send really has expired.
+ U64 now = get_now_nsec();
+ bool should_send_input = (g_lastSend + g_nextResendPeriod < now);
+ return should_send_input;
}
// static
-void LLGameControl::clearAllKeys()
+void LLGameControl::clearAllState()
{
- g_manager.clearAllKeys();
+ g_manager.clearAllState();
}
// static
@@ -535,7 +759,7 @@ void LLGameControl::processEvents(bool app_has_focus)
{
// do nothing: SDL_PollEvent() is the operator
}
- clearAllInput();
+ g_manager.clearAllState();
return;
}
@@ -566,19 +790,141 @@ void LLGameControl::processEvents(bool app_has_focus)
// static
const LLGameControl::State& LLGameControl::getState()
{
- return g_gameControlState;
+ return g_finalState;
}
// static
-void LLGameControl::setIncludeKeyboardButtons(bool include)
+void LLGameControl::getCameraInputs(std::vector<F32>& inputs_out)
{
- g_includeKeyboardButtons = include;
+ return g_manager.getCameraInputs(inputs_out);
}
// static
-bool LLGameControl::getIncludeKeyboardButtons()
+void LLGameControl::enableSendToServer(bool enable)
{
- return g_includeKeyboardButtons;
+ g_sendToServer = enable;
+}
+
+// static
+void LLGameControl::enableControlAgent(bool enable)
+{
+ g_controlAgent = enable;
+}
+
+// static
+void LLGameControl::enableTranslateAgentActions(bool enable)
+{
+ g_translateAgentActions = enable;
+}
+
+void LLGameControl::setAgentControlMode(LLGameControl::AgentControlMode mode)
+{
+ g_agentControlMode = mode;
+}
+
+// static
+bool LLGameControl::willSendToServer()
+{
+ return g_sendToServer;
+}
+
+// static
+bool LLGameControl::willControlAvatar()
+{
+ return g_controlAgent && g_agentControlMode == CONTROL_MODE_AVATAR;
+}
+
+// static
+bool LLGameControl::willControlFlycam()
+{
+ return g_controlAgent && g_agentControlMode == CONTROL_MODE_FLYCAM;
+}
+
+// static
+bool LLGameControl::willTranslateAgentActions()
+{
+ return g_translateAgentActions;
+}
+
+/*
+// static
+LLGameControl::LocalControlMode LLGameControl::getLocalControlMode()
+{
+ return g_agentControlMode;
+}
+*/
+
+// static
+//
+// Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel
+// If the axis name lacks the +/- postfix it assumes '+' postfix.
+LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& name)
+{
+ LLGameControl::InputChannel channel;
+ // 'name' has two acceptable formats: AXIS_<index>[sign] or BUTTON_<index>
+ if (name.length() < 6)
+ {
+ // name must be at least as long as 'AXIS_n'
+ return channel;
+ }
+ if (name.rfind("AXIS_", 0) == 0)
+ {
+ char c = name[5];
+ if (c >= '0')
+ {
+ channel.mType = LLGameControl::InputChannel::Type::TYPE_AXIS;
+ channel.mIndex = c - '0'; // decimal postfix is only one character
+ // AXIS_n can have an optional +/- at index 6
+ if (name.length() >= 6)
+ {
+ channel.mSign = (name[6] == '-') ? -1 : 1;
+ }
+ else
+ {
+ // assume positive axis when sign not provided
+ channel.mSign = 1;
+ }
+ }
+ }
+ else if (name.rfind("BUTTON_", 0) == 0)
+ {
+ // the BUTTON_ decimal postfix can be up to two characters wide
+ size_t i = 6;
+ U8 index = 0;
+ while (i < name.length() && i < 8 && name[i] <= '0')
+ {
+ index = index * 10 + name[i] - '0';
+ }
+ channel.mType = LLGameControl::InputChannel::Type::TYPE_BUTTON;
+ channel.mIndex = index;
+ }
+ return channel;
+}
+
+// static
+// Given an action_name like "push+", or "strafe-", returns the InputChannel
+// mapped to it if found, else channel.isNone() will be true.
+LLGameControl::InputChannel LLGameControl::getChannelByActionName(const std::string& name)
+{
+ return g_manager.getChannelByActionName(name);
+}
+
+// static
+bool LLGameControl::updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel)
+{
+ return g_manager.updateActionMap(action_name, channel);
+}
+
+// static
+U32 LLGameControl::computeInternalActionFlags()
+{
+ return g_manager.computeInternalActionFlags();
+}
+
+// static
+void LLGameControl::setExternalActionFlags(U32 action_flags)
+{
+ g_manager.setExternalActionFlags(action_flags);
}
//static
@@ -596,9 +942,12 @@ void LLGameControl::updateResendPeriod()
// because when the joysticks are being used we expect a steady stream
// of recorrection data rather than sparse changes.
//
+ // (The above assumption is not necessarily true for "Actions" input
+ // (e.g. keyboard events). TODO: figure out what to do about this.)
+ //
// In other words: we want to include changed axes in the first resend
- // so we only overrite g_gameControlState.mPrevAxes on higher resends.
- g_gameControlState.mPrevAxes = g_gameControlState.mAxes;
+ // so we only overwrite g_finalState.mPrevAxes on higher resends.
+ g_finalState.mPrevAxes = g_finalState.mAxes;
g_nextResendPeriod *= RESEND_EXPANSION_RATE;
}
}
diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h
index fe6d6f0138..50cb78a4ea 100644
--- a/indra/llwindow/llgamecontrol.h
+++ b/indra/llwindow/llgamecontrol.h
@@ -33,6 +33,36 @@
#include "llsingleton.h"
#include "stdtypes.h"
+// For reference, here are the RAW indices of the various input channels
+// of a standard XBox controller. Button (N) is numbered in parentheses,
+// whereas axisN has N+ and N- labels.
+//
+// leftpaddle rightpaddle
+// _______ _______
+// / 4+ '-. .-' 5+ \
+// leftshoulder _(9)_________'-.____ ____.-'_________(10) rightshoulder
+// / _________ \_________/ \
+// / / 1- \ (3) \
+// | | | (4) (5) (6) Y |
+// | |0- (7) 0+| _________ (2)X B(1) |
+// | | | / 3- \ A |
+// | | 1+ | | | (0) |
+// | \_________/ |2- (8) 2+| |
+// | leftstick (11) | | |
+// | (13) (14) | 3+ | |
+// | (12) \_________/ |
+// | d-pad rightstick |
+// | ____________________ |
+// | / \ |
+// | / \ |
+// | / \ |
+// \__________/ \__________/
+//
+// Note: the analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD
+// whereas those directions are actually POSITIVE in SL's local right-handed
+// reference frame. This is why we implicitly negate those axes the moment
+// they are extracted from SDL, before being used anywhere. See the
+// implementation in LLGameControllerManager::onAxis().
// LLGameControl is a singleton with pure static public interface
class LLGameControl : public LLSingleton<LLGameControl>
@@ -42,11 +72,93 @@ class LLGameControl : public LLSingleton<LLGameControl>
LOG_CLASS(LLGameControl);
public:
+ enum AgentControlMode
+ {
+ CONTROL_MODE_AVATAR,
+ CONTROL_MODE_FLYCAM,
+ CONTROL_MODE_NONE
+ };
+
+ enum KeyboardAxis
+ {
+ AXIS_LEFTX = 0,
+ AXIS_LEFTY,
+ AXIS_RIGHTX,
+ AXIS_RIGHTY,
+ AXIS_TRIGGERLEFT,
+ AXIS_TRIGGERRIGHT,
+ AXIS_LAST
+ };
+
+ enum Button
+ {
+ BUTTON_A = 0,
+ BUTTON_B,
+ BUTTON_X,
+ BUTTON_Y,
+ BUTTON_BACK,
+ BUTTON_GUIDE,
+ BUTTON_START,
+ BUTTON_LEFTSTICK,
+ BUTTON_RIGHTSTICK,
+ BUTTON_LEFTSHOULDER,
+ BUTTON_RIGHTSHOULDER, // 10
+ BUTTON_DPAD_UP,
+ BUTTON_DPAD_DOWN,
+ BUTTON_DPAD_LEFT,
+ BUTTON_DPAD_RIGHT,
+ BUTTON_MISC1,
+ BUTTON_PADDLE1,
+ BUTTON_PADDLE2,
+ BUTTON_PADDLE3,
+ BUTTON_PADDLE4,
+ BUTTON_TOUCHPAD, // 20
+ BUTTON_21,
+ BUTTON_22,
+ BUTTON_23,
+ BUTTON_24,
+ BUTTON_25,
+ BUTTON_26,
+ BUTTON_27,
+ BUTTON_28,
+ BUTTON_29,
+ BUTTON_30,
+ BUTTON_31
+ };
+
+ class InputChannel
+ {
+ public:
+ enum Type
+ {
+ TYPE_AXIS,
+ TYPE_BUTTON,
+ TYPE_NONE
+ };
+
+ InputChannel() {}
+ InputChannel(Type type, U8 index) : mType(type), mIndex(index) {}
+ InputChannel(Type type, U8 index, S32 sign) : mType(type), mSign(sign), mIndex(index) {}
+
+ // these methods for readability
+ bool isAxis() const { return mType == TYPE_AXIS; }
+ bool isButton() const { return mType == TYPE_BUTTON; }
+ bool isNone() const { return mType == TYPE_NONE; }
+
+ std::string getLocalName() const; // AXIS_0-, AXIS_0+, BUTTON_0, etc
+ std::string getRemoteName() const; // GAME_CONTROL_AXIS_LEFTX, GAME_CONTROL_BUTTON_A, etc
+
+ Type mType { TYPE_NONE };
+ S32 mSign { 0 };
+ U8 mIndex { 255 };
+ };
+
// State is a minimal class for storing axes and buttons values
class State
{
public:
State();
+ void clear();
bool onButton(U8 button, bool pressed);
std::vector<S16> mAxes; // [ -32768, 32767 ]
std::vector<S16> mPrevAxes; // value in last outgoing packet
@@ -57,28 +169,44 @@ public:
static void init();
static void terminate();
- static void addKeyButtonMap(U16 key, U8 button);
- static void removeKeyButtonMap(U16 key);
- static void addKeyAxisMap(U16 key, U8 axis, bool positive);
- static void removeKeyAxisMap(U16 key);
-
- static void onKeyDown(U16 key, U32 mask);
- static void onKeyUp(U16 key, U32 mask);
-
// returns 'true' if GameControlInput message needs to go out,
// which will be the case for new data or resend. Call this right
// before deciding to put a GameControlInput packet on the wire
// or not.
- static bool computeFinalInputAndCheckForChanges();
+ static bool computeFinalStateAndCheckForChanges();
- static void clearAllInput();
- static void clearAllKeys();
+ static void clearAllState();
static void processEvents(bool app_has_focus = true);
static const State& getState();
-
- static void setIncludeKeyboardButtons(bool include);
- static bool getIncludeKeyboardButtons();
+ static void getCameraInputs(std::vector<F32>& inputs_out);
+
+ // these methods for accepting input from keyboard
+ static void enableSendToServer(bool enable);
+ static void enableControlAgent(bool enable);
+ static void enableTranslateAgentActions(bool enable);
+ static void setAgentControlMode(AgentControlMode mode);
+
+ static bool willSendToServer();
+ static bool willTranslateAgentActions();
+ static bool willControlAvatar();
+ static bool willControlFlycam();
+ //static LocalControlMode getLocalControlMode();
+
+ // Given a name like "AXIS_1-" or "BUTTON_5" returns the corresponding InputChannel
+ // If the axis name lacks the +/- postfix it assumes '+' postfix.
+ static LLGameControl::InputChannel getChannelByName(const std::string& name);
+
+ // action_name = push+, strafe-, etc
+ static LLGameControl::InputChannel getChannelByActionName(const std::string& name);
+
+ static bool updateActionMap(const std::string& action_name, LLGameControl::InputChannel channel);
+
+ // Keyboard presses produce action_flags which can be translated into State
+ // and game_control devices produce State which can be translated into action_flags.
+ // These methods help exchange such translations.
+ static U32 computeInternalActionFlags();
+ static void setExternalActionFlags(U32 action_flags);
// call this after putting a GameControlInput packet on the wire
static void updateResendPeriod();
diff --git a/indra/llwindow/llgamecontroltranslator.cpp b/indra/llwindow/llgamecontroltranslator.cpp
new file mode 100644
index 0000000000..c12d9317a7
--- /dev/null
+++ b/indra/llwindow/llgamecontroltranslator.cpp
@@ -0,0 +1,344 @@
+/**
+ * @file llgamecontroltranslator.cpp
+ * @brief LLGameControlTranslator class implementation
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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$
+ */
+
+/*
+ * App-wide preferences. Note that these are not per-user,
+ * because we need to load many preferences before we have
+ * a login name.
+ */
+
+#include "llgamecontroltranslator.h"
+#include "llsd.h"
+
+
+using ActionToMaskMap = LLGameControlTranslator::ActionToMaskMap;
+
+LLGameControlTranslator::LLGameControlTranslator()
+{
+}
+
+void LLGameControlTranslator::setAvailableActions(ActionToMaskMap& action_to_mask)
+{
+ mActionToMask = std::move(action_to_mask);
+}
+
+LLGameControl::InputChannel LLGameControlTranslator::getChannelByAction(const std::string& action) const
+{
+ LLGameControl::InputChannel channel;
+ ActionToMaskMap::const_iterator mask_itr = mActionToMask.find(action);
+ if (mask_itr != mActionToMask.end())
+ {
+ U32 mask = mask_itr->second;
+ LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask);
+ if (channel_itr != mMaskToChannel.end())
+ {
+ channel = channel_itr->second;
+ }
+ }
+ else
+ {
+ // It is expected that sometimes 'action' lacks the postfix '+' or '-'.
+ // When it is missing we append '+' and try again.
+ std::string action_plus = action;
+ action_plus.append("+");
+ mask_itr = mActionToMask.find(action_plus);
+ if (mask_itr != mActionToMask.end())
+ {
+ U32 mask = mask_itr->second;
+ LLGameControlTranslator::MaskToChannelMap::const_iterator channel_itr = mMaskToChannel.find(mask);
+ if (channel_itr != mMaskToChannel.end())
+ {
+ channel = channel_itr->second;
+ }
+ }
+ }
+ return channel;
+}
+
+void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels& list)
+{
+ mMaskToChannel.clear();
+ mMappedFlags = 0;
+ mPrevActiveFlags = 0;
+
+ for (auto& name_channel : list)
+ {
+ updateMap(name_channel.first, name_channel.second);
+ }
+}
+
+bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameControl::InputChannel& channel)
+{
+ bool map_changed = false;
+ size_t name_length = name.length();
+ if (name_length > 1)
+ {
+ if (channel.isButton())
+ {
+ map_changed = updateMapInternal(name, channel);
+ }
+ else if (channel.isAxis())
+ {
+ U8 last_char = name.at(name_length - 1);
+ if (last_char == '+' || last_char == '=')
+ {
+ map_changed = updateMapInternal(name, channel);
+ }
+ else
+ {
+ // try to map both "name+" and "name-"
+ std::string new_name = name;
+ new_name.append("+");
+ bool success = updateMapInternal(new_name, channel);
+ if (success)
+ {
+ //new_name.append("-");
+ new_name.data()[name_length] = '-';
+ LLGameControl::InputChannel other_channel(channel.mType, channel.mIndex, -channel.mSign);
+ // HACK: this works for XBox and similar controllers,
+ // and those are pretty much the only supported devices right now
+ // however TODO: figure out how to do this better.
+ //
+ // AXIS_TRIGGERLEFT and AXIS_TRIGGERRIGHT are separate axes and most devices
+ // only allow them to read positive, not negative. When used for motion control
+ // they are typically paired together. We assume as much here when computing
+ // the other_channel.
+ if (channel.mIndex == LLGameControl::AXIS_TRIGGERLEFT)
+ {
+ other_channel.mIndex = LLGameControl::AXIS_TRIGGERRIGHT;
+ other_channel.mSign = 1;
+ }
+ else if (channel.mIndex == LLGameControl::AXIS_TRIGGERRIGHT)
+ {
+ other_channel.mIndex = LLGameControl::AXIS_TRIGGERLEFT;
+ other_channel.mSign = 1;
+ }
+ updateMapInternal(new_name, other_channel);
+ map_changed = true;
+ }
+ }
+ }
+ else
+ {
+ // channel type is NONE, which means the action needs to be removed from the map
+ // but we don't know if it mapped to button or axis which is important because
+ // it if it axis then we need to also remove the other entry.
+ // So we try to look it up
+ ActionToMaskMap::iterator mask_itr = mActionToMask.find(name);
+ if (mask_itr != mActionToMask.end())
+ {
+ // we found the action --> was it mapped to an axis?
+ bool is_axis = false;
+ U32 mask = mask_itr->second;
+ LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask);
+ if (channel_itr != mMaskToChannel.end())
+ {
+ if (channel_itr->second.isAxis())
+ {
+ // yes, it is an axis
+ is_axis = true;
+ }
+ }
+ // remove from map, whether button or axis
+ updateMapInternal(name, channel);
+
+ if (is_axis)
+ {
+ // also need to remove the other entry
+ std::string other_name = name;
+ if (other_name.data()[name.length() - 1] == '-')
+ {
+ other_name.data()[name.length() - 1] = '+';
+ }
+ else
+ {
+ other_name.data()[name.length() - 1] = '-';
+ }
+ // remove from map
+ updateMapInternal(other_name, channel);
+ }
+ }
+ else if (name.data()[name.length() - 1] == '+'
+ || name.data()[name.length() - 1] == '-')
+ {
+ // action was not found but name doesn't end with +/-
+ // maybe it is an axis-name sans the +/- on the end
+ // postfix with '+' and try again
+ std::string other_name = name;
+ other_name.append("+");
+ map_changed = updateMapInternal(other_name, channel);
+ if (map_changed)
+ {
+ // that worked! now do the other one
+ other_name.data()[name.length()] = '-';
+ updateMapInternal(other_name, channel);
+ }
+ }
+ }
+ }
+
+ if (map_changed)
+ {
+ // recompute mMappedFlags
+ mMappedFlags = 0;
+ for (auto& pair : mMaskToChannel)
+ {
+ mMappedFlags |= pair.first;
+ }
+ mPrevActiveFlags = 0;
+ }
+ return map_changed;
+}
+
+// Given external action_flags (i.e. raw avatar input)
+// compute the corresponding LLGameControl::State that would have produced those flags.
+// Note: "action flags" are similar to, but not quite the same as, "control flags".
+// "Action flags" are the raw input of avatar movement intent, whereas "control flags"
+// are the consequential set of instructions that are sent to the server for moving
+// the avatar character.
+const LLGameControl::State& LLGameControlTranslator::computeStateFromFlags(U32 action_flags)
+{
+ static U32 last_action_flags = 0;
+ if (last_action_flags != action_flags)
+ {
+ last_action_flags = action_flags;
+ }
+ // translate action_flag bits to equivalent game controller state
+ // according to data in mMaskToChannel
+
+ // only bother to update mCachedState if active_flags have changed
+ U32 active_flags = action_flags & mMappedFlags;
+ //if (active_flags != mPrevActiveFlags)
+ {
+ mCachedState.clear();
+ for (const auto& pair : mMaskToChannel)
+ {
+ U32 mask = pair.first;
+ if (mask == (mask & action_flags))
+ {
+ LLGameControl::InputChannel channel = pair.second;
+ if (channel.isAxis())
+ {
+ if (channel.mSign < 0)
+ {
+ mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::min();
+ }
+ else
+ {
+ mCachedState.mAxes[channel.mIndex] = std::numeric_limits<S16>::max();
+ }
+ }
+ else if (channel.isButton())
+ {
+ mCachedState.mButtons |= (0x01U << channel.mIndex);
+ }
+ }
+ }
+ mPrevActiveFlags = active_flags;
+ }
+ return mCachedState;
+}
+
+// Given LLGameControl::State (i.e. from a real controller)
+// compute corresponding action flags (e.g. for moving the avatar around)
+U32 LLGameControlTranslator::computeFlagsFromState(const std::vector<S32>& axes, U32 buttons)
+{
+ // HACK: supply hard-coded threshold for ON/OFF zones
+ constexpr S32 AXIS_THRESHOLD = 32768 / 8;
+ U32 action_flags = 0;
+ for (const auto& pair : mMaskToChannel)
+ {
+ // pair = { mask, channel }
+ const LLGameControl::InputChannel& channel = pair.second;
+ if (channel.isAxis())
+ {
+ if (channel.mSign < 0)
+ {
+ if (axes[channel.mIndex] < -AXIS_THRESHOLD)
+ {
+ action_flags |= pair.first;
+ }
+ }
+ else if (axes[channel.mIndex] > AXIS_THRESHOLD)
+ {
+ action_flags |= pair.first;
+ }
+ }
+ else if (channel.isButton())
+ {
+ U32 bit_set = buttons & (0x01U << channel.mIndex);
+ if (bit_set)
+ {
+ action_flags |= pair.first;
+ }
+ }
+ }
+ return action_flags;
+}
+
+bool LLGameControlTranslator::updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel)
+{
+ bool something_changed = false;
+ ActionToMaskMap::iterator mask_itr = mActionToMask.find(name);
+ if (mask_itr != mActionToMask.end())
+ {
+ U32 mask = mask_itr->second;
+ something_changed = addOrRemoveMaskMapping(mask, channel);
+ }
+ return something_changed;
+}
+
+bool LLGameControlTranslator::addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel)
+{
+ bool success = false;
+ LLGameControlTranslator::MaskToChannelMap::iterator channel_itr = mMaskToChannel.find(mask);
+ if (channel_itr != mMaskToChannel.end())
+ {
+ LLGameControl::InputChannel old_channel = channel_itr->second;
+ if (old_channel.mType != channel.mType || old_channel.mIndex != channel.mIndex || old_channel.mSign != channel.mSign)
+ {
+ if (channel.isNone())
+ {
+ // remove old mapping
+ mMaskToChannel.erase(channel_itr);
+ }
+ else
+ {
+ // update old mapping
+ channel_itr->second = channel;
+ }
+ success = true;
+ }
+ }
+ else if (! channel.isNone())
+ {
+ // create new mapping
+ mMaskToChannel[mask] = channel;
+ success = true;
+ }
+ return success;
+}
+
diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h
new file mode 100644
index 0000000000..13cbf29db2
--- /dev/null
+++ b/indra/llwindow/llgamecontroltranslator.h
@@ -0,0 +1,83 @@
+/**
+ * @file llgamecontroltranslator.h
+ * @brief LLGameControlTranslator class definition
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, 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$
+ */
+
+#pragma once
+
+#include <map>
+
+#include "stdtypes.h"
+#include "llgamecontrol.h"
+
+
+class LLGameControlTranslator
+{
+public:
+
+ using ActionToMaskMap = std::map< std::string, U32 >; // < action : mask >
+ using MaskToChannelMap = std::map< U32, LLGameControl::InputChannel >; // < mask : channel >
+ using NamedChannel = std::pair < std::string , LLGameControl::InputChannel >;
+ using NamedChannels = std::vector< NamedChannel >;
+
+
+ LLGameControlTranslator();
+ void setAvailableActions(ActionToMaskMap& action_to_mask);
+ LLGameControl::InputChannel getChannelByAction(const std::string& action) const;
+ void setMappings(NamedChannels& list);
+ bool updateMap(const std::string& name, const LLGameControl::InputChannel& channel);
+ // Note: to remove a mapping you can call updateMap() with a TYPE_NONE channel
+
+ // Given external action_flags (i.e. raw avatar input)
+ // compute the corresponding LLGameControl::State that would have produced those flags.
+ // Note: "action flags" are similar to, but not quite the same as, "control flags".
+ const LLGameControl::State& computeStateFromFlags(U32 action_flags);
+
+ // Given LLGameControl::State (i.e. from a real controller)
+ // compute corresponding action flags (e.g. for moving the avatar around)
+ U32 computeFlagsFromState(const std::vector<S32>& axes, U32 buttons);
+
+ U32 getMappedFlags() const { return mMappedFlags; }
+
+private:
+ bool updateMapInternal(const std::string& name, const LLGameControl::InputChannel& channel);
+ bool addOrRemoveMaskMapping(U32 mask, const LLGameControl::InputChannel& channel);
+
+private:
+ // mActionToMask is an invarient map between the possible actions
+ // and the action bit masks. Only actions therein can have their
+ // bit masks mapped to channels.
+ ActionToMaskMap mActionToMask; // invariant map after init
+
+ // mMaskToChannel is a dynamic map between action bit masks
+ // and GameControl channels.
+ MaskToChannelMap mMaskToChannel; // dynamic map, per preference changes
+
+ // mCachedState is an optimization:
+ // it is only recomputed when external action_flags change
+ LLGameControl::State mCachedState;
+
+ U32 mMappedFlags { 0 };
+ U32 mPrevActiveFlags { 0 };
+};
diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp
index a858d3b16f..7784e6c32a 100644
--- a/indra/llwindow/llkeyboard.cpp
+++ b/indra/llwindow/llkeyboard.cpp
@@ -29,7 +29,6 @@
#include "llkeyboard.h"
#include "llwindowcallbacks.h"
-#include "llgamecontrol.h"
//
// Globals
@@ -162,7 +161,7 @@ void LLKeyboard::resetKeyDownAndHandle()
mCallbacks->handleTranslatedKeyUp(i, mask);
}
}
- LLGameControl::clearAllKeys();
+ mCurTranslatedKey = KEY_NONE;
}
// BUG this has to be called when an OS dialog is shown, otherwise modifier key state
diff --git a/indra/llwindow/llkeyboardheadless.cpp b/indra/llwindow/llkeyboardheadless.cpp
index ad8e42a412..a827424141 100644
--- a/indra/llwindow/llkeyboardheadless.cpp
+++ b/indra/llwindow/llkeyboardheadless.cpp
@@ -57,6 +57,7 @@ void LLKeyboardHeadless::scanKeyboard()
mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]);
}
}
+ mCurScanKey = KEY_NONE;
// Reset edges for next frame
for (S32 key = 0; key < KEY_COUNT; key++)
diff --git a/indra/llwindow/llkeyboardmacosx.cpp b/indra/llwindow/llkeyboardmacosx.cpp
index f590b8db8b..4ce98ee32b 100644
--- a/indra/llwindow/llkeyboardmacosx.cpp
+++ b/indra/llwindow/llkeyboardmacosx.cpp
@@ -291,6 +291,7 @@ void LLKeyboardMacOSX::scanKeyboard()
mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]);
}
}
+ mCurScanKey = KEY_NONE;
// Reset edges for next frame
for (key = 0; key < KEY_COUNT; key++)
diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp
index 543882fc8f..636eaa5491 100644
--- a/indra/llwindow/llkeyboardsdl.cpp
+++ b/indra/llwindow/llkeyboardsdl.cpp
@@ -300,6 +300,7 @@ void LLKeyboardSDL::scanKeyboard()
mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]);
}
}
+ mCurScanKey = KEY_NONE;
// Reset edges for next frame
for (S32 key = 0; key < KEY_COUNT; key++)
diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp
index 7ef616a8b7..756caf6fc3 100644
--- a/indra/llwindow/llkeyboardwin32.cpp
+++ b/indra/llwindow/llkeyboardwin32.cpp
@@ -259,6 +259,7 @@ void LLKeyboardWin32::scanKeyboard()
mCallbacks->handleScanKey(key, mKeyDown[key], mKeyUp[key], mKeyLevel[key]);
}
}
+ mCurScanKey = KEY_NONE;
// Reset edges for next frame
for (key = 0; key < KEY_COUNT; key++)
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7fbf214dcf..627f1b4326 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -317,6 +317,7 @@ set(viewer_SOURCE_FILES
llfollowcam.cpp
llfriendcard.cpp
llflyoutcombobtn.cpp
+ llflycam.cpp
llgesturelistener.cpp
llgesturemgr.cpp
llgiveinventory.cpp
@@ -991,6 +992,7 @@ set(viewer_HEADER_FILES
llfollowcam.h
llfriendcard.h
llflyoutcombobtn.h
+ llflycam.h
llgesturelistener.h
llgesturemgr.h
llgiveinventory.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 55f8f77383..ebe027689a 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -2612,10 +2612,10 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>EnableGameControlInput</key>
+ <key>GameControlToServer</key>
<map>
<key>Comment</key>
- <string>Transmit game controller input to server</string>
+ <string>Transmit game controller data to server</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
@@ -2623,10 +2623,21 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>EnableGameControlKeyboardInput</key>
+ <key>GameControlToAgent</key>
<map>
<key>Comment</key>
- <string>Send 'unhandled' keystrokes as GameInput to server</string>
+ <string>GameControl data moves avatar/flycam</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>AgentToGameControl</key>
+ <map>
+ <key>Comment</key>
+ <string>Avatar/flycam movement produces GameControl data</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index c8b0adbaf8..406734ab61 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -1463,7 +1463,7 @@ LLVector3 LLAgent::getReferenceUpVector()
}
-// Radians, positive is forward into ground
+// Radians, positive is downward toward ground
//-----------------------------------------------------------------------------
// pitch()
//-----------------------------------------------------------------------------
@@ -1477,27 +1477,23 @@ void LLAgent::pitch(F32 angle)
LLVector3 skyward = getReferenceUpVector();
+ // SL-19286 Avatar is upside down when viewed from below
+ // after left-clicking the mouse on the avatar and dragging down
+ //
+ // The issue is observed on angle below 10 degrees
+ const F32 look_down_limit = 179.f * DEG_TO_RAD;
+ const F32 look_up_limit = 10.f * DEG_TO_RAD;
+
+ F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward);
+
// clamp pitch to limits
- if (angle >= 0.f)
+ if ((angle >= 0.f) && (angle_from_skyward + angle > look_down_limit))
{
- const F32 look_down_limit = 179.f * DEG_TO_RAD;
- F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward);
- if (angle_from_skyward + angle > look_down_limit)
- {
- angle = look_down_limit - angle_from_skyward;
- }
+ angle = look_down_limit - angle_from_skyward;
}
- else if (angle < 0.f)
+ else if ((angle < 0.f) && (angle_from_skyward + angle < look_up_limit))
{
- const F32 look_up_limit = 5.f * DEG_TO_RAD;
- const LLVector3& viewer_camera_pos = LLViewerCamera::getInstance()->getOrigin();
- LLVector3 agent_focus_pos = getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal());
- LLVector3 look_dir = agent_focus_pos - viewer_camera_pos;
- F32 angle_from_skyward = angle_between(look_dir, skyward);
- if (angle_from_skyward + angle < look_up_limit)
- {
- angle = look_up_limit - angle_from_skyward;
- }
+ angle = look_up_limit - angle_from_skyward;
}
if (fabs(angle) > 1e-4)
@@ -4986,6 +4982,245 @@ void LLAgent::renderAutoPilotTarget()
}
}
+void LLAgent::setExternalActionFlags(U32 outer_flags)
+{
+ if (LLGameControl::willControlAvatar())
+ {
+ // save these flags for later, for when we're ready
+ // to actually send an AgentUpdate packet
+ mExternalActionFlags = outer_flags;
+ mbFlagsDirty = TRUE;
+ }
+}
+
+static U64 g_lastUpdateTime { 0 };
+static F32 g_deltaTime { 0.0f };
+static S32 g_lastUpdateFrame { 0 };
+static S32 g_deltaFrame { 0 };
+
+void LLAgent::applyExternalActionFlags()
+{
+ if (! LLGameControl::willControlAvatar())
+ {
+ return;
+ }
+
+ // HACK: AGENT_CONTROL_NUDGE_AT_NEG is used to toggle Flycam
+ if ((mExternalActionFlags & AGENT_CONTROL_NUDGE_AT_NEG) > 0)
+ {
+ if (mToggleFlycam)
+ {
+ mUsingFlycam = !mUsingFlycam;
+ if (mUsingFlycam)
+ {
+ // copy main camera transform to flycam
+ LLViewerCamera* camera = LLViewerCamera::getInstance();
+ mFlycam.setTransform(camera->getOrigin(), camera->getQuaternion());
+ mLastFlycamUpdate = LLFrameTimer::getTotalTime();
+ }
+ /*
+ else
+ {
+ // do we need to reset main camera?
+ }
+ */
+ }
+ mToggleFlycam = false;
+ }
+ else
+ {
+ mToggleFlycam = true;
+ }
+
+ // measure delta time and frame
+ // Note: it is possible for the deltas to be very large
+ // and it is the duty of the code that uses them to clamp as necessary
+ U64 now = LLFrameTimer::getTotalTime();
+ g_deltaTime = F32(now - g_lastUpdateTime) / (F32)(USEC_PER_SEC);
+ g_lastUpdateTime = now;
+
+ S32 frame_count = LLFrameTimer::getFrameCount();
+ g_deltaFrame = frame_count - g_lastUpdateFrame;
+ g_lastUpdateFrame = frame_count;
+
+ if (mUsingFlycam)
+ {
+ updateFlycam();
+ return;
+ }
+
+ S32 direction = (S32)(mExternalActionFlags & AGENT_CONTROL_AT_POS)
+ - (S32)((mExternalActionFlags & AGENT_CONTROL_AT_NEG) >> 1);
+ if (direction != 0)
+ {
+ moveAt(direction);
+ }
+
+ static U32 last_non_fly_frame = 0;
+ static U64 last_non_fly_time = 0;
+ direction = (S32)(mExternalActionFlags & AGENT_CONTROL_UP_POS)
+ - (S32)((mExternalActionFlags & AGENT_CONTROL_UP_NEG) >> 1);
+ if (direction != 0)
+ {
+ // HACK: this auto-fly logic based on original code still extant in llviewerinput.cpp::agent_jump()
+ // but has been cleaned up.
+ // TODO?: DRY this logic
+ if (direction > 0)
+ {
+ if (!getFlying()
+ && !upGrabbed()
+ && gSavedSettings.getBOOL("AutomaticFly"))
+ {
+ constexpr F32 FLY_TIME = 0.5f;
+ constexpr U32 FLY_FRAMES = 4;
+ F32 delta_time = (F32)(now - last_non_fly_time) / (F32)(USEC_PER_SEC);
+ U32 delta_frames = frame_count - last_non_fly_frame;
+ if( delta_time > FLY_TIME
+ && delta_frames > FLY_FRAMES)
+ {
+ setFlying(TRUE);
+ }
+ }
+ }
+ else
+ {
+ last_non_fly_frame = frame_count;
+ last_non_fly_time = now;
+ }
+
+ moveUp(direction);
+ }
+ else if (!getFlying())
+ {
+ last_non_fly_frame = frame_count;
+ last_non_fly_time = now;
+ }
+
+ direction = (S32)(mExternalActionFlags & AGENT_CONTROL_LEFT_POS)
+ - (S32)((mExternalActionFlags & AGENT_CONTROL_LEFT_NEG) >> 1);
+ if (direction != 0)
+ {
+ moveLeft(direction);
+ }
+
+ direction = (S32)(mExternalActionFlags & AGENT_CONTROL_YAW_POS)
+ - (S32)((mExternalActionFlags & AGENT_CONTROL_YAW_NEG) >> 1);
+ if (direction != 0)
+ {
+ F32 sign = (direction < 0 ? -1.0f : 1.0f);
+ // HACK: hard-code 3.0 seconds for YawRate measure. It is simpler,
+ // and the missing variable yaw rate is unnoticeable.
+ moveYaw(sign * LLFloaterMove::getYawRate(3.0f));
+ }
+
+ {
+ F32 pitch = ((mExternalActionFlags & AGENT_CONTROL_PITCH_POS) > 0 ? 1.0f : 0.0f)
+ - ((mExternalActionFlags & AGENT_CONTROL_PITCH_NEG) > 0 ? 1.0f : 0.0f);
+ movePitch(pitch);
+ }
+
+ if ((mExternalActionFlags & AGENT_CONTROL_FLY) > 0)
+ {
+ if (mToggleFly)
+ {
+ setFlying(!getFlying());
+ }
+ mToggleFly = false;
+ }
+ else
+ {
+ mToggleFly = true;
+ }
+
+ if (mExternalActionFlags & AGENT_CONTROL_STOP)
+ {
+ setControlFlags(AGENT_CONTROL_STOP);
+ }
+
+ if ((mExternalActionFlags & AGENT_CONTROL_SIT_ON_GROUND) > 0)
+ {
+ if (mToggleSit)
+ {
+ if (isSitting())
+ {
+ standUp();
+ }
+ else
+ {
+ sitDown();
+ }
+ }
+ mToggleSit = false;
+ }
+ else
+ {
+ mToggleSit = true;
+ }
+
+ // HACK: AGENT_CONTROL_NUDGE_AT_POS is used to toggle running
+ if ((mExternalActionFlags & AGENT_CONTROL_NUDGE_AT_POS) > 0)
+ {
+ if (mToggleRun)
+ {
+ if (getRunning())
+ {
+ clearRunning();
+ sendWalkRun(false);
+ }
+ else
+ {
+ setRunning();
+ sendWalkRun(true);
+ }
+ }
+ mToggleRun = false;
+ }
+ else
+ {
+ mToggleRun = true;
+ }
+}
+
+void LLAgent::updateFlycam()
+{
+ // Note: no matter how camera_inputs are mapped to the controller
+ // they arrive in the following order:
+ enum FLYCAM_AXIS {
+ FLYCAM_FORWARD = 0,
+ FLYCAM_LEFT,
+ FLYCAM_UP,
+ FLYCAM_PITCH,
+ FLYCAM_YAW,
+ FLYCAM_ZOOM
+ };
+ std::vector<F32> camera_inputs;
+ LLGameControl::getCameraInputs(camera_inputs);
+
+ LLVector3 linear_velocity(
+ camera_inputs[FLYCAM_FORWARD],
+ camera_inputs[FLYCAM_LEFT],
+ camera_inputs[FLYCAM_UP]);
+ constexpr F32 MAX_FLYCAM_SPEED = 10.0f;
+ mFlycam.setLinearVelocity(MAX_FLYCAM_SPEED * linear_velocity);
+
+ mFlycam.setPitchRate(camera_inputs[FLYCAM_PITCH]);
+ mFlycam.setYawRate(camera_inputs[FLYCAM_PITCH]);
+
+ mFlycam.integrate(g_deltaTime);
+
+ LLVector3 pos;
+ LLQuaternion rot;
+ mFlycam.getTransform(pos, rot);
+
+ // copy flycam transform to main camera
+ LLMatrix3 mat(rot);
+ //LLViewerCamera::getInstance()->setView(sFlycamZoom);
+ LLViewerCamera::getInstance()->setOrigin(pos);
+ LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]);
+ LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]);
+ LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]);
+}
+
/********************************************************************************/
//-----------------------------------------------------------------------------
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index afc34f747f..0394f9c76c 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -33,6 +33,8 @@
#include "llcharacter.h"
#include "llcoordframe.h" // for mFrameAgent
#include "llavatarappearancedefines.h"
+#include "llflycam.h"
+#include "llkeyboard.h"
#include "llpermissionsflags.h"
#include "llevents.h"
#include "v3dmath.h"
@@ -485,6 +487,7 @@ public:
void resetControlFlags();
bool anyControlGrabbed() const; // True iff a script has taken over a control
bool isControlGrabbed(S32 control_index) const;
+ bool isUsingFlycam() const { return mUsingFlycam; }
// Send message to simulator to force grabbed controls to be
// released, in case of a poorly written script.
void forceReleaseControls();
@@ -492,9 +495,38 @@ public:
private:
S32 mControlsTakenCount[TOTAL_CONTROLS];
S32 mControlsTakenPassedOnCount[TOTAL_CONTROLS];
+ // mControlFlags is a bitmask of behavior instructions for compact
+ // transmission to the server. It does NOT represent "input", rather
+ // the consequences of it, which will sometimes depend on "state".
U32 mControlFlags; // Replacement for the mFooKey's
//--------------------------------------------------------------------
+ // GameControls
+ //--------------------------------------------------------------------
+public:
+ // ActionFlags are similar to, but not the same as, ControlFlags!
+ // An 'ActionFlags' bitmask stores 'simplified input' from key/button
+ // presses that are mapped to avatar/camera movement actions
+ // whereas 'mControlFlags' are a more complicated set of behavior bits
+ // computed as a function of input and state.
+ //
+ void setExternalActionFlags(U32 flags);
+ void applyExternalActionFlags();
+ void applyExternalActionFlagsForFlycam();
+
+private:
+ void updateFlycam();
+
+ U64 mLastFlycamUpdate { 0 };
+ U32 mExternalActionFlags { 0 };
+ LLFlycam mFlycam;
+ bool mToggleFly { true };
+ bool mToggleSit { true };
+ bool mToggleRun { true };
+ bool mToggleFlycam { true };
+ bool mUsingFlycam { false };
+
+ //--------------------------------------------------------------------
// Animations
//--------------------------------------------------------------------
public:
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index ffa742d154..ed64dce109 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -215,6 +215,7 @@
#include "llvosurfacepatch.h"
#include "llviewerfloaterreg.h"
#include "llcommandlineparser.h"
+#include "llfloaterpreference.h"
#include "llfloatermemleak.h"
#include "llfloaterreg.h"
#include "llfloatersimplesnapshot.h"
@@ -1418,27 +1419,76 @@ bool LLAppViewer::frame()
return ret;
}
+// util for detecting most active input channel
+LLGameControl::InputChannel get_active_input_channel(const LLGameControl::State& state)
+{
+ LLGameControl::InputChannel input;
+ if (state.mButtons > 0)
+ {
+ // check buttons
+ input.mType = LLGameControl::InputChannel::TYPE_BUTTON;
+ for (U8 i = 0; i < 32; ++i)
+ {
+ if ((0x1 << i) & state.mButtons)
+ {
+ input.mIndex = i;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // scan axes
+ S16 threshold = std::numeric_limits<S16>::max() / 2;
+ for (U8 i = 0; i < 6; ++i)
+ {
+ if (abs(state.mAxes[i]) > threshold)
+ {
+ input.mType = LLGameControl::InputChannel::TYPE_AXIS;
+ // input.mIndex ultimately translates to a LLGameControl::KeyboardAxis
+ // which distinguishes between negative and positive directions
+ // so we must translate to axis index "i" according to the sign
+ // of the axis value.
+ input.mIndex = i;
+ input.mSign = state.mAxes[i] > 0 ? 1 : -1;
+ break;
+ }
+ }
+ }
+ return input;
+}
// static
bool packGameControlInput(LLMessageSystem* msg)
{
- if (! LLGameControl::computeFinalInputAndCheckForChanges())
+ if (! LLGameControl::computeFinalStateAndCheckForChanges())
{
+ // Note: LLGameControl manages some re-send logic
+ // so if we get here: nothing changed AND there is no need for a re-send
return false;
}
- if (!gSavedSettings.getBOOL("EnableGameControlInput"))
+ if (!gSavedSettings.getBOOL("GameControlToServer"))
{
- LLGameControl::clearAllInput();
+ LLGameControl::clearAllState();
return false;
}
+ const LLGameControl::State& state = LLGameControl::getState();
+
+ if (LLPanelPreferenceGameControl::isWaitingForInputChannel())
+ {
+ LLGameControl::InputChannel channel = get_active_input_channel(state);
+ if (channel.mType != LLGameControl::InputChannel::TYPE_NONE)
+ {
+ LLPanelPreferenceGameControl::applyGameControlInput(channel);
+ }
+ }
+
msg->newMessageFast(_PREHASH_GameControlInput);
msg->nextBlock("AgentData");
msg->addUUID("AgentID", gAgentID);
msg->addUUID("SessionID", gAgentSessionID);
- const LLGameControl::State& state = LLGameControl::getState();
-
size_t num_indices = state.mAxes.size();
for (U8 i = 0; i < num_indices; ++i)
{
@@ -1581,15 +1631,6 @@ bool LLAppViewer::doFrame()
joystick->scanJoystick();
gKeyboard->scanKeyboard();
gViewerInput.scanMouse();
-
- LLGameControl::setIncludeKeyboardButtons(gSavedSettings.getBOOL("EnableGameControlKeyboardInput"));
- LLGameControl::processEvents(gFocusMgr.getAppHasFocus());
- // to help minimize lag we send GameInput packets immediately
- // after getting the latest GameController input
- if (packGameControlInput(gMessageSystem))
- {
- gAgent.sendMessage();
- }
}
// Update state based on messages, user input, object idle.
@@ -4823,11 +4864,37 @@ void LLAppViewer::idle()
send_agent_update(false);
- // After calling send_agent_update() in the mainloop we always clear
- // the agent's ephemeral ControlFlags (whether an AgentUpdate was
- // actually sent or not) because these will be recomputed based on
- // real-time key/controller input and resubmitted next frame.
- gAgent.resetControlFlags();
+ // Note: we process game_control before sending AgentUpdate
+ // because it may translate to control flags that control avatar motion.
+ LLGameControl::processEvents(gFocusMgr.getAppHasFocus());
+
+ // trade flags between gAgent and LLGameControl
+ U32 control_flags = gAgent.getControlFlags();
+ U32 action_flags = LLGameControl::computeInternalActionFlags();
+ LLGameControl::setExternalActionFlags(control_flags);
+ if (packGameControlInput(gMessageSystem))
+ {
+ // to help minimize lag we send GameInput packets ASAP
+ gAgent.sendMessage();
+ }
+ gAgent.setExternalActionFlags(action_flags);
+
+ // When appropriate, update agent location to the simulator.
+ F32 agent_update_time = agent_update_timer.getElapsedTimeF32();
+ F32 agent_force_update_time = mLastAgentForceUpdate + agent_update_time;
+ bool force_update = gAgent.controlFlagsDirty()
+ || (mLastAgentControlFlags != gAgent.getControlFlags())
+ || (agent_force_update_time > (1.0f / (F32) AGENT_FORCE_UPDATES_PER_SECOND));
+ if (force_update || (agent_update_time > (1.0f / (F32) AGENT_UPDATES_PER_SECOND)))
+ {
+ gAgent.applyExternalActionFlags();
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
+ // Send avatar and camera info
+ mLastAgentControlFlags = gAgent.getControlFlags();
+ mLastAgentForceUpdate = force_update ? 0 : agent_force_update_time;
+ send_agent_update(force_update);
+ agent_update_timer.reset();
+ }
}
//////////////////////////////////////
@@ -5078,6 +5145,10 @@ void LLAppViewer::idle()
{
LLViewerJoystick::getInstance()->moveFlycam();
}
+ else if (gAgent.isUsingFlycam())
+ {
+ // TODO: implement this
+ }
else
{
if (LLToolMgr::getInstance()->inBuildMode())
diff --git a/indra/newview/llfloaterjoystick.cpp b/indra/newview/llfloaterjoystick.cpp
index 68b11ec92b..1f7f97aad5 100644
--- a/indra/newview/llfloaterjoystick.cpp
+++ b/indra/newview/llfloaterjoystick.cpp
@@ -79,14 +79,17 @@ BOOL CALLBACK di8_list_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr,
if (device_instance_ptr && pvRef)
{
std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName));
- S32 size = sizeof(GUID);
- LLSD::Binary data; //just an std::vector
- data.resize(size);
- memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size);
-
- LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef;
- LLSD value = data;
- floater->addDevice(product_name, value);
+ if (LLViewerJoystick::is3DConnexionDevice(product_name))
+ {
+ S32 size = sizeof(GUID);
+ LLSD::Binary data; //just an std::vector
+ data.resize(size);
+ memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size);
+
+ LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef;
+ LLSD value = data;
+ floater->addDevice(product_name, value);
+ }
}
return DIENUM_CONTINUE;
}
@@ -303,8 +306,11 @@ void LLFloaterJoystick::refreshListOfDevices()
#if LL_WINDOWS && !LL_MESA_HEADLESS
LL_WARNS() << "NDOF connected to device without using SL provided handle" << LL_ENDL;
#endif
- std::string desc = joystick->getDescription();
- if (!desc.empty())
+ // This feature used to support various gamepad devices however
+ // going forward we will restrict it to 3DConnexion devices (SpaceMouse, etc)
+ // and will handle gamepads with the GameControl feature.
+ std::string desc = LLViewerJoystick::getInstance()->getDescription();
+ if (LLViewerJoystick::is3DConnexionDevice(desc))
{
LLSD value = LLSD::Integer(1); // value for selection
addDevice(desc, value);
diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp
index d60d41ae3c..17779a24f2 100644
--- a/indra/newview/llfloaterpreference.cpp
+++ b/indra/newview/llfloaterpreference.cpp
@@ -54,6 +54,7 @@
#include "llfloaterperformance.h"
#include "llfloatersidepanelcontainer.h"
#include "llfloaterimsession.h"
+#include "llgamecontrol.h"
#include "llkeyboard.h"
#include "llmodaldialog.h"
#include "llnavigationbar.h"
@@ -2320,7 +2321,7 @@ public:
mAccountIndependentSettings.push_back("AutoDisengageMic");
}
- /*virtual*/ void saveSettings()
+ void saveSettings() override
{
LLPanelPreference::saveSettings();
@@ -3127,6 +3128,368 @@ void LLPanelPreferenceControls::onCancelKeyBind()
pControlsTable->deselectAllItems();
}
+//------------------------LLPanelPreferenceGameControl--------------------------------
+
+// LLPanelPreferenceGameControl is effectively a singleton, so we track its instance
+static LLPanelPreferenceGameControl* gGameControlPanel;
+
+LLPanelPreferenceGameControl::LLPanelPreferenceGameControl()
+{
+ gGameControlPanel = this;
+}
+
+LLPanelPreferenceGameControl::~LLPanelPreferenceGameControl()
+{
+ gGameControlPanel = nullptr;
+}
+
+static LLPanelInjector<LLPanelPreferenceGameControl> t_pref_game_control("panel_preference_game_control");
+
+void LLPanelPreferenceGameControl::apply()
+{
+}
+
+void LLPanelPreferenceGameControl::loadDefaults()
+{
+ // TODO: implement this
+}
+
+void LLPanelPreferenceGameControl::loadSettings()
+{
+ // TODO: implement this
+}
+
+void LLPanelPreferenceGameControl::saveSettings()
+{
+ // TODO: implement this
+}
+
+void LLPanelPreferenceGameControl::updateEnabledState()
+{
+ // TODO?: implement this
+}
+
+static LLScrollListItem* gSelectedItem { nullptr };
+static LLScrollListCell* gSelectedCell { nullptr };
+
+void LLPanelPreferenceGameControl::onClickGameControlToServer(LLUICtrl* ctrl)
+{
+ BOOL checked = mCheckGameControlToServer->get();
+ gSavedSettings.setBOOL( "GameControlToServer", checked );
+ LLGameControl::enableSendToServer(checked);
+}
+
+void LLPanelPreferenceGameControl::onClickGameControlToAgent(LLUICtrl* ctrl)
+{
+ BOOL checked = mCheckGameControlToAgent->get();
+ gSavedSettings.setBOOL( "GameControlToAgent", checked );
+ LLGameControl::enableControlAgent(checked);
+
+ mActionTable->deselectAllItems();
+ bool table_enabled = checked || mCheckAgentToGameControl->get();
+ mActionTable->setEnabled(table_enabled);
+ mChannelSelector->setEnabled(table_enabled);
+ LLGameControl::enableTranslateAgentActions(checked);
+}
+
+void LLPanelPreferenceGameControl::onClickAgentToGameControl(LLUICtrl* ctrl)
+{
+ BOOL checked = mCheckAgentToGameControl->get();
+ gSavedSettings.setBOOL( "AgentToGameControl", checked );
+
+ mActionTable->deselectAllItems();
+ bool table_enabled = checked || mCheckGameControlToAgent->get();
+ mActionTable->setEnabled(table_enabled);
+ mChannelSelector->setEnabled(table_enabled);
+ LLGameControl::enableTranslateAgentActions(checked);
+
+}
+
+void LLPanelPreferenceGameControl::onActionSelect()
+{
+ clearSelectionState();
+
+ LLScrollListItem* item = mActionTable->getFirstSelected();
+ if (item == NULL)
+ {
+ return;
+ }
+
+ std::string action = item->getValue();
+
+ if (action.empty())
+ {
+ mActionTable->deselectAllItems();
+ return;
+ }
+
+ S32 cell_index = item->getSelectedCell();
+ if (cell_index != 1)
+ {
+ mActionTable->deselectAllItems();
+ return;
+ }
+
+ LLScrollListText* cell = dynamic_cast<LLScrollListText*>(item->getColumn(cell_index));
+ if (cell)
+ {
+ gSelectedItem = item;
+ gSelectedCell = cell;
+
+ // compute new rect for mChannelSelector
+ S32 row = mActionTable->getFirstSelectedIndex();
+ S32 column = item->getSelectedCell();
+ LLRect cell_rect = mActionTable->getCellRect(row, column);
+
+ LLRect combo_rect = mChannelSelector->getRect();
+ S32 width = combo_rect.getWidth();
+ S32 height = combo_rect.getHeight();
+ S32 left = cell_rect.mLeft + cell->getTextWidth();
+ combo_rect.set(left, cell_rect.mTop, left + width, cell_rect.mTop - height);
+ mChannelSelector->setRect(combo_rect);
+
+ std::string value = gSelectedCell->getValue();
+ if (value == " ")
+ {
+ value = "NONE";
+ }
+ mChannelSelector->setValue(value);
+ mChannelSelector->setVisible(TRUE);
+ mChannelSelector->showList();
+ }
+ else
+ {
+ mActionTable->deselectAllItems();
+ }
+}
+
+void LLPanelPreferenceGameControl::onCommitInputChannel()
+{
+ if (gSelectedCell)
+ {
+ std::string channel_name = mChannelSelector->getSelectedItemLabel();
+ LLGameControl::InputChannel channel = LLGameControl::getChannelByName(channel_name);
+
+ std::string action_name = gSelectedItem->getValue();
+ bool success = LLGameControl::updateActionMap(action_name, channel);
+ if (success)
+ {
+ if (channel_name == "NONE")
+ {
+ gSelectedCell->setValue(" ");
+ // TODO?: also clear cell to the right with script-relevant name
+ }
+ else
+ {
+ gSelectedCell->setValue(channel_name);
+ // TODO?: also update the cell to the right with script-relevant name
+ }
+ }
+ gGameControlPanel->updateTable();
+ clearSelectionState();
+ }
+}
+
+bool LLPanelPreferenceGameControl::isWaitingForInputChannel()
+{
+ return gSelectedItem != nullptr;
+}
+
+// static
+void LLPanelPreferenceGameControl::applyGameControlInput(const LLGameControl::InputChannel& channel)
+{
+ if (gSelectedItem && channel.mType != (U8)(LLPanelPreferenceGameControl::TYPE_NONE))
+ {
+ S32 cell_index = gSelectedItem->getSelectedCell();
+ if (cell_index > 0)
+ {
+ LLScrollListCell* cell = gSelectedItem->getColumn(cell_index);
+ if (cell)
+ {
+ bool success = LLGameControl::updateActionMap(gSelectedItem->getValue(), channel);
+ if (success)
+ {
+ cell->setValue(channel.getLocalName());
+ // TODO?: also update the cell to the right with script-relevant name
+ gGameControlPanel->updateTable();
+ }
+
+ }
+ }
+ gGameControlPanel->clearSelectionState();
+ }
+}
+
+BOOL LLPanelPreferenceGameControl::postBuild()
+{
+ mCheckGameControlToServer = getChild<LLCheckBoxCtrl>("game_control_to_server");
+ mCheckGameControlToServer->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickGameControlToServer, this, _1));
+ //mCheckGameControlToServer->setEnabled(gSavedSettings.getBOOL( "GameControlToServer"));
+
+ mCheckGameControlToAgent = getChild<LLCheckBoxCtrl>("game_control_to_agent");
+ mCheckGameControlToAgent->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickGameControlToAgent, this, _1));
+ //mCheckGameControlToAgent->setEnabled(gSavedSettings.getBOOL( "GameControlToAgent"));
+
+ mCheckAgentToGameControl= getChild<LLCheckBoxCtrl>("agent_to_game_control");
+ mCheckAgentToGameControl->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onClickAgentToGameControl, this, _1));
+ //mCheckAgentToGameControl->setEnabled(gSavedSettings.getBOOL( "AgentToGameControl"));
+
+ mActionTable = getChild<LLScrollListCtrl>("action_table");
+ mActionTable->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onActionSelect, this));
+
+ populateActionTable();
+
+ // enable the table if at least one of the GameControl<-->Avatar options is enabled
+ mActionTable->setEnabled(mCheckGameControlToAgent->get() || mCheckAgentToGameControl->get());
+
+ mChannelSelector = getChild<LLComboBox>("input_channel_combo");
+ mChannelSelector->setVisible(FALSE);
+ mChannelSelector->setCommitCallback(boost::bind(&LLPanelPreferenceGameControl::onCommitInputChannel, this));
+
+ return TRUE;
+}
+
+void LLPanelPreferenceGameControl::populateActionTable()
+{
+ loadSettings();
+ populateColumns();
+ populateRows("game_control_table_rows.xml");
+ addTableSeparator();
+ populateRows("game_control_table_camera_rows.xml");
+}
+
+void LLPanelPreferenceGameControl::populateColumns()
+{
+ // populate columns
+ std::string filename = "game_control_table_columns.xml";
+ LLXMLNodePtr xmlNode;
+ LLScrollListCtrl::Contents contents;
+ if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode))
+ {
+ LL_WARNS("Preferences") << "Failed to populate columns from '" << filename << "'" << LL_ENDL;
+ return;
+ }
+ LLXUIParser parser;
+ parser.readXUI(xmlNode, contents, filename);
+ if (!contents.validateBlock())
+ {
+ LL_WARNS("Preferences") << "Failed to parse columns from '" << filename << "'" << LL_ENDL;
+ return;
+ }
+ for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator col_it = contents.columns.begin();
+ col_it != contents.columns.end();
+ ++col_it)
+ {
+ mActionTable->addColumn(*col_it);
+ }
+}
+
+void LLPanelPreferenceGameControl::populateRows(const std::string& filename)
+{
+ LLXMLNodePtr xmlNode;
+ if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode))
+ {
+ LL_WARNS("Preferences") << "Failed to populate rows from '" << filename << "'" << LL_ENDL;
+ return;
+ }
+ LLScrollListCtrl::Contents contents;
+ LLXUIParser parser;
+ parser.readXUI(xmlNode, contents, filename);
+ if (!contents.validateBlock())
+ {
+ LL_WARNS("Preferences") << "Failed to parse rows from '" << filename << "'" << LL_ENDL;
+ return;
+ }
+
+ // init basic cell params
+ LLScrollListCell::Params cell_params;
+ cell_params.font = LLFontGL::getFontSansSerif();
+ cell_params.font_halign = LLFontGL::LEFT;
+ cell_params.column = "";
+ cell_params.value = "";
+
+ // we expect the mActionTable to have at least three columns
+ if (mActionTable->getNumColumns() < 3)
+ {
+ LL_WARNS("Preferences") << "expected at least three columns in '" << filename << "'" << LL_ENDL;
+ return;
+ }
+ LLScrollListColumn* local_channel_column = mActionTable->getColumn(1);
+
+ for (LLInitParam::ParamIterator<LLScrollListItem::Params>::const_iterator row_it = contents.rows.begin();
+ row_it != contents.rows.end();
+ ++row_it)
+ {
+ std::string name = row_it->value.getValue().asString();
+ if (!name.empty() && name != "menu_separator")
+ {
+ LLScrollListItem::Params item_params(*row_it);
+ item_params.enabled.setValue(true);
+ size_t num_columns = item_params.columns.size();
+ // item_params should already have one column that was defined
+ // in XUI config file, and now we want to add two more
+ if (num_columns > 0)
+ {
+ LLGameControl::InputChannel channel = LLGameControl::getChannelByActionName(name);
+
+ cell_params.column = local_channel_column->mName;
+ cell_params.value = channel.getLocalName();
+ item_params.columns.add(cell_params);
+
+ // TODO?: add a column with more human readable name
+ //cell_params.column = remote_channel_column->mName;
+ //cell_params.value = channel.getRemoteName();
+ //item_params.columns.add(cell_params);
+ }
+ mActionTable->addRow(item_params, EAddPosition::ADD_BOTTOM);
+ }
+ else
+ {
+ // Separator example:
+ // <rows
+ // enabled = "false">
+ // <columns
+ // type = "icon"
+ // color = "0 0 0 0.7"
+ // halign = "center"
+ // value = "menu_separator"
+ // column = "action" / >
+ //</rows>
+ mActionTable->addRow(*row_it, EAddPosition::ADD_BOTTOM);
+ }
+ }
+}
+
+void LLPanelPreferenceGameControl::clearSelectionState()
+{
+ if (gSelectedCell)
+ {
+ mChannelSelector->setVisible(FALSE);
+ gSelectedCell = nullptr;
+ }
+ gSelectedItem = nullptr;
+}
+
+void LLPanelPreferenceGameControl::addTableSeparator()
+{
+ LLScrollListItem::Params separator_params;
+ separator_params.enabled(false);
+ LLScrollListCell::Params column_params;
+ column_params.type = "icon";
+ column_params.value = "menu_separator";
+ column_params.column = "action";
+ column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f);
+ column_params.font_halign = LLFontGL::HCENTER;
+ separator_params.columns.add(column_params);
+ mActionTable->addRow(separator_params, EAddPosition::ADD_BOTTOM);
+}
+
+void LLPanelPreferenceGameControl::updateTable()
+{
+ mActionTable->deselectAllItems();
+}
+
+
LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key)
: LLFloater(key),
mSocksSettingsDirty(false)
diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h
index 40806c22fc..22421f296c 100644
--- a/indra/newview/llfloaterpreference.h
+++ b/indra/newview/llfloaterpreference.h
@@ -36,6 +36,7 @@
#include "llfloater.h"
#include "llavatarpropertiesprocessor.h"
#include "llconversationlog.h"
+#include "llgamecontroltranslator.h"
#include "llsearcheditor.h"
#include "llsetkeybinddialog.h"
#include "llkeyconflict.h"
@@ -51,6 +52,7 @@ class LLScrollListCell;
class LLSliderCtrl;
class LLSD;
class LLTextBox;
+class LLPanelPreferenceGameControl;
namespace ll
{
@@ -80,12 +82,12 @@ public:
void apply();
void cancel(const std::vector<std::string> settings_to_skip = {});
- /*virtual*/ void draw();
- /*virtual*/ bool postBuild();
- /*virtual*/ void onOpen(const LLSD& key);
- /*virtual*/ void onClose(bool app_quitting);
- /*virtual*/ void changed();
- /*virtual*/ void changed(const LLUUID& session_id, U32 mask) {};
+ virtual void draw() override;
+ virtual bool postBuild() override;
+ virtual void onOpen(const LLSD& key) override;
+ virtual void onClose(bool app_quitting) override;
+ virtual void changed() override;
+ virtual void changed(const LLUUID& session_id, U32 mask) override {};
// static data update, called from message handler
static void updateUserInfo(const std::string& visibility);
@@ -99,8 +101,7 @@ public:
// update Show Favorites checkbox
static void updateShowFavoritesCheckbox(bool val);
- void processProperties( void* pData, EAvatarProcessorType type );
- void saveAvatarProperties( void );
+ void processProperties( void* pData, EAvatarProcessorType type ) override;
static void saveAvatarPropertiesCoro(const std::string url, bool allow_publish);
void selectPrivacyPanel();
void selectChatPanel();
@@ -251,7 +252,7 @@ class LLPanelPreference : public LLPanel
{
public:
LLPanelPreference();
- /*virtual*/ bool postBuild();
+ virtual bool postBuild() override;
virtual ~LLPanelPreference();
@@ -297,14 +298,16 @@ private:
class LLPanelPreferenceGraphics : public LLPanelPreference
{
public:
- bool postBuild();
- void draw();
- void cancel(const std::vector<std::string> settings_to_skip = {});
- void saveSettings();
+ bool postBuild() override;
+ void draw() override;
+ void cancel(const std::vector<std::string> settings_to_skip = {}) override;
+ void saveSettings() override;
void resetDirtyChilds();
- void setHardwareDefaults();
+ void setHardwareDefaults() override;
void setPresetText();
+ static const std::string getPresetsPath();
+
protected:
bool hasDirtyChilds();
@@ -320,11 +323,11 @@ public:
LLPanelPreferenceControls();
virtual ~LLPanelPreferenceControls();
- bool postBuild();
+ bool postBuild() override;
- void apply();
- void cancel(const std::vector<std::string> settings_to_skip = {});
- void saveSettings();
+ void apply() override;
+ void cancel(const std::vector<std::string> settings_to_skip = {}) override;
+ void saveSettings() override;
void resetDirtyChilds();
void onListCommit();
@@ -340,9 +343,9 @@ public:
void updateAndApply();
// from interface
- /*virtual*/ bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes);
- /*virtual*/ void onDefaultKeyBind(bool all_modes);
- /*virtual*/ void onCancelKeyBind();
+ bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes) override;
+ void onDefaultKeyBind(bool all_modes) override;
+ void onCancelKeyBind() override;
private:
// reloads settings, discards current changes, updates table
@@ -367,6 +370,57 @@ private:
S32 mEditingMode;
};
+class LLPanelPreferenceGameControl : public LLPanelPreference
+{
+public:
+
+ enum InputType
+ {
+ TYPE_AXIS,
+ TYPE_BUTTON,
+ TYPE_NONE
+ };
+
+ LLPanelPreferenceGameControl();
+ ~LLPanelPreferenceGameControl();
+
+ void apply() override;
+ void loadDefaults();
+ void loadSettings();
+ void saveSettings() override;
+ void updateEnabledState();
+
+ void onClickGameControlToServer(LLUICtrl* ctrl);
+ // "Agent" in this context means either Avatar or Flycam
+ void onClickGameControlToAgent(LLUICtrl* ctrl);
+ void onClickAgentToGameControl(LLUICtrl* ctrl);
+ void onActionSelect();
+ void onCommitInputChannel();
+
+ static bool isWaitingForInputChannel();
+ static void applyGameControlInput(const LLGameControl::InputChannel& channel);
+protected:
+ bool postBuild() override;
+
+ void populateActionTable();
+ void populateColumns();
+ void populateRows(const std::string& filename);
+
+private:
+ void clearSelectionState();
+ void addTableSeparator();
+ void updateTable();
+ LOG_CLASS(LLPanelPreferenceGameControl);
+
+ LLCheckBoxCtrl *mCheckGameControlToServer; // send game_control data to server
+ LLCheckBoxCtrl *mCheckGameControlToAgent; // use game_control data to move avatar
+ LLCheckBoxCtrl *mCheckAgentToGameControl; // translate external avatar actions to game_control data
+
+ LLScrollListCtrl* mActionTable;
+ LLComboBox* mChannelSelector;
+ LLGameControlTranslator mActionTranslator;
+};
+
class LLAvatarComplexityControls
{
public:
@@ -391,13 +445,13 @@ public:
void cancel();
protected:
- bool postBuild();
- void onOpen(const LLSD& key);
- void onClose(bool app_quitting);
+ bool postBuild() override;
+ void onOpen(const LLSD& key) override;
+ void onClose(bool app_quitting) override;
void saveSettings();
void onBtnOk();
void onBtnCancel();
- void onClickCloseBtn(bool app_quitting = false);
+ void onClickCloseBtn(bool app_quitting = false) override;
void onChangeSocksSettings();
diff --git a/indra/newview/llflycam.cpp b/indra/newview/llflycam.cpp
new file mode 100644
index 0000000000..53b8388f98
--- /dev/null
+++ b/indra/newview/llflycam.cpp
@@ -0,0 +1,117 @@
+/**
+ * @file llflycam.cpp
+ * @brief LLFlycam class implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llflycam.h"
+
+LLFlycam::LLFlycam()
+{
+}
+
+
+void LLFlycam::setTransform(const LLVector3& position, const LLQuaternion& rotation)
+{
+ mPosition = position;
+ mRotation = rotation;
+ mRotation.normalize();
+}
+
+void LLFlycam::getTransform(LLVector3& position_out, LLQuaternion& rotation_out)
+{
+ position_out = mPosition;
+ rotation_out = mRotation;
+}
+
+
+void LLFlycam::setLinearVelocity(const LLVector3& velocity)
+{
+ // TODO: cap input
+ mLinearVelocity = velocity;
+}
+
+
+void LLFlycam::setPitchRate(F32 pitch_rate)
+{
+ // TODO: cap input
+ mPitchRate = pitch_rate;
+}
+
+
+void LLFlycam::setYawRate(F32 yaw_rate)
+{
+ // TODO: cap input
+ mYawRate = yaw_rate;
+}
+
+
+void LLFlycam::integrate(F32 delta_time)
+{
+ // cap delta_time to slow camera motion when framerates are low
+ constexpr F32 MAX_DELTA_TIME = 0.2f;
+ if (delta_time > MAX_DELTA_TIME)
+ {
+ delta_time = MAX_DELTA_TIME;
+ }
+
+ if (mLinearVelocity.lengthSquared() > 0.0f)
+ {
+ mPosition += delta_time * mLinearVelocity;
+ }
+
+ F32 angle = mPitchRate * delta_time;
+ bool needs_renormalization = false;
+ if (fabsf(angle) > 0.0f)
+ {
+ LLQuaternion dQ;
+ dQ.setAngleAxis(angle, 0.0f, 1.0f, 0.0f);
+ mRotation = dQ * mRotation;
+ needs_renormalization = true;
+ }
+
+ angle = mYawRate * delta_time;
+ if (fabsf(angle) > 0.0f)
+ {
+ LLQuaternion dQ;
+ dQ.setAngleAxis(angle, 0.0f, 0.0f, 1.0f);
+ mRotation = dQ * mRotation;
+ needs_renormalization = true;
+ }
+
+ if (needs_renormalization)
+ {
+ mRotation.normalize();
+ }
+
+
+ /*
+ // from llviewerjoystick.cpp
+ 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]);
+ */
+}
diff --git a/indra/newview/llflycam.h b/indra/newview/llflycam.h
new file mode 100644
index 0000000000..1125767a19
--- /dev/null
+++ b/indra/newview/llflycam.h
@@ -0,0 +1,55 @@
+/**
+ * @file llflycam.h
+ * @brief LLFlycam class header file
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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$
+ */
+
+#pragma once
+
+#include "llcoordframe.h"
+#include "v3math.h"
+#include "llquaternion.h"
+
+class LLFlycam
+{
+public:
+
+ LLFlycam();
+
+ void setTransform(const LLVector3& position, const LLQuaternion& rotation);
+ void getTransform(LLVector3& position_out, LLQuaternion& rotation_out);
+
+ void setLinearVelocity(const LLVector3& velocity);
+ void setPitchRate(F32 pitch_rate);
+ void setYawRate(F32 yaw_rate);
+
+ void integrate(F32 delta_time);
+
+
+protected:
+ LLVector3 mPosition;
+ LLVector3 mLinearVelocity;
+ LLQuaternion mRotation;
+ F32 mPitchRate { 0.0f };
+ F32 mYawRate { 0.0f };
+};
diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp
index ea3088613f..4968df943f 100644
--- a/indra/newview/llviewerinput.cpp
+++ b/indra/newview/llviewerinput.cpp
@@ -35,6 +35,7 @@
#include "llagentcamera.h"
#include "llfloaterimnearbychat.h"
#include "llfocusmgr.h"
+#include "llgamecontrol.h"
#include "llkeybind.h" // LLKeyData
#include "llmorphview.h"
#include "llmoveview.h"
@@ -157,9 +158,6 @@ static void agent_handle_doubletap_run(EKeystate s, LLAgent::EDoubleTapRunMode m
static void agent_push_forwardbackward( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode )
{
- agent_handle_doubletap_run(s, mode);
- if (KEYSTATE_UP == s) return;
-
F32 time = gKeyboard->getCurKeyElapsedTime();
S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount());
@@ -668,6 +666,7 @@ bool start_gesture( EKeystate s )
bool run_forward(EKeystate s)
{
+ // HACK: we use AGENT_CONTROL_NUDGE_AT_POS to signify "run forward"
if (KEYSTATE_UP != s)
{
if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD)
@@ -693,6 +692,7 @@ bool run_forward(EKeystate s)
bool run_backward(EKeystate s)
{
+ // HACK: we use AGENT_CONTROL_NUDGE_AT_NEG to signify "run backward"
if (KEYSTATE_UP != s)
{
if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD)
@@ -718,6 +718,7 @@ bool run_backward(EKeystate s)
bool run_left(EKeystate s)
{
+ // HACK: we use AGENT_CONTROL_NUDGE_LEFT_POS to signify "run left"
if (KEYSTATE_UP != s)
{
if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT)
@@ -768,6 +769,7 @@ bool run_right(EKeystate s)
bool toggle_run(EKeystate s)
{
+ // HACK: we use AGENT_CONTROL_FAST_AT to signify "run button"
if (KEYSTATE_DOWN != s) return true;
bool run = gAgent.getAlwaysRun();
if (run)
@@ -786,6 +788,7 @@ bool toggle_run(EKeystate s)
bool toggle_sit(EKeystate s)
{
+ // HACK: we use AGENT_CONTROL_SIT_ON_GROUND to signify "sit button"
if (KEYSTATE_DOWN != s) return true;
if (gAgent.isSitting())
{
diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp
index 7543fb3743..42238da418 100644
--- a/indra/newview/llviewerjoystick.cpp
+++ b/indra/newview/llviewerjoystick.cpp
@@ -148,18 +148,12 @@ BOOL CALLBACK di8_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVO
LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID();
- bool init_device = false;
- if (guid.isBinary())
+ bool init_device = LLViewerJoystick::is3DConnexionDevice(product_name);
+ if (init_device && 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)
{
@@ -1513,14 +1507,23 @@ std::string LLViewerJoystick::getDescription()
return res;
}
+// static
+bool LLViewerJoystick::is3DConnexionDevice(const std::string& device_name)
+{
+ bool answer = device_name.find("Space") == 0
+ && ( (device_name.find("SpaceNavigator") == 0)
+ || (device_name.find("SpaceExplorer") == 0)
+ || (device_name.find("SpaceTraveler") == 0)
+ || (device_name.find("SpacePilot") == 0)
+ || (device_name.find("SpaceMouse") == 0));
+ return answer;
+}
+
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));
+ && is3DConnexionDevice(mNdofDev->product));
#else
return false;
#endif
diff --git a/indra/newview/llviewerjoystick.h b/indra/newview/llviewerjoystick.h
index c989615653..b4fe3877f4 100644
--- a/indra/newview/llviewerjoystick.h
+++ b/indra/newview/llviewerjoystick.h
@@ -81,6 +81,8 @@ public:
std::string getDescription();
void saveDeviceIdToSettings();
+ static bool is3DConnexionDevice(const std::string& device_name);
+
protected:
void updateEnabled(bool autoenable);
void handleRun(F32 inc);
diff --git a/indra/newview/skins/default/textures/bottomtray/Dpad.png b/indra/newview/skins/default/textures/bottomtray/Dpad.png
new file mode 100644
index 0000000000..00fcb4beea
--- /dev/null
+++ b/indra/newview/skins/default/textures/bottomtray/Dpad.png
Binary files differ
diff --git a/indra/newview/skins/default/xui/en/floater_joystick.xml b/indra/newview/skins/default/xui/en/floater_joystick.xml
index e6f0420698..597744238c 100644
--- a/indra/newview/skins/default/xui/en/floater_joystick.xml
+++ b/indra/newview/skins/default/xui/en/floater_joystick.xml
@@ -3,9 +3,9 @@
legacy_header_height="18"
height="500"
layout="topleft"
- name="Joystick"
+ name="3Dconnexion Device"
help_topic="Viewerhelp:Joystick_Configuration"
- title="JOYSTICK CONFIGURATION"
+ title="3DCONNEXION DEVICE CONFIGURATION"
width="569">
<floater.string
name="JoystickDisabled">
@@ -22,7 +22,7 @@
width="50"
mouse_opaque="false"
name="joystick_lbl">
- Joystick:
+ Device:
</text>
<combo_box
allow_text_entry="false"
@@ -35,7 +35,7 @@
<spinner
bottom="56"
height="10"
- control_name="JoystickAxis1"
+ control_name="Axis1"
decimal_digits="0"
increment="1"
label="X Axis Mapping"
@@ -44,12 +44,12 @@
left="20"
max_val="5"
min_val="-1"
- name="JoystickAxis1"
+ name="Axis1"
width="140" />
<spinner
bottom_delta="0"
height="10"
- control_name="JoystickAxis2"
+ control_name="Axis2"
decimal_digits="0"
increment="1"
label="Y Axis Mapping"
@@ -58,12 +58,12 @@
left="190"
max_val="5"
min_val="-1"
- name="JoystickAxis2"
+ name="Axis2"
width="140" />
<spinner
bottom_delta="0"
height="10"
- control_name="JoystickAxis0"
+ control_name="Axis0"
decimal_digits="0"
increment="1"
label="Z Axis Mapping"
@@ -72,12 +72,12 @@
left="360"
max_val="5"
min_val="-1"
- name="JoystickAxis0"
+ name="Axis0"
width="140" />
<spinner
bottom="76"
height="10"
- control_name="JoystickAxis4"
+ control_name="Axis4"
decimal_digits="0"
increment="1"
label="Pitch Mapping"
@@ -86,12 +86,12 @@
left="20"
max_val="5"
min_val="-1"
- name="JoystickAxis4"
+ name="Axis4"
width="140" />
<spinner
bottom_delta="0"
height="10"
- control_name="JoystickAxis5"
+ control_name="Axis5"
decimal_digits="0"
increment="1"
label="Yaw Mapping"
@@ -100,12 +100,12 @@
left="190"
max_val="5"
min_val="-1"
- name="JoystickAxis5"
+ name="Axis5"
width="140" />
<spinner
bottom_delta="0"
height="10"
- control_name="JoystickAxis3"
+ control_name="Axis3"
decimal_digits="0"
increment="1"
label="Roll Mapping"
@@ -114,12 +114,12 @@
left="360"
max_val="5"
min_val="-1"
- name="JoystickAxis3"
+ name="Axis3"
width="140" />
<spinner
bottom="96"
height="10"
- control_name="JoystickAxis6"
+ control_name="Axis6"
decimal_digits="0"
increment="1"
label="Zoom Mapping"
@@ -128,7 +128,7 @@
left="20"
max_val="5"
min_val="-1"
- name="JoystickAxis6"
+ name="Axis6"
width="140" />
<check_box
bottom_delta="18"
@@ -203,7 +203,7 @@
width="60" />
<stat_view
height="270"
- label="Joystick Monitor"
+ label="Axis Monitor"
layout="topleft"
left="359"
name="axis_view"
diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml
index 4614f2f06c..e0dc538af4 100644
--- a/indra/newview/skins/default/xui/en/floater_preferences.xml
+++ b/indra/newview/skins/default/xui/en/floater_preferences.xml
@@ -171,12 +171,12 @@
help_topic="preferences_controls_tab"
name="controls" />
<panel
- class="panel_preference_game_controls"
- filename="panel_preferences_game_controls.xml"
- label="Game Controls"
+ class="panel_preference_game_control"
+ filename="panel_preferences_game_control.xml"
+ label="Game Control"
layout="topleft"
- help_topic="preferences_game_controls_tab"
- name="gamecontrols" />
+ help_topic="preferences_game_control_tab"
+ name="game_control" />
</tab_container>
</floater>
diff --git a/indra/newview/skins/default/xui/en/game_control_table_camera_rows.xml b/indra/newview/skins/default/xui/en/game_control_table_camera_rows.xml
new file mode 100644
index 0000000000..b2381c8493
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/game_control_table_camera_rows.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<contents>
+ <rows
+ enabled="false"
+ name="camera_actions"
+ value="">
+ <columns
+ type="icontext"
+ column="action"
+ font="SansSerif"
+ halign="left"
+ label="Camera"
+ name="action"
+ value="Cam_FreeCam_Off" />
+ </rows>
+ <rows
+ name="move"
+ value="move">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera move forward/backward"
+ value="Move Forward" />
+ </rows>
+ <rows
+ name="pan"
+ value="pan">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera pan left/right"
+ value="Pan Left" />
+ </rows>
+ <rows
+ name="rise"
+ value="rise">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera rise/fall"
+ value="Rise Up" />
+ </rows>
+ <rows
+ name="pitch"
+ value="pitch">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera adjust pitch"
+ value="Pitch Down" />
+ </rows>
+ <rows
+ name="yaw"
+ value="yaw">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera turn left/right"
+ value="Yaw CCW" />
+ </rows>
+ <rows
+ name="zoom"
+ value="zoom">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera zoom in/out"
+ value="Zoom In" />
+ </rows>
+ <!--
+ <rows
+ name="roll_ccw"
+ value="roll_ccw">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera roll counterclockwise"
+ value="Roll Counterclockwise" />
+ </rows>
+ <rows
+ name="roll_cw"
+ value="roll_cw">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ tool_tip="Camera roll clockwise"
+ value="Roll Clockwise" />
+ </rows>
+ -->
+</contents>
diff --git a/indra/newview/skins/default/xui/en/game_control_table_columns.xml b/indra/newview/skins/default/xui/en/game_control_table_columns.xml
new file mode 100644
index 0000000000..f88fc8305c
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/game_control_table_columns.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<contents>
+ <columns
+ relative_width="0.25"
+ label="Action"
+ name="action" />
+ <columns
+ relative_width="0.25"
+ label="GameControl"
+ name="index" />
+ <columns
+ relative_width="0.50"
+ label=" "
+ name="foo" />
+</contents>
diff --git a/indra/newview/skins/default/xui/en/game_control_table_rows.xml b/indra/newview/skins/default/xui/en/game_control_table_rows.xml
new file mode 100644
index 0000000000..90e6990842
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/game_control_table_rows.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<contents>
+ <rows
+ enabled="false"
+ name="move_actions"
+ value="">
+ <columns
+ type="icontext"
+ column="action"
+ font="SansSerif"
+ halign="left"
+ label="Move Actions"
+ name="action"
+ value="Move_Walk_Off" />
+ </rows>
+ <rows
+ name="push"
+ value="push">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Walk Forward" />
+ </rows>
+ <rows
+ name="slide"
+ value="slide">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Strafe Left" />
+ </rows>
+ <rows
+ name="jump"
+ value="jump">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Jump Up" />
+ </rows>
+ <rows
+ name="turn"
+ value="turn">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Turn Left" />
+ </rows>
+ <rows
+ name="look"
+ value="look">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Look Down" />
+ </rows>
+
+ <rows
+ name="stop"
+ value="stop">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Stop" />
+ </rows>
+ <rows
+ name="toggle_run"
+ value="toggle_run">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Run" />
+ </rows>
+ <rows
+ name="toggle_fly"
+ value="toggle_fly">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Fly" />
+ </rows>
+ <rows
+ name="toggle_sit"
+ value="toggle_sit">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Sit" />
+ </rows>
+ <rows
+ name="toggle_flycam"
+ value="toggle_flycam">
+ <columns
+ column="action"
+ font="SansSerif"
+ halign="left"
+ name="action"
+ value="Flycam" />
+ </rows>
+</contents>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml
new file mode 100644
index 0000000000..d48c0fe0d2
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_preferences_game_control.xml
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="utf-8"?>
+<panel
+ border="true"
+ follows="all"
+ height="408"
+ label="Game Controls"
+ layout="topleft"
+ left="102"
+ name="gamecontrol"
+ top="1"
+ width="517">
+ <check_box
+ control_name="GameControlToServer"
+ follows="top|left"
+ height="15"
+ label="Send GameControl Data to server"
+ layout="topleft"
+ left="10"
+ name="game_control_to_server"
+ top="6"
+ width="232"/>
+ <check_box
+ control_name="GameControlToAgent"
+ follows="top|left"
+ height="15"
+ label="GameControl moves avatar and flycam"
+ layout="topleft"
+ left="10"
+ name="game_control_to_agent"
+ top="27"
+ width="232"/>
+ <check_box
+ control_name="AgentToGameControl"
+ follows="top|left"
+ height="15"
+ label="Avatar and flycam actions interpreted as GameControl"
+ layout="topleft"
+ left="10"
+ name="agent_to_game_control"
+ top="48"
+ width="232"/>
+ <scroll_list
+ draw_heading="true"
+ follows="all"
+ layout="topleft"
+ column_padding="0"
+ selection_type="header"
+ top="66"
+ left="3"
+ bottom="-3"
+ right="-3"
+ can_sort="false"
+ multi_select="false"
+ name="action_table"
+ fg_disable_color="ScrollUnselectedColor"/>
+ <combo_box
+ height="23"
+ layout="topleft"
+ left="10"
+ name="input_channel_combo"
+ top_pad="5"
+ width="90">
+ <combo_box.item
+ label="AXIS_0-"
+ name="AXIS_0-"
+ value="AXIS_0-"/>
+ <combo_box.item
+ label="AXIS_0+"
+ name="AXIS_0+"
+ value="AXIS_0+"/>
+ <combo_box.item
+ label="AXIS_1-"
+ name="AXIS_1-"
+ value="AXIS_1-"/>
+ <combo_box.item
+ label="AXIS_1+"
+ name="AXIS_1+"
+ value="AXIS_1+"/>
+ <combo_box.item
+ label="AXIS_2-"
+ name="AXIS_2-"
+ value="AXIS_2-"/>
+ <combo_box.item
+ label="AXIS_2+"
+ name="AXIS_2+"
+ value="AXIS_2+"/>
+ <combo_box.item
+ label="AXIS_3-"
+ name="AXIS_3-"
+ value="AXIS_3-"/>
+ <combo_box.item
+ label="AXIS_3+"
+ name="AXIS_3+"
+ value="AXIS_3+"/>
+ <combo_box.item
+ label="AXIS_4-"
+ name="AXIS_4-"
+ value="AXIS_4-"/>
+ <combo_box.item
+ label="AXIS_4+"
+ name="AXIS_4+"
+ value="AXIS_4+"/>
+ <combo_box.item
+ label="AXIS_5-"
+ name="AXIS_5-"
+ value="AXIS_5-"/>
+ <combo_box.item
+ label="AXIS_5+"
+ name="AXIS_5+"
+ value="AXIS_5+"/>
+ <combo_box.item
+ label="BUTTON_0"
+ name="BUTTON_0"
+ value="BUTTON_0"/>
+ <combo_box.item
+ label="BUTTON_1"
+ name="BUTTON_1"
+ value="BUTTON_1"/>
+ <combo_box.item
+ label="BUTTON_2"
+ name="BUTTON_2"
+ value="BUTTON_2"/>
+ <combo_box.item
+ label="BUTTON_3"
+ name="BUTTON_3"
+ value="BUTTON_3"/>
+ <combo_box.item
+ label="BUTTON_4"
+ name="BUTTON_4"
+ value="BUTTON_4"/>
+ <combo_box.item
+ label="BUTTON_5"
+ name="BUTTON_5"
+ value="BUTTON_5"/>
+ <combo_box.item
+ label="BUTTON_6"
+ name="BUTTON_6"
+ value="BUTTON_6"/>
+ <combo_box.item
+ label="BUTTON_7"
+ name="BUTTON_7"
+ value="BUTTON_7"/>
+ <combo_box.item
+ label="BUTTON_8"
+ name="BUTTON_8"
+ value="BUTTON_8"/>
+ <combo_box.item
+ label="BUTTON_9"
+ name="BUTTON_9"
+ value="BUTTON_9"/>
+ <combo_box.item
+ label="BUTTON_10"
+ name="BUTTON_10"
+ value="BUTTON_10"/>
+ <combo_box.item
+ label="BUTTON_11"
+ name="BUTTON_11"
+ value="BUTTON_11"/>
+ <combo_box.item
+ label="BUTTON_12"
+ name="BUTTON_12"
+ value="BUTTON_12"/>
+ <combo_box.item
+ label="BUTTON_13"
+ name="BUTTON_13"
+ value="BUTTON_13"/>
+ <combo_box.item
+ label="BUTTON_14"
+ name="BUTTON_14"
+ value="BUTTON_14"/>
+ <combo_box.item
+ label="BUTTON_15"
+ name="BUTTON_15"
+ value="BUTTON_15"/>
+ <combo_box.item
+ label="BUTTON_16"
+ name="BUTTON_16"
+ value="BUTTON_16"/>
+ <combo_box.item
+ label="BUTTON_17"
+ name="BUTTON_17"
+ value="BUTTON_17"/>
+ <combo_box.item
+ label="BUTTON_18"
+ name="BUTTON_18"
+ value="BUTTON_18"/>
+ <combo_box.item
+ label="BUTTON_19"
+ name="BUTTON_19"
+ value="BUTTON_19"/>
+ <combo_box.item
+ label="BUTTON_20"
+ name="BUTTON_20"
+ value="BUTTON_20"/>
+ <combo_box.item
+ label="BUTTON_21"
+ name="BUTTON_21"
+ value="BUTTON_21"/>
+ <combo_box.item
+ label="BUTTON_22"
+ name="BUTTON_22"
+ value="BUTTON_22"/>
+ <combo_box.item
+ label="BUTTON_23"
+ name="BUTTON_23"
+ value="BUTTON_23"/>
+ <combo_box.item
+ label="BUTTON_24"
+ name="BUTTON_24"
+ value="BUTTON_24"/>
+ <combo_box.item
+ label="BUTTON_25"
+ name="BUTTON_25"
+ value="BUTTON_25"/>
+ <combo_box.item
+ label="BUTTON_26"
+ name="BUTTON_26"
+ value="BUTTON_26"/>
+ <combo_box.item
+ label="BUTTON_27"
+ name="BUTTON_27"
+ value="BUTTON_27"/>
+ <combo_box.item
+ label="BUTTON_28"
+ name="BUTTON_28"
+ value="BUTTON_28"/>
+ <combo_box.item
+ label="BUTTON_29"
+ name="BUTTON_29"
+ value="BUTTON_29"/>
+ <combo_box.item
+ label="BUTTON_30"
+ name="BUTTON_30"
+ value="BUTTON_30"/>
+ <combo_box.item
+ label="BUTTON_31"
+ name="BUTTON_31"
+ value="BUTTON_31"/>
+ <combo_box.item
+ label="NONE"
+ name="NONE"
+ value="NONE"/>
+ </combo_box>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml b/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml
deleted file mode 100644
index 4b693e8955..0000000000
--- a/indra/newview/skins/default/xui/en/panel_preferences_game_controls.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<panel
- border="true"
- follows="all"
- height="408"
- label="Game Controls"
- layout="topleft"
- left="102"
- name="gamecontrols"
- top="1"
- width="517">
- <check_box
- control_name="EnableGameControlInput"
- follows="top|left"
- height="15"
- label="Send GameControl Input to server"
- layout="topleft"
- left="10"
- name="enable_game_control_input"
- top="6"
- width="232"/>
- <check_box
- control_name="EnableGameControlKeyboardInput"
- follows="top|left"
- height="15"
- label="Include otherwise 'unhandled' Keyboard events in GameControl Input"
- layout="topleft"
- left="10"
- name="game_control_keyboard_input"
- top="27"
- width="232"/>
-</panel>
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_move.xml b/indra/newview/skins/default/xui/en/panel_preferences_move.xml
index 0412466b4f..8a372256dc 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_move.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_move.xml
@@ -270,7 +270,7 @@
width="200" />
<button
height="23"
- label="Other Devices"
+ label="3Dconnexion devices"
left="30"
name="joystick_setup_button"
top="30"
diff --git a/indra/newview/tests/llgamecontrol_stub.cpp b/indra/newview/tests/llgamecontrol_stub.cpp
index 0872f647d7..b06a7fd132 100644
--- a/indra/newview/tests/llgamecontrol_stub.cpp
+++ b/indra/newview/tests/llgamecontrol_stub.cpp
@@ -24,53 +24,76 @@
* $/LicenseInfo$
*/
+#pragma once
+
#include "llgamecontrol.h"
#include "SDL2/SDL_events.h"
+LLGameControl::State g_state;
-void LLGameControl::addKeyButtonMap(U16 key, U8 button)
+// static
+bool LLGameControl::isInitialized()
{
+ return false;
}
-void LLGameControl::removeKeyButtonMap(U16 key)
+// static
+void LLGameControl::init()
{
}
-void LLGameControl::addKeyAxisMap(U16 key, U8 axis, bool positive)
+// static
+void LLGameControl::terminate()
{
}
-void LLGameControl::removeKeyAxisMap(U16 key)
+// static
+bool LLGameControl::computeFinalStateAndCheckForChanges()
{
+ return false;
}
-void LLGameControl::onKeyDown(U16 key, U32 mask)
+// static
+void LLGameControl::clearAllState()
{
}
-void LLGameControl::onKeyUp(U16 key, U32 mask)
+// static
+void LLGameControl::processEvents(bool app_has_focus)
{
}
// static
-bool LLGameControl::isInitialized()
+const LLGameControl::State& LLGameControl::getState()
+{
+ return g_state;
+}
+
+// static
+void LLGameControl::setIncludeKeyboard(bool include)
+{
+}
+
+// static
+bool LLGameControl::getIncludeKeyboard()
{
return false;
}
// static
-void LLGameControl::init()
+LLGameControl::InputChannel LLGameControl::getChannelByActionName(const std::string& name)
{
+ return LLGameControl::InputChannel();
}
// static
-void LLGameControl::terminate()
+void LLGameControl::addActionMapping(const std::string& name, LLGameControl::InputChannel channel)
{
}
// static
-void LLGameControl::processEvents(bool app_has_focus)
+void LLGameControl::setActionFlags(U32 action_flags)
{
}
diff --git a/indra/newview/tests/llversioninfo_test.cpp b/indra/newview/tests/llversioninfo_test.cpp
index 9eb5146f2b..b07800cdcd 100644
--- a/indra/newview/tests/llversioninfo_test.cpp
+++ b/indra/newview/tests/llversioninfo_test.cpp
@@ -31,7 +31,7 @@
#include <iostream>
-#include "llgamecontrol_stub.cpp"
+//#include "llgamecontrol_stub.cpp"
// LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The