summaryrefslogtreecommitdiff
path: root/indra/llwindow
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llwindow')
-rw-r--r--indra/llwindow/llgamecontrol.cpp524
-rw-r--r--indra/llwindow/llgamecontrol.h18
-rw-r--r--indra/llwindow/llgamecontroltranslator.cpp14
-rw-r--r--indra/llwindow/llgamecontroltranslator.h13
4 files changed, 347 insertions, 222 deletions
diff --git a/indra/llwindow/llgamecontrol.cpp b/indra/llwindow/llgamecontrol.cpp
index 9853eb763a..a1ab2dd52d 100644
--- a/indra/llwindow/llgamecontrol.cpp
+++ b/indra/llwindow/llgamecontrol.cpp
@@ -35,10 +35,51 @@
#include "SDL2/SDL_joystick.h"
#include "indra_constants.h"
+#include "llfile.h"
#include "llgamecontroltranslator.h"
constexpr size_t NUM_AXES = 6;
+// util for dumping SDL_GameController info
+std::ostream& operator<<(std::ostream& out, SDL_GameController* c)
+{
+ if (!c)
+ {
+ return out << "nullptr";
+ }
+ out << "{";
+ out << " name='" << SDL_GameControllerName(c) << "'";
+ out << " type='" << SDL_GameControllerGetType(c) << "'";
+ out << " vendor='" << SDL_GameControllerGetVendor(c) << "'";
+ out << " product='" << SDL_GameControllerGetProduct(c) << "'";
+ out << " version='" << SDL_GameControllerGetProductVersion(c) << "'";
+ //CRASH! out << " serial='" << SDL_GameControllerGetSerial(c) << "'";
+ out << " }";
+ return out;
+}
+
+// util for dumping SDL_Joystick info
+std::ostream& operator<<(std::ostream& out, SDL_Joystick* j)
+{
+ if (!j)
+ {
+ return out << "nullptr";
+ }
+ out << "{";
+ out << " p=0x" << (void*)(j);
+ out << " name='" << SDL_JoystickName(j) << "'";
+ out << " type='" << SDL_JoystickGetType(j) << "'";
+ out << " instance='" << SDL_JoystickInstanceID(j) << "'";
+ out << " product='" << SDL_JoystickGetProduct(j) << "'";
+ out << " version='" << SDL_JoystickGetProductVersion(j) << "'";
+ out << " num_axes=" << SDL_JoystickNumAxes(j);
+ out << " num_balls=" << SDL_JoystickNumBalls(j);
+ out << " num_hats=" << SDL_JoystickNumHats(j);
+ out << " num_buttons=" << SDL_JoystickNumHats(j);
+ out << " }";
+ return out;
+}
+
std::string LLGameControl::InputChannel::getLocalName() const
{
// HACK: we hard-code English channel names, but
@@ -192,34 +233,42 @@ public:
void onAxis(SDL_JoystickID id, U8 axis, S16 value);
void onButton(SDL_JoystickID id, U8 button, bool pressed);
- void clearAllState();
- size_t getControllerIndex(SDL_JoystickID id) const;
+ void clearAllStates();
void accumulateInternalState();
void computeFinalState(LLGameControl::State& state);
- LLGameControl::InputChannel getChannelByName(const std::string& name) const;
LLGameControl::InputChannel getChannelByActionName(const std::string& action_name) const;
+ LLGameControl::InputChannel getFlycamChannelByActionName(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 getFlycamInputs(std::vector<F32>& inputs_out);
+ void setExternalInput(U32 action_flags, U32 buttons);
void clear();
private:
- std::vector<SDL_JoystickID> mControllerIDs;
- std::vector<SDL_GameController*> mControllers;
- std::vector<LLGameControl::State> mStates; // one state per device
+ bool updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel);
+
+ std::list<LLGameControl::State> mStates; // one state per device
+ using state_it = std::list<LLGameControl::State>::iterator;
+ state_it findState(SDL_JoystickID id)
+ {
+ return std::find_if(mStates.begin(), mStates.end(),
+ [id](LLGameControl::State& state)
+ {
+ return state.getJoystickID() == id;
+ });
+ }
LLGameControl::State mExternalState;
LLGameControlTranslator mActionTranslator;
- ActionToChannelMap mCameraChannelMap;
+ std::vector<LLGameControl::InputChannel> mFlycamChannels;
std::vector<S32> mAxesAccumulator;
U32 mButtonAccumulator { 0 };
- U32 mLastActionFlags { 0 };
- U32 mLastCameraActionFlags { 0 };
+ U32 mLastActiveFlags { 0 };
+ U32 mLastFlycamActionFlags { 0 };
};
// local globals
@@ -235,7 +284,7 @@ namespace
//
// To reduce the likelihood of buttons being stuck "pressed" forever
// on the receiving side (for lost final packet) we resend the last
- // data state. However, to keep th ambient resend bandwidth low we
+ // data state. However, to keep the ambient resend bandwidth low we
// expand the resend period at a geometric rate.
//
constexpr U64 MSEC_PER_NSEC = 1e6;
@@ -267,6 +316,12 @@ LLGameControl::State::State() : mButtons(0)
mPrevAxes.resize(NUM_AXES, 0);
}
+void LLGameControl::State::setDevice(int joystickID, void* controller)
+{
+ mJoystickID = joystickID;
+ mController = controller;
+}
+
void LLGameControl::State::clear()
{
std::fill(mAxes.begin(), mAxes.end(), 0);
@@ -299,7 +354,7 @@ LLGameControllerManager::LLGameControllerManager()
{
mAxesAccumulator.resize(NUM_AXES, 0);
- // Here we build an invarient map between the named agent actions
+ // Here we build an invariant 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.
@@ -319,13 +374,12 @@ LLGameControllerManager::LLGameControllerManager()
// 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
+ // (e.g. "push" instead of "push+" and "push-") because mActionTranslator
// automatially expands action names as necessary.
using type = LLGameControl::InputChannel::Type;
std::vector< std::pair< std::string, LLGameControl::InputChannel> > agent_defaults =
@@ -337,90 +391,50 @@ LLGameControllerManager::LLGameControllerManager()
{ "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) } }
+ // Flycam actions don't need bitwise translation, so we maintain the map
+ // of channels here directly rather than using an LLGameControlTranslator.
+ mFlycamChannels = {
+ { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTY), 1 }, // advance
+ { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_LEFTX), 1 }, // pan
+ { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_TRIGGERRIGHT), 1 }, // rise
+ { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTY), -1 }, // pitch
+ { type::TYPE_AXIS, (U8)(LLGameControl::AXIS_RIGHTX), 1 }, // yaw
+ { type::TYPE_NONE, 0 } // zoom
};
}
void LLGameControllerManager::addController(SDL_JoystickID id, SDL_GameController* controller)
{
- if (controller)
+ LL_INFOS("GameController") << "joystick id: " << id << ", controller: " << controller << LL_ENDL;
+
+ llassert(id >= 0);
+ llassert(controller);
+
+ if (findState(id) != mStates.end())
{
- size_t i = 0;
- for (; i < mControllerIDs.size(); ++i)
- {
- if (id == mControllerIDs[i])
- {
- break;
- }
- }
- if (i == mControllerIDs.size())
- {
- mControllerIDs.push_back(id);
- mControllers.push_back(controller);
- mStates.push_back(LLGameControl::State());
- LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
- << " controller=" << controller
- << LL_ENDL;
- }
+ LL_WARNS("GameController") << "device already added" << LL_ENDL;
+ return;
}
+
+ mStates.emplace_back().setDevice(id, controller);
+ LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
+ << " controller=" << controller
+ << LL_ENDL;
}
void LLGameControllerManager::removeController(SDL_JoystickID id)
{
- size_t i = 0;
- size_t num_controllers = mControllerIDs.size();
- for (; i < num_controllers; ++i)
- {
- if (id == mControllerIDs[i])
- {
- LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
- << " controller=" << mControllers[i]
- << LL_ENDL;
-
- mControllerIDs[i] = mControllerIDs[num_controllers - 1];
- mControllers[i] = mControllers[num_controllers - 1];
- mStates[i] = mStates[num_controllers - 1];
-
- mControllerIDs.pop_back();
- mControllers.pop_back();
- mStates.pop_back();
- break;
- }
- }
-}
+ LL_INFOS("GameController") << "joystick id: " << id << LL_ENDL;
-size_t LLGameControllerManager::getControllerIndex(SDL_JoystickID id) const
-{
- constexpr size_t UNREASONABLY_HIGH_INDEX = 1e6;
- size_t index = UNREASONABLY_HIGH_INDEX;
- for (size_t i = 0; i < mControllers.size(); ++i)
- {
- if (id == mControllerIDs[i])
+ mStates.remove_if([id](LLGameControl::State& state)
{
- index = i;
- break;
- }
- }
- return index;
+ return state.getJoystickID() == id;
+ });
}
void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value)
@@ -429,10 +443,11 @@ void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value)
{
return;
}
- size_t index = getControllerIndex(id);
- if (index < mControllers.size())
+
+ state_it it = findState(id);
+ if (it != mStates.end())
{
- // Note: the RAW analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD
+ // Note: the RAW analog joysticks 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.
@@ -454,33 +469,33 @@ void LLGameControllerManager::onAxis(SDL_JoystickID id, U8 axis, S16 value)
LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
<< " axis=" << (S32)(axis)
<< " value=" << (S32)(value) << LL_ENDL;
- mStates[index].mAxes[axis] = value;
+ it->mAxes[axis] = value;
}
}
void LLGameControllerManager::onButton(SDL_JoystickID id, U8 button, bool pressed)
{
- size_t index = getControllerIndex(id);
- if (index < mControllers.size())
+ state_it it = findState(id);
+ if (it != mStates.end())
{
- if (mStates[index].onButton(button, pressed))
+ if (it->onButton(button, pressed))
{
LL_DEBUGS("SDL2") << "joystick=0x" << std::hex << id << std::dec
<< " button i=" << (S32)(button)
- << " pressed=" << pressed << LL_ENDL;
+ << " pressed=" << pressed << LL_ENDL;
}
}
}
-void LLGameControllerManager::clearAllState()
+void LLGameControllerManager::clearAllStates()
{
for (auto& state : mStates)
{
state.clear();
}
mExternalState.clear();
- mLastActionFlags = 0;
- mLastCameraActionFlags = 0;
+ mLastActiveFlags = 0;
+ mLastFlycamActionFlags = 0;
}
void LLGameControllerManager::accumulateInternalState()
@@ -512,10 +527,6 @@ void LLGameControllerManager::computeFinalState(LLGameControl::State& final_stat
{
// 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)
{
@@ -525,17 +536,26 @@ void LLGameControllerManager::computeFinalState(LLGameControl::State& final_stat
// 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));
+ S32 axis = mAxesAccumulator[i];
+ if (g_translateAgentActions)
+ {
+ // Note: we accumulate mExternalState onto local 'axis' variable
+ // rather than onto mAxisAccumulator[i] because the internal
+ // accumulated value is also used to drive the Flycam, and
+ // we don't want any external state leaking into that value.
+ axis += (S32)(mExternalState.mAxes[i]);
+ }
+ axis = (S16)(std::min(std::max(axis, -32768), 32767));
// check for change
- if (final_state.mAxes[i] != new_axis)
+ if (final_state.mAxes[i] != axis)
{
// When axis changes we explicitly update the corresponding prevAxis
- // prior to storing new_axis. The only other place where prevAxis
+ // prior to storing 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;
+ final_state.mAxes[i] = axis;
g_nextResendPeriod = 0; // packet needs to go out ASAP
}
}
@@ -546,12 +566,53 @@ LLGameControl::InputChannel LLGameControllerManager::getChannelByActionName(cons
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;
- }
+ // maybe we're looking for a flycam action
+ channel = getFlycamChannelByActionName(name);
+ }
+ return channel;
+}
+
+// helper
+S32 get_flycam_index_by_name(const std::string& name)
+{
+ // the Flycam action<-->channel relationship
+ // is implicitly stored in std::vector in a known order
+ S32 index = -1;
+ if (name.rfind("advance", 0) == 0)
+ {
+ index = 0;
+ }
+ else if (name.rfind("pan", 0) == 0)
+ {
+ index = 1;
+ }
+ else if (name.rfind("rise", 0) == 0)
+ {
+ index = 2;
+ }
+ else if (name.rfind("pitch", 0) == 0)
+ {
+ index = 3;
+ }
+ else if (name.rfind("yaw", 0) == 0)
+ {
+ index = 4;
+ }
+ else if (name.rfind("zoom", 0) == 0)
+ {
+ index = 5;
+ }
+ return index;
+}
+
+LLGameControl::InputChannel LLGameControllerManager::getFlycamChannelByActionName(const std::string& name) const
+{
+ // the Flycam channels are stored in a strict order
+ LLGameControl::InputChannel channel;
+ S32 index = get_flycam_index_by_name(name);
+ if (index != -1)
+ {
+ channel = mFlycamChannels[index];
}
return channel;
}
@@ -561,17 +622,12 @@ bool LLGameControllerManager::updateActionMap(const std::string& action, LLGame
bool success = mActionTranslator.updateMap(action, channel);
if (success)
{
- mLastActionFlags = 0;
+ mLastActiveFlags = 0;
}
else
{
- // maybe we're looking for a camera action
- ActionToChannelMap::iterator itr = mCameraChannelMap.find(action);
- if (itr != mCameraChannelMap.end())
- {
- itr->second = channel;
- success = true;
- }
+ // maybe we're looking for a flycam action
+ success = updateFlycamMap(action, channel);
}
if (!success)
{
@@ -580,6 +636,17 @@ bool LLGameControllerManager::updateActionMap(const std::string& action, LLGame
return success;
}
+bool LLGameControllerManager::updateFlycamMap(const std::string& action, LLGameControl::InputChannel channel)
+{
+ S32 index = get_flycam_index_by_name(action);
+ if (index != -1)
+ {
+ mFlycamChannels[index] = channel;
+ return true;
+ }
+ return false;
+}
+
U32 LLGameControllerManager::computeInternalActionFlags()
{
// add up device inputs
@@ -591,34 +658,85 @@ U32 LLGameControllerManager::computeInternalActionFlags()
return 0;
}
-void LLGameControllerManager::getCameraInputs(std::vector<F32>& inputs_out)
+void LLGameControllerManager::getFlycamInputs(std::vector<F32>& inputs)
{
- // TODO: fill inputs_out with real data
- inputs_out.resize(6);
- for (auto& value : inputs_out)
+ // The inputs are packed in the same order as they exist in mFlycamChannels:
+ //
+ // advance
+ // pan
+ // rise
+ // pitch
+ // yaw
+ // zoom
+ //
+ for (const auto& channel: mFlycamChannels)
{
- value = 0.0f;
+ S16 axis;
+ if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERLEFT)
+ || channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT))
+ {
+ // TIED TRIGGER HACK: we assume the two triggers are paired together
+ S32 total_axis = mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERLEFT)]
+ - mAxesAccumulator[(U8)(LLGameControl::AXIS_TRIGGERRIGHT)];
+ if (channel.mIndex == (U8)(LLGameControl::AXIS_TRIGGERRIGHT))
+ {
+ // negate previous math when TRIGGERRIGHT is positive channel
+ total_axis *= -1;
+ }
+ axis = S16(std::min(std::max(total_axis, -32768), 32767));
+ }
+ else
+ {
+ axis = S16(std::min(std::max(mAxesAccumulator[channel.mIndex], -32768), 32767));
+ }
+ // value arrives as S16 in range [-32768, 32767]
+ // so we scale positive and negative values by slightly different factors
+ // to try to map it to [-1, 1]
+ F32 input = F32(axis) * ((axis > 0.0f) ? 3.051850476e-5 : 3.0517578125e-5f) * channel.mSign;
+ inputs.push_back(input);
}
}
-// static
-void LLGameControllerManager::setExternalActionFlags(U32 action_flags)
+void LLGameControllerManager::setExternalInput(U32 action_flags, U32 buttons)
{
if (g_translateAgentActions)
{
+ // HACK: these are the bits we can safely translate from control flags to GameControl
+ // Extracting LLGameControl::InputChannels that are mapped to other bits is a WIP.
+ // TODO: translate other bits to GameControl, which might require measure of gAgent
+ // state changes (e.g. sitting <--> standing, flying <--> not-flying, etc)
+ const U32 BITS_OF_INTEREST =
+ AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_NEG
+ | AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_LEFT_NEG
+ | AGENT_CONTROL_UP_POS | AGENT_CONTROL_UP_NEG
+ | AGENT_CONTROL_YAW_POS | AGENT_CONTROL_YAW_NEG
+ | AGENT_CONTROL_PITCH_POS | AGENT_CONTROL_PITCH_NEG
+ | AGENT_CONTROL_STOP
+ | AGENT_CONTROL_FAST_AT
+ | AGENT_CONTROL_FAST_LEFT
+ | AGENT_CONTROL_FAST_UP;
+ action_flags &= BITS_OF_INTEREST;
+
U32 active_flags = action_flags & mActionTranslator.getMappedFlags();
- if (active_flags != mLastActionFlags)
+ if (active_flags != mLastActiveFlags)
{
- mLastActionFlags = active_flags;
+ mLastActiveFlags = active_flags;
mExternalState = mActionTranslator.computeStateFromFlags(action_flags);
+ mExternalState.mButtons |= buttons;
+ }
+ else
+ {
+ mExternalState.mButtons = buttons;
}
}
+ else
+ {
+ mExternalState.mButtons = buttons;
+ }
}
void LLGameControllerManager::clear()
{
- mControllerIDs.clear();
- mControllers.clear();
mStates.clear();
}
@@ -628,57 +746,50 @@ U64 get_now_nsec()
return (std::chrono::steady_clock::now() - t0).count();
}
-// util for dumping SDL_GameController info
-std::ostream& operator<<(std::ostream& out, SDL_GameController* c)
+void onJoystickDeviceAdded(const SDL_Event& event)
{
- if (! c)
+ LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL;
+
+ if (SDL_Joystick* joystick = SDL_JoystickOpen(event.cdevice.which))
{
- return out << "nullptr";
+ LL_INFOS("GameController") << "joystick: " << joystick << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("GameController") << "Can't open joystick: " << SDL_GetError() << LL_ENDL;
}
- out << "{";
- out << " name='" << SDL_GameControllerName(c) << "'";
- out << " type='" << SDL_GameControllerGetType(c) << "'";
- out << " vendor='" << SDL_GameControllerGetVendor(c) << "'";
- out << " product='" << SDL_GameControllerGetProduct(c) << "'";
- out << " version='" << SDL_GameControllerGetProductVersion(c) << "'";
- //CRASH! out << " serial='" << SDL_GameControllerGetSerial(c) << "'";
- out << " }";
- return out;
}
-// util for dumping SDL_Joystick info
-std::ostream& operator<<(std::ostream& out, SDL_Joystick* j)
+void onJoystickDeviceRemoved(const SDL_Event& event)
{
- if (! j)
- {
- return out << "nullptr";
- }
- out << "{";
- out << " p=0x" << (void*)(j);
- out << " name='" << SDL_JoystickName(j) << "'";
- out << " type='" << SDL_JoystickGetType(j) << "'";
- out << " instance='" << SDL_JoystickInstanceID(j) << "'";
- out << " product='" << SDL_JoystickGetProduct(j) << "'";
- out << " version='" << SDL_JoystickGetProductVersion(j) << "'";
- out << " num_axes=" << SDL_JoystickNumAxes(j);
- out << " num_balls=" << SDL_JoystickNumBalls(j);
- out << " num_hats=" << SDL_JoystickNumHats(j);
- out << " num_buttons=" << SDL_JoystickNumHats(j);
- out << " }";
- return out;
+ LL_INFOS("GameController") << "joystick id: " << event.cdevice.which << LL_ENDL;
}
void onControllerDeviceAdded(const SDL_Event& event)
{
- int device_index = event.cdevice.which;
- SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(device_index);
- SDL_GameController* controller = SDL_GameControllerOpen(device_index);
+ LL_INFOS("GameController") << "device index: " << event.cdevice.which << LL_ENDL;
+
+ SDL_JoystickID id = SDL_JoystickGetDeviceInstanceID(event.cdevice.which);
+ if (id < 0)
+ {
+ LL_WARNS("GameController") << "Can't get device instance ID: " << SDL_GetError() << LL_ENDL;
+ return;
+ }
+
+ SDL_GameController* controller = SDL_GameControllerOpen(event.cdevice.which);
+ if (!controller)
+ {
+ LL_WARNS("GameController") << "Can't open game controller: " << SDL_GetError() << LL_ENDL;
+ return;
+ }
g_manager.addController(id, controller);
}
void onControllerDeviceRemoved(const SDL_Event& event)
{
+ LL_INFOS("GameController") << "joystick id=" << event.cdevice.which << LL_ENDL;
+
SDL_JoystickID id = event.cdevice.which;
g_manager.removeController(id);
}
@@ -708,13 +819,39 @@ void sdl_logger(void *userdata, int category, SDL_LogPriority priority, const ch
}
// static
-void LLGameControl::init()
+void LLGameControl::init(const std::string& gamecontrollerdb_path)
{
if (!g_gameControl)
{
- g_gameControl = LLGameControl::getInstance();
- SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
+ int result = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
+ if (result < 0)
+ {
+ // This error is critical, we stop working with SDL and return
+ LL_WARNS("GameController") << "Error initializing the subsystems : " << SDL_GetError() << LL_ENDL;
+ return;
+ }
+
SDL_LogSetOutputFunction(&sdl_logger, nullptr);
+
+ // The inability to read this file is not critical, we can continue working
+ if (!LLFile::isfile(gamecontrollerdb_path.c_str()))
+ {
+ LL_WARNS("GameController") << "Device mapping db file not found: " << gamecontrollerdb_path << LL_ENDL;
+ }
+ else
+ {
+ int count = SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str());
+ if (count < 0)
+ {
+ LL_WARNS("GameController") << "Error adding mappings from " << gamecontrollerdb_path << " : " << SDL_GetError() << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("GameController") << "Total " << count << " mappings added from " << gamecontrollerdb_path << LL_ENDL;
+ }
+ }
+
+ g_gameControl = LLGameControl::getInstance();
}
}
@@ -732,20 +869,21 @@ void LLGameControl::terminate()
// or not.
bool LLGameControl::computeFinalStateAndCheckForChanges()
{
- // Note: LLGameControllerManager::computeFinalState() can modify g_nextResendPeriod as a side-effect
+ // Note: LLGameControllerManager::computeFinalState() modifies g_nextResendPeriod as a side-effect
g_manager.computeFinalState(g_finalState);
- // 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;
+ // should send input when:
+ // sending is enabled and
+ // g_lastSend has "expired"
+ // either because g_nextResendPeriod has been zeroed
+ // or the last send really has expired.
+ return g_sendToServer && (g_lastSend + g_nextResendPeriod < get_now_nsec());
}
// static
-void LLGameControl::clearAllState()
+void LLGameControl::clearAllStates()
{
- g_manager.clearAllState();
+ g_manager.clearAllStates();
}
// static
@@ -759,7 +897,7 @@ void LLGameControl::processEvents(bool app_has_focus)
{
// do nothing: SDL_PollEvent() is the operator
}
- g_manager.clearAllState();
+ g_manager.clearAllStates();
return;
}
@@ -767,6 +905,12 @@ void LLGameControl::processEvents(bool app_has_focus)
{
switch (event.type)
{
+ case SDL_JOYDEVICEADDED:
+ onJoystickDeviceAdded(event);
+ break;
+ case SDL_JOYDEVICEREMOVED:
+ onJoystickDeviceRemoved(event);
+ break;
case SDL_CONTROLLERDEVICEADDED:
onControllerDeviceAdded(event);
break;
@@ -794,9 +938,9 @@ const LLGameControl::State& LLGameControl::getState()
}
// static
-void LLGameControl::getCameraInputs(std::vector<F32>& inputs_out)
+void LLGameControl::getFlycamInputs(std::vector<F32>& inputs_out)
{
- return g_manager.getCameraInputs(inputs_out);
+ return g_manager.getFlycamInputs(inputs_out);
}
// static
@@ -823,38 +967,12 @@ void LLGameControl::setAgentControlMode(LLGameControl::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.
@@ -891,7 +1009,7 @@ LLGameControl::InputChannel LLGameControl::getChannelByName(const std::string& n
// 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')
+ while (i < name.length() && i < 8 && name[i] >= '0')
{
index = index * 10 + name[i] - '0';
}
@@ -922,9 +1040,9 @@ U32 LLGameControl::computeInternalActionFlags()
}
// static
-void LLGameControl::setExternalActionFlags(U32 action_flags)
+void LLGameControl::setExternalInput(U32 action_flags, U32 buttons)
{
- g_manager.setExternalActionFlags(action_flags);
+ g_manager.setExternalInput(action_flags, buttons);
}
//static
diff --git a/indra/llwindow/llgamecontrol.h b/indra/llwindow/llgamecontrol.h
index 50cb78a4ea..104ae3a2c6 100644
--- a/indra/llwindow/llgamecontrol.h
+++ b/indra/llwindow/llgamecontrol.h
@@ -58,7 +58,7 @@
// | / \ |
// \__________/ \__________/
//
-// Note: the analog joystics provide NEGATIVE X,Y values for LEFT,FORWARD
+// Note: the analog joysticks 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
@@ -156,8 +156,12 @@ public:
// State is a minimal class for storing axes and buttons values
class State
{
+ int mJoystickID { -1 };
+ void* mController { nullptr };
public:
State();
+ void setDevice(int joystickID, void* controller);
+ int getJoystickID() const { return mJoystickID; }
void clear();
bool onButton(U8 button, bool pressed);
std::vector<S16> mAxes; // [ -32768, 32767 ]
@@ -166,7 +170,7 @@ public:
};
static bool isInitialized();
- static void init();
+ static void init(const std::string& gamecontrollerdb_path);
static void terminate();
// returns 'true' if GameControlInput message needs to go out,
@@ -175,11 +179,11 @@ public:
// or not.
static bool computeFinalStateAndCheckForChanges();
- static void clearAllState();
+ static void clearAllStates();
static void processEvents(bool app_has_focus = true);
static const State& getState();
- static void getCameraInputs(std::vector<F32>& inputs_out);
+ static void getFlycamInputs(std::vector<F32>& inputs_out);
// these methods for accepting input from keyboard
static void enableSendToServer(bool enable);
@@ -187,11 +191,7 @@ public:
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.
@@ -206,7 +206,7 @@ public:
// 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);
+ static void setExternalInput(U32 action_flags, U32 buttons_from_keys);
// 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
index c12d9317a7..9654cb04f1 100644
--- a/indra/llwindow/llgamecontroltranslator.cpp
+++ b/indra/llwindow/llgamecontroltranslator.cpp
@@ -83,6 +83,7 @@ void LLGameControlTranslator::setMappings(LLGameControlTranslator::NamedChannels
mMaskToChannel.clear();
mMappedFlags = 0;
mPrevActiveFlags = 0;
+ mCachedState.clear();
for (auto& name_channel : list)
{
@@ -103,7 +104,7 @@ bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameCon
else if (channel.isAxis())
{
U8 last_char = name.at(name_length - 1);
- if (last_char == '+' || last_char == '=')
+ if (last_char == '+' || last_char == '-')
{
map_changed = updateMapInternal(name, channel);
}
@@ -115,10 +116,9 @@ bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameCon
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,
+ // TIED TRIGGER 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.
//
@@ -209,6 +209,7 @@ bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameCon
mMappedFlags |= pair.first;
}
mPrevActiveFlags = 0;
+ mCachedState.clear();
}
return map_changed;
}
@@ -221,17 +222,12 @@ bool LLGameControlTranslator::updateMap(const std::string& name, const LLGameCon
// 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)
+ if (active_flags != mPrevActiveFlags)
{
mCachedState.clear();
for (const auto& pair : mMaskToChannel)
diff --git a/indra/llwindow/llgamecontroltranslator.h b/indra/llwindow/llgamecontroltranslator.h
index 13cbf29db2..f47c4f4a5f 100644
--- a/indra/llwindow/llgamecontroltranslator.h
+++ b/indra/llwindow/llgamecontroltranslator.h
@@ -32,6 +32,17 @@
#include "llgamecontrol.h"
+// GameControl data is sent to the server to expose game controller input to LSL scripts,
+// however not everyone will have a game controller device. To allow keyboard users to provide
+// GameControl data we allow the User to configure equivalences between avatar actions
+// (i.e. "push forward", "strafe left", etc) and keyboard buttons to GameControl axes
+// and buttons.
+//
+// The LLGameControlTranslator stores the equivalences and translates avatar action_flags
+// and keyboard state into GameContrl data, and in some cases the other direction: from
+// LLGameControl::State into avatar action_flags.
+//
+
class LLGameControlTranslator
{
public:
@@ -51,7 +62,7 @@ public:
// 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".
+ // 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)