import math
from panda3d.core import BitMask32, Mat4, NodePath, Point3, VBase3, Vec3, Vec4, rad2Deg
from direct.showbase.DirectObject import DirectObject
from direct.showbase import ShowBaseGlobal
from .DirectUtil import CLAMP, useDirectRenderStyle
from .DirectGeometry import getCrankAngle, getScreenXY
from . import DirectGlobals as DG
from .DirectSelection import SelectionRay
from direct.interval.IntervalGlobal import Sequence, Func
from direct.directnotify import DirectNotifyGlobal
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
CAM_MOVE_DURATION = 1.2
COA_MARKER_SF = 0.0075
Y_AXIS = Vec3(0, 1, 0)
[docs]class DirectCameraControl(DirectObject):
    notify = DirectNotifyGlobal.directNotify.newCategory('DirectCameraControl')
[docs]    def __init__(self):
        # Create the grid
        self.startT = 0.0
        self.startF = 0
        self.orthoViewRoll = 0.0
        self.lastView = 0
        self.coa = Point3(0, 100, 0)
        self.coaMarker = ShowBaseGlobal.loader.loadModel('models/misc/sphere')
        self.coaMarker.setName('DirectCameraCOAMarker')
        self.coaMarker.setTransparency(1)
        self.coaMarker.setColor(1, 0, 0, 0)
        self.coaMarker.setPos(0, 100, 0)
        useDirectRenderStyle(self.coaMarker)
        self.coaMarkerPos = Point3(0)
        self.coaMarkerColorIval = None
        self.fLockCOA = 0
        self.nullHitPointCount = 0
        self.cqEntries = []
        self.coaMarkerRef = ShowBaseGlobal.direct.group.attachNewNode('coaMarkerRef')
        self.camManipRef = ShowBaseGlobal.direct.group.attachNewNode('camManipRef')
        self.switchDirBelowZero = True
        self.manipulateCameraTask = None
        self.manipulateCameraInterval = None
        t = CAM_MOVE_DURATION
        self.actionEvents = [
            ['DIRECT-mouse1', self.mouseRotateStart],
            ['DIRECT-mouse1Up', self.mouseDollyStop],
            ['DIRECT-mouse2', self.mouseFlyStart],
            ['DIRECT-mouse2Up', self.mouseFlyStop],
            ['DIRECT-mouse3', self.mouseDollyStart],
            ['DIRECT-mouse3Up', self.mouseDollyStop],
        ]
        # [gjeon] moved all of the hotkeys to single place for easy remapping
