Relative mouse movement

Hi All -
Does anyone know how to get relative mouse coordinates in Panda3D? In most first person shooters, for example, dragging the mouse to the right rotates the view to the left without moving the cursor (crosshairs) from the center of the screen [0,0]. Stopping the mouse causes the rotation to stop. The data is the amount of mouse movement, not the actual cursor position (since the cursor position never changes).
The code below uses the traditional “getMouseX() / getMouseY()” and will return the amount (in screen coordinates) that the cursor has moved since the last frame, but once the cursor moves to the edge of the screen it crashes. If I were using this to control a camera rotation, I wouldn’t be able to keep rotating to the right once my screen coordinates hit +1.

 Is there a method call to get relative movement directly?
                                        --or--
 Is there a way to manually reset the cursor's position to [0,0] at the end of each frame so the approach below would work?  
import direct.directbase.DirectStart
from direct.task import Task


mousePos 		= [0,0]
mousePrevPos 	= [0,0]

def mouseTask(task):
	global mousePos, mousePrevPos
	try:
		# Get mouse coordinates from mouse watcher
		x=base.mouseWatcherNode.getMouseX()
		y=base.mouseWatcherNode.getMouseY()
		mousePos = [x,y]
		# Calculate mouse movement from last frame and output (print)
		move = [mousePrevPos[0] - mousePos[0], mousePrevPos[1] - mousePos[1]]
		print "Moved:\t %f right, \t %f up." %(move[0], move[1])
		# Save current position for next calculation
		mousePrevPos = mousePos
		
	except:
		# If mouse goes outside window, an exception is thrown
		#	and script crashes / mouse support is lost, 
		#	so catch the exception here and pass gracefully.
		pass
		
	return Task.cont
	

# Add the task and Run
taskMgr.add(mouseTask, 'myTask')
run()

There is a way to reset the cursor and to get the absolute cursor position:

        md = base.win.getPointer( 0 )
        x = md.getX( )
        y = md.getY( )

        if base.win.movePointer( 0, centerx, centery ):
            deltaHeading = ( x - centerx ) * factor
            deltaPitch = ( y - centery ) * factor

centerx, centery are the coordinates of the screens center, e.g. 400 & 300 for 800x600 display size. But every other point works too.

I found the “Roaming Ralph” and the “Normal Mapping” demos very helpful since they both have nice camera controls.

Also have a look at the demos in these threads to see how a first person camera or third person camera could be implemented:
https://discourse.panda3d.org/viewtopic.php?t=2385
https://discourse.panda3d.org/viewtopic.php?p=11372#11372 (More complex “shoulder” camera, third person and first person switchable by using the mouse wheel)

Here’s my guess at what your code is trying to do. Let me know if I miss anything…

# Create a handle for pointer device #0
md = base.win.getPointer( 0 )
        # Get the absolute [x,y] screen coordinates of the cursor
        x = md.getX( )
        y = md.getY( )

# Try to move pointer #0 to the coordinates [centerx, centery],
#     if the operation succeeds, it will return True (False otherwise)
if base.win.movePointer( 0, centerx, centery ):
       # Compare the [x,y] readings above to the screen center
       #    and use the difference to calculate the camera movement
       deltaHeading = ( x - centerx ) * factor
       deltaPitch = ( y - centery ) * factor 

Assuming that this is what your code is doing, I think that it will work nicely. I’ll try to post my final code later.

This is my final code to get relative mouse coordinates for mouse navigation. It does the following…

(a) Find the coordinates of the center of the screen (i.e., [300,400] for a window that is 800 x 600)
(b) On every frame, calculate how far the mouse has moved (in x,y screen coordinates) from [0,0]
© After calculating the movement, reset the mouse position to the screen center (which will equal [0,0] in screen coordinates).

Currently the x,y movements are just printed out to the DOS window, but they can be used to change the heading and pitch of the camera.

import direct.directbase.DirectStart
from direct.task import Task

def findCenter():
	# Querry the screen size
	#   and calculate the center
	props = base.win.getProperties()
	winX = props.getXSize()
	winY = props.getYSize()
	return [winX / 2, winY / 2]
	
	
# Global variable to save center coordinates
center 	= findCenter()


