Game Development Community

Remapping Axis input

by Ronald J Nelson · in Hardware Issues · 08/10/2005 (8:35 am) · 19 replies

I have been working at this one now and have yet to figure out how to do it. Has anyone else come up with a method that allows a user to remap axis inputs at the control panel?

#1
08/10/2005 (8:52 am)
Default.bind.cs maps the axis. You change the map like you change any other map :)
#2
08/10/2005 (9:04 am)
OK I guess perhaps I was not specific enough. What I mean is when you pull up the control menu you have the option to remap the commands by double-clicking on the command then choosing the new button or key. I would like to know if there is some way to do the same thing with an axis and have it recognize it as an axis then display that you have that command mapped to an axis?
#3
08/12/2005 (6:33 am)
Well I figured out the recognition once the axis already mapped part. Here is how:
function getjgdMapDisplayName( %device, %action )
{
	if ( strstr( %device, "" ) != -1 )
	{
		// Substitute "Device Button" for "button" in the action string:
		%pos = strstr( %action, "button" );
		if ( %pos != -1 )
		{
			%mods = getSubStr( %action, 0, %pos );
			%object = getSubStr( %action, %pos, 1000 );
			%instance = getSubStr( %object, strlen( "button" ), 1000 );
			return( %mods @ "Device Button " @ ( %instance + 1 ) );
		}
		else
	   { 
	      %pos = strstr( %action, "" );
         if ( %pos != -1 )
         {
            %wordCount = getWordCount( %action );
            %mods = %wordCount > 1 ? getWords( %action, 0, %wordCount - 2 ) @ " " : "";
            %object = getWord( %action, %wordCount - 1 );
            switch$ ( %object )
            {
               case "upov":   %object = "POV1 up";
               case "dpov":   %object = "POV1 down";
               case "lpov":   %object = "POV1 left";
               case "rpov":   %object = "POV1 right";
               case "upov2":  %object = "POV2 up";
               case "dpov2":  %object = "POV2 down";
               case "lpov2":  %object = "POV2 left";
               case "rpov2":  %object = "POV2 right";
               case "xAxis":  %object = "Device X Axis";
               case "yAxis":  %object = "Device Y Axis";
               case "zAxis":  %object = "Device Z Axis";
               case "rAxis":  %object = "Device R Axis";
               case "uAxis":  %object = "Device U Axis";
               case "vAxis":  %object = "Device V Axis";
               case "slider": %object = "Device Slider";
               default:       %object = "??";
            }
            return( %mods @ %object );
         }
         else
            error( "Unsupported Joystick input object passed to getDisplayMapName!" );
      }
	}
		
	return( "??" );		
}
#4
08/12/2005 (6:36 am)
Now then, does anyone have a working method to make it read the axis in first place. I already know the trick about just mapping it to it. However, I want it to recognize it when you try to remap the command at the Options Controls menu.
#5
08/12/2005 (1:58 pm)
OK what I guess I need to know is what function can I call that would be able to check to see if input like x-axis, y-axis, ect is present (meaning you are moving the device) while not being in the game?
#6
08/18/2005 (3:54 am)
Well a really nice guy named Ben Garney recommended I just trace back the input code and here is what I found, tried, and the results. I'm going to keep posting that here until it all works then I will likely put it out as a resource.

So far I managed to find that part of the problem was within the guiInputCtrl.cc file because when the altremapDlg.gui was waiting for an input it was waitng for a response from this function.

bool GuiInputCtrl::onInputEvent( const InputEvent &event )

So I decided to modify that function to make it recognize analog input from axes and pov. This is what I did to it.

bool GuiInputCtrl::onInputEvent( const InputEvent &event )
{
	// TODO - add POV support...
	if ( event.action == SI_MAKE || event.action == SI_MOVE )
	{
		if ( event.objType == SI_BUTTON 
		  || event.objType == SI_POV
		  || event.objType == SI_XPOV
		  || event.objType == SI_UPOV
		  || event.objType == SI_DPOV
		  || event.objType == SI_LPOV
		  || event.objType == SI_RPOV
		  || event.objType == SI_XAXIS
		  || event.objType == SI_YAXIS
		  || event.objType == SI_ZAXIS
		  || event.objType == SI_RXAXIS
		  || event.objType == SI_RYAXIS
		  || event.objType == SI_RZAXIS
		  || event.objType == SI_SLIDER
		  || ( ( event.objType == SI_KEY ) && !isModifierKey( event.objInst ) ) ) 
		{
			char deviceString[32];
			if ( !ActionMap::getDeviceName( event.deviceType, event.deviceInst, deviceString ) )
				return( false );

			const char* actionString = ActionMap::buildActionString( &event );

			Con::executef( this, 4, "onInputEvent", deviceString, actionString, "1" );
         return( true );
		}
	}
	else if ( event.action == SI_BREAK )
	{
		if ( ( event.objType == SI_KEY ) && isModifierKey( event.objInst ) )
		{
			char keyString[32];
			if ( !ActionMap::getKeyString( event.objInst, keyString ) )
				return( false );

			Con::executef( this, 4, "onInputEvent", "keyboard", keyString, "0" );
         return( true );
		}
	}

   return( false );
}

