From 75568f7d90b54737025f5e30549fbdcb1db859c8 Mon Sep 17 00:00:00 2001 From: Peter Veenstra Date: Mon, 4 Dec 2017 19:27:31 +0000 Subject: [PATCH] Fix bug where joysticks got disabled after a section restart (when changing parameters while running). Add code to map circularly restricted analogue input to be mapped to squares. Add deadzone support in both square and circular mode. Add deadzone=100 as fake digital device (idea by Hidden Asbestos) Imported-from: https://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk@4065 --- src/dosbox.cpp | 10 ++- src/hardware/joystick.cpp | 175 ++++++++++++++++++++++++++++++-------- 2 files changed, 150 insertions(+), 35 deletions(-) diff --git a/src/dosbox.cpp b/src/dosbox.cpp index 95fa429e..e78fa007 100644 --- a/src/dosbox.cpp +++ b/src/dosbox.cpp @@ -686,10 +686,18 @@ void DOSBOX_Init(void) { Pbool->Set_help("continuously fires as long as you keep the button pressed."); Pbool = secprop->Add_bool("swap34",Property::Changeable::WhenIdle,false); - Pbool->Set_help("swap the 3rd and the 4th axis. can be useful for certain joysticks."); + Pbool->Set_help("swap the 3rd and the 4th axis. Can be useful for certain joysticks."); Pbool = secprop->Add_bool("buttonwrap",Property::Changeable::WhenIdle,false); Pbool->Set_help("enable button wrapping at the number of emulated buttons."); + + Pbool = secprop->Add_bool("circularinput",Property::Changeable::WhenIdle,false); + Pbool->Set_help("enable translation of circular input to square output.\n" + "Try enabling this if your left analog stick can only move in a circle."); + + Pint = secprop->Add_int("deadzone",Property::Changeable::WhenIdle,10); + Pint->SetMinMax(0,100); + Pint->Set_help("the percentage of motion to ignore. 100 turns the stick into a digital one."); secprop=control->AddSection_prop("serial",&SERIAL_Init,true); const char* serials[] = { "dummy", "disabled", "modem", "nullmodem", diff --git a/src/hardware/joystick.cpp b/src/hardware/joystick.cpp index d30d234c..a9ae2321 100644 --- a/src/hardware/joystick.cpp +++ b/src/hardware/joystick.cpp @@ -18,6 +18,7 @@ #include +#include #include "dosbox.h" #include "inout.h" #include "setup.h" @@ -25,6 +26,12 @@ #include "pic.h" #include "support.h" + +//TODO: higher axis can't be mapped. Find out why again + +//Set to true, to enable automated switching back to square on circle mode if the inputs are outside the cirle. +#define SUPPORT_MAP_AUTO 0 + #define RANGE 64 #define TIMEOUT 10 @@ -33,17 +40,104 @@ #define S_PER_OHM 0.000000011 struct JoyStick { + enum {JOYMAP_SQUARE,JOYMAP_CIRCLE,JOYMAP_INBETWEEN} mapstate; bool enabled; - float xpos,ypos; - double xtick,ytick; - Bitu xcount,ycount; + float xpos, ypos; //position as set by SDL. + double xtick, ytick; + Bitu xcount, ycount; bool button[2]; + int deadzone; //Deadzone (value between 0 and 100) interpreted as percentage. + bool transformed; //Whether xpos,ypos have been converted to xfinal and yfinal. Cleared when new xpos orypos have been set + float xfinal, yfinal; //position returned to the game for stick 0. + + void clip() { + if (xfinal > 1.0) xfinal = 1.0; + else if (xfinal < -1.0) xfinal = -1.0; + if (yfinal > 1.0) yfinal = 1.0; + else if (yfinal < -1.0) yfinal = -1.0; + } + + void fake_digital() { + if (xpos > 0.5f) xfinal = 1.0f; + else if (xpos < -0.5f) xfinal = -1.0f; + else xfinal = 0.0f; + if (ypos > 0.5f) yfinal = 1.0f; + else if (ypos < -0.5f) yfinal = -1.0f; + else yfinal = 0.0f; + } + + void transform_circular(){ + float r = sqrt(xpos * xpos + ypos * ypos); + if (r == 0.0) {xfinal = xpos; yfinal = ypos; return;} + float deadzone_f = deadzone / 100.0f; + float s = 1.0f - deadzone_f; + if (r < deadzone_f) { + xfinal = yfinal = 0.0f; + return; + } + + float deadzonescale = (r - deadzone_f) / s; //r if deadzone=0; + float xa = fabs(xpos); + float ya = fabs(ypos); + float maxpos = (ya>xa?ya:xa); + xfinal = xpos * deadzonescale/maxpos; + yfinal = ypos * deadzonescale/maxpos; + } + + void transform_square() { + float deadzone_f = deadzone / 100.0f; + float s = 1.0f - deadzone_f; + + if (xpos > deadzone_f) { + xfinal = (xpos - deadzone_f)/ s; + } else if ( xpos < -deadzone_f) { + xfinal = (xpos + deadzone_f) / s; + } else xfinal = 0.0f; + if (ypos > deadzone_f) { + yfinal = (ypos - deadzone_f)/ s; + } else if ( ypos < - deadzone_f) { + yfinal = (ypos + deadzone_f) / s; + } else yfinal = 0.0f; + } + +#if SUPPORT_MAP_AUTO + void transform_inbetween(){ + //First transform to a circle and crop the values to -1.0 -> 1.0 + //then keep on doing this in future calls until it is safe to switch square mapping + // safe = 0.95 as ratio for both axis, or in deadzone + transform_circular(); + clip(); + + + float xrate = xpos / xfinal; + float yrate = ypos / yfinal; + if (xrate > 0.95 && yrate > 0.95) { + mapstate = JOYMAP_SQUARE; //TODO misschien xfinal=xpos... + //LOG_MSG("switched to square %f %f",xrate,yrate); + } + } +#endif + void transform_input(){ + if (transformed) return; + transformed = true; + if (deadzone == 100) fake_digital(); + else { + if (mapstate == JOYMAP_SQUARE) transform_square(); + else if (mapstate == JOYMAP_CIRCLE) transform_circular(); +#if SUPPORT_MAP_AUTO + if (mapstate == JOYMAP_INBETWEEN) transform_inbetween(); //No else here +#endif + clip(); + } + } + + }; JoystickType joytype; static JoyStick stick[2]; -static Bit32u last_write = 0; +static Bitu last_write = 0; static bool write_active = false; static bool swap34 = false; bool button_wrapping_enabled = true; @@ -114,8 +208,9 @@ static void write_p201(Bitu port,Bitu val,Bitu iolen) { write_active = true; last_write = PIC_Ticks; if (stick[0].enabled) { - stick[0].xcount=(Bitu)((stick[0].xpos*RANGE)+RANGE); - stick[0].ycount=(Bitu)((stick[0].ypos*RANGE)+RANGE); + stick[0].transform_input(); + stick[0].xcount=(Bitu)((stick[0].xfinal*RANGE)+RANGE); + stick[0].ycount=(Bitu)((stick[0].yfinal*RANGE)+RANGE); } if (stick[1].enabled) { stick[1].xcount=(Bitu)(((swap34? stick[1].ypos : stick[1].xpos)*RANGE)+RANGE); @@ -124,16 +219,16 @@ static void write_p201(Bitu port,Bitu val,Bitu iolen) { } static void write_p201_timed(Bitu port,Bitu val,Bitu iolen) { - // Store writetime index // Axes take time = 24.2 microseconds + ( 0.011 microsecons/ohm * resistance ) // to reset to 0 - // Precalculate the time at which each axis hits 0 here + // Pre-calculate the time at which each axis hits 0 here double currentTick = PIC_FullIndex(); if (stick[0].enabled) { + stick[0].transform_input(); stick[0].xtick = currentTick + 1000.0*( JOY_S_CONSTANT + S_PER_OHM * - (double)(((stick[0].xpos+1.0)* OHMS)) ); + (double)(((stick[0].xfinal+1.0)* OHMS)) ); stick[0].ytick = currentTick + 1000.0*( JOY_S_CONSTANT + S_PER_OHM * - (double)(((stick[0].ypos+1.0)* OHMS)) ); + (double)(((stick[0].yfinal+1.0)* OHMS)) ); } if (stick[1].enabled) { stick[1].xtick = currentTick + 1000.0*( JOY_S_CONSTANT + S_PER_OHM * @@ -144,23 +239,27 @@ static void write_p201_timed(Bitu port,Bitu val,Bitu iolen) { } void JOYSTICK_Enable(Bitu which,bool enabled) { - if (which<2) stick[which].enabled=enabled; + if (which<2) stick[which].enabled = enabled; } void JOYSTICK_Button(Bitu which,Bitu num,bool pressed) { - if ((which<2) && (num<2)) stick[which].button[num]=pressed; + if ((which<2) && (num<2)) stick[which].button[num] = pressed; } void JOYSTICK_Move_X(Bitu which,float x) { - if (which<2) { - stick[which].xpos=x; - } + if(which > 2) return; + if (stick[which].xpos == x) return; + stick[which].xpos = x; + stick[which].transformed = false; +// if( which == 0 || joytype != JOY_FCS) +// stick[which].applied_conversion; //todo } void JOYSTICK_Move_Y(Bitu which,float y) { - if (which<2) { - stick[which].ypos=y; - } + if(which > 2) return; + if (stick[which].ypos == y) return; + stick[which].ypos = y; + stick[which].transformed = false; } bool JOYSTICK_IsEnabled(Bitu which) { @@ -174,13 +273,15 @@ bool JOYSTICK_GetButton(Bitu which, Bitu num) { } float JOYSTICK_GetMove_X(Bitu which) { - if (which<2) return stick[which].xpos; - return 0.0f; + if (which > 1) return 0.0f; + if (which == 0) { stick[0].transform_input(); return stick[0].xfinal;} + return stick[1].xpos; } float JOYSTICK_GetMove_Y(Bitu which) { - if (which<2) return stick[which].ypos; - return 0.0f; + if (which > 1) return 0.0f; + if (which == 0) { stick[0].transform_input(); return stick[0].yfinal;} + return stick[1].ypos; } class JOYSTICK:public Module_base{ @@ -189,20 +290,20 @@ private: IO_WriteHandleObject WriteHandler; public: JOYSTICK(Section* configuration):Module_base(configuration){ - Section_prop * section=static_cast(configuration); - const char * type=section->Get_string("joysticktype"); - if (!strcasecmp(type,"none")) joytype = JOY_NONE; - else if (!strcasecmp(type,"false")) joytype = JOY_NONE; - else if (!strcasecmp(type,"auto")) joytype = JOY_AUTO; - else if (!strcasecmp(type,"2axis")) joytype = JOY_2AXIS; - else if (!strcasecmp(type,"4axis")) joytype = JOY_4AXIS; + Section_prop * section = static_cast(configuration); + const char * type = section->Get_string("joysticktype"); + if (!strcasecmp(type,"none")) joytype = JOY_NONE; + else if (!strcasecmp(type,"false")) joytype = JOY_NONE; + else if (!strcasecmp(type,"auto")) joytype = JOY_AUTO; + else if (!strcasecmp(type,"2axis")) joytype = JOY_2AXIS; + else if (!strcasecmp(type,"4axis")) joytype = JOY_4AXIS; else if (!strcasecmp(type,"4axis_2")) joytype = JOY_4AXIS_2; - else if (!strcasecmp(type,"fcs")) joytype = JOY_FCS; - else if (!strcasecmp(type,"ch")) joytype = JOY_CH; + else if (!strcasecmp(type,"fcs")) joytype = JOY_FCS; + else if (!strcasecmp(type,"ch")) joytype = JOY_CH; else joytype = JOY_AUTO; bool timed = section->Get_bool("timed"); - if(timed) { + if (timed) { ReadHandler.Install(0x201,read_p201_timed,IO_MB); WriteHandler.Install(0x201,write_p201_timed,IO_MB); } else { @@ -212,10 +313,16 @@ public: autofire = section->Get_bool("autofire"); swap34 = section->Get_bool("swap34"); button_wrapping_enabled = section->Get_bool("buttonwrap"); - stick[0].enabled = false; - stick[1].enabled = false; stick[0].xtick = stick[0].ytick = stick[1].xtick = stick[1].ytick = PIC_FullIndex(); + stick[0].xpos = stick[0].ypos = stick[1].xpos = stick[1].ypos = 0.0f; + stick[0].transformed = false; + + + stick[0].mapstate = JoyStick::JOYMAP_SQUARE; + bool circ = section->Get_bool("circularinput"); + if (circ) stick[0].mapstate = JoyStick::JOYMAP_CIRCLE; + stick[0].deadzone = section->Get_int("deadzone"); } }; static JOYSTICK* test;