Mouse Control in Linux

Hello,
I am quite new to Panda3D. I have been looking at some examples on the web and got a game where the mouse control is a bit like GTA, i.e. a FPS-type game mouse control. The code work perfectly well on windows but when I am on Linux, it doesn’t work at all. Here is the Mouse Control Code:

    def MouseTask(self):
        try:
   
            if not self.m_c:
                x = base.win.getPointer(0).getX()
                y = base.win.getPointer(0).getY()
                if base.win.movePointer(0, 100, 100):
                   self.heading = self.heading - (x - 100)*0.2
                   self.pitch = self.pitch - (y - 100)*0.2
                if (self.pitch < -45): self.pitch = -45
                if (self.pitch >  20): self.pitch =  20
                self.c_node.setHpr(self.heading,self.pitch,0)
                self.ralph.setH(self.heading +180)          
        except: pass

From what i have understood, is that when the pointer moves, this program re-position it on the coordinate 0,100,100

But the problem is that when I am on Linux, the Pointer remains on the point 0,100,100 and the character just doesn’t move.

Did anyone ever encounter this? if yes, is there a way to get the GTA type of mouse control in Panda3D on Linux?

Thanks for considering my question, and sorry if this has already been posted before…

Hey, Linux user here. I toyed with your code by popping it into Roaming Ralph, and indeed the cursor is (for the most part) being repositioned to (0,100,100) as it should; and indeed neither the character nor the camera is moving. I left the try and except in there because I figured it’d have to be raising an exception (note that you’re having it pass on exception, which would explain why it appears that what should very well be happening is neither happening properly nor crashing the application).

After tossing some print() methods in to find where it’s breaking, I found that an exception gets raised when you try to calculate self.heading and self.pitch. Once I saw that, I realised that calculation depends on the variable existing…

self.heading = self.heading - (x - 100)*0.2
self.pitch = self.pitch - (y - 100)*0.2

But, at least in the code you provided, self.heading and self.pitch were never given values to begin with. After adding this to the init() method…

self.heading = 0
self.pitch = 0

… I got the character and camera to rotate according to mouse movement.

Hello,
Thanks for this quick reply!

I made a mistake in my Earlier post. Actually the program I was trying when I posted this question was a modified one where self.heading and self.pitch was not set. On the original code, the problem I am actually having is that the movement of the camera and of the character is not smooth at all on Linux. But the Character is actually moving. The movement becomes smooth if I remove the line

if base.win.movePointer(0, 100, 100): 

and replace it with

if True:

but the problem is that the pointer goes to the edge of the screen and the character stops turning. The only solution would me to reposition the mouse to the center of the screen, but this is causing an uneven movement, a bit crisp and not smooth at all. Did you experience this?

Note that the same code works well on Windows and the movement is smooth.

Hmmm, well, I just bumped into this problem myself. Don’t know if your still watching this thread, but sorry nobody answered this one before - the problem is obscenely subtle however, though I wonder why nobody has complained before.

Firstly, the reason (I think - this is entirely built on inference rather than hard evidence.), feel free to ignore:
In Windows the mouse cursor is an integer and in the background windows deals with the fact that the mouse actually moves between pixels at a far higher resolution than the screen. In Linux however that is not hidden - it returns a float, to represent points between pixels. The problem is that panda then converts that to an integer and loses the sub-pixel component. As a result if the frame rate is high enough because the mouse position is being reset each frame those sub-pixel components never sum to more than one and are lost - the screen never moves. If you jerk the mouse quickly however it causes enough movement in a frame to be noticed, and that is what makes it jerky. (Note that a fully featured app probably wouldn’t have as much of a problem, lower framerate = less of a problem.)

I would classify this as a bug in Panda myself - the solution is to hack the movePointer method so it keeps the sub-pixel component and behaves like windows. However, in the mean time I offer a ‘solution’, though its a horrible hack - simply allow the mouse cursor to move around in the window but pull it back if it gets too close to the edge - this means the movePointer command happens far less often and less data is lost.

To fix it swap out the MouseTask with the below, noting those extra variables I have added to the class. Note that I haven’t tested this - simply edited it using the code in the program I was playing with, so there is a strong possibility of a mistake.

originX = 0
originY = 0