##         self.keyEvents = [
##             ['c', self.centerCamIn, 0.5],
##             ['f', self.fitOnWidget],                  # Note: This function doesn't work as intended
##             ['h', self.homeCam],
##             ['shift-v', self.toggleMarkerVis],
##             ['m', self.moveToFit],                      # Note: This function doesn't work as intended; the object dissappears and screen flashes
##             ['n', self.pickNextCOA],
##             ['u', self.orbitUprightCam],
##             ['shift-u', self.uprightCam],
##             [repr(1), self.spawnMoveToView, 1],
##             [repr(2), self.spawnMoveToView, 2],
##             [repr(3), self.spawnMoveToView, 3],
##             [repr(4), self.spawnMoveToView, 4],
##             [repr(5), self.spawnMoveToView, 5],
##             [repr(6), self.spawnMoveToView, 6],
##             [repr(7), self.spawnMoveToView, 7],
##             [repr(8), self.spawnMoveToView, 8],
##             ['9', self.swingCamAboutWidget, -90.0, t],
##             ['0', self.swingCamAboutWidget,  90.0, t],
##             ['`', self.removeManipulateCameraTask],
##             ['=', self.zoomCam, 0.5, t],
##             ['+', self.zoomCam, 0.5, t],
##             ['-', self.zoomCam, -2.0, t],
##             ['_', self.zoomCam, -2.0, t],
##             ]
        self.keyEvents = [
            ['DIRECT-centerCamIn', self.centerCamIn, 0.5],
            ['DIRECT-fitOnWidget', self.fitOnWidget],                  # Note: This function doesn't work as intended
            ['DIRECT-homeCam', self.homeCam],
            ['DIRECT-toggleMarkerVis', self.toggleMarkerVis],
            ['DIRECT-moveToFit', self.moveToFit],                      # Note: This function doesn't work as intended; the object dissappears and screen flashes
            ['DIRECT-pickNextCOA', self.pickNextCOA],
            ['DIRECT-orbitUprightCam', self.orbitUprightCam],
            ['DIRECT-uprightCam', self.uprightCam],
            ['DIRECT-spwanMoveToView-1', self.spawnMoveToView, 1],
            ['DIRECT-spwanMoveToView-2', self.spawnMoveToView, 2],
            ['DIRECT-spwanMoveToView-3', self.spawnMoveToView, 3],
            ['DIRECT-spwanMoveToView-4', self.spawnMoveToView, 4],
            ['DIRECT-spwanMoveToView-5', self.spawnMoveToView, 5],
            ['DIRECT-spwanMoveToView-6', self.spawnMoveToView, 6],
            ['DIRECT-spwanMoveToView-7', self.spawnMoveToView, 7],
            ['DIRECT-spwanMoveToView-8', self.spawnMoveToView, 8],
            ['DIRECT-swingCamAboutWidget-0', self.swingCamAboutWidget, -90.0, t],
            ['DIRECT-swingCamAboutWidget-1', self.swingCamAboutWidget,  90.0, t],
            ['DIRECT-removeManipulateCameraTask', self.removeManipulateCameraTask],
            ['DIRECT-zoomInCam', self.zoomCam, 0.5, t],
            ['DIRECT-zoomOutCam', self.zoomCam, -2.0, t],
        ]
        # set this to true to prevent the camera from rolling
        self.lockRoll = False
        # NIK - flag to determine whether to use maya camera controls
        self.useMayaCamControls = 0
        self.altDown = 0
        self.perspCollPlane = None # [gjeon] used for new LE
        self.perspCollPlane2 = None # [gjeon] used for new LE 
[docs]    def toggleMarkerVis(self):
        if self.coaMarker.isHidden():
            self.coaMarker.show()
        else:
            self.coaMarker.hide() 
[docs]    def mouseRotateStart(self, modifiers):
        if self.useMayaCamControls and modifiers == 4:          # alt is pressed - use maya controls
            # base.direct.pushUndo([base.direct.camera])        # Wasteful use of undo
            self.spawnMouseRotateTask() 
[docs]    def mouseDollyStart(self, modifiers):
        if self.useMayaCamControls and modifiers == 4: # alt is pressed - use maya controls
            # Hide the marker for this kind of motion
            self.coaMarker.hide()
            # Record time of start of mouse interaction
            base = ShowBaseGlobal.base
            self.startT = base.clock.getFrameTime()
            self.startF = base.clock.getFrameCount()
            # If the cam is orthogonal, spawn differentTask
            direct = ShowBaseGlobal.direct
            if hasattr(direct, "manipulationControl") and \
               
direct.manipulationControl.fMultiView and \
               
direct.camera.getName() != 'persp':
                self.spawnOrthoZoom()
            else:
                # Start manipulation
                self.spawnHPanYZoom() 
    def __stopManipulateCamera(self):
        if self.manipulateCameraTask:
            taskMgr.remove(self.manipulateCameraTask)
            self.manipulateCameraTask = None
        if self.manipulateCameraInterval:
            self.manipulateCameraInterval.finish()
            self.manipulateCameraInterval = None
    def __startManipulateCamera(self, func = None, task = None, ival = None):
        self.__stopManipulateCamera()
        if func:
            assert task is None
            task = Task.Task(func)
        if task:
            self.manipulateCameraTask = taskMgr.add(task, 'manipulateCamera')
        if ival:
            ival.start()
            self.manipulateCameraInterval = ival
[docs]    def mouseDollyStop(self):
        self.__stopManipulateCamera() 
[docs]    def mouseFlyStart(self, modifiers):
        # Record undo point
        base = ShowBaseGlobal.base
        direct = ShowBaseGlobal.direct
        #direct.pushUndo([direct.camera])            # Wasteful use of undo
        if self.useMayaCamControls and modifiers == 4:          # alt is down, use maya controls
            # Hide the marker for this kind of motion
            self.coaMarker.hide()
            # Record time of start of mouse interaction
            self.startT = base.clock.getFrameTime()
            self.startF = base.clock.getFrameCount()
            # Start manipulation
            # If the cam is orthogonal, spawn differentTask
            if hasattr(direct, "manipulationControl") and \
               
