Welcome! Log In Create A New Profile

Advanced

VbaGX Input Code Fix

Posted by Trillest 
VbaGX Input Code Fix
January 24, 2012 12:39PM
I was using the emulator with the WiiMote+Nunchuk, and having issues with the controls while using the analog stick as a D-Pad, so I came up with a fix. (With a little help from my friends at CodeProject.Com, my math was a bit off.)

This works great for Aria of Sorrow, I haven't tried other games, but, I'm assuming it's fine.

If you are also having issues, or are the author of VbaGX this might be of use to you. (There are instructions on compiling the application at the Google code page for the program.)

Anyways, it's a minor change to the file "Input.cpp", just replace the following function, with my supplied version. (Though, keep in mind, I DID NOT update the portion that handles the classic controller, that needs implemented, or else this won't work properly for ppl using that controller type. Not an issue if this is for personal use, and you use the WiiMote+Nunchuk setup like I do.)

u32 StandardMovement(unsigned short chan)
{
	u32 J = 0;
	s8 pad_x = userInput[chan].pad.stickX;
	s8 pad_y = userInput[chan].pad.stickY;
	#ifdef HW_RVL
	s8 wm_ax = userInput[0].WPAD_StickX(0);
	s8 wm_ay = userInput[0].WPAD_StickY(0);
	#endif
	/***
	Gamecube Joystick input, same as normal
	***/
	// Is XY inside the "zone"?
	if (pad_x * pad_x + pad_y * pad_y > PADCAL * PADCAL)
	{
		if (pad_y == 0)
		{
			if(pad_x > 0)
			{ 
				J |= VBA_RIGHT;
			}
			else if(pad_x < 0)
			{
				J |= VBA_LEFT;
			}
		}
		if (pad_x == 0)
		{
			if(pad_y > 0)
			{
				J |= VBA_UP;
			}
			else if(pad_y < 0)
			{
				J |= VBA_DOWN;
			}
		}

		if ((pad_x|pad_y) != 0)
		{
			if ((fabs((double)(pad_y) / (double)(pad_x))) < 2.41421356237)
			{
				if (pad_x >= 0)
					J |= VBA_RIGHT;
				else
					J |= VBA_LEFT;
			}
			if ((fabs((double)(pad_x) / (double)(pad_y))) < 2.41421356237)
			{
				if (pad_y >= 0)
					J |= VBA_UP;
				else
					J |= VBA_DOWN;
			}
		}
	}
#ifdef HW_RVL
	/***
	Wii Joystick (classic, nunchuk) input
	***/
        // OLD CODE: Doesn't work properly..
	//// Is XY inside the "zone"? 
	//if (wm_ax * wm_ax + wm_ay * wm_ay > PADCAL * PADCAL)
	//{
	//	/*** we don't want division by zero ***/
	//	if (wm_ay == 0)
	//	{
	//		if(wm_ax > 0)
	//		{ 
	//			J |= VBA_RIGHT;
	//			printf("RIGHT");
	//			return J;
	//		}
	//		else if(pad_x < 0)
	//		{
	//			J |= VBA_LEFT;
	//			printf("LEFT");
	//			return J;
	//		}
	//	}
	//	if (wm_ax == 0)
	//	{
	//		if(wm_ay > 0)
	//		{
	//			J |= VBA_UP;
	//			printf("UP");
	//			return J;
	//		}
	//		else if(pad_y < 0)
	//		{
	//			J |= VBA_DOWN;
	//			printf("DOWN");
	//			return J;
	//		}
	//	}

////
	//	if (wm_ax != 0 && wm_ay != 0)
	//	{
	//		if ((fabs((double)(wm_ay) / (double)(wm_ax))) < 2.41421356237)
	//		{
	//			if (wm_ax >= 0)
	//			{
	//				J |= VBA_RIGHT;
	//				printf("RIGHT");
	//				return J;
	//			}
	//			else
	//			{
	//				J |= VBA_LEFT;
	//				printf("LEFT");
	//				return J;
	//			}
	//		}
	//		if ((fabs((double)(wm_ax) / (double)(wm_ay))) < 2.41421356237)
	//		{
	//			if (wm_ay >= 0)
	//			{
	//				J |= VBA_UP;
	//				return J;
	//			}
	//			else
	//			{
	//				J |= VBA_DOWN;
	//				return J;
	//			}
	//		}
	//	}
	//}
        /// OLD CODE: Doesn't work properly.
		
	//  Input Detected (NEW CODE)
	if (wm_ax * wm_ax + wm_ay * wm_ay > PADCAL * PADCAL)
	{
		// inside the 'valid direction' block.
		double angle = atan2(wm_ay, wm_ax);
		const double THRES = 1.0 / sqrt(2.0);
 
		if ( cos(angle) > THRES ) 
			J |= VBA_RIGHT;
		else if ( cos(angle) < -THRES )
			J |= VBA_LEFT;
		if ( sin(angle) > THRES )
			J |= VBA_UP;
		else if ( sin(angle) < -THRES)
			J |= VBA_DOWN;		
	}

#endif
	return J;
}