Well now it recognizes the input and closes the altremapDlg.gui, but does not map the axis. This works with all of the above inputs.
#7
08/18/2005 (4:00 am)
Now the next thing I went for is trying to find out what happened or was not happening when I moved the analog axis and the altremapDlg.gui closed.

In the log file I get this at the time of the axis move.

Could not create a description for binding:
ActionMap::getKeyString: no string for action 0

Next I compiled the debug version of the engine and ran it with my setup. This time I got a pop up:

Fatal: (c:\torque\sdk\engine\sim\actionmap.cc @ 311)
Error, no key was specified!

So I took a look at line 311 to see what was up.

It reads as follows:

AssertFatal(dStrlen(pObjectString) != 0, "Error, no key was specified!");


I haven't gotten any further yet but I would appreciate any help out there because I am sure there are a lot of you with more time under the hood of this thing than I have.

Thanks in advance.
#8
08/18/2005 (6:46 am)
Well I figured out what my problem is. It is in this function in the actionMap.cc:
bool ActionMap::getKeyString(const U32 action, char* buffer)
{
   U16 asciiCode = Input::getAscii(action, STATE_LOWER);
   
//   if (action >= KEY_A && action <= KEY_Z) {
//      buffer[0] = char(action - KEY_A + 'a');
//      buffer[1] = '[[60c1e22714b5d]]';
//      return true;
//   } else if (action >= KEY_0 && action <= KEY_9) {
//      buffer[0] = char(action - KEY_0 + '0');
//      buffer[1] = '[[60c1e22714b5d]]';
   if ( (asciiCode != 0) && dIsDecentChar((char)asciiCode))
   {
      for (U32 i = 0; gAsciiMap[i].asciiCode != 0xFFFF; i++) {
         if (gAsciiMap[i].asciiCode == asciiCode) 
         {
            dStrcpy(buffer, gAsciiMap[i].pDescription);
            return true;
         }
      }
      // Must not have found a string for that ascii code just record the char
      buffer[0] = char(asciiCode);
      buffer[1] = '[[60c1e22714b5d]]';
      return true;
   } 
   else 
   {
      if (action >= KEY_A && action <= KEY_Z) 
      {
         buffer[0] = char(action - KEY_A + 'a');
         buffer[1] = '[[60c1e22714b5d]]';
         return true;
      } 
      else if (action >= KEY_0 && action <= KEY_9) {
         buffer[0] = char(action - KEY_0 + '0');
         buffer[1] = '[[60c1e22714b5d]]';
         return true;
      }
      for (U32 i = 0; gVirtualMap[i].code != 0xFFFFFFFF; i++) {
         if (gVirtualMap[i].code == action) {
            dStrcpy(buffer, gVirtualMap[i].pDescription);
            return true;
         }
      }
   }

   Con::errorf( "ActionMap::getKeyString: no string for action %d", action );
   return false;
}

The problem lies within the fact that when it is trying to identify the action it is only looking for keys A through Z and number keys 0 through 9. This is because the code is not differentiating between the need to acces the getKeyString the actionMap.cc file and the getDrivingAxesString in the winDInputDevice.cc as far as I can tell.

Now how to fix it???
#9
08/18/2005 (8:46 am)
Well it seems that the gVirtualMap is supposed to take care of the problems I am experiencing but it doesn't take a genius to see it isn't fully doing its job. It only works for keystrokes and buttons.



Hmm I noticed something else perhaps someone could explain to me.

Why is it that in event.h, SI_MOVE is listed in the Device Event Action Types, but as a Device Event Type in actionMap.cc?

Also, Why is SI_POV listed as a Device Event Type in both event.h and actionMap.cc but its activation in winDInputDevice.cc is commented out?

Even more, why is it that in event.h that SI_XAXIS is shown in the comments as an object type?

And finally, isn't it likely the main reason I can't get axis input to register correctly at the remapping controls gui, that all of this is so messed up?
#10
08/18/2005 (9:24 am)
Well it looks like I answered my own question on the last one just now. Since that which appeared to be the correct code had been commented out I restored it all in its correct places. Still the same log errors and suddenly my cofig file is showing me remapped to page up and page down.
#11
07/17/2006 (2:58 pm)
So did you ever get this to work because i need it bad..
#12
07/17/2006 (3:35 pm)
Well first let me say thanks, because you reminded me of something I needed to go test since I thought it worked right. Second, yeah it kind of works, if you have a POV it still screws up with mixing up x and y axis for POV input sometimes. Going to have to look at it some more then maybe I will release it.
#13
07/17/2006 (3:54 pm)
Well what i'm really looking for is a way to get the xaxis/yaxis to be mappable by the user from within the OptionsDlg for control remapping.

