def __init__(self, *args, **kwargs): NodePath.__init__(self, *args, **kwargs) DirectObject.__init__(self) self.scale_signal = Signal() self.reset()
class Camera(NodePath, DirectObject): """ The world map camera for Orbital Industry. Replaces base.camera. """ CAMERA_DISTANCE = 1000 SCALE_THRESHOLD = 1000.0 def __init__(self, *args, **kwargs): NodePath.__init__(self, *args, **kwargs) DirectObject.__init__(self) self.scale_signal = Signal() self.reset() def display(self, display=True): """ Displays/hides the camera. @type display: boolean @param display: True to display the camera. """ if display: self.reparentTo(render) self.listen(True) else: self.detachNode() self.listen(False) def listen(self, listen=True): """ Causes the camera to associate/disassociate with user input. @type listen: boolean @param listen: True to active camera controls. """ if listen: self.addTask(self.updateTask, 'camera.updateTask') self.accept("mouse1", self.startPan) self.accept("mouse1-up", self.stopPan) self.accept("mouse3", self.startZoom) self.accept("mouse3-up", self.stopZoom) else: self.removeAllTasks() self.ignoreAll() def reset(self): """ Reset the camera to factory defaults. """ # State variables self.leftDown = False self.rightDown = False self.panning = False self.zooming = False self.scale = 0.00003 self.scale_level = 0 self.rotation = 0.0 self.inclination = omath.PI / 4.0 self.mouseX = 0.0 self.mouseY = 0.0 self._updatePosition(0, 0, True) def startPan(self): """ Set panning variable. """ self.panning = True self._updateMouse() def stopPan(self): """ Unset panning variable. """ self.panning = False self._updateMouse() def startZoom(self): """ Set zooming variable. """ self.zooming = True self._updateMouse() def stopZoom(self): """ Unset zooming variable. """ self.zooming = False self._updateMouse() def _getMouse(self): """ Return the current positoin of the mouse. @rtype: (number, number) @return: Tuple with mouse x/y coordinates. Returns (None, None) if mouse is off-screen. """ if base.mouseWatcherNode.hasMouse(): return (base.mouseWatcherNode.getMouseX(), base.mouseWatcherNode.getMouseY()) else: return (None, None) def _updateMouse(self): """ Updates the self.mouseX and self.mouseY "last known position" coordinates. """ self.mouseX, self.mouseY = self._getMouse() def _updatePosition(self, mouseX, mouseY, force=False): """ Update the position of the camera from mouseX and mouseY. @type mouseX: number @param mouseX: X-delta of the mouse. @type mouseY: number @param mouseY: Y-delta of the mouse. @type force: boolean @param force: True to force camera update regardless of mouse state. """ # Calculate pan if force or self.panning: self.rotation = self.rotation + (self.mouseX - mouseX) * 5 self.rotation %= omath.TWOPI self.inclination = self.inclination + (self.mouseY - mouseY) * 2 self.inclination = min(omath.PI/2.0, max(-omath.PI/2.0, self.inclination)) # Calculate inclination limiter (pulls in horizontal displacement # as vertical displacement increases). This confines our camera to # a spherical path. limiter = math.cos(self.inclination) cosRot = math.cos(self.rotation) sinRot = math.sin(self.rotation) self.setPos(cosRot * limiter * Camera.CAMERA_DISTANCE, sinRot * limiter * Camera.CAMERA_DISTANCE, math.sin(self.inclination) * Camera.CAMERA_DISTANCE) # Camera "up" vector is just the camera vector inclined by 90 deg upInclination = self.inclination + (omath.PI / 2.0) upLimiter = math.cos(upInclination) self.lookAt((0,0,0), Vec3(cosRot * upLimiter, sinRot * upLimiter, math.sin(upInclination))) # Calculate zoom if force or self.zooming: self.scale *= ((mouseY - self.mouseY) + 1) ** 4 prev_level = self.scale_level while self.scale > Camera.SCALE_THRESHOLD: self.scale_level += 1 self.scale /= Camera.SCALE_THRESHOLD while self.scale < 1: self.scale_level -= 1 self.scale *= Camera.SCALE_THRESHOLD # emit signals if we changed scale_level if self.scale_level != prev_level: multiplier = 1 if 0 > self.scale_level: for i in range(-self.scale_level): multiplier /= Camera.SCALE_THRESHOLD elif 0 < scale_level: for i in range(self.scale_level): multiplier *= Camera.SCALE_THRESHOLD self.scale_signal.emit(self.scale_level, multiplier) def updateTask(self, task): """ Gets the current mouse offsets and updates the camera accordingly. """ mouseX,mouseY = self._getMouse() # If the mouse is just reentering the frame, overwrite the None values # with the current ones. This will cause the first mousedelta to be # zero upon entering the frame. if (self.mouseX == None or self.mouseY == None): self.mouseX = mouseX self.mouseY = mouseY self._updatePosition(mouseX, mouseY) # Store away current mouse values as last known values self.mouseX = mouseX self.mouseY = mouseY return task.cont