This new code just calculates the angle the user is holding the analog stick at, then figures out which quadrant that angle falls into. (It's a math thang..)

I can add a link to my compiled version, if it's requested\wanted.

Anyways, just wanted to help out a bit, give back a little to the community, enjoy. :)

-------------------------

Here is the new code by itself, this could be used in any program that needs to use the analog stick like a D-Pad, though, it doesn't support diagonals as is.

if (wm_ax * wm_ax + wm_ay * wm_ay > PADCAL * PADCAL)
{
	// inside the 'valid direction' block.
	double angle = atan2(wm_ay, wm_ax);
	const double THRES = 1.0 / sqrt(2.0);
 
	if ( cos(angle) > THRES ) 
		J |= VBA_RIGHT;
	else if ( cos(angle) < -THRES )
		J |= VBA_LEFT;
	if ( sin(angle) > THRES )
		J |= VBA_UP;
	else if ( sin(angle) < -THRES)
		J |= VBA_DOWN;		
}

---

Here's a link to a math page that has some nice visual representations of quadrants, angles, etc,.

[www.themathpage.com]



Edited 4 time(s). Last edit at 01/26/2012 03:36AM by Trillest.
Re: VbaGX Input Code Fix
January 26, 2012 02:24AM
Turns out, Snes9xGX has the same issue..

Here is the fix, again, this goes in "input.cpp", just replace the original code with my version.