direct.manipulationControl.fMultiView and \
               
direct.camera.getName() != 'persp':
                self.spawnOrthoTranslate()
            else:
                self.spawnXZTranslate()
            self.altDown = 1
        elif not self.useMayaCamControls:
            # Where are we in the display region?
            if abs(direct.dr.mouseX) < 0.9 and abs(direct.dr.mouseY) < 0.9:
                # MOUSE IS IN CENTRAL REGION
                # Hide the marker for this kind of motion
                self.coaMarker.hide()
                # Record time of start of mouse interaction
                self.startT = base.clock.getFrameTime()
                self.startF = base.clock.getFrameCount()
                # Start manipulation
                self.spawnXZTranslateOrHPanYZoom()
                # END MOUSE IN CENTRAL REGION
            elif abs(direct.dr.mouseX) > 0.9 and abs(direct.dr.mouseY) > 0.9:
                # Mouse is in corners, spawn roll task
                self.spawnMouseRollTask()
            else:
                # Mouse is in outer frame, spawn mouseRotateTask
                self.spawnMouseRotateTask()
        if not modifiers == 4:
            self.altDown = 0 
[docs]    def mouseFlyStop(self):
        self.__stopManipulateCamera()
        base = ShowBaseGlobal.base
        stopT = base.clock.getFrameTime()
        deltaT = stopT - self.startT
        stopF = base.clock.getFrameCount()
        deltaF = stopF - self.startF
        ## No reason this shouldn't work with Maya cam on
        # if not self.useMayaCamControls and (deltaT <= 0.25) or (deltaF <= 1):
        # Do this when not trying to manipulate camera
        direct = ShowBaseGlobal.direct
        if not self.altDown and len(direct.selected.getSelectedAsList()) == 0:
            # Check for a hit point based on
            # current mouse position
            # Allow intersection with unpickable objects
            # And then spawn task to determine mouse mode
            # Don't intersect with hidden or backfacing objects
            skipFlags = DG.SKIP_HIDDEN | DG.SKIP_BACKFACE
            # Skip camera (and its children), unless control key is pressed
            skipFlags |= DG.SKIP_CAMERA * (1 - base.getControl())
            self.computeCOA(direct.iRay.pickGeom(skipFlags = skipFlags))
            # Record reference point
            self.coaMarkerRef.setPosHprScale(base.cam, 0, 0, 0, 0, 0, 0, 1, 1, 1)
            # Record entries
            self.cqEntries = []
            for i in range(direct.iRay.getNumEntries()):
                self.cqEntries.append(direct.iRay.getEntry(i))
        # Show the marker
        self.coaMarker.show()
        # Resize it
        self.updateCoaMarkerSize() 
[docs]    def mouseFlyStartTopWin(self):
        print("Moving mouse 2 in new window") 
        #altIsDown = base.getAlt()
        #if altIsDown:
        #    print "Alt is down"
[docs]    def mouseFlyStopTopWin(self):
        print("Stopping mouse 2 in new window") 
