Tal's picture

[Solved Mostly]Can't read joystick properly

Here is my Joystick test(part of a GameWindow code and I use VS debugger):

protected override void OnUpdateFrame(FrameEventArgs e)
{
    base.OnUpdateFrame(e);
 
    if (Keyboard[Key.Escape])
        Exit();
    int cont = Joysticks[0].Button.Count;
    for (int i = 0; i < cont; i++ )
        if (Joysticks[0].Button[i])
            throw (new Exception());
}

As you can guess by the title, no exeption is thrown when I push all the buttons!
I also check that the I have one and only one joystick by this test:

protected override void OnUpdateFrame(FrameEventArgs e)
{
    base.OnUpdateFrame(e);
 
    if (Keyboard[Key.Escape])
        Exit();
 
    int cont = Joysticks.Count;
    if (cont == 1)
        throw (new Exception());
}

And as you can guess, the exeption was thrown.
(Please don't critic me about my tests that they are'nt like NUnit and etc. :-) )
My joystick is made by Xcom and it looks like a standard Playstation's joystick. Here is a picture of it(sorry about the size):

It's fully supported by DirectX, and I play TrackMania with it without problems.
So what is the problem here?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Hortus Longus's picture

Let us have a look in the function where you handle the event joystickbuttonpressed.

the Fiddler's picture

OpenTK 1.0rc1 has a few joystick-related bugs that need to be fixed. If possible, I'd like to see your Joystick test - having a complete test case would help track down and fix those bugs.

Tal's picture

I didn't thought about using event, but I gave it a chance.
I put this in the end of OnLoad method:
Joysticks[0].ButtonDown += JoystickUpdate;
And this is the method:

private void JoystickUpdate(object sender, JoystickButtonEventArgs e)
{
    throw (new Exception());
}

I press on all the buttons, and no exeption is thrown.
I have Win XP so I checed again in Control Panel -> Game Controllers and my joystick works fine.
And one more strange thing: I checked in VS debugger and the Joysticks[0].Description value is null.
So how can I read my joystick?
**edit**
Sorry I post this message after you. You post yours before I pressed "Post comment".
So I'll try to make an orginized test using console application.

Tal's picture

Here is my test:

using System;
using System.Collections.Generic;
using System.Text;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace Joysticks_Test_in_OpenTK
{
    public class Game : GameWindow
    {
        public Game()
 
        {
            VSync = VSyncMode.Adaptive;
        }
 
        private void ReportJoystick(int num)
        {
            Console.WriteLine("    Joystick number " + num + ":");
            string desc = Joysticks[num].Description;
            if (desc == null)
                Console.WriteLine("    No description(null)");
            else
                Console.WriteLine("    Description: " + desc);
            Console.WriteLine("    Device Type: " + Joysticks[num].DeviceType);
        }
 
        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
            int joysCount = Joysticks.Count;
            if (joysCount < 2)
                Console.WriteLine("There is " + joysCount + " joystick connected.");
            else
                Console.WriteLine("There are " + joysCount + " joysticks connected.");
            for (int i = 0; i < joysCount; i++)
            {
                ReportJoystick(i);
 
                Joysticks[i].ButtonDown += JoystickPress;
                Joysticks[i].ButtonUp += JoystickPress;
                Joysticks[i].Move += JoystickMove;
            }
        }
 
        //Never has been invoked.
        private void JoystickPress(object sender, JoystickButtonEventArgs e)
        {
            if (e.Pressed)
                Console.WriteLine("Joystick button " + (int)e.Button + " was pressed.");
            else
                Console.WriteLine("Joystick button " + (int)e.Button + " was released.");
        }
 
        //Never has been invoked(ANALOG button is on).
        private void JoystickMove(object sender, JoystickMoveEventArgs e)
        {
            Console.WriteLine("Axis number " + (int)e.Axis + " has moved:");
            Console.WriteLine("    Delta: " + e.Delta);
            Console.WriteLine("    Value: " + e.Value);
        }
 
        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
 
            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);
 
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }
 
        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
 
            if (Keyboard[Key.Escape])
                Exit();
 
            for (int joyCount = Joysticks.Count, i = 0; i < joyCount; i++)
                for (int butCount = Joysticks[i].Button.Count, j = 0; j < butCount; j++)
                    if (Joysticks[i].Button[j])//Always false.
                        Console.WriteLine("Joystick button " + i + " is pressed.");
        }
 
        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
 
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
            Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            //modelview = Matrix4.LookAt(-Vector3.UnitZ * 10 , Vector3.UnitZ, Vector3.UnitY);
            //modelview *= Matrix4.CreateRotationZ(0.5f);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);
 
            GL.Begin(BeginMode.Triangles);
 
            GL.Color3(1.0f, 1.0f, 0.0f); GL.Vertex3(-1.0f, -1.0f, 4.0f);
            GL.Color3(1.0f, 0.0f, 0.0f); GL.Vertex3(1.0f, -1.0f, 4.0f);
            GL.Color3(0.2f, 0.9f, 1.0f); GL.Vertex3(0.0f, 1.0f, 4.0f);
 
            GL.End();
 
            SwapBuffers();
        }
 
        static void Main(string[] args)
        {
            using (Game game = new Game())
                game.Run();
        }
    }
}

