Mouse look lib -- fpp in 2 versions, orbit, pan

I was extracting this out of my game and my editor into a separate module – something I’ve been wanting to do for a long time now, yet could never get to it – and thought people could find it useful. There are many snippets like this laying around here, but I’m not sure any of them has those three things (fpp, orbit and pan) together and easy to use, so there you go.

The movement is without the character controller stuff, just something like “fly” or “noclip” mode you can find in games, but you don’t have to use it – it’s there for stuff like editors or aforementioned game modes or the likes.

EDIT: A little bug fix.
EDIT2: Some parts of the code were referring to base.win instead of self.targetWin

from panda3d.core import Vec3, Quat, Point3, WindowProperties

class MouseLook(object):
	
	MLMFPPNoCenter = 0
	MLMOrbit = 1
	MLMPan = 2
	MLMFPPCenter = 3
	
	def __init__(self, targetCam = None, targetWin = None):
		self.setTargetCamera(targetCam)
		self.setTargetWin(targetWin)
		
		self.orbitCenter = Point3(0, 0, 0)
		
		self.mouseLookMode = self.MLMFPPCenter
		self.zoomOrtho = False
		
		self.limitH = {
			"left": None,
			"right": None,
			"relative": render,
		}
		
		self.movementSpeed = 128.0
		self.wheelSpeed = 8.0
		self.mouseLookSpeed = [0.1, 0.1]
		
		self.camMove = dict(
			forward = False,
			backward = False,
			strafeLeft = False,
			strafeRight = False,
			down = False,
			up = False,
		)
	
	def zoom(self, direction):
		step = 64
		top = 32768
		bottom = 64
		
		fsH, size = self.lens.getFilmSize()
		size -= direction * step
		
		if size < bottom:
			size = bottom
		
		for vp in self.editorBase.viewports[1:4]:
			vp.zoomLocal(size)
	
	def zoomLocal(self, size):
		fsH = size * self.aspectRatio
		fsV = size
		self.lens.setFilmSize(fsH, fsV)
	
	
	
	def enable(self, ownsTask = True):
		self.prevX = self.targetWin.getPointer(0).getX()
		self.prevY = self.targetWin.getPointer(0).getY()
		if ownsTask:
			taskMgr.add(self.update, "UpdateMouseLook")
	
	def disable(self):
		taskMgr.remove("UpdateMouseLook")
	
	def clearMovement(self):
		self.camMove = dict(
			forward = False,
			backward = False,
			strafeLeft = False,
			strafeRight = False,
			down = False,
			up = False,
			)
	
	def setLimitH(self, left, right, relative = None):
		if relative is None:
			relative = render
		self.limitH = {
			"left": left,
			"right": right,
			"relative": relative
		}
	
	def enableMovement(self):
		base.accept("w", self.setCamMove, extraArgs = ["forward", True])
		base.accept("w-up", self.setCamMove, extraArgs = ["forward", False])
		base.accept("s", self.setCamMove, extraArgs = ["backward", True])
		base.accept("s-up", self.setCamMove, extraArgs = ["backward", False])
		base.accept("a", self.setCamMove, extraArgs = ["strafeLeft", True])
		base.accept("a-up", self.setCamMove, extraArgs = ["strafeLeft", False])
		base.accept("d", self.setCamMove, extraArgs = ["strafeRight", True])
		base.accept("d-up", self.setCamMove, extraArgs = ["strafeRight", False])
		
		base.accept("control", self.setCamMove, extraArgs = ["down", True])
		base.accept("control-up", self.setCamMove, extraArgs = ["down", False])
		base.accept("space", self.setCamMove, extraArgs = ["up", True])
		base.accept("space-up", self.setCamMove, extraArgs = ["up", False])
		
		base.accept("wheel_up", self.moveCamera, extraArgs = [Vec3(0, 1, 0)])
		base.accept("wheel_down", self.moveCamera, extraArgs = [Vec3(0, -1, 0)])
	
	def setCamMove(self, key, val):
		self.camMove[key] = val
	
	def update(self, task = None):
		mouse = self.targetWin.getPointer(0)
		x = mouse.getX()
		y = mouse.getY()
		
		deltaX = (x - self.prevX) * self.mouseLookSpeed[0]
		deltaY = (y - self.prevY) * self.mouseLookSpeed[1]
		
		self.prevX = x
		self.prevY = y
		
		if self.mouseLookMode == self.MLMFPPNoCenter:
			print "update fpp no center"
			self.updateFPPNoCenter(deltaX, deltaY)
		elif self.mouseLookMode == self.MLMOrbit:
			print "update orbit"
			self.updateOrbit(deltaX, deltaY)
		elif self.mouseLookMode == self.MLMPan:
			print "update pan"
			self.updatePan(deltaX, deltaY)
		if self.mouseLookMode == self.MLMFPPCenter:
			print "update fpp center"
			self.updateFPPCenter()
		
		if self.limitH["left"] is not None:
			rel = self.limitH["relative"]
			h = self.targetCamera.getH(rel)
			
			if h < self.limitH["left"]:
				h = self.limitH["left"]
			elif h > self.limitH["right"]:
				h = self.limitH["right"]
			
			self.targetCamera.setH(rel, h)
		
		linVel = Vec3(0,0,0)
		if self.camMove["forward"]: linVel[1] = self.movementSpeed
		if self.camMove["backward"]: linVel[1] = -self.movementSpeed
		if self.camMove["strafeLeft"]: linVel[0] = -self.movementSpeed
		if self.camMove["strafeRight"]: linVel[0] = self.movementSpeed
		if self.camMove["up"]: linVel[2] = self.movementSpeed
		if self.camMove["down"]: linVel[2] = -self.movementSpeed
		
		linVel *= globalClock.getDt()
		self.moveCamera(linVel)
		
		if task is not None:
			return task.cont
	
	def moveCamera(self, vector):
		self.targetCamera.setPos(self.targetCamera, vector * self.wheelSpeed)
	
	def rotateAround(self, node, point, axis, angle, relative):
		quat = Quat()
		quat.setFromAxisAngle(angle, render.getRelativeVector(relative, axis))
		
		relativePos = node.getPos(render) - point
		relativePosRotated = quat.xform(relativePos)
		absolutePosRotated = relativePosRotated + point
		
		node.setPos(render, absolutePosRotated)
		node.setQuat(render, node.getQuat(render) * quat)
	
	def setTargetCamera(self, cam):
		if cam is None:
			self.targetCamera = base.camera
		else:
			self.targetCamera = cam
	
	def setTargetWin(self, win):
		if win is None:
			self.targetWin = base.win
		else:
			self.targetWin = win
	
	def setMouseModeRelative(self, state):
		props = WindowProperties()
		if not state:
			props.setMouseMode(WindowProperties.MAbsolute)
		else:
			props.setMouseMode(WindowProperties.MRelative)
		self.targetWin.requestProperties(props)
	
	def setCursorHidden(self, state):
		props = WindowProperties()
		props.setCursorHidden(state)
		self.targetWin.requestProperties(props)
	
	def updateFPPNoCenter(self, deltaX, deltaY):
		p = self.targetCamera.getP() - deltaY
		h = self.targetCamera.getH() - deltaX
		if abs(p) > 90:
			p = 90 * cmp(p, 0)
		self.targetCamera.setP(p)
		self.targetCamera.setH(h)
	
	def updateFPPCenter(self):
		winSizeX = self.targetWin.getXSize()/2
		winSizeY = self.targetWin.getYSize()/2
		
		mouse = self.targetWin.getPointer(0)
		
		x = mouse.getX() - winSizeX
		y = mouse.getY() - winSizeY
		
		h = x * (self.mouseLookSpeed[0])
		h = self.targetCamera.getH() - h
		self.targetCamera.setH(h)
		
		p = y * (self.mouseLookSpeed[1])
		p = self.targetCamera.getP() - p
		if p < 90.0 and p > -90.0:
			self.targetCamera.setP(p)
		
		self.targetWin.movePointer(0, winSizeX, winSizeY)
	
	def updateOrbit(self, deltaX, deltaY):
		self.rotateAround(self.targetCamera, self.orbitCenter, Vec3(0, 0, 1), -deltaX, render)
		self.rotateAround(self.targetCamera, self.orbitCenter, Vec3(1, 0, 0), -deltaY, self.targetCamera)
	
	def updatePan(self, deltaX, deltaY):
		vector = Vec3(-deltaX, 0, deltaY) * 1/globalClock.getDt() * 0.01
		self.moveCamera(vector)

if __name__ == "__main__":
   from direct.showbase.ShowBase import ShowBase
   import sys
   
   ShowBase()
   
   base.camLens.setFov(90.0)
   base.disableMouse()
   
   base.setFrameRateMeter(True)
   
   globalClock.setMode(globalClock.MLimited)
   globalClock.setFrameRate(120)
   
   smiley = loader.loadModel("smiley")
   smiley.reparentTo(render)
   smiley.setScale(128)
   
   base.accept("escape", sys.exit)
   
   base.camera.setY(-1024)
   
   m = MouseLook()
   m.enable()
   m.enableMovement()
   m.setMouseModeRelative(True)
   m.setCursorHidden(True)
   
   mode = m.mouseLookMode
   def cycle():
      global mode
      mode += 1
      if mode > 3:
         mode = 0
      print mode
      m.mouseLookMode = mode
   
   base.accept("f1", cycle)
   
   run() 

Thanks. This is a nice base for developing own camera behaviors.