[docs]    def spawnXZTranslateOrHPanYZoom(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn the new task
        t = Task.Task(self.XZTranslateOrHPanYZoomTask)
        # For HPanYZoom
        t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
        self.__startManipulateCamera(task = t) 
[docs]    def spawnXZTranslateOrHPPan(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        self.__startManipulateCamera(func = self.XZTranslateOrHPPanTask) 
[docs]    def spawnXZTranslate(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        self.__startManipulateCamera(func = self.XZTranslateTask) 
[docs]    def spawnOrthoTranslate(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        self.__startManipulateCamera(func = self.OrthoTranslateTask) 
[docs]    def spawnHPanYZoom(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        t = Task.Task(self.HPanYZoomTask)
        t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
        self.__startManipulateCamera(task = t) 
[docs]    def spawnOrthoZoom(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        t = Task.Task(self.OrthoZoomTask)
        self.__startManipulateCamera(task = t) 
[docs]    def spawnHPPan(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Spawn new task
        self.__startManipulateCamera(func = self.HPPanTask) 
[docs]    def XZTranslateOrHPanYZoomTask(self, state):
        if ShowBaseGlobal.direct.fShift:
            return self.XZTranslateTask(state)
        else:
            return self.HPanYZoomTask(state) 
[docs]    def XZTranslateOrHPPanTask(self, state):
        if ShowBaseGlobal.direct.fShift:
            # Panning action
            return self.HPPanTask(state)
        else:
            # Translation action
            return self.XZTranslateTask(state) 
[docs]    def XZTranslateTask(self, state):
        direct = ShowBaseGlobal.direct
        coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
        xlateSF = coaDist / direct.dr.near
        direct.camera.setPos(direct.camera,
                             (-0.5 * direct.dr.mouseDeltaX *
                              direct.dr.nearWidth *
                              xlateSF),
                             0.0,
                             (-0.5 * direct.dr.mouseDeltaY *
                              direct.dr.nearHeight *
                              xlateSF))
        return Task.cont 
[docs]    def OrthoTranslateTask(self, state):
        # create ray from the camera to detect 3d position
        direct = ShowBaseGlobal.direct
        iRay = SelectionRay(direct.camera)
        iRay.collider.setFromLens(direct.camNode, direct.dr.mouseX, direct.dr.mouseY)
        #iRay.collideWithBitMask(1)
        iRay.collideWithBitMask(BitMask32.bit(21))
        iRay.ct.traverse(direct.grid)
        entry = iRay.getEntry(0)
        hitPt = entry.getSurfacePoint(entry.getFromNodePath())
        iRay.collisionNodePath.removeNode()
        del iRay
        if hasattr(state, 'prevPt'):
            direct.camera.setPos(direct.camera, (state.prevPt - hitPt))
        state.prevPt = hitPt
        return Task.cont 
[docs]    def HPanYZoomTask(self, state):
        # If the cam is orthogonal, don't rotate or zoom.
        direct = ShowBaseGlobal.direct
        if (hasattr(direct.cam.node(), "getLens") and
            direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
            return
        if direct.fControl:
            moveDir = Vec3(self.coaMarker.getPos(direct.camera))
            # If marker is behind camera invert vector
            if moveDir[1] < 0.0:
                moveDir.assign(moveDir * -1)
            moveDir.normalize()
        else:
            moveDir = Vec3(Y_AXIS)
        if self.useMayaCamControls: # use maya controls
            moveDir.assign(moveDir * ((direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY)
                                    * state.zoomSF))
            hVal = 0.0
        else:
            moveDir.assign(moveDir * (-1.0 * direct.dr.mouseDeltaY *
                                        state.zoomSF))
            if direct.dr.mouseDeltaY > 0.0:
                moveDir.setY(moveDir[1] * 1.0)
            hVal = 0.5 * direct.dr.mouseDeltaX * direct.dr.fovH
        direct.camera.setPosHpr(direct.camera,
                                moveDir[0],
                                moveDir[1],
                                moveDir[2],
                                hVal,
                                0.0, 0.0)
        if self.lockRoll:
            # flatten roll
            direct.camera.setR(0)
        return Task.cont 
[docs]    def OrthoZoomTask(self, state):
        direct = ShowBaseGlobal.direct
        filmSize = direct.camNode.getLens().getFilmSize()
        factor = (direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY) * 0.1
        x = direct.dr.getWidth()
        y = direct.dr.getHeight()
        direct.dr.orthoFactor -= factor
        if direct.dr.orthoFactor < 0:
            direct.dr.orthoFactor = 0.0001
        direct.dr.updateFilmSize(x, y)
        return Task.cont 
[docs]    def HPPanTask(self, state):
        direct = ShowBaseGlobal.direct
        direct.camera.setHpr(direct.camera,
                             (0.5 * direct.dr.mouseDeltaX *
                              direct.dr.fovH),
                             (-0.5 * direct.dr.mouseDeltaY *
                              direct.dr.fovV),
                             0.0)
        return Task.cont 
[docs]    def spawnMouseRotateTask(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        direct = ShowBaseGlobal.direct
        if self.perspCollPlane:
            iRay = SelectionRay(direct.camera)
            iRay.collider.setFromLens(direct.camNode, 0.0, 0.0)
            iRay.collideWithBitMask(1)
            if direct.camera.getPos().getZ() >=0:
                iRay.ct.traverse(self.perspCollPlane)
            else:
                iRay.ct.traverse(self.perspCollPlane2)
            if iRay.getNumEntries() > 0:
                entry = iRay.getEntry(0)
                hitPt = entry.getSurfacePoint(entry.getFromNodePath())
                # create a temp nodePath to get the position
                np = NodePath('temp')
                np.setPos(direct.camera, hitPt)
                self.coaMarkerPos = np.getPos()
                np.removeNode()
                self.coaMarker.setPos(self.coaMarkerPos)
            iRay.collisionNodePath.removeNode()
            del iRay
        # Set at markers position in render coordinates
        self.camManipRef.setPos(self.coaMarkerPos)
        self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
        t = Task.Task(self.mouseRotateTask)
        if abs(direct.dr.mouseX) > 0.9:
            t.constrainedDir = 'y'
        else:
            t.constrainedDir = 'x'
        self.__startManipulateCamera(task = t) 
[docs]    def mouseRotateTask(self, state):
        # If the cam is orthogonal, don't rotate.
        direct = ShowBaseGlobal.direct
        if (hasattr(direct.cam.node(), "getLens") and
            direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
            return
        # If moving outside of center, ignore motion perpendicular to edge
        if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
            deltaX = 0
            deltaY = direct.dr.mouseDeltaY
        elif ((state.constrainedDir == 'x') and (abs(direct.dr.mouseY) > 0.9)):
            deltaX = direct.dr.mouseDeltaX
            deltaY = 0
        else:
            deltaX = direct.dr.mouseDeltaX
            deltaY = direct.dr.mouseDeltaY
        if direct.fShift:
            direct.camera.setHpr(direct.camera,
                                 (deltaX * direct.dr.fovH),
                                 (-deltaY * direct.dr.fovV),
                                 0.0)
            if self.lockRoll:
                # flatten roll
                direct.camera.setR(0)
            self.camManipRef.setPos(self.coaMarkerPos)
            self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
        else:
            if direct.camera.getPos().getZ() >=0 or not self.switchDirBelowZero:
                dirX = -1
            else:
                dirX = 1
            wrt = direct.camera.getTransform(self.camManipRef)
            self.camManipRef.setHpr(self.camManipRef,
                                    (dirX * deltaX * 180.0),
                                    (deltaY * 180.0),
                                    0.0)
            if self.lockRoll:
                # flatten roll
                self.camManipRef.setR(0)
            direct.camera.setTransform(self.camManipRef, wrt)
        return Task.cont 
[docs]    def spawnMouseRollTask(self):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Set at markers position in render coordinates
        direct = ShowBaseGlobal.direct
        self.camManipRef.setPos(self.coaMarkerPos)
        self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
        t = Task.Task(self.mouseRollTask)
        t.coaCenter = getScreenXY(self.coaMarker)
        t.lastAngle = getCrankAngle(t.coaCenter)
        # Store the camera/manipRef offset transform
        t.wrt = direct.camera.getTransform(self.camManipRef)
        self.__startManipulateCamera(task = t) 
[docs]    def mouseRollTask(self, state):
        wrt = state.wrt
        angle = getCrankAngle(state.coaCenter)
        deltaAngle = angle - state.lastAngle
        state.lastAngle = angle
        self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
        if self.lockRoll:
            # flatten roll
            self.camManipRef.setR(0)
        ShowBaseGlobal.direct.camera.setTransform(self.camManipRef, wrt)
        return Task.cont 
[docs]    def lockCOA(self):
        self.fLockCOA = 1
        ShowBaseGlobal.direct.message('COA Lock On') 
[docs]    def unlockCOA(self):
        self.fLockCOA = 0
        ShowBaseGlobal.direct.message('COA Lock Off') 
[docs]    def toggleCOALock(self):
        self.fLockCOA = 1 - self.fLockCOA
        if self.fLockCOA:
            ShowBaseGlobal.direct.message('COA Lock On')
        else:
            ShowBaseGlobal.direct.message('COA Lock Off') 
[docs]    def pickNextCOA(self):
        """ Cycle through collision handler entries """
        if self.cqEntries:
            # Get next entry and rotate entries
            entry = self.cqEntries[0]
            self.cqEntries = self.cqEntries[1:] + self.cqEntries[:1]
            # Filter out object's under camera
            nodePath = entry.getIntoNodePath()
            if ShowBaseGlobal.direct.camera not in nodePath.getAncestors():
                # Compute new hit point
                hitPt = entry.getSurfacePoint(entry.getFromNodePath())
                # Move coa marker to new point
                self.updateCoa(hitPt, ref = self.coaMarkerRef)
            else:
                # Remove offending entry
                self.cqEntries = self.cqEntries[:-1]
                self.pickNextCOA() 
[docs]    def computeCOA(self, entry):
        coa = Point3(0)
        dr = ShowBaseGlobal.direct.drList.getCurrentDr()
        if self.fLockCOA:
            # COA is locked, use existing point
            # Use existing point
            coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
            # Reset hit point count
            self.nullHitPointCount = 0
        elif entry:
            # Got a hit point (hit point is in camera coordinates)
            # Set center of action
            hitPt = entry.getSurfacePoint(entry.getFromNodePath())
            hitPtDist = Vec3(hitPt).length()
            coa.assign(hitPt)
            # Handle case of bad coa point (too close or too far)
            if ((hitPtDist < (1.1 * dr.near)) or
                (hitPtDist > dr.far)):
                # Just use existing point
                coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
            # Reset hit point count
            self.nullHitPointCount = 0
        else:
            # Increment null hit point count
            self.nullHitPointCount = (self.nullHitPointCount + 1) % 7
            # No COA lock and no intersection point
            # Use a point out in front of camera
            # Distance to point increases on multiple null hit points
            # MRM: Would be nice to be able to control this
            # At least display it
            dist = pow(10.0, self.nullHitPointCount)
            ShowBaseGlobal.direct.message('COA Distance: ' + repr(dist))
            coa.set(0, dist, 0)
        # Compute COA Dist
        coaDist = Vec3(coa - DG.ZERO_POINT).length()
        if coaDist < (1.1 * dr.near):
            coa.set(0, 100, 0)
            coaDist = 100
        # Update coa and marker
        self.updateCoa(coa, coaDist = coaDist) 
[docs]    def updateCoa(self, ref2point, coaDist = None, ref = None):
        self.coa.set(ref2point[0], ref2point[1], ref2point[2])
        if not coaDist:
            coaDist = Vec3(self.coa - DG.ZERO_POINT).length()
        # Place the marker in render space
        if ref is None:
            # KEH: use the current display region
            # ref = base.cam
            ref = ShowBaseGlobal.direct.drList.getCurrentDr().cam
        self.coaMarker.setPos(ref, self.coa)
        pos = self.coaMarker.getPos()
        self.coaMarker.setPosHprScale(pos, Vec3(0), Vec3(1))
        # Resize it
        self.updateCoaMarkerSize(coaDist)
        # Record marker pos in render space
        self.coaMarkerPos.assign(self.coaMarker.getPos()) 
[docs]    def updateCoaMarkerSizeOnDeath(self):
        # Needed because tasks pass in state as first arg
        self.updateCoaMarkerSize() 
[docs]    def updateCoaMarkerSize(self, coaDist = None):
        if not coaDist:
            coaDist = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
        # Nominal size based on default 30 degree vertical FOV
        # Need to adjust size based on distance and current FOV
        sf = COA_MARKER_SF * coaDist * (ShowBaseGlobal.direct.drList.getCurrentDr().fovV/30.0)
        if sf == 0.0:
            sf = 0.1
        self.coaMarker.setScale(sf)
        # Lerp color to fade out
        if self.coaMarkerColorIval:
            self.coaMarkerColorIval.finish()
        self.coaMarkerColorIval = Sequence(
            Func(self.coaMarker.unstash),
            self.coaMarker.colorInterval(1.5, Vec4(1, 0, 0, 0),
                                         startColor = Vec4(1, 0, 0, 1),
                                         blendType = 'easeInOut'),
            Func(self.coaMarker.stash)
        )
        self.coaMarkerColorIval.start() 
[docs]    def homeCam(self):
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        direct.camera.reparentTo(ShowBaseGlobal.base.render)
        direct.camera.clearMat()
        # Resize coa marker
        self.updateCoaMarkerSize() 
[docs]    def uprightCam(self):
        self.__stopManipulateCamera()
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        # Pitch camera till upright
        currH = direct.camera.getH()
        ival = direct.camera.hprInterval(CAM_MOVE_DURATION,
                                         (currH, 0, 0),
                                         other=ShowBaseGlobal.base.render,
                                         blendType='easeInOut',
                                         name='manipulateCamera')
        self.__startManipulateCamera(ival=ival) 
[docs]    def orbitUprightCam(self):
        self.__stopManipulateCamera()
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        # Transform camera z axis to render space
        render = ShowBaseGlobal.base.render
        mCam2Render = Mat4(Mat4.identMat()) # [gjeon] fixed to give required argument
        mCam2Render.assign(direct.camera.getMat(render))
        zAxis = Vec3(mCam2Render.xformVec(DG.Z_AXIS))
        zAxis.normalize()
        # Compute rotation angle needed to upright cam
        orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(DG.Z_AXIS), -1, 1)))
        # Check angle
        if orbitAngle < 0.1:
            # Already upright
            return
        # Compute orthogonal axis of rotation
        rotAxis = Vec3(zAxis.cross(DG.Z_AXIS))
        rotAxis.normalize()
        # Find angle between rot Axis and render X_AXIS
        rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(DG.X_AXIS), -1, 1)))
        # Determine sign or rotation angle
        if rotAxis[1] < 0:
            rotAngle *= -1
        # Position ref CS at coa marker with xaxis aligned with rot axis
        self.camManipRef.setPos(self.coaMarker, Vec3(0))
        self.camManipRef.setHpr(render, rotAngle, 0, 0)
        # Reparent Cam to ref Coordinate system
        parent = direct.camera.getParent()
        direct.camera.wrtReparentTo(self.camManipRef)
        # Rotate ref CS to final orientation
        ival = self.camManipRef.hprInterval(CAM_MOVE_DURATION,
                                            (rotAngle, orbitAngle, 0),
                                            other = render,
                                            blendType = 'easeInOut')
        ival = Sequence(ival, Func(self.reparentCam, parent),
                        name = 'manipulateCamera')
        self.__startManipulateCamera(ival = ival) 
[docs]    def centerCam(self):
        self.centerCamIn(1.0) 
[docs]    def centerCamNow(self):
        self.centerCamIn(0.) 
[docs]    def centerCamIn(self, t):
        self.__stopManipulateCamera()
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        # Determine marker location
        markerToCam = self.coaMarker.getPos(direct.camera)
        dist = Vec3(markerToCam - DG.ZERO_POINT).length()
        scaledCenterVec = Y_AXIS * dist
        delta = markerToCam - scaledCenterVec
        self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
        ival = direct.camera.posInterval(CAM_MOVE_DURATION,
                                         Point3(delta),
                                         other=self.camManipRef,
                                         blendType='easeInOut')
        ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                        name = 'manipulateCamera')
        self.__startManipulateCamera(ival = ival) 
[docs]    def zoomCam(self, zoomFactor, t):
        self.__stopManipulateCamera()
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        # Find a point zoom factor times the current separation
        # of the widget and cam
        zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
        # Put a target nodePath there
        self.camManipRef.setPos(direct.camera, zoomPtToCam)
        # Move to that point
        ival = direct.camera.posInterval(CAM_MOVE_DURATION,
                                         DG.ZERO_POINT,
                                         other=self.camManipRef,
                                         blendType='easeInOut')
        ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                        name = 'manipulateCamera')
        self.__startManipulateCamera(ival = ival) 
[docs]    def spawnMoveToView(self, view):
        # Kill any existing tasks
        self.__stopManipulateCamera()
        # Record undo point
        direct = ShowBaseGlobal.direct
        direct.pushUndo([direct.camera])
        # Calc hprOffset
        hprOffset = VBase3()
        if view == 8:
            # Try the next roll angle
            self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0
            # but use the last view
            view = self.lastView
        else:
            self.orthoViewRoll = 0.0
        # Adjust offset based on specified view
        if view == 1:
            hprOffset.set(180., 0., 0.)
        elif view == 2:
            hprOffset.set(0., 0., 0.)
        elif view == 3:
            hprOffset.set(90., 0., 0.)
        elif view == 4:
            hprOffset.set(-90., 0., 0.)
        elif view == 5:
            hprOffset.set(0., -90., 0.)
        elif view == 6:
            hprOffset.set(0., 90., 0.)
        elif view == 7:
            hprOffset.set(135., -35.264, 0.)
        # Position target
        self.camManipRef.setPosHpr(self.coaMarker, DG.ZERO_VEC,
                                   hprOffset)
        # Scale center vec by current distance to target
        offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) -
                              DG.ZERO_POINT).length()
        scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
        # Now put the camManipRef at that point
        self.camManipRef.setPosHpr(self.camManipRef,
                                   scaledCenterVec,
                                   DG.ZERO_VEC)
        # Record view for next time around
        self.lastView = view
        ival = direct.camera.posHprInterval(CAM_MOVE_DURATION,
                                            pos=DG.ZERO_POINT,
                                            hpr=VBase3(0, 0, self.orthoViewRoll),
                                            other=self.camManipRef,
                                            blendType='easeInOut')
        ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
                        name = 'manipulateCamera')
        self.__startManipulateCamera(ival = ival) 