And the output:

There is 1 joystick connected.
Joystick number 0:
No description(null)
Device Type: Hid

No matter how many time I move/press all my joystick, the result is nothing,

Mincus's picture

RC1 doesn't update the joystick data.

I found a solution by recompiling OpenTK and adding to GameWindow.cs: add InputDriver.Poll(); just before OnUpdateFrameInternal(update_args);
That's on line 452 in svn revision 2701, I'm not sure where it is exactly in the RC source, but probably not far off.

This works, but I'm not sure how close it is to being how The Fiddler wants it or how efficient it is. It seems fine for testing or small projects though.

Tal's picture

Ok I understand what you're saying, but there is a second problem(can't compile):
http://www.opentk.com/node/1849
And don't forget - the joystick device description is null, so it's not just the updates.

the Fiddler's picture

Can you please perform a fresh SVN checkout from https://opentk.svn.sourceforge.net/svnroot/opentk/branches/1.0? (instructions)

It contains a number of fixes on the joystick issues (plus you'll be able to get it to compile!)

Tal's picture

Ok, so I download it and use this line in cmd:
svn co https://opentk.svn.sourceforge.net/svnroot/opentk/trunk opentk
The process end up with V mark, but the compile errors are the same(where is the "OpenTK.snk" file?), and even if I renew the OpenTK assembly, the test result is the same.
I didn't reboot my computer after I installed SVN, but I saw the process working fine so I don't think it's the problem.

Tal's picture

The compile problem has been fixed.
The test yeild results only by Mincus's solution.
OK test result(finally)!
good:
All buttons works great.
The Value of the axes are 100% correct.
bad:
Desription is always null(I also find out in the source code that the description is never been set).
Move event is invoked even if non of the axes move.
Delta parameter in Move event(but Value is ok):
In POV(arrows):
Axis 4(horizontal):
Right- Del = -1 Value = 1
Left- Del = 1 Value = -1
Axis 5(vertical):
Up - Del = -1; Val = 1
Down - Del = 1; Val = -1
And in the other axes(also in POV), Delta = -Value(again, Value is correct).
So I'm happy I can use full joystick input now, even know I can't relay on the Move event and the Description.
Thank you all!

JWman's picture

New here, but I've made some basic improvements to windows joystick support. (FWIW, I LOVE that it's using winm.dll instead of DirectX -- I made these changes explicitly to avoid DirectX and go with an OpenTK implementation). I didn't touch it on linux yet as I'm working in windows at the moment.

Two main fixes:
1) No description. DirectX gives you a nice name for the joystick device plugged in. this is because it goes to the registry and looks it up from what the driver installs there. I didn't get that to work (time and dind't need to really). What I DID do was simply assign the vendorID and productID to the description, so for example, an XBox controller's description will come up as "1118:654". This si because I needed a way to uniquely identify what joystick was plugged in to dynamically reassign axes/buttons to different actions when different joysticks are used. Previously there was no simple way to tell the difference between two joysticks.