def MouseTask(self):
  if not self.m_c:
    md = base.win.getPointer(0)
    ox = md.getX() - self.originX
    oy = md.getY() - self.originY
    self.originX = md.getX()
    self.originY = md.getY()

    # The if statement is not necessary - it exists so if you start the program with the mouse cursor outside the window and then move it into the window the camera will not jerk. It of course could prevent really fast rotation in game.
    if abs(ox)<base.win.getXSize()//3 and abs(oy)<base.win.getYSize()//3:
      self.heading = self.heading - ox*0.2
      self.pitch = self.pitch - oy*0.2
      if (self.pitch < -45): self.pitch = -45
      if (self.pitch >  20): self.pitch =  20
      self.c_node.setHpr(self.heading,self.pitch,0)
      self.ralph.setH(self.heading +180)

    xoob = self.originX<base.win.getXSize()//4 or  self.originX>(base.win.getXSize()*3)//4
    yoob = self.originY<base.win.getYSize()//4 or self.originY>(base.win.getYSize()*3)//4
    if xoob or yoob:
      cx = base.win.getXSize()//2
      cy = base.win.getYSize()//2
      if base.win.movePointer(0,cx,cy):
        self.originX = cx
        self.originY = cy       

I think your guess is right, on some level. However, that level is below Panda’s level, and must in fact be deep within the X library, because the X interface returns only integer mouse positions to applications, and accepts only integers in the MovePointer() function call.

Still, it appears that this problem can be solved by simply modifying Panda to check the current mouse position, and only call MovePointer() if it is in fact moving the mouse position to a different integer position. I have just committed this change, and it does appear to help the mouselook smoothness on Linux considerably.

David

Yeah, I tested that technique myself - first thing I tried as its easier than the above. But the above does get a noticeably smoother result - its a shame there is no better solution really - guess I’ll just have to stick to the above code. (Unless someone knows their way around X and knows a solution.)

Hello,

Thanks for the reply.

I have myself been trying the different types of controls I could give to the player, though due to lack of time, I did not go through all the possibilities…

For the game I am making, here is the Mouse.py file I devised for mouse control.

import direct.directbase.DirectStart
from pandac.PandaModules import CollisionTraverser,CollisionNode
from pandac.PandaModules import CollisionHandlerQueue,CollisionRay
from pandac.PandaModules import Filename
from pandac.PandaModules import PandaNode,NodePath,Camera,TextNode
from pandac.PandaModules import Vec3,Vec4,BitMask32
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties

import random, sys, os, math, platform

class Mouse:
   
    def __init__(self,actor,c_node):
        
        self.actor=actor
        self.c_node=c_node
        self.mouseControl=True
        self.justOpened=True
        self.playerDir=180
        self.px=0
        self.py=0
        self.heading = 180
        self.pitch=0
        s = platform.system()
        windows = (s == 'Windows') or (s == 'Microsoft')
        freebsd = (s == 'FreeBSD')
        linux   = (s == 'Linux')
        osx     = (s == 'Darwin')
        unix    = freebsd or linux or osx

        if(windows):
            taskMgr.add(self.moveWindows,"MouseMoveTask")
        else:
            taskMgr.add(self.moveLinux,"MouseMoveTask")


     
    def moveLinux(self,task):
        try:
            if self.mouseControl and base.mouseWatcherNode.hasMouse():
                mpos = base.mouseWatcherNode.getMouse()
                if(abs(mpos.getX()*4)>2):
                    self.px=self.px-(mpos.getX()*4)
                if(abs(mpos.getY()*3)>2):
                    self.py=self.py+(mpos.getY()*3)
                if(self.py>5): self.py=5
                if (self.py < -50): self.py = -50
                if self.justOpened:
                    self.py=0
                    self.px=-(self.playerDir+1)
                    self.justOpened = not self.justOpened
                self.c_node.setP(self.py)#pitch)
                self.c_node.setH(self.px)#heading)
                self.actor.setH(self.px)#heading)
        except: pass
        return Task.cont

    def moveWindows(self,task):
        try:
            if self.mouseControl:
                x = base.win.getPointer(0).getX()
                y = base.win.getPointer(0).getY()
                if base.win.movePointer(0, base.win.getXSize()/2, base.win.getYSize()/2):
                    self.heading = self.heading -  (x - base.win.getXSize()/2)*0.2
                    self.pitch = self.pitch - (y - base.win.getYSize()/2)*0.2
                if (self.pitch < -50): self.pitch = -50 #up movement i.e, how much down you can see
                if (self.pitch >  5): self.pitch =  5   #down movement i.e, how much up you can see
                self.c_node.setP(self.pitch)
                self.c_node.setH(self.heading)
                self.actor.setH(self.heading)
        except: pass
        return Task.cont
    
    
    def setMouseControl(self,mControl=True):
        self.mouseControl=mControl
        return mControl

    def setPlayerDir(self,playerDir=180):
        self.playerDir=playerDir
        return playerDir

