Camera Confusion

I am trying to write a scene navigation camera that enables the user to do many more things including seamless switching from the standard trackball (base.trackball.node()) to the standard camera (base.camera). My requirement here is that when the user switches modes (Drive versus Trackball) that the apparent viewer state remain the same … that is … the viewer retains its position and orientation between mode switches.

There is no problem when I switch from trackball mode to drive mode. The viewer state (base.camera.getPos(), base.camera.getHpr()) does not change. However, if I switch to drive mode and move the camera position, then switch back to trackball mode, the trackball remembers the last position it was before I changed to drive mode and plops me right back at that point.

I see that the trackball has both an origin and an offset from that origin but I am having difficulty figuring out how tofix this problem. Any ideas?

My code so far follows … The methods of interest here are switchToDriveMode and switchToTrackballMode in class Camera. You can see some of the ideas I have been experimenting with in the commented out code sections in these methods.

Notes On Controlling Camera:

Starts out in standard Panda3D trackball mode. The ‘n’ key cycles the mode from camera to drive and back.

‘+’ - Increases the translation gain (speeds up motion)
‘-’ - Decreases the translation gain (slows down motion)

‘mouse1’ - When depressed, slides the camera laterally in trackball mode, changes the heading angle in drive mode.
‘mouse2’ - When depressed, slides the camera forward/backward in trackball mode, changes the pitch angle in drive mode.

When in trackball mode, the following keys are ignored. They function in drive mode.

‘d’ - Drive forward
‘c’ - Drive backward
‘s’ - Slide left
‘f’ - Slide right
‘e’ - Slide up
‘v’ - Slide down

Thx,
Paul

Code for Camera.py

# Camera.py

