Another Camera Controller, Orbit style

This is a script I created to use as a default camera control, because I find the built in camera control really awkward.

The script creates a blank NodePath to use as an anchor and reparents the camera to it. The script has 3 inputs to control the camera. They are:

The left mouse button causes the anchor to move forward, taking the camera with it.

Holding the right mouse button and moving the mouse causes the anchor to rotate left, right, up, and down, which makes the camera orbit around the anchor’s location.

Holding the middle mouse button and moving the mouse up or down causes the camera to move closer to, or away from, the anchor.

There are 4 variables to help adjust the controls to meet the needs of a given usage. Distances are all in panda units. The variables are:

self.initZoom - the distance from the anchor that the camera will start at.

self.zoomInLimit - the minimum distance the camera can be from the anchor. If this value is negative, the camera can move past the anchor when zooming.

self.zoomOutLimit - the maximum distance the camera can be from the anchor.

self.moveSpeed - the number of units the anchor will move, per frame, when the left mouse button is held.

To use this camera controller, just import and instantiate the class.

1 Like
from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import *

class CameraController(DirectObject):
	def	__init__(self):
		base.disableMouse()
		
		self.setupVars()
		self.setupCamera()
		self.setupInput()
		self.setupTasks()
		
	def setupVars(self):
		self.initZoom = 5			#Camera's initial distance from anchor
		self.zoomInLimit = 1		#Camera's minimum distance from anchor
		self.zoomOutLimit = 1000	#Camera's maximum distance from anchor
		self.moveSpeed = .5			#Rate of movement for the anchor
		self.zoom = None
		self.orbit = None
		self.move = None
	
	def setupCamera(self):
		self.camAnchor = render.attachNewNode("Cam Anchor")
		base.camera.reparentTo(self.camAnchor)
		base.camera.setPos(0, -self.initZoom, 0)
		base.camera.lookAt(self.camAnchor)
		
	def setupInput(self):
		self.accept("mouse1", self.setMove, [True])
		self.accept("mouse1-up", self.setMove, [False])
		self.accept("mouse2", self.setZoom, [True])
		self.accept("mouse2-up", self.setZoom, [False])
		self.accept("mouse3", self.setOrbit, [True])
		self.accept("mouse3-up", self.setOrbit, [False])
	
	def setupTasks(self):
		taskMgr.add(self.cameraOrbit, "Camera Orbit")
		taskMgr.add(self.cameraZoom, "Camera Zoom")
		taskMgr.add(self.cameraMove, "Camera Move")
		
	def setOrbit(self, orbit):
		if(orbit == True):
			props = base.win.getProperties()
			winX = props.getXSize()
			winY = props.getYSize()
			if base.mouseWatcherNode.hasMouse():
				mX = base.mouseWatcherNode.getMouseX()
				mY = base.mouseWatcherNode.getMouseY()
				mPX = winX * ((mX+1)/2)
				mPY = winY * ((-mY+1)/2)
			self.orbit = [[mX, mY], [mPX, mPY]]
		else:
			self.orbit = None
	
	def cameraOrbit(self, task):
		if(self.orbit != None):
			if base.mouseWatcherNode.hasMouse():
				
				mpos = base.mouseWatcherNode.getMouse()
				
				base.win.movePointer(0, int(self.orbit[1][0]), int(self.orbit[1][1]))
				
				deltaH = 90 * (mpos[0] - self.orbit[0][0])
				deltaP = 90 * (mpos[1] - self.orbit[0][1])
				
				limit = .5
				
				if(-limit < deltaH and deltaH < limit):
					deltaH = 0
				elif(deltaH > 0):
					deltaH - limit
				elif(deltaH < 0):
					deltaH + limit
					
				if(-limit < deltaP and deltaP < limit):
					deltaP = 0
				elif(deltaP > 0):
					deltaP - limit
				elif(deltaP < 0):
					deltaP + limit

				newH = (self.camAnchor.getH() + -deltaH)
				newP = (self.camAnchor.getP() + deltaP)
				if(newP < -90): newP = -90
				if(newP > 90): newP = 90
			
				self.camAnchor.setHpr(newH, newP, 0)				
			
		return task.cont
	
	def setZoom(self, zoom):
		if(zoom == True):
			props = base.win.getProperties()
			winX = props.getXSize()
			winY = props.getYSize()
			if base.mouseWatcherNode.hasMouse():
				mX = base.mouseWatcherNode.getMouseX()
				mY = base.mouseWatcherNode.getMouseY()
				mPX = winX * ((mX+1)/2)
				mPY = winY * ((-mY+1)/2)
			self.zoom = [[mX, mY], [mPX, mPY]]
		else:
			self.zoom = None
		
	def cameraZoom(self, task):
		if(self.zoom != None):
			if base.mouseWatcherNode.hasMouse():
				
				mpos = base.mouseWatcherNode.getMouse()
				
				base.win.movePointer(0, int(self.zoom[1][0]), int(self.zoom[1][1]))
				
				deltaY = (mpos[1] - self.zoom[0][1]) * base.camera.getY()
				
				limit = .5
				
				if(-limit < deltaY and deltaY < limit):
					deltaY = 0
				elif(deltaY > 0):
					deltaY - limit
				elif(deltaY < 0):
					deltaY + limit

				newY = (base.camera.getY() - deltaY)
				if(newY > -self.zoomInLimit): newY = -self.zoomInLimit
				if(newY < -self.zoomOutLimit): newY = -self.zoomOutLimit
			
				base.camera.setY(newY)				
			
		return task.cont
		
	def setMove(self, value):
		self.move = value
		
	def cameraMove(self, task):
		if(self.move == True):
			self.camAnchor.setY(self.camAnchor, self.moveSpeed)
			
		return task.cont
1 Like