static void decodepad (int chan)
{
	int i, offset;
	float t;

	s8 pad_x = userInput[chan].pad.stickX;
	s8 pad_y = userInput[chan].pad.stickY;
	u32 jp = userInput[chan].pad.btns_h;

#ifdef HW_RVL
	s8 wm_ax = userInput[chan].WPAD_StickX(0);
	s8 wm_ay = userInput[chan].WPAD_StickY(0);
	u32 wp = userInput[chan].wpad->btns_h;

	u32 exp_type;
	if ( WPAD_Probe(chan, &exp_type) != 0 )
		exp_type = WPAD_EXP_NONE;
#endif

	/***
	Gamecube Joystick input
	***/
	// Is XY inside the "zone"?
	if (pad_x * pad_x + pad_y * pad_y > PADCAL * PADCAL)
	{
		/*** we don't want division by zero ***/
		if (pad_x > 0 && pad_y == 0)
			jp |= PAD_BUTTON_RIGHT;
		if (pad_x < 0 && pad_y == 0)
			jp |= PAD_BUTTON_LEFT;
		if (pad_x == 0 && pad_y > 0)
			jp |= PAD_BUTTON_UP;
		if (pad_x == 0 && pad_y < 0)
			jp |= PAD_BUTTON_DOWN;

		if (pad_x != 0 && pad_y != 0)
		{
			/*** Recalc left / right ***/
			t = (float) pad_y / pad_x;
			if (t >= -2.41421356237 && t < 2.41421356237)
			{
				if (pad_x >= 0)
					jp |= PAD_BUTTON_RIGHT;
				else
					jp |= PAD_BUTTON_LEFT;
			}

			/*** Recalc up / down ***/
			t = (float) pad_x / pad_y;
			if (t >= -2.41421356237 && t < 2.41421356237)
			{
				if (pad_y >= 0)
					jp |= PAD_BUTTON_UP;
				else
					jp |= PAD_BUTTON_DOWN;
			}
		}
	}
#ifdef HW_RVL
	/***
	Wii Joystick (classic, nunchuk) input
	***/
	// Is XY inside the "zone"?
	if (wm_ax * wm_ax + wm_ay * wm_ay > PADCAL * PADCAL)
	{

		// inside the 'valid direction' block.
		double angle = atan2(wm_ay, wm_ax);
		const double THRES = 1.0 / sqrt(2.0);
 
		if ( cos(angle) > THRES ) 
			wp |= (exp_type == WPAD_EXP_CLASSIC) ? WPAD_CLASSIC_BUTTON_RIGHT : WPAD_BUTTON_RIGHT;
		else if ( cos(angle) < -THRES )
			wp |= (exp_type == WPAD_EXP_CLASSIC) ? WPAD_CLASSIC_BUTTON_LEFT : WPAD_BUTTON_LEFT;
		if ( sin(angle) > THRES )
			wp |= (exp_type == WPAD_EXP_CLASSIC) ? WPAD_CLASSIC_BUTTON_UP : WPAD_BUTTON_UP;
		else if ( sin(angle) < -THRES)
			wp |= (exp_type == WPAD_EXP_CLASSIC) ? WPAD_CLASSIC_BUTTON_DOWN : WPAD_BUTTON_DOWN;
		
	}
#endif

	/*** Fix offset to pad ***/
	offset = ((chan + 1) << 4);

	/*** Report pressed buttons (gamepads) ***/
	for (i = 0; i < MAXJP; i++)
    {
		if ( (jp & btnmap[CTRL_PAD][CTRLR_GCPAD])											// gamecube controller
#ifdef HW_RVL
		|| ( (exp_type == WPAD_EXP_NONE) && (wp & btnmap[CTRL_PAD][CTRLR_WIIMOTE]) )	// wiimote
		|| ( (exp_type == WPAD_EXP_CLASSIC) && (wp & btnmap[CTRL_PAD][CTRLR_CLASSIC]) )	// classic controller
		|| ( (exp_type == WPAD_EXP_NUNCHUK) && (wp & btnmap[CTRL_PAD][CTRLR_NUNCHUK]) )	// nunchuk + wiimote
#endif
		)
			S9xReportButton (offset + i, true);
		else
			S9xReportButton (offset + i, false);
    }

	/*** Superscope ***/
	if (Settings.SuperScopeMaster && chan == 0) // report only once
	{
		// buttons
		offset = 0x50;
		for (i = 0; i < 6; i++)
		{
			if (jp & btnmap[CTRL_SCOPE][CTRLR_GCPAD]
#ifdef HW_RVL
			|| wp & btnmap[CTRL_SCOPE][CTRLR_WIIMOTE]
#endif
			)
			{
				if(i == 3 || i == 4) // turbo
				{
					if((i == 3 && scopeTurbo == 1) || // turbo ON already, don't change
						(i == 4 && scopeTurbo == 0)) // turbo OFF already, don't change
					{
						S9xReportButton(offset + i, false);
					}
					else // turbo changed to ON or OFF
					{
						scopeTurbo = 4-i;
						S9xReportButton(offset + i, true);
					}
				}
				else
					S9xReportButton(offset + i, true);
			}
			else
				S9xReportButton(offset + i, false);
		}
		// pointer
		offset = 0x80;
		UpdateCursorPosition(chan, cursor_x[0], cursor_y[0]);
		S9xReportPointer(offset, (u16) cursor_x[0], (u16) cursor_y[0]);
	}
	/*** Mouse ***/
	else if (Settings.MouseMaster && chan == 0)
	{
		// buttons
		offset = 0x60 + (2 * chan);
		for (i = 0; i < 2; i++)
		{
			if (jp & btnmap[CTRL_MOUSE][CTRLR_GCPAD]
#ifdef HW_RVL
			|| wp & btnmap[CTRL_MOUSE][CTRLR_WIIMOTE]
#endif
			)
				S9xReportButton(offset + i, true);
			else
				S9xReportButton(offset + i, false);
		}
		// pointer
		offset = 0x81;
		UpdateCursorPosition(chan, cursor_x[1 + chan], cursor_y[1 + chan]);
		S9xReportPointer(offset + chan, (u16) cursor_x[1 + chan],
				(u16) cursor_y[1 + chan]);
	}
	/*** Justifier ***/
	else if (Settings.JustifierMaster && chan < 2)
	{
		// buttons
		offset = 0x70 + (3 * chan);
		for (i = 0; i < 3; i++)
		{
			if (jp & btnmap[CTRL_JUST][CTRLR_GCPAD]
#ifdef HW_RVL
			|| wp & btnmap[CTRL_JUST][CTRLR_WIIMOTE]
#endif
			)
				S9xReportButton(offset + i, true);
			else
				S9xReportButton(offset + i, false);
		}
		// pointer
		offset = 0x83;
		UpdateCursorPosition(chan, cursor_x[3 + chan], cursor_y[3 + chan]);
		S9xReportPointer(offset + chan, (u16) cursor_x[3 + chan],
				(u16) cursor_y[3 + chan]);
	}

#ifdef HW_RVL
	// screenshot (temp)
	if (wp & CLASSIC_CTRL_BUTTON_ZR)
		S9xReportButton(0x90, true);
	else
		S9xReportButton(0x90, false);
#endif
}

Btw, if you find this months from now, you will be better off stripping out the new code, and reimplementing it, ie, the author could have made massive changes to the source by then..

The new code is shown separately in my first post at the bottom, just in case of such a scenario. :)

(I've emailed the author, and pointed him here, but I don't know if the email address I found was valid, etc,. But, hopefully this gets implemented into the official release.)