2) I added dynamic updates to the joystick device list. Previously the list of joysticks was read once, when a GameWindow is created. If you plugged in a joystick later, or plugged in a different joystick, it wouldn't catch the changes. It now reads new joysticks and removes ones that are no longer attached from the list. I also added a Connected property to JoystickDevice.cs so that the application using the JoystickDevice could know whether it was still connected or not (if it's not connected it won't throw an exception, it will just return back 0's for all data). This all assumes you are polling the device -- no changes will show up until you poll the joystick (GameWindow.InputDriver.Poll()). Unfortunately, the InputDriver is deprecated, but needs to be replaced by something that exposes the Poll() method. Perhaps it could be exposed in JoystickDevice...?

Another improvement might be to change the data representation from JoystickAxisCollection / JoystickButtonCollection to simply float[] / bool[]. As a rule, I never want my code to rely too heavily on external libraries, and so I immediately convert the collections to primitive arrays anyway. But I'm quirky that way....

Here's the changes:

JoystickDevice.cs

public bool Connected { get { return connected; } set { connected = value; } }

Changed/New methods in WinMMJoystick.cs

public WinMMJoystick()
{
   sticks_readonly = sticks.AsReadOnly();
 
   FindJoysticks();
}
 
JoystickDevice<WinMMJoyDetails> OpenJoystick(int number)
        {
            JoystickDevice<WinMMJoyDetails> stick = null;
 
            JoyCaps caps;
            JoystickError result = UnsafeNativeMethods.joyGetDevCaps(number, out caps, JoyCaps.SizeInBytes);
            if (result != JoystickError.NoError)
                return null;
 
            int num_axes = caps.NumAxes;
            if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
                num_axes += 2;
 
            stick = new JoystickDevice<WinMMJoyDetails>(number, num_axes, caps.NumButtons);            
            stick.Details = new WinMMJoyDetails(num_axes);
 
            // Make sure to reverse the vertical axes, so that +1 points up and -1 points down.
            int axis = 0;
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.XMin; stick.Details.Max[axis] = caps.XMax; axis++; }
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.YMax; stick.Details.Max[axis] = caps.YMin; axis++; }
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.ZMax; stick.Details.Max[axis] = caps.ZMin; axis++; }
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.RMin; stick.Details.Max[axis] = caps.RMax; axis++; }
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.UMin; stick.Details.Max[axis] = caps.UMax; axis++; }
            if (axis < caps.NumAxes)
            { stick.Details.Min[axis] = caps.VMax; stick.Details.Max[axis] = caps.VMin; axis++; }
 
            if ((caps.Capabilities & JoystCapsFlags.HasPov) != 0)
            {
                stick.Details.PovType = PovType.Exists;
                if ((caps.Capabilities & JoystCapsFlags.HasPov4Dir) != 0)
                    stick.Details.PovType |= PovType.Discrete;
                if ((caps.Capabilities & JoystCapsFlags.HasPovContinuous) != 0)
                    stick.Details.PovType |= PovType.Continuous;
            }
            //at least give it a unique identifier
            stick.Description = String.Format("{0}:{1}", caps.Mid, caps.ProductId);
            // Todo: Try to get the device name from the registry. Oh joy!
            //string key_path = String.Format("{0}\\{1}\\{2}", RegistryJoyConfig, caps.RegKey, RegstryJoyCurrent);
            //RegistryKey key = Registry.LocalMachine.OpenSubKey(key_path, false);
            //if (key == null)
            //    key = Registry.CurrentUser.OpenSubKey(key_path, false);
            //if (key == null)
            //    stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons)", number, stick.Axis.Count, stick.Button.Count);
            //else
            //{
            //    key.Close();
            //}
 
            if (stick != null)
                Debug.Print("Found joystick on device number {0}", number);
            return stick;
        }
 