def mouseTask(task):
	global center
	try:
		# Get mouse coordinates from mouse watcher
		x=base.mouseWatcherNode.getMouseX()
		y=base.mouseWatcherNode.getMouseY()
		# The cursor was reset to [0,0], so the 
		#	[x,y] position = the [x,y] movement
		move = [x,y]
		# Print out mouse movement for now
		# **** move[0] and move[1] can be used
		#	to change camera yaw / pitch
		print "Moved:\t %f, \t %f." %(move[0], move[1])
		# Reset the cursor to the center of the screen
		base.win.movePointer(0, center[0], center[1])
		
	except:
		# If mouse goes outside window, catch the 
		#    exception here and pass gracefully.
		pass
		
	return Task.cont
	

# Add the task and Run
taskMgr.add(mouseTask, 'myTask')
run()

Exactly. Though I don’t think the try/except is necessary, since if the mouse is outside the window or anything else happens base.win.movePointer( ) just returns False.

ehm…, hodge is not using base.win.getPointer, but mouseWatcherNode. If you use it and your mouse get outside the window, it would generate error, since it have to pass hasMouse state. You can try checking mouseWatcherNode.hasMouse first, if it return true, you’re ok to query your mouse position.

Ooops… missed this. Thanks.

Just because I haven’t posted enough code in this thread yet…

Here’s a second version that uses absolute mouse position for pitch and relative mouse position for yaw / heading. It also maps the information onto the camera in a meaningful way.

import direct.directbase.DirectStart
from direct.task import Task

CameraHeading = 0
MouseSensitivity 	= 75
InvertPitch			= False
	
def mouseMove(task):
	"""Use mouse to control orientation 
	similar to a video game:"""
	global CameraHeading, MouseSensitivity, InvertPitch
	try:
		# X is in relative coordinates ( distance from 0 )
		# Y is in absolute coordinates ( -1 to 1 )
		x=base.mouseWatcherNode.getMouseX()
		y=base.mouseWatcherNode.getMouseY() 
		# Invert Y to invert pitch if desired
		y *= [1, -1][(InvertPitch)]
		# Scale y to range from zero to one & calculate 
		#	pitch to range +/- 90 degrees
		y = (y+1) * .5 
		pitch=((y/2.0)*360)-90	
		# Set roll to zero
		roll=0					
		# Add x movement to current Yaw / Heading
		#	and correct for circularity to keep
		#	heading within +/- 180 degrees
		CameraHeading=(( (CameraHeading + (-x * MouseSensitivity)) + 180) %360) -180
		# Reset cursor position to the horizontal center (x=0)
		#	but don't adjust the height (y value)
		base.win.movePointer(0, base.win.getProperties().getXSize() / 2, base.win.getPointer(0).getY() )
		# Move Camera
		base.camera.setHpr(CameraHeading, pitch, roll)
		# *** For debugging, print out results 
		print int(CameraHeading), "\t", int(pitch), "\t", int(roll)
		return Task.cont
	except:
		return Task.cont
	

# Add the task and Run
taskMgr.add(mouseMove, 'myTask')
run()

Just saw this myself … take a peek at the tickUpdate() method of the camera class in discourse.panda3d.org/viewtopic.php?t=2405

This method computes the differential mouse coordinates per your request above. Hope this helps …

I tweaked the above answer to make mouse/keyboard controls. You can use ws to move forward and back, ad to move left and right, space to move up, shift to move down, and click and drag to look around. I added some mouse smoothing as well because moving exactly according to mouse movements gets a little jarring. No citation needed, feel free to use this however you like

from panda3d.core import *