Edited 3 time(s). Last edit at 01/26/2012 03:34AM by Trillest.
Re: VbaGX Input Code Fix
January 26, 2012 05:13AM
This seems kind of backwards; you start with wm_ax and wm_ay which you use to find the angle, then you take the sine and cosine... doesn't that just give you back the original wm_ax and wm_ay values?
Re: VbaGX Input Code Fix
January 26, 2012 10:21AM
No, just tested it out, it seems different to me. (But, admittedly, math is not my strong point, and that's also the bit I sought help on, I knew what needed to be done, but not how to accomplish it.)

Heres the code from the simulation.

Point p = new Point(0, -100);

double Angle = Math.Atan2(p.Y, p.X);
double Thresh = 1.0 / Math.Sqrt(2.0);

if ( Math.Cos(Angle) > Thresh ) 
    Console.WriteLine("Right");
else if ( Math.Cos(Angle) < -Thresh )
    Console.WriteLine("Left");
else if ( Math.Sin(Angle) > Thresh )
    Console.WriteLine("Up");
else if ( Math.Sin(Angle) < -Thresh)
    Console.WriteLine("Down");

double Result1 = Math.Sin(Angle);
double Result2 = Math.Cos(Angle);

Console.WriteLine(String.Format("Angle: {0}\n Sin: {1}\n Cos: {2}\n Threshold: {3}\n", Angle, Result1, Result2, Thresh));

Results:

Down
Angle: -1.5707963267949
Sin: -1
Cos: 6.12303176911189E-17
Threshold: 0.707106781186547

Seems okay to me, though, as I said, I'm not a math expert. (In any case, the in-game results are what you'd expect, so I'm not stressing it, ie, it works, and if someone can make an even more accurate version, I won't be mad.)

(Been a long day, sigh...)



Edited 2 time(s). Last edit at 01/26/2012 11:00AM by Trillest.
Re: VbaGX Input Code Fix
January 27, 2012 08:25PM
Just a quick update:

The author contacted me and let me know that this has been implemented into all the emulators he manages. (I don't know when he plans to release the next versions, he just updated them not that long ago, so it's hard to tell. Most authors like to let a few changes build up to make a new version worthwhile.)

Anyways, he applied it to all analog controller types, so everyone should benefit from this update.



Edited 1 time(s). Last edit at 01/27/2012 08:26PM by Trillest.
Re: VbaGX Input Code Fix
February 02, 2012 02:02AM
Quote
tueidj
This seems kind of backwards; you start with wm_ax and wm_ay which you use to find the angle, then you take the sine and cosine... doesn't that just give you back the original wm_ax and wm_ay values?

There is a flaw in your math logic. The sine and cosine of an angle don't give you the value of X and Y, it gives you a decimal value between -1 and 1.

1) The inverse Tan of x/y gives you the angle
2) The sine of the angle gives you a value between -1 (full down) and 1 (full up)
3) The cosine of the angle gives you a value between -1 (full left) and 1 (full right)
4) The Threshold calculated is a 45 degrees angle (1 / square root of 2). If the joystick has an inclination of more than 45 degrees in any direction, it activates the press of that direction.

I hope this explanation makes it clearer on why this new approach works.
Re: VbaGX Input Code Fix
February 02, 2012 03:44AM
Quote
Axel
There is a flaw in your math logic. The sine and cosine of an angle don't give you the value of X and Y, it gives you a decimal value between -1 and 1.

1) The inverse Tan of x/y gives you the angle
2) The sine of the angle gives you a value between -1 (full down) and 1 (full up)
3) The cosine of the angle gives you a value between -1 (full left) and 1 (full right)
4) The Threshold calculated is a 45 degrees angle (1 / square root of 2). If the joystick has an inclination of more than 45 degrees in any direction, it activates the press of that direction.

I hope this explanation makes it clearer on why this new approach works.

Yes, I know the limits of basic trig functions. The sine and cosine values returned are simply the original wm_ax and wm_ay values scaled by their hypotenuse, which is already calculated for a threshold comparison similar to the one listed in your point 4:
if (wm_ax * wm_ax + wm_ay * wm_ay > PADCAL * PADCAL)
It's not checking an angle or "inclination", it's the magnitude of the stick compared to the center position i.e. if the stick has been pushed far enough to register as a digital movement.
So instead of getting values that are between [-128,128], the trig functions change them to being [-1,1]. If the THRESH value was multiplied by 128, the wm_ax and wm_ay values could be used and cos/sin would be unnecessary.
The only thing the new code does differently is fix the logic errors that prevented diagonal directions being returned. But this could have been fixed by changing a few lines of the original code rather than replacing it all with slower trig functions.
Sorry, only registered users may post in this forum.

Click here to login