You will notice that I put the player’s initial direction, I was actually hoping to add some more features whereby the player could lock his view on a certain opponent during combat but did not get the time to complete it.
you will notice that the moveLinux function doesn’t do what I intended to do, maybe i could replace it with lethe’s solution.

Anyway, for the problem you are saying coming from X, well during my study of the different Engines, I tried CrystalSpace3D and the binary it provides, CELStart. I tried it with a sample from their website, the castle.celzip file, and the mouse control over there is what I was expecting. And its totally smooth over there. So i tried to see how it was moving the mouse simply by alt-tabbing to another windows, and guess what? I saw my cursor each time being brought to the centre of the screen just like the moveWindows task does in my class. So how is it working without any problem over there but we are having difficulties here. Maybe we could see how they implemented the mouse control in that engine, considering its open source too, so that should not be a problem…

Thanks for your help guys, its good to know there are others working in a Linux environment with panda3D(my project partner always pesters me on moving to windows :wink: ).

It does seem like Panda could do a better job of automating this sort of thing. For instance, you really should be setting the window into M_relative mouse mode first, in order for this to work well on a Mac. (The Mac has funny rules about what you can do to the mouse and when.)

Also, Panda already has a concept of “pointer mode” and “raw mode” for its auxiliary mouse pointers, in case you happen to have multiple mice. This is intended to support additional input devices that are connected and appear to the OS as mice. In “raw mode”, the mouse coordinates are received irrespective of the window boundaries–it’s very much like the M_relative mode.

It seems like all of these should be integrated. Panda should have a convenient way to switch the mouse–the primary mouse, or any auxiliary mosue–into a mouselook mode, in which coordinates are given relative to the previous frame, perhaps, or at least increase without bounds; and in which all the various foibles from the different OS’s are automatically handled.

This needs a bit more thought.

David

Oh. My. Goodness. I ran my game today and finally I was able to use mouse controls again. And all these weeks I thought there was an error in my code.
Thanks so much, drwr! :smiley:

Although your fix made mouse movement smoother, it’s not smooth enough yet. I’ve looked into raw mouse reading but it seems that linux’ structures for raw mouse reading still return integers.

After reading through the source code of some well-known linux games, like quake and warsow, it appears like most of them use an X11 extension called xf86dga or so.
Here’s a bit of source code from warsow, heavily simplified:

int MajorVersion, MinorVersion;
if(XF86DGAQueryVersion(x11display, &MajorVersion, &MinorVersion))
{
	XF86DGADirectVideo(x11display, x11screen, XF86DGADirectMouse);
	XWarpPointer(x11display, None, x11window, 0, 0, 0, 0, 0, 0);

}	else { // Not supported.

  XWarpPointer(x11display, None, x11window, 0, 0, 0, 0, x11window.width/2, x11window.height/2 );
}

Looking in my package manager’s description of libxxf86dga1:

libXxf86dga provides the XFree86-DGA extension, which allows direct graphics access to a framebuffer-like region, and also allows relative mouse reporting, et al.  It is mainly used by games and emulators for games.

drwr, does this sound worth implementing into Panda? I’ve heard somewhere that it’s deprecated, but still the only real way for FPS games to get mouse movement right.

Sure, that certainly sounds worthwhile. We already have lots of deprecated code support in glxdisplay for various archaic X11 libraries that people might have installed, and I have no objections to adding a bit more. But usually, an interface is not deprecated until it has been superceded by something better, so I wonder if there is a “better” replacement for this?

David

Hmm, I couldn’t find any replacement, and since this is what all the games are using, I’d think it’s the only solution.

Anyways, I just checked it in. Now, when you switch to MRelative, the mouse locks into place and since then, it uses the relative coordinates to add or subtract from the current mouse pos - so basically it can go from -infinity to infinity then.
When you toggle back to MAbsolute, the mouse position pops back to the position where you left it in when you toggled to MRelative, and continues from there in absolute mode.

The only downside is that the cursor doesn’t move while in MRelative mode. Also, when you switch back to MAbsolute the mouse position will pop back to where it was when you toggled to MRelative in the first place.

Is this the right way to handle it, and will it be compatible with how it is handled on Windows and OSX?

… and the FPS mouse movement is smooth now. Oh yeah!

Yes, that’s the right way to handle it; it’s the same way OSX does it. Excellent!

David

Blech, it looks like I’d have to apply it for egldisplay, tinyxdisplay and glxdisplay now. Wouldn’t it be a good idea to have an x11display or so where those modules inherit from?

Yeah, it’s a good idea. We already have windisplay that all Windows-based window types inherit from; we should have an x11display too.

David