""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Description: Dual mode (FPS / Trackball) Camera.

	$Author: pleopard $

	$Modtime: 10/19/06 8:46p $

	$Revision: 1 $ 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """

# Standard imports

import sys
from math import fabs

# Panda imports

from pandac.PandaModules import *
from direct.showbase import DirectObject
from direct.showbase.DirectObject import DirectObject
import direct.directbase.DirectStart

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Internal stuff

def Sign(x):
	if x<0:
		return -1
	return 1

def ComputeMapping(a0,a1,b0,b1) :
	if (a1-a0)==0 :
		slope = 0
		intercept = 0
	else :
		slope = (b1-b0)/(a1-a0)
		intercept = b1 - slope*a1
	return [slope,intercept]

class CameraControlDefs:
	def getDriveForwardEvent(self):
		return 'd'
	def getDriveReverseEvent(self):
		return 'c'
	def getSlideLeftEvent(self):
		return 's'
	def getSlideRightEvent(self):
		return 'f'
	def getSlideUpEvent(self):
		return 'e'
	def getSlideDownEvent(self):
		return 'v'
	def getModeCyclingEvent(self):
		return 'n'
	def getMotionGainIncreaseEvent(self):
		return '+'
	def getMotionGainDecreaseEvent(self):
		return '-'
	def getTurboBoostEvent(self):
		return 'space'
	def getHeadingAngleEvent(self):
		return 'mouse1'
	def getPitchAngleEvent(self):
		return 'mouse2'
	def getRollAngleEvent(self):
		return 'mouse3'

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

class Camera(DirectObject):

	TrackBallMode = 0
	DriveMode = 1

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def __init__(self,msgDisplay,camControlsDef):

		# *** Get controls definitions
		self.mDriveForwardEvent = camControlsDef.getDriveForwardEvent()
		self.mDriveReverseEvent = camControlsDef.getDriveReverseEvent()
		self.mSlideLeftEvent = camControlsDef.getSlideLeftEvent()
		self.mSlideRightEvent = camControlsDef.getSlideRightEvent()
		self.mSlideUpEvent = camControlsDef.getSlideUpEvent()
		self.mSlideDownEvent = camControlsDef.getSlideDownEvent()
		self.mModeCyclingEvent = camControlsDef.getModeCyclingEvent()
		self.mTurboBoostEvent = camControlsDef.getTurboBoostEvent()
		self.mMotionGainIncreaseEvent = \
			camControlsDef.getMotionGainIncreaseEvent()
		self.mMotionGainDecreaseEvent = \
			camControlsDef.getMotionGainDecreaseEvent()
		self.mHeadingAngleEvent = camControlsDef.getHeadingAngleEvent()
		self.mPitchAngleEvent = camControlsDef.getPitchAngleEvent()
		self.mRollAngleEvent = camControlsDef.getRollAngleEvent()

		# *** Setup static controls
		self.accept(self.mModeCyclingEvent,self.cycleMode)

		control = self.mMotionGainIncreaseEvent
		self.accept(control,self.motionGainIncrease)
		control = control + '-repeat'
		self.accept(control,self.motionGainIncrease)

		control = self.mMotionGainDecreaseEvent
		self.accept(control,self.motionGainDecrease)
		control = control + '-repeat'
		self.accept(control,self.motionGainDecrease)

		control = self.mTurboBoostEvent
		self.accept(control,self.turboBoostOn)
		control = control + '-up'
		self.accept(control,self.turboBoostOff)

		# *** Note current position
		self.mLastPos = base.camera.getPos()
		self.mLastHpr = base.camera.getHpr()

		# *** Other setup
		self.mStatusDisplay = msgDisplay
		self.mCameraMode = Camera.TrackBallMode
		base.useTrackball()
		self.mMotionGain = 1
		self.mHeadingGain = 5
		self.mPitchGain = 5
		self.mRollGain = 12
		self.mDriveDir = 0
		self.mLateralSlideDir = 0
		self.mVerticalSlideDir = 0
		self.mDevButtons = [\
			Camera.__DeviceButton(),
			Camera.__DeviceButton(),
			Camera.__DeviceButton()
		]
		self.mTrackBallScale = base.trackball.node().getForwardScale()
		self.mLastTime = globalClock.getFrameTime()

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def tickUpdate(self):

		if self.mCameraMode == Camera.TrackBallMode:
			return

		# Get time differential
		dt = globalClock.getFrameTime() - self.mLastTime
		self.mLastTime = globalClock.getFrameTime()

		# Get window size
		wp = base.win.getProperties()
		winWidth = wp.getXSize()
		winWidth2 = winWidth/2
		winHeight = wp.getYSize()
		winHeight2 = winHeight/2

		# Compute mapping from (0,W-1) onto (-W/2,W/2)
		xTrans = \
			ComputeMapping(\
				0.0,float(winWidth-1), \
				-float(winWidth2), float(winWidth2) \
			)

		# Compute mapping from (0,H-1) onto (-H/2,H/2)
		yTrans = \
			ComputeMapping(\
				0.0,float(winHeight-1), \
				-float(winHeight2), float(winHeight2) \
			)

		# Get cursor coordinates
		md = base.win.getPointer(0)
		cursorX = md.getX()
		cursorY = md.getY()

		# Convert using transform
		xCursor = xTrans[0]*float(cursorX)+xTrans[1]
		yCursor = yTrans[0]*float(cursorY)+yTrans[1]

		aButtonIsDown = False

		# Init delta angles
		dHeading = 0
		dPitch = 0
		dRoll = 0

		# Heading and pitch (mouse)
		mouseButton0Down = self.mDevButtons[0].mIsPressed
		if mouseButton0Down :

			delx = float(self.mDevButtons[0].mCoordinates[0]) - xCursor
			dely = float(self.mDevButtons[0].mCoordinates[1]) - yCursor

			delx = xCursor*dt
			dely = yCursor*dt

			sx = Sign(delx)
			sy = Sign(dely)

			dx = delx*delx*sx*self.mHeadingGain
			dy = dely*dely*sy*self.mPitchGain

			dHeading += -dx
			dPitch += dy
			aButtonIsDown = True

		# Roll
		mouseButton2Down = self.mDevButtons[2].mIsPressed
		if mouseButton2Down :
			delx = float(self.mDevButtons[0].mCoordinates[0]) - xCursor
			# dely = float(self.mDevButtons[0].mCoordinates[1]) - yCursor

			delx = xCursor*dt
			# dely = yCursor*dt

			dir = Sign(delx)

			da = fabs(delx*delx)*dir*self.mRollGain
			dPitch += da

			aButtonIsDown = True


		# Get direction of travel
		mouseButton1Down = self.mDevButtons[1].mIsPressed
		direction = 1
		if mouseButton1Down :
			aButtonIsDown = True
			direction = -1

		dPos = Vec3(0,0,0)*direction

		# Update camera
		if self.mDriveDir != 0:
			quat = base.camera.getQuat()
			fw = quat.getForward()
			cPos = base.camera.getPos()
			dPos = dPos + fw*self.mMotionGain*self.mDriveDir

		if self.mLateralSlideDir != 0:
			quat = base.camera.getQuat()
			rt = quat.getRight()
			cPos = base.camera.getPos()
			dPos = dPos + rt*self.mMotionGain*self.mLateralSlideDir

		if self.mVerticalSlideDir != 0:
			quat = base.camera.getQuat()
			up = quat.getUp()
			cPos = base.camera.getPos()
			dPos = dPos + up*self.mMotionGain*self.mVerticalSlideDir

		dPos = dPos*self.mMotionGain*dt
		cPos = base.camera.getPos() + dPos
		base.camera.setPos(cPos)

		dHpr = Vec3(dHeading,dPitch,dRoll)*dt
		cHpr = base.camera.getHpr() + dHpr
		base.camera.setHpr(cHpr)

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def getPos(self):
		return base.camera.getPos()

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def getHpr(self):
		return base.camera.getHpr()

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def __str__(self):
		if self.mCameraMode == Camera.DriveMode:
			s = "Drive"
		elif self.mCameraMode == Camera.TrackBallMode:
			s = "TrackBall"
		pos = base.camera.getPos()
		hpr = base.camera.getHpr()
		s1 = " Pos(%7.2f,%7.2f,%7.2f)" % (pos[0],pos[1],pos[2])
		s2 = " Hpr(%7.2f,%7.2f,%7.2f)" % (hpr[0],hpr[1],hpr[2])
		s = s + s1 + s2
		return s

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# Internal utility class
	class __DeviceButton :
		def __init__(self) :
			self.mIsPressed = False
			self.mCoordinates = [0,0]

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# Internal function
	def __SetDeviceButton(self, button, newState):
		md = base.win.getPointer(0)
		cursorX = md.getX()
		cursorY = md.getY()
		self.mDevButtons[button].mIsPressed = newState
		self.mDevButtons[button].mCoordinates = [cursorX,cursorY]

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def motionGainIncrease(self):

		if self.mCameraMode == Camera.DriveMode:
			self.mMotionGain = self.mMotionGain*1.05
			print "Motion Gain : ",self.mMotionGain

		elif self.mCameraMode == Camera.TrackBallMode:
			scale = base.trackball.node().getForwardScale()*1.05
			base.trackball.node().setForwardScale(scale)
			print "Trackball Gain : ",scale

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def motionGainDecrease(self):

		if self.mCameraMode == Camera.DriveMode:
			self.mMotionGain = self.mMotionGain*0.95
			print "Motion Gain : ",self.mMotionGain

		elif self.mCameraMode == Camera.TrackBallMode:
			self.mTrackBallScale = base.trackball.node().getForwardScale()*0.95
			base.trackball.node().setForwardScale(self.mTrackBallScale)
			print "Trackball Gain : ",self.mTrackBallScale

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def turboBoostOn(self):

		self.mMotionGain *= 10.0
		self.mTrackBallScale *= 10.0
		base.trackball.node().setForwardScale(self.mTrackBallScale)

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def turboBoostOff(self):

		self.mMotionGain *= 0.10
		self.mTrackBallScale *= 0.10
		base.trackball.node().setForwardScale(self.mTrackBallScale)

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def driveReverse(self):
		self.mDriveDir = -1

	def driveForward(self):
		self.mDriveDir = 1

	def stopDriving(self):
		self.mDriveDir = 0

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def lateralSlideRight(self):
		self.mLateralSlideDir = 1

	def lateralSlideLeft(self):
		self.mLateralSlideDir = -1

	def stopLateralSliding(self):
		self.mLateralSlideDir = 0

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def verticalSlideDown(self):
		self.mVerticalSlideDir = -1

	def verticalSlideUp(self):
		self.mVerticalSlideDir = 1

	def stopVerticalSliding(self):
		self.mVerticalSlideDir = 0

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def switchToDriveMode(self):
		self.mLastPos = base.camera.getPos()
		self.mLastHpr = base.camera.getHpr()

		self.mCameraMode = Camera.DriveMode
		base.useDrive()
		base.disableMouse()
#		base.camera.setPos(self.mLastPos)
#		base.camera.setHpr(self.mLastHpr)

#		xPos = self.mLastPos*-1.0
#		xHpr = self.mLastHpr*-1.0

#		base.trackball.node().setPos(self.mLastPos)
#		base.trackball.node().setHpr(self.mLastHpr)
#		base.camera.setPos(xPos)
#		base.camera.setHpr(xHpr)

		self.mDriveDir = 0
		self.mLateralSlideDir = 0
		self.mVerticalSlideDir = 0

		control = self.mDriveForwardEvent
		self.accept(control,self.driveForward)
		control = control + '-up'
		self.accept(control,self.stopDriving)

		control = self.mDriveReverseEvent
		self.accept(control,self.driveReverse)
		control = control + '-up'
		self.accept(control,self.stopDriving)

		control = self.mSlideLeftEvent
		self.accept(control,self.lateralSlideLeft)
		control = control + '-up'
		self.accept(control,self.stopLateralSliding)

		control = self.mSlideRightEvent
		self.accept(control,self.lateralSlideRight)
		control = control + '-up'
		self.accept(control,self.stopLateralSliding)

		control = self.mSlideUpEvent
		self.accept(control,self.verticalSlideUp)
		control = control + '-up'
		self.accept(control,self.stopVerticalSliding)

		control = self.mSlideDownEvent
		self.accept(control,self.verticalSlideDown)
		control = control + '-up'
		self.accept(control,self.stopVerticalSliding)

		control = self.mHeadingAngleEvent
		self.accept(control,self.__SetDeviceButton, [0, 1])
		control = control + '-up'
		self.accept(control,self.__SetDeviceButton, [0, 0])

		control = self.mPitchAngleEvent
		self.accept(control,self.__SetDeviceButton, [1, 1])
		control = control + '-up'
		self.accept(control,self.__SetDeviceButton, [1, 0])

		control = self.mRollAngleEvent
		self.accept(control,self.__SetDeviceButton, [2, 1])
		control = control + '-up'
		self.accept(control,self.__SetDeviceButton, [2, 0])

		self.mStatusDisplay.postStatusMessage("Camera Mode : Drive")

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def switchToTrackBallMode(self):
		self.mLastPos = base.camera.getPos()
		self.mLastHpr = base.camera.getHpr()

		self.mCameraMode = Camera.TrackBallMode
		base.useTrackball()
		base.enableMouse()

#		xPos = self.mLastPos*-1.0
#		xHpr = self.mLastHpr*-1.0

#		base.trackball.node().setPos(xPos)
#		base.trackball.node().setHpr(xHpr)

#		base.camera.setPos(self.mLastPos)
#		base.camera.setHpr(self.mLastHpr)

		control = self.mDriveForwardEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		control = self.mDriveReverseEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		control = self.mSlideLeftEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		control = self.mSlideRightEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		control = self.mSlideUpEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		control = self.mSlideDownEvent
		self.ignore(control)
		control = control + '-up'
		self.ignore(control)

		self.mDriveDir = 0
		self.mLateralSlideDir = 0
		self.mVerticalSlideDir = 0

		self.ignore('mouse1')
		self.ignore('mouse1-up')
		self.ignore('mouse2')
		self.ignore('mouse2-up')
		self.ignore('mouse3')
		self.ignore('mouse3-up')

		self.mStatusDisplay.postStatusMessage("Camera Mode : Trackball")

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def cycleMode(self):

		if self.mCameraMode == 0:
			self.switchToDriveMode()
		elif self.mCameraMode == 1:
			self.switchToTrackBallMode()

Code for Main.py

# Main.py

""" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	Description: Test harness for the Camera class.

	$Author: pleopard $

	$Modtime: 10/19/06 8:46p $

	$Revision: 1 $ 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """

# Standard imports

import sys

# Panda imports

from pandac.PandaModules import *
loadPrcFile("CameraTester.prc")
from direct.gui.OnscreenText import OnscreenText 
from direct.showbase import DirectObject
from direct.showbase.DirectObject import DirectObject
import direct.directbase.DirectStart
from direct.task import Task
from direct.showbase import Audio3DManager 

# Local imports

from Camera import *

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

def Max(a,b):
	if a>b:
		return a
	return b

def CreateTextLabel(\
		text,
		color,
		i,
		xStart=-1.3,
		yStart=0.95,
		yOffset = 0.1,
		tFont=None
	):
	if tFont==None:
		return OnscreenText(\
				text = text,
				pos = (xStart, yStart-yOffset*i),
				fg=color,
				mayChange = True,
				align=TextNode.ALeft
			)
	else:
		return OnscreenText(\
				text = text,
				pos = (xStart, yStart-yOffset*i),
				fg=color,
				mayChange = True,
				align=TextNode.ALeft,
				font=tFont
			)

def P3DCreateGridXY(xCellCount=10,yCellCount=10,lineThickness=1,doCenter=True):

	if doCenter==True:
		offset = -0.5
	else:
		offset = 0

	ls = LineSegs()
	ls.setThickness(lineThickness)

	offset = -0.5

	deltaX = 1.0/float(xCellCount)
	deltaY = 1.0/float(yCellCount)

	for i in range(xCellCount):

		x0 = float(i)*deltaX + offset
		x1 = float(i+1)*deltaX + offset

		for j in range(yCellCount):

			y0 = float(j)*deltaY + offset
			y1 = float(j+1)*deltaY + offset

			ls.moveTo(x0,y0,0)
			ls.drawTo(x0,y1,0)
			ls.drawTo(x1,y1,0)
			ls.drawTo(x1,y0,0)
			ls.drawTo(x0,y0,0)

	node = ls.create()
	return NodePath(node)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

class World(DirectObject):

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def timeUpdate(self,task):
		self.mCamera.tickUpdate()
		s = str(self.mCamera)
		self.mCamDisplay.setText(s)
		return Task.cont

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def __init__(self):

		# *** Set program inputs
		worldDims = (1000.0,1000.0,100.0)
		bkgColor = (0.0,0.05,0.1,1.0)
		titleColor = (1.0,1.0,0.0,1.0)
		msgColor = (0.0,1.0,0.0,1.0)
		messageColor = (1,1,0,1)
		windowTitle = 'Test harness for the Camera class.'
		gridColor = (0,0.4,0.4)
		cursorHidden = False
		axesScale = .25
		textXStart = -1.3
		textYStart = 0.95
		textYOffset = 0.1
		verbose = False
		lightLevel = 0.7
		lightPos = (0.0,-10.0,10.0)
		lightHpr = (0.0,-26.0,0.0)
		backgroundMusicFile = "None"

		# *** Setup basics
		wp = WindowProperties()
		wp.setTitle(windowTitle)
		wp.setCursorHidden(cursorHidden)
		base.win.requestProperties(wp)

		# *** Setup text displays
		self.mTitleDisplay = \
			CreateTextLabel(
				windowTitle,
				titleColor,
				1,
				textXStart,
				textYStart,
				textYOffset,
				None
			)
		self.mMessageDisplay = \
			CreateTextLabel(
				'> ',
				msgColor,
				2,
				textXStart,
				textYStart,
				textYOffset,
				None
			)
		self.mCamDisplay = \
			CreateTextLabel(
				'',
				msgColor,
				3,
				textXStart,
				textYStart,
				textYOffset,
				None
			)

		# *** Setup world dimensions
		self.mWorldDims = worldDims
		maxWorldDim = \
			Max(\
				Max(self.mWorldDims[0],self.mWorldDims[1]),
				self.mWorldDims[2]
			)
		base.camLens.setNearFar(0.1,2.5*maxWorldDim)

		# *** Setup lighting
		dlight = DirectionalLight('dlight')
		dlight.setColor(VBase4(lightLevel, lightLevel, lightLevel, 1))
		dlnp = render.attachNewNode(dlight.upcastToPandaNode())
		dlnp.setHpr(lightHpr[0],lightHpr[1],lightHpr[2])
		dlnp.setPos(lightPos[0],lightPos[1],lightPos[2])
		render.setLight(dlnp)
 
		alight = AmbientLight('alight') 
		alight.setColor(VBase4(0.2, 0.2, 0.2, 1)) 
		alnp = render.attachNewNode(alight.upcastToPandaNode()) 

		# *** Setup camera stuff
		self.mCamera = Camera(self,CameraControlDefs())

		# *** Setup scene
		base.setBackgroundColor(\
			bkgColor[0],
			bkgColor[1],
			bkgColor[2],
			bkgColor[3]
		)

		self.mGrid = P3DCreateGridXY(50,50,1,True)
		self.mGrid.setColor(gridColor[0],gridColor[1],gridColor[2])
		self.mGrid.setScale(\
			self.mWorldDims[0],
			self.mWorldDims[1],
			self.mWorldDims[2]
		)
		self.mGrid.setPos(0,0,0)
		self.mGrid.setLightOff()
		self.mGrid.setTwoSided(True)
		self.mGrid.reparentTo(render)
		self.mGridVisible = True

		aScale = axesScale*maxWorldDim
		self.mAxes = loader.loadModel("Axes3D.egg")
		if self.mAxes==None:
			self.mAxes = P3DCreateAxes()
		self.mAxes.setScale(aScale,aScale,aScale)
		self.mAxes.reparentTo(render)
		self.mAxesVisible = True

		# *** Setup events
		self.setupKeyBindings()

		# *** Setup basic audio
		self.mFxMgr = base.sfxManagerList[0]
		self.m3DAudioMgr = Audio3DManager.Audio3DManager(self.mFxMgr, camera)
		self.m3DAudioMgr.setDropOffFactor( 0.7 )
		if backgroundMusicFile=="None":
			self.mBkgMusic = None
		else:
			self.mBkgMusic = self.mFxMgr.getSound("T2-Ice.mp3")
			self.mBkgMusic.setLoop(True)
			self.mBkgMusic.setVolume(1.0)

		# *** Done, setup time update task
		taskMgr.add(self.timeUpdate,'TimeUpdate')

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def snapShot(self):
		base.screenshot('Snap')
		self.postStatusMessage("Snapshot Saved")

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def getBoundingVolume(self):
		return BoundingVolume(\
				0,0,0,\
				self.mWorldDims[0],\
				self.mWorldDims[1],\
				self.mWorldDims[2]
			)

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def getElevation(self,x,y):
		return 0;

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def postStatusMessage(self,msg):
		print msg
		self.mMessageDisplay.setText("> "+msg)
		taskMgr.doMethodLater(
			1.2,
			self.clearStatusDisplay,
			'',extraArgs =[]
		)

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	# 
	def clearStatusDisplay(self):
		self.mMessageDisplay.setText("> ")

	# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	#
	# Method name : setupKeyBindings
	#
	# Description:
	#
	#	Load and register key bindings
	#
	# Input(s):
	#
	#	None
	#
	# Output(s):
	#
	#	None
	#
	def setupKeyBindings(self) :
		self.accept('p',self.snapShot)
		self.accept('escape',sys.exit)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 

world = World()

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Run the program

run()

N E Ideas?

I wasn’t able to run your code, because of missing methods. But here is a wild guess:
https://discourse.panda3d.org/viewtopic.php?p=5929#5929

Oh … I was disabling mouse in drive mode and enabling it in trackball mode. I suppose I need to disable it in trackball mode, trap the events, and directly manipulate the trackball if I want to achieve this.

Thanks,
Paul