private void FindJoysticks()
        {
            //If a joystick gets disconnected, reconnect to it, or try to find another one
            sticks.Clear();
            // WinMM supports up to 16 joysticks.
            int number = 0;
            int maxDev = UnsafeNativeMethods.joyGetNumDevs();
            JoyInfoEx info = new JoyInfoEx();
            info.Size = JoyInfoEx.SizeInBytes;
            info.Flags = JoystickFlags.All;
            while (number < maxDev)
            {
                JoystickError errorCode = UnsafeNativeMethods.joyGetPosEx(number, ref info);
                //only add to the list if it is actually attached without errors
                if (errorCode == JoystickError.NoError)
                {
                    Connect(number);
                }
                number++;
            }
        }
        private void Connect(int Id)
        {
            JoystickDevice<WinMMJoyDetails> joystick = OpenJoystick(Id);
            if (joystick != null)
            {
                joystick.Connected = true;
                sticks.Add(joystick);
            }
        }
        private void Disconnect(JoystickDevice joystick)
        {
            joystick.Connected = false;
            sticks.Remove(joystick);
        }
        public void Poll()
        {
            //find joysticks, if there are none
            if (sticks.Count == 0)
            {
                FindJoysticks();
            }
            foreach(JoystickDevice<WinMMJoyDetails> js in sticks)
            {
 
                JoystickError result = ReadDataFromJoystick(js);
 
                if (result != JoystickError.NoError) //something went wrong
                {
                    Disconnect(js);
                    break;
                }
 
            }
        }
        private JoystickError ReadDataFromJoystick(JoystickDevice<WinMMJoyDetails> js)
        {
            JoyInfoEx info = new JoyInfoEx();
            info.Size = JoyInfoEx.SizeInBytes;
            info.Flags = JoystickFlags.All;
            JoystickError result = UnsafeNativeMethods.joyGetPosEx(js.Id, ref info);
            if (result != JoystickError.NoError)
            {
                return result;
            }
            int num_axes = js.Axis.Count;
            if ((js.Details.PovType & PovType.Exists) != 0)
                num_axes -= 2; // Because the last two axis are used for POV input.
            int axis = 0;
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.XPos, axis)); axis++; }
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.YPos, axis)); axis++; }
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.ZPos, axis)); axis++; }
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.RPos, axis)); axis++; }
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.UPos, axis)); axis++; }
            if (axis < num_axes)
            { js.SetAxis((JoystickAxis)axis, js.Details.CalculateOffset((float)info.VPos, axis)); axis++; }
 
            if ((js.Details.PovType & PovType.Exists) != 0)
            {
                float x = 0, y = 0;
 
                // A discrete POV returns specific values for left, right, etc.
                // A continuous POV returns an integer indicating an angle in degrees * 100, e.g. 18000 == 180.00 degrees.
                // The vast majority of joysticks have discrete POVs, so we'll treat all of them as discrete for simplicity.
                if ((JoystickPovPosition)info.Pov != JoystickPovPosition.Centered)
                {
                    if (info.Pov > (int)JoystickPovPosition.Left || info.Pov < (int)JoystickPovPosition.Right)
                    { y = 1; }
                    if ((info.Pov > (int)JoystickPovPosition.Forward) && (info.Pov < (int)JoystickPovPosition.Backward))
                    { x = 1; }
                    if ((info.Pov > (int)JoystickPovPosition.Right) && (info.Pov < (int)JoystickPovPosition.Left))
                    { y = -1; }
                    if (info.Pov > (int)JoystickPovPosition.Backward)
                    { x = -1; }
                }
                //if ((js.Details.PovType & PovType.Discrete) != 0)
                //{
                //    if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Centered)
                //    { x = 0; y = 0; }
                //    else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Forward)
                //    { x = 0; y = -1; }
                //    else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Left)
                //    { x = -1; y = 0; }
                //    else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Backward)
                //    { x = 0; y = 1; }
                //    else if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Right)
                //    { x = 1; y = 0; }
                //}
                //else if ((js.Details.PovType & PovType.Continuous) != 0)
                //{
                //    if ((JoystickPovPosition)info.Pov == JoystickPovPosition.Centered)
                //    {
                //        // This approach moves the hat on a circle, not a square as it should.
                //        float angle = (float)(System.Math.PI * info.Pov / 18000.0 + System.Math.PI / 2);
                //        x = (float)System.Math.Cos(angle);
                //        y = (float)System.Math.Sin(angle);
                //        if (x < 0.001)
                //            x = 0;
                //        if (y < 0.001)
                //            y = 0;
                //    }
                //}
                //else
                //    throw new NotImplementedException("Please post an issue report at http://www.opentk.com/issues");
 
                js.SetAxis((JoystickAxis)axis++, x);
                js.SetAxis((JoystickAxis)axis++, y);
            }
 
            int button = 0;
            while (button < js.Button.Count)
            {
                js.SetButton((JoystickButton)button, (info.Buttons & (1 << button)) != 0);
                button++;
            }
            return result;
        }