def setUpCameraControls():
  base.disableMouse() # disable default mouse controls


  # model for the camera to orbit along
  model = loader.loadModel('eggModels/sphere.egg')
  sphereTexture = loader.loadTexture('eggModels/empty.bmp')
  model.setTexture(sphereTexture)
  model.setTransparency(TransparencyAttrib.MAlpha)
  model.reparentTo(render)

  # dummy node for camera
  parentnode = render.attachNewNode('camparent')
  parentnode.reparentTo(model) # inherit transforms
  parentnode.setEffect(CompassEffect.make(render)) # NOT inherit rotation

  keyMap = {"a":0, "d":0, "w":0, "s":0, "space": 0, "shift": 0, 'mouse1': 0, 'mouse2': 0, 'mouse3': 0}

  #Records the state of the arrow keys
  def setKey(key, value):
     keyMap[key] = value

  # the camera
  base.camera.reparentTo(parentnode)
  base.camera.lookAt(parentnode)
  base.camera.setY(0) # camera distance from model

  global prevMouseX
  global prevMouseY
  global prevMouseVal
  global prevDxs
  global prevDys
  global totalSmoothStore
  
  
  prevMouseVal = 0
  prevMouseX = 0
  prevMouseY = 0
  prevDxs = []
  prevDys = []
  totalSmoothStore = 10
  
  def cameraMovement(task):
    global prevMouseX
    global prevMouseY
    global prevMouseVal
    global prevDxs
    global prevDys
    global totalSmoothStore

    if (keyMap["a"]!=0):
      curPos = parentnode.getPos()
      forwardVector = render.getRelativeVector(camera, Vec3.left())
      newPos = curPos + forwardVector*20*globalClock.getDt()
      parentnode.setPos(newPos)
    if (keyMap["d"]!=0):
      curPos = parentnode.getPos()
      forwardVector = render.getRelativeVector(camera, Vec3.right())
      newPos = curPos + forwardVector*20*globalClock.getDt()
      parentnode.setPos(newPos)

    if (keyMap['w']!=0):
      curPos = parentnode.getPos()
      forwardVector = render.getRelativeVector(camera, Vec3.forward())
      newPos = curPos + forwardVector*20*globalClock.getDt()
      parentnode.setPos(newPos)
         
    if (keyMap['s']!=0):
      curPos = parentnode.getPos()
      forwardVector = render.getRelativeVector(camera, Vec3.forward())
      newPos = curPos + forwardVector*-20*globalClock.getDt()
      parentnode.setPos(newPos)

    if (keyMap['space']!=0):
      curPos = parentnode.getPos()
      forwardVector = Vec3.up()
      newPos = curPos + forwardVector*20*globalClock.getDt()
      parentnode.setPos(newPos)
         
    if (keyMap['shift']!=0):
      curPos = parentnode.getPos()
      forwardVector = Vec3.up()
      newPos = curPos + forwardVector*-20*globalClock.getDt()
      parentnode.setPos(newPos)
    
    if (keyMap['mouse1']!=0):
      centerX = base.win.getXSize()/2
      centerY = base.win.getYSize()/2
      md = base.win.getPointer(0)
      x = md.getX()
      y = md.getY()
      if prevMouseVal == 0:
        prevMouseVal = 1
        prevDxs = []
        prevDys = []
      else:
        dx = x - prevMouseX
        dy = y - prevMouseY
        if len(prevDxs) > totalSmoothStore: prevDxs.pop(0)
        if len(prevDys) > totalSmoothStore: prevDys.pop(0)
        
        prevDxs.append(dx)
        prevDys.append(dy)
        
        curAverageDx = 0.
        for curDx in prevDxs:
          curAverageDx += curDx
        curAverageDx = curAverageDx / len(prevDxs)
        
        curAverageDy = 0.
        for curDy in prevDys:
          curAverageDy += curDy
        curAverageDy = curAverageDy / len(prevDys)
        
        
        parentnode.setP(parentnode.getP()-10*globalClock.getDt()*curAverageDy)
        parentnode.setH(parentnode.getH()-10*globalClock.getDt()*curAverageDx)
        
      prevMouseX = x
      prevMouseY = y
    else:
      prevMouseVal = 0
      
        
    return task.cont
  taskMgr.add(cameraMovement, 'cameraMovement')
  
  # camera rotation
  base.accept('a', setKey, ["a",1])
  base.accept('a-up', setKey, ["a",0])
  base.accept('d', setKey, ["d",1])
  base.accept('d-up', setKey, ["d",0])
  base.accept('w', setKey, ["w",1])
  base.accept('w-up', setKey, ["w",0])
  base.accept('s', setKey, ["s",1])
  base.accept('s-up', setKey, ["s",0])
  base.accept('space', setKey, ["space",1])
  base.accept('space-up', setKey, ["space",0])
  base.accept('shift', setKey, ["shift",1])
  base.accept('shift-up', setKey, ["shift",0])
  base.accept('mouse1',setKey,['mouse1',1])
  base.accept('mouse1-up',setKey,['mouse1',0])
  base.accept('mouse2',setKey,['mouse2',1])
  base.accept('mouse2-up',setKey,['mouse2',0])
  base.accept('mouse3',setKey,['mouse3',1])
  base.accept('mouse3-up',setKey,['mouse3',0])

setUpCameraControls()