I've been unable to get that to work for now.
#14
07/18/2006 (8:36 am)
I decided to implement a "Calibrate Joystick" button that watches for Axis input. It basically prompts the user to press TOPLEFT, then Neutral, then BOTTOM RIGHT. It captures these events by binding the jstick axis to a function that records the values it receives during this process. It then stores those values as preferences and uses them as the bounds on the jstick axis.
#15
08/10/2006 (11:13 am)
Almost done. Just need to figure out a way to set up a dead zone in guiInputCtrl.cc. Darn thing is too sensitive. So far it works perfectly with all buttons and POV input. Problem is currently that the axis input seems to need some sort of buffer to keep low level noise from analog systems from triggering a input, otherwise it continues to recognize the axis input even when it is not used.
#16
08/28/2006 (3:29 pm)
By the way Vinh, that is exactly what mine does. Just have to figure out an analog deadzone system in my gui to keep extra input (analog noise) from being registered as an input.
#17
03/12/2007 (7:57 pm)
Ron,
I think the way to do this is create your own: ActionMap::buildActionString( &event ); for analog inputs. The reason is it looks like the buildActionString is specific to certain types. I would add a function called buildActionStringAnalog or some such. I will look into what it takes. It looks like you have gotten us close to what we want so far.

Thanks,
Frank
#18
03/17/2007 (7:22 pm)
Well thanks Frank, I look forward to seeing what you come up with, been a bit sidetracked on getting back around to this.
#19
04/03/2007 (3:35 pm)
Here is my guiInputCtrl.cc for those interested. It still needs to have some sort of filter added to allow for an analog dead zone to prevent analog noise from registering as input.

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "gui/utility/guiInputCtrl.h"
#include "sim/actionMap.h"

IMPLEMENT_CONOBJECT(GuiInputCtrl);

//------------------------------------------------------------------------------
bool GuiInputCtrl::onWake()
{
   // Set the default profile on start-up:
   if ( !mProfile )
   {
      SimObject *obj = Sim::findObject("GuiInputCtrlProfile");
      if ( obj )
         mProfile = dynamic_cast<GuiControlProfile*>( obj );
   }

   if ( !Parent::onWake() )
      return( false );

   mouseLock();
   setFirstResponder();

   return( true );
}


//------------------------------------------------------------------------------
void GuiInputCtrl::onSleep()
{
   Parent::onSleep();
   mouseUnlock();
   clearFirstResponder();
}


//------------------------------------------------------------------------------
static bool isModifierKey( U16 keyCode )
{
   switch ( keyCode )
   {
      case KEY_LCONTROL:
      case KEY_RCONTROL:
      case KEY_LALT:
      case KEY_RALT:
      case KEY_LSHIFT:
      case KEY_RSHIFT:
         return( true );
   }

   return( false );
}

//------------------------------------------------------------------------------
bool GuiInputCtrl::onInputEvent( const InputEvent &event )
{
	// TODO - add POV support...
	if ( event.action == SI_MAKE && event.action != SI_MOVE )
	{
		if ( event.objType == SI_BUTTON 
		  || event.objType == SI_POV
		  || ( ( event.objType == SI_KEY ) && !isModifierKey( event.objInst ) ) ) 
		{
			char deviceString[32];
			if ( !ActionMap::getDeviceName( event.deviceType, event.deviceInst, deviceString ) )
				return( false );

			const char* actionString = ActionMap::buildActionString( &event );

			Con::executef( this, 4, "onInputEvent", deviceString, actionString, "1" );
			return( true );
		}
	}
	else if ( event.action == SI_MOVE && event.action != SI_MAKE )
	{
        char deviceString[32];

        const char* actionString = ActionMap::buildActionString( &event );

        if ( !ActionMap::getDeviceName( event.deviceType, event.deviceInst, deviceString ) )
            return( false );

        else if ( event.objType == SI_XAXIS )
        {
            actionString = "xaxis";
        }
        else if ( event.objType == SI_YAXIS )
        {
            actionString = "yaxis";
        }
        else if ( event.objType == SI_ZAXIS )
        {
            actionString = "zaxis";
        }
        else if ( event.objType == SI_RXAXIS )
        {
            actionString = "raxis";
        }
        else if ( event.objType == SI_RYAXIS )
        {
            actionString = "uaxis";
        }
        else if ( event.objType == SI_RZAXIS )
        {
            actionString = "vaxis";
        }
        else if ( event.objType == SI_SLIDER )
        {
            actionString = "slider";
        }

        Con::executef( this, 4, "onInputEvent", deviceString, actionString, "1" );
        return( true );
	}
    else if ( event.action == SI_BREAK )
	{
		if ( ( event.objType == SI_KEY ) && isModifierKey( event.objInst ) )
		{
            char keyString[32];
            if ( !ActionMap::getKeyString( event.objInst, keyString ) )
                return( false );

            Con::executef( this, 4, "onInputEvent", "keyboard", keyString, "0" );

            return( true );
		}
	}
    return( false );
}

If you manage to update that part of the code to make a dead zone or any other updats, plz post them.