[docs]    def reparentCam(self, parent):
        ShowBaseGlobal.direct.camera.wrtReparentTo(parent)
        self.updateCoaMarkerSize() 
[docs]    def moveToFit(self):
        # How big is the active widget?
        direct = ShowBaseGlobal.direct
        widgetScale = direct.widget.scalingNode.getScale(ShowBaseGlobal.base.render)
        maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
        # At what distance does the widget fill 50% of the screen?
        camY = ((2 * direct.dr.near * (1.5 * maxScale)) /
                min(direct.dr.nearWidth, direct.dr.nearHeight))
        # Find a point this distance along the Y axis
        # MRM: This needs to be generalized to support non uniform frusta
        centerVec = Y_AXIS * camY
        # Before moving, record the relationship between the selected nodes
        # and the widget, so that this can be maintained
        direct.selected.getWrtAll()
        # Push state onto undo stack
        direct.pushUndo(direct.selected)
        # Remove the task to keep the widget attached to the object
        taskMgr.remove('followSelectedNodePath')
        # Spawn a task to keep the selected objects with the widget
        taskMgr.add(self.stickToWidgetTask, 'stickToWidget')
        # Spawn a task to move the widget
        ival = direct.widget.posInterval(CAM_MOVE_DURATION,
                                         Point3(centerVec),
                                         other=direct.camera,
                                         blendType='easeInOut')
        ival = Sequence(ival, Func(lambda: taskMgr.remove('stickToWidget')),
                        name = 'moveToFit')
        ival.start() 
[docs]    def enableMouseFly(self, fKeyEvents = 1):
        # disable C++ fly interface
        base = ShowBaseGlobal.base
        base.disableMouse()
        # Enable events
        for event in self.actionEvents:
            self.accept(event[0], event[1], extraArgs = event[2:])
        if fKeyEvents:
            for event in self.keyEvents:
                self.accept(event[0], event[1], extraArgs = event[2:])
        # Show marker
        self.coaMarker.reparentTo(ShowBaseGlobal.direct.group) 
[docs]    def disableMouseFly(self):
        # Hide the marker
        self.coaMarker.reparentTo(ShowBaseGlobal.hidden)
        # Ignore events
        for event in self.actionEvents:
            self.ignore(event[0])
        for event in self.keyEvents:
            self.ignore(event[0])
        # Kill tasks
        self.removeManipulateCameraTask()
        taskMgr.remove('stickToWidget')
        ShowBaseGlobal.base.enableMouse() 
[docs]    def removeManipulateCameraTask(self):
        self.__stopManipulateCamera()