Files
maszyna/gamepadinput.cpp

354 lines
12 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "gamepadinput.h"
#include "logs.h"
#include "timer.h"
#include "usefull.h"
glm::vec2 circle_to_square( glm::vec2 const &Point, int const Roundness = 0 ) {
// Determine the theta angle
auto angle = std::atan2( Point.x, Point.y ) + M_PI;
glm::vec2 squared;
// Scale according to which wall we're clamping to
// X+ wall
if( angle <= M_PI_4 || angle > 7 * M_PI_4 )
squared = Point * (float)( 1.0 / std::cos( angle ) );
// Y+ wall
else if( angle > M_PI_4 && angle <= 3 * M_PI_4 )
squared = Point * (float)( 1.0 / std::sin( angle ) );
// X- wall
else if( angle > 3 * M_PI_4 && angle <= 5 * M_PI_4 )
squared = Point * (float)( -1.0 / std::cos( angle ) );
// Y- wall
else if( angle > 5 * M_PI_4 && angle <= 7 * M_PI_4 )
squared = Point * (float)( -1.0 / std::sin( angle ) );
// Early-out for a perfect square output
if( Roundness == 0 )
return squared;
// Find the inner-roundness scaling factor and LERP
auto const length = glm::length( Point );
auto const factor = std::pow( length, Roundness );
return interpolate( Point, squared, factor );
}
gamepad_input::gamepad_input() {
m_modecommands = {
{ user_command::mastercontrollerincrease, user_command::mastercontrollerdecrease },
{ user_command::trainbrakedecrease, user_command::trainbrakeincrease },
{ user_command::secondcontrollerincrease, user_command::secondcontrollerdecrease },
{ user_command::independentbrakedecrease, user_command::independentbrakeincrease }
};
}
bool
gamepad_input::init() {
// NOTE: we're only checking for joystick_1 and rely for it to stay connected throughout.
// not exactly flexible, but for quick hack it'll do
auto const name = glfwGetJoystickName( GLFW_JOYSTICK_1 );
if( name != nullptr ) {
WriteLog( "Connected gamepad: " + std::string( name ) );
m_deviceid = GLFW_JOYSTICK_1;
}
else {
// no joystick,
WriteLog( "No gamepad detected" );
m_axes.clear();
m_buttons.clear();
return false;
}
int count;
glfwGetJoystickAxes( m_deviceid, &count );
m_axes.resize( count );
glfwGetJoystickButtons( m_deviceid, &count );
m_buttons.resize( count );
return true;
}
// checks state of the controls and sends issued commands
void
gamepad_input::poll() {
if( m_deviceid == -1 ) {
// if there's no gamepad we can skip the rest
return;
}
int count; std::size_t idx = 0;
// poll button state
auto const buttons = glfwGetJoystickButtons( m_deviceid, &count );
if( count ) { // safety check in case joystick gets pulled out
for( auto &button : m_buttons ) {
if( button != buttons[ idx ] ) {
// button pressed or released, both are important
on_button(
static_cast<gamepad_button>( idx ),
( buttons[ idx ] == 1 ?
GLFW_PRESS :
GLFW_RELEASE ) );
}
else {
// otherwise we only pass info about button being held down
if( button == 1 ) {
on_button(
static_cast<gamepad_button>( idx ),
GLFW_REPEAT );
}
}
button = buttons[ idx ];
++idx;
}
}
// poll axes state
idx = 0;
glm::vec2 leftstick, rightstick, triggers;
auto const axes = glfwGetJoystickAxes( m_deviceid, &count );
if( count ) {
// safety check in case joystick gets pulled out
if( count >= 2 ) {
leftstick = glm::vec2(
( std::abs( axes[ gamepad_axes::leftstick_x ] ) > m_deadzone ?
axes[ gamepad_axes::leftstick_x ] :
0.0f ),
( std::abs( axes[ gamepad_axes::leftstick_y ] ) > m_deadzone ?
axes[ gamepad_axes::leftstick_y ] :
0.0f ) );
}
if( count >= 4 ) {
rightstick = glm::vec2(
( std::abs( axes[ gamepad_axes::rightstick_x ] ) > m_deadzone ?
axes[ gamepad_axes::rightstick_x ] :
0.0f ),
( std::abs( axes[ gamepad_axes::rightstick_y ] ) > m_deadzone ?
axes[ gamepad_axes::rightstick_y ] :
0.0f ) );
}
if( count >= 6 ) {
triggers = glm::vec2(
( axes[ gamepad_axes::lefttrigger ] > m_deadzone ?
axes[ gamepad_axes::lefttrigger ] :
0.0f ),
( axes[ gamepad_axes::righttrigger ] > m_deadzone ?
axes[ gamepad_axes::righttrigger ] :
0.0f ) );
}
}
process_axes( leftstick, rightstick, triggers );
}
void
gamepad_input::on_button( gamepad_button const Button, int const Action ) {
switch( Button ) {
// NOTE: this is rigid coupling, down the road we should support more flexible binding of functions with buttons
case gamepad_button::a:
case gamepad_button::b:
case gamepad_button::x:
case gamepad_button::y: {
if( Action == GLFW_RELEASE ) {
// TODO: send GLFW_RELEASE for whatever command could be issued by the mode active until now
// if the button was released the stick switches to control the movement
m_mode = control_mode::entity;
// zero the stick and the accumulator so the input won't bleed between modes
m_leftstick = glm::vec2();
m_modeaccumulator = 0.0f;
}
else {
// otherwise set control mode to match pressed button
m_mode = static_cast<control_mode>( Button );
}
break;
}
default: {
break;
}
}
}
void
gamepad_input::process_axes( glm::vec2 Leftstick, glm::vec2 const &Rightstick, glm::vec2 const &Triggers ) {
// right stick, look around
if( ( Rightstick.x != 0.0f ) || ( Rightstick.y != 0.0f ) ) {
// TODO: make toggles for the axis flip
auto const deltatime = Timer::GetDeltaRenderTime() * 60.0;
double const turnx = Rightstick.x * 10.0 * deltatime;
double const turny = -Rightstick.y * 10.0 * deltatime;
m_relay.post(
user_command::viewturn,
reinterpret_cast<std::uint64_t const &>( turnx ),
reinterpret_cast<std::uint64_t const &>( turny ),
GLFW_PRESS,
// as we haven't yet implemented either item id system or multiplayer, the 'local' controlled vehicle and entity have temporary ids of 0
// TODO: pass correct entity id once the missing systems are in place
0 );
}
// left stick, either movement or controls, depending on currently active mode
if( m_mode == control_mode::entity ) {
if( ( Leftstick.x != 0.0 || Leftstick.y != 0.0 )
|| ( m_leftstick.x != 0.0 || m_leftstick.y != 0.0 ) ) {
double const movex = static_cast<double>( Leftstick.x );
double const movez = static_cast<double>( Leftstick.y );
m_relay.post(
user_command::movevector,
reinterpret_cast<std::uint64_t const &>( movex ),
reinterpret_cast<std::uint64_t const &>( movez ),
GLFW_PRESS,
0 );
}
}
else {
// vehicle control modes
process_mode( Leftstick.y, 0 );
}
m_rightstick = Rightstick;
m_leftstick = Leftstick;
m_triggers = Triggers;
}
void
gamepad_input::process_axis( float const Value, float const Previousvalue, float const Multiplier, user_command Command, std::uint16_t const Recipient ) {
process_axis( Value, Previousvalue, Multiplier, Command, Command, Recipient );
}
void
gamepad_input::process_axis( float const Value, float const Previousvalue, float const Multiplier, user_command Command1, user_command Command2, std::uint16_t const Recipient ) {
user_command command{ Command1 };
if( Value * Multiplier > 0.9 ) {
command = Command2;
}
if( Value * Multiplier > 0.0f ) {
m_relay.post(
command,
0, 0,
GLFW_PRESS,
Recipient
);
}
else {
// if we had movement before but not now, report this as 'button' release
if( Previousvalue != 0.0f ) {
m_relay.post(
command, // doesn't matter which movement 'mode' we report
0, 0,
GLFW_RELEASE,
0
);
}
}
}
void
gamepad_input::process_mode( float const Value, std::uint16_t const Recipient ) {
// TODO: separate multiplier for each mode, to allow different, customizable sensitivity for each control
auto const deltatime = Timer::GetDeltaTime() * 15.0;
auto const &lookup = m_modecommands.at( static_cast<size_t>( m_mode ) );
if( Value >= 0.0f ) {
if( m_modeaccumulator < 0.0f ) {
// reset accumulator if we're going in the other direction i.e. issuing opposite control
// this also means we should indicate the previous command no longer applies
// (normally it's handled when the stick enters dead zone, but it's possible there's no actual dead zone)
m_relay.post(
lookup.second,
0, 0,
GLFW_RELEASE,
Recipient );
m_modeaccumulator = 0.0f;
}
if( Value > m_deadzone ) {
m_modeaccumulator += ( Value - m_deadzone ) / ( 1.0 - m_deadzone ) * deltatime;
// we're making sure there's always a positive charge left in the accumulator,
// to more reliably decect when the stick goes from active to dead zone, below
while( m_modeaccumulator > 1.0f ) {
// send commands if the accumulator(s) was filled
m_relay.post(
lookup.first,
0, 0,
GLFW_PRESS,
Recipient );
m_modeaccumulator -= 1.0f;
}
}
else {
// if the accumulator isn't empty it's an indicator the stick moved from active to neutral zone
// indicate it with proper RELEASE command
m_relay.post(
lookup.first,
0, 0,
GLFW_RELEASE,
Recipient );
m_modeaccumulator = 0.0f;
}
}
else {
if( m_modeaccumulator > 0.0f ) {
// reset accumulator if we're going in the other direction i.e. issuing opposite control
// this also means we should indicate the previous command no longer applies
// (normally it's handled when the stick enters dead zone, but it's possible there's no actual dead zone)
m_relay.post(
lookup.first,
0, 0,
GLFW_RELEASE,
Recipient );
m_modeaccumulator = 0.0f;
}
if( Value < m_deadzone ) {
m_modeaccumulator += ( Value + m_deadzone ) / ( 1.0 - m_deadzone ) * deltatime;
// we're making sure there's always a negative charge left in the accumulator,
// to more reliably decect when the stick goes from active to dead zone, below
while( m_modeaccumulator < -1.0f ) {
// send commands if the accumulator(s) was filled
m_relay.post(
lookup.second,
0, 0,
GLFW_PRESS,
Recipient );
m_modeaccumulator += 1.0f;
}
}
else {
// if the accumulator isn't empty it's an indicator the stick moved from active to neutral zone
// indicate it with proper RELEASE command
m_relay.post(
lookup.second,
0, 0,
GLFW_RELEASE,
Recipient );
m_modeaccumulator = 0.0f;
}
}
}
//---------------------------------------------------------------------------