Pārlūkot izejas kodu

*** empty log message ***

Mark Mine 25 gadi atpakaļ
vecāks
revīzija
8ecb2f612c

+ 334 - 224
direct/src/directutil/DirectCameraControl.py

@@ -1,5 +1,5 @@
 from PandaObject import *
-from DirectGeometry import useDirectRenderStyle
+from DirectGeometry import *
 
 CAM_MOVE_DURATION = 1.0
 COA_MARKER_SF = 0.0075
@@ -19,26 +19,24 @@ class DirectCameraControl(PandaObject):
         useDirectRenderStyle(self.coaMarker)
         self.coaMarkerPos = Point3(0)
 	self.camManipRef = direct.group.attachNewNode('camManipRef')
-        self.zeroBaseVec = VBase3(0)
-        self.zeroVector = Vec3(0)
-        self.centerVec = Vec3(0, 1, 0)
-        self.zeroPoint = Point3(0)
         t = CAM_MOVE_DURATION
         self.actionEvents = [
             ['handleMouse2', self.mouseFlyStart],
             ['handleMouse2Up', self.mouseFlyStop],
-            ['u', self.uprightCam],
             ['c', self.centerCamIn, 0.5],
-            ['h', self.homeCam],
             ['f', self.fitOnWidget],
-            [`1`, self.SpawnMoveToView, 1],
-            [`2`, self.SpawnMoveToView, 2],
-            [`3`, self.SpawnMoveToView, 3],
-            [`4`, self.SpawnMoveToView, 4],
-            [`5`, self.SpawnMoveToView, 5],
-            [`6`, self.SpawnMoveToView, 6],
-            [`7`, self.SpawnMoveToView, 7],
-            [`8`, self.SpawnMoveToView, 8],
+            ['h', self.homeCam],
+            ['m', self.moveToFit],
+            ['u', self.orbitUprightCam],
+            ['U', self.uprightCam],
+            [`1`, self.spawnMoveToView, 1],
+            [`2`, self.spawnMoveToView, 2],
+            [`3`, self.spawnMoveToView, 3],
+            [`4`, self.spawnMoveToView, 4],
+            [`5`, self.spawnMoveToView, 5],
+            [`6`, self.spawnMoveToView, 6],
+            [`7`, self.spawnMoveToView, 7],
+            [`8`, self.spawnMoveToView, 8],
             ['9', self.swingCamAboutWidget, -90.0, t],
             ['0', self.swingCamAboutWidget,  90.0, t],
             ['`', self.removeManipulateCameraTask],
@@ -59,102 +57,241 @@ class DirectCameraControl(PandaObject):
             # MOUSE IS IN CENTRAL REGION
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
-            # See if the shift key is pressed
-            if (direct.fShift):
-                # If shift key is pressed, just perform horiz and vert pan:
-                self.spawnHPPan()
-            else:
-                # Otherwise, check for a hit point based on
-                # current mouse position
-                # And then spawn task to determine mouse mode
-                numEntries = direct.iRay.pickGeom(
-                    render,direct.dr.mouseX,direct.dr.mouseY)
-                # Filter out hidden nodes from entry list
-                indexList = []
-                for i in range(0,numEntries):
-                    entry = direct.iRay.cq.getEntry(i)
-                    node = entry.getIntoNode()
-                    if node.isHidden():
-                        pass
-                    else:
-                        # Not one of the widgets, use it
-                        indexList.append(i)
-                coa = Point3(0)
-                if(indexList):
-                    # Start off with first point
-                    minPt = indexList[0]
-                    # Find hit point in camera's space
-                    hitPt = direct.iRay.camToHitPt(minPt)
-                    coa.set(hitPt[0],hitPt[1],hitPt[2])
-                    coaDist = Vec3(coa - self.zeroPoint).length()
-                    # Check other intersection points, sorting them
-                    # TBD: Use TBS C++ function to do this
-                    if len(indexList) > 1:
-                        for i in range(1,len(indexList)):
-                            entryNum = indexList[i]
-                            hitPt = direct.iRay.camToHitPt(entryNum)
-                            dist = Vec3(hitPt - self.zeroPoint).length()
-                            if (dist < coaDist):
-                                coaDist = dist
-                                coa.set(hitPt[0],hitPt[1],hitPt[2])
-                                minPt = i
-                    # Handle case of bad coa point (too close or too far)
-                    if ((coaDist < (1.1 * direct.dr.near)) |
-                        (coaDist > direct.dr.far)):
-                        # Just use existing point
-                        coa.assign(self.coaMarker.getPos(direct.camera))
-                        coaDist = Vec3(coa - self.zeroPoint).length()
-                        if coaDist < (1.1 * direct.dr.near):
-                            coa.set(0,100,0)
-                            coaDist = 100
+            # Check for a hit point based on
+            # current mouse position
+            # And then spawn task to determine mouse mode
+            numEntries = direct.iRay.pickGeom(
+                render,direct.dr.mouseX,direct.dr.mouseY)
+            # Sort intersection points
+            direct.iRay.cq.sortEntries()
+            # Then filter out hidden nodes from entry list
+            indexList = []
+            for i in range(0,numEntries):
+                entry = direct.iRay.cq.getEntry(i)
+                node = entry.getIntoNode()
+                if node.isHidden():
+                    pass
                 else:
-                    # If no intersection point:
-                    # Use existing point
+                    # Not one of the widgets, use it
+                    indexList.append(i)
+            coa = Point3(0)
+            if(indexList):
+                # Grab first point (it should be the closest)
+                minPt = indexList[0]
+                # Find hit point in camera's space
+                hitPt = direct.iRay.camToHitPt(minPt)
+                coa.set(hitPt[0],hitPt[1],hitPt[2])
+                coaDist = Vec3(coa - ZERO_POINT).length()
+                """
+                # Check other intersection points, sorting them
+                # TBD: Use TBS C++ function to do this
+                if len(indexList) > 1:
+                for i in range(1,len(indexList)):
+                entryNum = indexList[i]
+                        hitPt = direct.iRay.camToHitPt(entryNum)
+                        dist = Vec3(hitPt - ZERO_POINT).length()
+                        if (dist < coaDist):
+                            coaDist = dist
+                            coa.set(hitPt[0],hitPt[1],hitPt[2])
+                            minPt = i
+                """
+                # Handle case of bad coa point (too close or too far)
+                if ((coaDist < (1.1 * direct.dr.near)) |
+                    (coaDist > direct.dr.far)):
+                    # Just use existing point
                     coa.assign(self.coaMarker.getPos(direct.camera))
-                    coaDist = Vec3(coa - self.zeroPoint).length()
+                    coaDist = Vec3(coa - ZERO_POINT).length()
                     if coaDist < (1.1 * direct.dr.near):
                         coa.set(0,100,0)
                         coaDist = 100
-                # Update coa and marker
-                self.updateCoa(coa, coaDist)
-                # Now spawn task to determine mouse fly mode
-                self.determineMouseFlyMode()
+            else:
+                # If no intersection point:
+                # Use existing point
+                coa.assign(self.coaMarker.getPos(direct.camera))
+                coaDist = Vec3(coa - ZERO_POINT).length()
+                # Check again its not to close 
+                if coaDist < (1.1 * direct.dr.near):
+                    coa.set(0,100,0)
+                    coaDist = 100
+            # Update coa and marker
+            self.updateCoa(coa, coaDist)
+            # Start manipulation
+            self.spawnXZTranslateOrHPanYZoom()
             # END MOUSE IN CENTRAL REGION
         else:
-            # Mouse is in outer frame, spawn mouseRotateTask
-            self.spawnMouseRotateTask()
+            if ((abs(self.initMouseX) > 0.9) & (abs(self.initMouseY) > 0.9)):
+                # Mouse is in corners, spawn roll task
+                self.spawnMouseRollTask()
+            else:
+                # Mouse is in outer frame, spawn mouseRotateTask
+                self.spawnMouseRotateTask()
 
     def mouseFlyStop(self):
-	taskMgr.removeTasksNamed('determineMouseFlyMode')
 	taskMgr.removeTasksNamed('manipulateCamera')
         # Show the marker
         self.coaMarker.show()
         # Resize it
         self.updateCoaMarkerSize()
 
-    def determineMouseFlyMode(self):
-        # Otherwise, determine mouse fly mode
-        t = Task.Task(self.determineMouseFlyModeTask)
-        taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode')
+    def spawnXZTranslateOrHPanYZoom(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Spawn the new task
+        t = Task.Task(self.XZTranslateOrHPanYZoomTask)
+        # For HPanYZoom
+        t.zoomSF = Vec3(self.coa).length()
+        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
+
+    def spawnXZTranslateOrHPPan(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Spawn new task
+        taskMgr.spawnMethodNamed(self.XZTranslateOrHPPanTask,
+                                 'manipulateCamera')
+
+    def spawnXZTranslate(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Spawn new task
+        taskMgr.spawnMethodNamed(self.XZTranslateTask, 'manipulateCamera')
+
+    def spawnHPanYZoom(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Spawn new task
+        t = Task.Task(self.HPanYZoomTask)
+        t.zoomSF = Vec3(self.coa).length()
+        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
+
+    def spawnHPPan(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Spawn new task
+        taskMgr.spawnMethodNamed(self.HPPanTask, 'manipulateCamera')
 
-    def determineMouseFlyModeTask(self, state):
-        deltaX = direct.dr.mouseX - self.initMouseX
-        deltaY = direct.dr.mouseY - self.initMouseY
-        if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)):
-            return Task.cont
+    def XZTranslateOrHPanYZoomTask(self, state):
+        if direct.fShift:
+            return self.XZTranslateTask(state)
         else:
-            if (abs(deltaY) > 0.1):
-                self.spawnHPanYZoom()
-            else:
-                self.spawnXZTranslate()
-            return Task.done
+            return self.HPanYZoomTask(state)
+
+    def XZTranslateOrHPPanTask(self, state):
+        if direct.fShift:
+            # Panning action
+            return self.HPPanTask(state)
+        else:
+            # Translation action
+            return self.XZTranslateTask(state)
+
+    def XZTranslateTask(self,state):
+        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
+
+    def HPanYZoomTask(self,state):
+        if direct.fControl:
+            moveDir = Vec3(Y_AXIS)
+        else:
+            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()
+        moveDir.assign(moveDir * (-2.0 * direct.dr.mouseDeltaY *
+                                        state.zoomSF))
+        direct.camera.setPosHpr(direct.camera,
+                                moveDir[0],
+                                moveDir[1],
+                                moveDir[2],
+                                (0.5 * direct.dr.mouseDeltaX *
+                                 direct.dr.fovH),
+                                0.0, 0.0)
+        return Task.cont
+
+    def HPPanTask(self, state):
+        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
+
+    def spawnMouseRotateTask(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Set at markers position in render coordinates
+	self.camManipRef.setPos(self.coaMarkerPos)
+	self.camManipRef.setHpr(direct.camera, ZERO_POINT)
+        t = Task.Task(self.mouseRotateTask)
+        if abs(direct.dr.mouseX) > 0.9:
+            t.constrainedDir = 'y'
+        else:
+            t.constrainedDir = 'x'
+        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
+
+    def mouseRotateTask(self, state):
+        # If moving within frame, ignore motion perpendicular to edge
+        if ((state.constrainedDir == 'y') & (abs(direct.dr.mouseX) > 0.9)):
+            deltaX = 0
+            deltaY = direct.dr.mouseDeltaY
+        elif ((state.constrainedDir == 'x') & (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)
+            self.camManipRef.setPos(self.coaMarkerPos)
+            self.camManipRef.setHpr(direct.camera, ZERO_POINT)
+        else:
+            wrtMat = direct.camera.getMat( self.camManipRef )
+            self.camManipRef.setHpr(self.camManipRef,
+                                    (-1 * deltaX * 180.0),
+                                    (deltaY * 180.0),
+                                    0.0)
+            direct.camera.setMat(self.camManipRef, wrtMat)
+        return Task.cont
+
+    def spawnMouseRollTask(self):
+        # Kill any existing tasks
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Set at markers position in render coordinates
+	self.camManipRef.setPos(self.coaMarkerPos)
+	self.camManipRef.setHpr(direct.camera, ZERO_POINT)
+        t = Task.Task(self.mouseRollTask)
+        t.coaCenter = getScreenXY(self.coaMarker)
+        t.lastAngle = getCrankAngle(t.coaCenter)
+	t.wrtMat = direct.camera.getMat( self.camManipRef )
+        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
+
+    def mouseRollTask(self, state):
+        wrtMat = state.wrtMat
+        angle = getCrankAngle(state.coaCenter)
+        deltaAngle = angle - state.lastAngle
+        state.lastAngle = angle
+        self.camManipRef.setHpr(self.camManipRef, 0, 0, -deltaAngle)
+        direct.camera.setMat(self.camManipRef, wrtMat)
+        return Task.cont
 
     def updateCoa(self, cam2point, coaDist = None):
         self.coa.set(cam2point[0], cam2point[1], cam2point[2])
         if coaDist:
             self.coaDist = coaDist
         else:
-            self.coaDist = Vec3(self.coa - self.zeroPoint).length()
+            self.coaDist = Vec3(self.coa - ZERO_POINT).length()
         # Place the marker in render space
         self.coaMarker.setPos(direct.camera,self.coa)
         # Resize it
@@ -162,6 +299,10 @@ class DirectCameraControl(PandaObject):
         # Record marker pos in render space
         self.coaMarkerPos.assign(self.coaMarker.getPos())
 
+    def updateCoaMarkerSizeOnDeath(self, state):
+        # Needed because tasks pass in state as first arg
+        self.updateCoaMarkerSize()
+
     def updateCoaMarkerSize(self, coaDist = None):
         if not coaDist:
             coaDist = Vec3(self.coaMarker.getPos( direct.camera )).length()
@@ -172,18 +313,59 @@ class DirectCameraControl(PandaObject):
         # Record undo point
         direct.pushUndo([direct.camera])
         direct.camera.setMat(Mat4.identMat())
+        # Resize coa marker
+        self.updateCoaMarkerSize()
 
     def uprightCam(self):
 	taskMgr.removeTasksNamed('manipulateCamera')
-        currH = direct.camera.getH()
         # Record undo point
         direct.pushUndo([direct.camera])
+        # Pitch camera till upright
+        currH = direct.camera.getH()
 	direct.camera.lerpHpr(currH, 0, 0,
                               CAM_MOVE_DURATION,
                               other = render,
                               blendType = 'easeInOut',
                               task = 'manipulateCamera')
 
+    def orbitUprightCam(self):
+	taskMgr.removeTasksNamed('manipulateCamera')
+        # Record undo point
+        direct.pushUndo([direct.camera])
+        # Transform camera z axis to render space
+        mCam2Render = camera.getMat(render)
+        zAxis = Vec3(mCam2Render.xformVec(Z_AXIS))
+        zAxis.normalize()
+        # Compute rotation angle needed to upright cam
+        orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(Z_AXIS),-1,1)))
+        # Check angle
+        if orbitAngle < 0.1:
+            # Already upright
+            return
+        # Compute orthogonal axis of rotation
+        rotAxis = Vec3(zAxis.cross(Z_AXIS))
+        rotAxis.normalize()
+        # Find angle between rot Axis and render X_AXIS
+        rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(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
+	t = self.camManipRef.lerpHpr(rotAngle, orbitAngle, 0,
+                                     CAM_MOVE_DURATION,
+                                     other = render,
+                                     blendType = 'easeInOut',
+                                     task = 'manipulateCamera')
+        # Upon death, reparent Cam to parent
+        t.parent = parent
+        t.uponDeath = self.reparentCam
+
     def centerCam(self):
 	self.centerCamIn(1.0)
         
@@ -196,15 +378,16 @@ class DirectCameraControl(PandaObject):
         direct.pushUndo([direct.camera])
         # Determine marker location
         markerToCam = self.coaMarker.getPos( direct.camera )
-	dist = Vec3(markerToCam - self.zeroPoint).length()
-	scaledCenterVec = self.centerVec * dist
+	dist = Vec3(markerToCam - ZERO_POINT).length()
+	scaledCenterVec = Y_AXIS * dist
 	delta = markerToCam - scaledCenterVec
 	self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
-	direct.camera.lerpPos(Point3(delta),
-                            CAM_MOVE_DURATION,
-                            other = self.camManipRef,
-                            blendType = 'easeInOut',
-                            task = 'manipulateCamera')
+	t = direct.camera.lerpPos(Point3(delta),
+                                  CAM_MOVE_DURATION,
+                                  other = self.camManipRef,
+                                  blendType = 'easeInOut',
+                                  task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
 
     def zoomCam(self, zoomFactor, t):
 	taskMgr.removeTasksNamed('manipulateCamera')
@@ -216,13 +399,14 @@ class DirectCameraControl(PandaObject):
 	# Put a target nodePath there
 	self.camManipRef.setPos(direct.camera, zoomPtToCam)
 	# Move to that point
-	direct.camera.lerpPos(self.zeroPoint,
-                            CAM_MOVE_DURATION,
-                            other = self.camManipRef,
-                            blendType = 'easeInOut',
-                            task = 'manipulateCamera')
+	t = direct.camera.lerpPos(ZERO_POINT,
+                                  CAM_MOVE_DURATION,
+                                  other = self.camManipRef,
+                                  blendType = 'easeInOut',
+                                  task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
         
-    def SpawnMoveToView(self, view):
+    def spawnMoveToView(self, view):
         # Kill any existing tasks
         taskMgr.removeTasksNamed('manipulateCamera')
         # Record undo point
@@ -252,25 +436,26 @@ class DirectCameraControl(PandaObject):
         elif view == 7:
             hprOffset.set(135., -35.264, 0.)
         # Position target
-        self.camManipRef.setPosHpr(self.coaMarker, self.zeroBaseVec,
+        self.camManipRef.setPosHpr(self.coaMarker, ZERO_VEC,
                                    hprOffset)
         # Scale center vec by current distance to target
         offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) - 
-                              self.zeroPoint).length()
-        scaledCenterVec = self.centerVec * (-1.0 * offsetDistance)
+                              ZERO_POINT).length()
+        scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
         # Now put the camManipRef at that point
         self.camManipRef.setPosHpr(self.camManipRef,
                                    scaledCenterVec,
-                                   self.zeroBaseVec)
+                                   ZERO_VEC)
         # Record view for next time around
         self.lastView = view
-        direct.camera.lerpPosHpr(self.zeroPoint,
-                                 VBase3(0,0,self.orthoViewRoll),
-                                 CAM_MOVE_DURATION,
-                                 other = self.camManipRef,
-                                 blendType = 'easeInOut',
-                                 task = 'manipulateCamera')
-
+        t = direct.camera.lerpPosHpr(ZERO_POINT,
+                                     VBase3(0,0,self.orthoViewRoll),
+                                     CAM_MOVE_DURATION,
+                                     other = self.camManipRef,
+                                     blendType = 'easeInOut',
+                                     task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
+        
         
     def swingCamAboutWidget(self, degrees, t):
         # Remove existing camera manipulation task
@@ -280,9 +465,9 @@ class DirectCameraControl(PandaObject):
         direct.pushUndo([direct.camera])
         
 	# Coincident with widget
-        self.camManipRef.setPos(self.coaMarker, self.zeroPoint)
+        self.camManipRef.setPos(self.coaMarker, ZERO_POINT)
 	# But aligned with render space
-	self.camManipRef.setHpr(self.zeroPoint)
+	self.camManipRef.setHpr(ZERO_POINT)
 
 	parent = direct.camera.getParent()
 	direct.camera.wrtReparentTo(self.camManipRef)
@@ -297,115 +482,7 @@ class DirectCameraControl(PandaObject):
 
     def reparentCam(self, state):
         direct.camera.wrtReparentTo(state.parent)
-
-    def spawnHPanYZoom(self):
-        # Kill any existing tasks
-	taskMgr.removeTasksNamed('manipulateCamera')
-        # hide the marker
-        self.coaMarker.hide()
-        # Negate vec to give it the correct sense for mouse motion below
-        targetVector = self.coa * -1
-        t = Task.Task(self.HPanYZoomTask)
-        t.targetVector = targetVector
-        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
-
-    def HPanYZoomTask(self,state):
-        targetVector = state.targetVector
-        # Can bring object to you by dragging across half the screen
-        distToMove = targetVector * (2.0 * direct.dr.mouseDeltaY)
-        direct.camera.setPosHpr(direct.camera,
-                                distToMove[0],
-                                distToMove[1],
-                                distToMove[2],
-                                (0.5 * direct.dr.mouseDeltaX *
-                                 direct.dr.fovH),
-                                0.0, 0.0)
-        return Task.cont
-
-
-    def spawnXZTranslateOrHPPan(self):
-        # Kill any existing tasks
-	taskMgr.removeTasksNamed('manipulateCamera')
-        # Hide the marker
-        self.coaMarker.hide()
-        t = Task.Task(self.XZTranslateOrHPPanTask)
-        t.scaleFactor = (self.coaDist / direct.dr.near)
-        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
-
-    def XZTranslateOrHPPanTask(self, state):
-        if direct.fShift:
-            direct.camera.setHpr(direct.camera,
-                               (0.5 * direct.dr.mouseDeltaX *
-                                direct.dr.fovH),
-                               (-0.5 * direct.dr.mouseDeltaY *
-                                direct.dr.fovV),
-                               0.0)
-        else:
-            direct.camera.setPos(direct.camera,
-                               (-0.5 * direct.dr.mouseDeltaX *
-                                direct.dr.nearWidth *
-                                state.scaleFactor),
-                               0.0,
-                               (-0.5 * direct.dr.mouseDeltaY *
-                                direct.dr.nearHeight *
-                                state.scaleFactor))
-        return Task.cont
-
-    def spawnXZTranslate(self):
-        # Kill any existing tasks
-	taskMgr.removeTasksNamed('manipulateCamera')
-        # Hide the marker
-        self.coaMarker.hide()
-        t = Task.Task(self.XZTranslateTask)
-        t.scaleFactor = (self.coaDist / direct.dr.near)
-        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
-
-    def XZTranslateTask(self,state):
-        direct.camera.setPos(direct.camera,
-                             (-0.5 * direct.dr.mouseDeltaX *
-                              direct.dr.nearWidth *
-                              state.scaleFactor),
-                             0.0,
-                             (-0.5 * direct.dr.mouseDeltaY *
-                              direct.dr.nearHeight *
-                              state.scaleFactor))
-        return Task.cont
-
-    def spawnMouseRotateTask(self):
-        # Kill any existing tasks
-	taskMgr.removeTasksNamed('manipulateCamera')
-        # Set at markers position in render coordinates
-	self.camManipRef.setPos(self.coaMarkerPos)
-	self.camManipRef.setHpr(direct.camera, self.zeroPoint)
-        t = Task.Task(self.mouseRotateTask)
-	t.wrtMat = direct.camera.getMat( self.camManipRef )
-        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
-
-    def mouseRotateTask(self, state):
-        wrtMat = state.wrtMat
-        self.camManipRef.setHpr(self.camManipRef,
-                                (-0.5 * direct.dr.mouseDeltaX * 180.0),
-                                (0.5 * direct.dr.mouseDeltaY * 180.0),
-                                0.0)
-        direct.camera.setMat(self.camManipRef, wrtMat)
-        return Task.cont
-
-    def spawnHPPan(self):
-        # Kill any existing tasks
-	taskMgr.removeTasksNamed('manipulateCamera')
-        # Hide the marker
-        self.coaMarker.hide()
-        t = Task.Task(self.HPPanTask)
-        taskMgr.spawnTaskNamed(t, 'manipulateCamera')
-
-    def HPPanTask(self, state):
-        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
+        self.updateCoaMarkerSize()
 
     def fitOnWidget(self):
         # Fit the node on the screen
@@ -444,6 +521,39 @@ class DirectCameraControl(PandaObject):
         fitTask.parent = parent
         fitTask.uponDeath = self.reparentCam                                
 
+    def moveToFit(self):
+        # How bit is the active widget?
+        widgetScale = direct.widget.scalingNode.getScale(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.removeTasksNamed('followSelectedNodePath')
+        # Spawn a task to keep the selected objects with the widget
+        taskMgr.spawnMethodNamed(self.stickToWidgetTask, 'stickToWidget')
+        # Spawn a task to move the widget
+        t = direct.widget.lerpPos(Point3(centerVec),
+                                  CAM_MOVE_DURATION,
+                                  other = direct.camera, 
+                                  blendType = 'easeInOut',
+                                  task = 'moveToFitTask')
+        t.uponDeath = lambda state: taskMgr.removeTasksNamed('stickToWidget')
+
+    def stickToWidgetTask(self, state):
+        # Move the objects with the widget
+        direct.selected.moveWrtWidgetAll()
+        # Continue
+        return Task.cont
+
     def enableMouseFly(self):
 	# disable C++ fly interface
 	base.disableMouse()

+ 39 - 0
direct/src/directutil/DirectGeometry.py

@@ -86,6 +86,45 @@ def ROUND_TO(value, divisor):
     return round(value/float(divisor)) * divisor
 def ROUND_INT(val):
     return int(round(val))
+def CLAMP(val, min, max):
+    if val < min:
+        return min
+    elif val > max:
+        return max
+    else:
+        return val
+
+def getNearProjectionPoint(nodePath):
+    # Find the position of the projection of the specified node path
+    # on the near plane
+    origin = nodePath.getPos(direct.camera)
+    # project this onto near plane
+    if origin[1] != 0.0:
+        return origin * (direct.dr.near / origin[1])
+    else:
+        # Object is coplaner with camera, just return something reasonable
+        return Point3(0, direct.dr.near, 0)
+
+def getScreenXY(nodePath):
+    # Where does the node path's projection fall on the near plane
+    nearVec = getNearProjectionPoint(nodePath)
+    # Clamp these coordinates to visible screen
+    nearX = CLAMP(nearVec[0], direct.dr.left, direct.dr.right)
+    nearY = CLAMP(nearVec[2], direct.dr.bottom, direct.dr.top)
+    # What percentage of the distance across the screen is this?
+    percentX = (nearX - direct.dr.left)/direct.dr.nearWidth
+    percentY = (nearY - direct.dr.bottom)/direct.dr.nearHeight
+    # Map this percentage to the same -1 to 1 space as the mouse
+    screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0)
+    # Return the resulting value
+    return screenXY
+
+def getCrankAngle(center):
+    # Used to compute current angle of mouse (relative to the coa's
+    # origin) in screen space
+    x = direct.dr.mouseX - center[0]
+    y = direct.dr.mouseY - center[2]
+    return (180 + rad2Deg(math.atan2(y,x)))
 
 # Set direct drawing style for an object
 # Never light object or draw in wireframe

+ 11 - 51
direct/src/directutil/DirectManipulation.py

@@ -28,6 +28,7 @@ class DirectManipulationControl(PandaObject):
         self.actionEvents = [
             ['handleMouse1', self.manipulationStart],
             ['handleMouse1Up', self.manipulationStop],
+            ['space', self.toggleObjectHandlesMode],
             ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
             ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
             [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
@@ -137,8 +138,6 @@ class DirectManipulationControl(PandaObject):
             # We had been scaling, need to reset object handles
             self.objectHandles.transferObjectHandlesScale()
             self.fScaling = 0
-        if self.fSetCoa:
-            self.objectHandles.manipModeColor()
         direct.selected.highlightAll()
         self.objectHandles.showAllHandles()
         self.objectHandles.hideGuides()
@@ -188,6 +187,13 @@ class DirectManipulationControl(PandaObject):
         if item in self.unpickable:
             self.unpickable.remove(item)
 
+    def toggleObjectHandlesMode(self):
+        self.fSetCoa = 1 - self.fSetCoa
+        if self.fSetCoa:
+            self.objectHandles.coaModeColor()
+        else:
+            self.objectHandles.manipModeColor()
+
     def removeManipulateObjectTask(self):
         taskMgr.removeTasksNamed('manipulateObject')
 
@@ -206,20 +212,10 @@ class DirectManipulationControl(PandaObject):
             self.objectHandles.showGuides()
             self.objectHandles.hideAllHandles()
             self.objectHandles.showHandle(self.constraint)
-            if self.fSetCoa:
-                self.objectHandles.coaModeColor()
-
             # Record relationship between selected nodes and widget
             direct.selected.getWrtAll()
-
             # hide the bbox of the selected objects during interaction
             direct.selected.dehighlightAll()
-
-            """
-            # Push the undo dcs for the selected objects
-            direct.undo.push(
-                (direct.selected, 'dcs'))
-            """
             # Manipulate the real object with the constraint
             # The constraint is passed as the name of the node 
             self.spawnManipulateObjectTask()
@@ -350,13 +346,6 @@ class DirectManipulationControl(PandaObject):
             y + self.initY * dr.mouseDeltaY,
             z)
     
-    def getCrankAngle(self):
-        # Used to compute current angle of mouse (relative to the widget's
-        # origin) in screen space
-        x = direct.dr.mouseX - self.rotationCenter[0]
-        y = direct.dr.mouseY - self.rotationCenter[2]
-        return (180 + rad2Deg(math.atan2(y,x)))
-
     def widgetCheck(self,type):
         # Utility to see if we are looking at the top or bottom of
         # a 2D planar widget or if we are looking at a 2D planar widget
@@ -388,27 +377,6 @@ class DirectManipulationControl(PandaObject):
             # Check angle between two vectors
             return(abs(widgetDir.dot(widgetAxis)) < .2)
 
-    def getWidgetsNearProjectionPoint(self):
-        # Find the position of the projection of the specified node path
-        # on the near plane
-        widgetOrigin = direct.widget.getPos(direct.camera)
-        # project this onto near plane
-        return widgetOrigin * (direct.dr.near / widgetOrigin[1])
-
-    def getScreenXY(self):
-        # Where does the widget's projection fall on the near plane
-        nearVec = self.getWidgetsNearProjectionPoint()
-        # Clamp these coordinates to visible screen
-        nearX = self.clamp(nearVec[0], direct.dr.left, direct.dr.right)
-        nearY = self.clamp(nearVec[2], direct.dr.bottom, direct.dr.top)
-        # What percentage of the distance across the screen is this?
-        percentX = (nearX - direct.dr.left)/direct.dr.nearWidth
-        percentY = (nearY - direct.dr.bottom)/direct.dr.nearHeight
-        # Map this percentage to the same -1 to 1 space as the mouse
-        screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0)
-        # Return the resulting value
-        return screenXY
-
     def rotate1D(self):
         # Constrained 1D rotation about the widget's main axis (X,Y, or Z)
         # Rotation depends upon circular motion of the mouse about the
@@ -421,11 +389,11 @@ class DirectManipulationControl(PandaObject):
             self.fHitInit = 0
             self.rotateAxis = self.constraint[:1]
             self.fWidgetTop = self.widgetCheck('top?')
-            self.rotationCenter = self.getScreenXY()
-            self.lastCrankAngle = self.getCrankAngle()
+            self.rotationCenter = getScreenXY(direct.widget)
+            self.lastCrankAngle = getCrankAngle(self.rotationCenter)
             
         # Rotate widget based on how far cursor has swung around origin
-        newAngle = self.getCrankAngle()
+        newAngle = getCrankAngle(self.rotationCenter)
         deltaAngle = self.lastCrankAngle - newAngle
         if self.fWidgetTop:
             deltaAngle = -1 * deltaAngle
@@ -491,14 +459,6 @@ class DirectManipulationControl(PandaObject):
             )
         direct.widget.setScale(currScale)
         
-    def clamp(self, val, min, max):
-        if val < min:
-            return min
-        elif val > max:
-            return max
-        else:
-            return val
-
 
 class ObjectHandles(NodePath,PandaObject):
     def __init__(self):

+ 33 - 12
direct/src/directutil/DirectSession.py

@@ -82,7 +82,8 @@ class DirectSession(PandaObject):
                           'escape', 'space', 'delete',
                           'shift', 'shift-up', 'alt', 'alt-up',
                           'control', 'control-up',
-                          'page_up', 'page_down',
+                          'page_up', 'page_down', 'tab',
+                          '[', '{', ']', '}',
                           'b', 'c', 'f', 'l', 's', 't', 'v', 'w']
         self.mouseEvents = ['mouse1', 'mouse1-up',
                             'mouse2', 'mouse2-up',
@@ -190,21 +191,27 @@ class DirectSession(PandaObject):
             self.downAncestry()
         elif input == 'escape':
             self.deselectAll()
+        elif input == 'delete':
+            self.removeAllSelected()
+        elif input == 'tab':
+            self.toggleWidgetVis()
+        elif input == 'b':
+            base.toggleBackface()
         elif input == 'l':
             self.lights.toggle()
         elif input == 's':
             if self.selected.last:
                 self.select(self.selected.last)
-        elif input == 'delete':
-            self.removeAllSelected()
-        elif input == 'v':
-            self.selected.toggleVisAll()
-        elif input == 'b':
-            base.toggleBackface()
         elif input == 't':
             base.toggleTexture()
+        elif input == 'v':
+            self.selected.toggleVisAll()
         elif input == 'w':
             base.toggleWireframe()
+        elif (input == '[') | (input == '{'):
+            self.undo()
+        elif (input == ']') | (input == '}'):
+            self.redo()
         
     def select(self, nodePath, fMultiselect = 0, fResetAncestry = 1):
         dnp = self.selected.select(nodePath, fMultiselect)
@@ -219,7 +226,7 @@ class DirectSession(PandaObject):
             self.readout.reparentTo(render2d)
             self.readout.setText(dnp.name)
             # Show the manipulation widget
-            self.widget.reparentTo(direct.group)
+            self.reparentWidgetTo('direct')
             # Update camera controls coa to this point
             # Coa2Camera = Coa2Dnp * Dnp2Camera
             mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(self.camera)
@@ -250,7 +257,7 @@ class DirectSession(PandaObject):
         dnp = self.selected.deselect(nodePath)
         if dnp:
             # Hide the manipulation widget
-            self.widget.reparentTo(hidden)
+            self.reparentWidgetTo('hidden')
             self.readout.reparentTo(hidden)
             self.readout.setText(' ')
             taskMgr.removeTasksNamed('followSelectedNodePath')
@@ -261,7 +268,7 @@ class DirectSession(PandaObject):
     def deselectAll(self):
         self.selected.deselectAll()
         # Hide the manipulation widget
-        self.widget.reparentTo(hidden)
+        self.reparentWidgetTo('hidden')
         self.readout.reparentTo(hidden)
         self.readout.setText(' ')
         taskMgr.removeTasksNamed('followSelectedNodePath')
@@ -384,7 +391,7 @@ class DirectSession(PandaObject):
         # Now record group
         self.undoList.append(undoGroup)
         # Truncate list
-        self.undoList = self.undoList[-5:]
+        self.undoList = self.undoList[-25:]
         # Alert anyone who cares
         messenger.send('pushUndo')
         if fResetRedo & (nodePathList != []):
@@ -413,7 +420,7 @@ class DirectSession(PandaObject):
         # Now record redo group
         self.redoList.append(redoGroup)
         # Truncate list
-        self.redoList = self.redoList[-5:]
+        self.redoList = self.redoList[-25:]
         # Alert anyone who cares
         messenger.send('pushRedo')
 
@@ -462,6 +469,20 @@ class DirectSession(PandaObject):
     def hideReadout(self):
 	self.readout.reparentTo(hidden)
 
+    def reparentWidgetTo(self, parent):
+        if parent == 'direct':
+            self.widget.reparentTo(direct.group)
+            self.widgetParent = 'direct'
+        else:
+            self.widget.reparentTo(hidden)
+            self.widgetParent = 'hidden'
+
+    def toggleWidgetVis(self):
+        if self.widgetParent == 'direct':
+            self.reparentWidgetTo('hidden')
+        else:
+            self.reparentWidgetTo('direct')
+
 class DisplayRegionList:
     def __init__(self):
         self.displayRegionList = []

+ 38 - 0
direct/src/showbase/Messenger.py

@@ -113,12 +113,46 @@ class Messenger:
         """
         self.dict.clear()
 
+    def listAllEvents(self):
+        str = 'Messenger\n'
+        str = str + '='*50 + '\n'
+        keys = self.dict.keys()
+        keys.sort()
+        for event in keys:
+            str = str + 'Event: ' + event + '\n'
+        str = str + '='*50 + '\n'
+        print str
+
     def __repr__(self):
         """__repr__(self)
         Print out the table in a readable format
         """
         str = 'Messenger\n'
         str = str + '='*50 + '\n'
+        keys = self.dict.keys()
+        keys.sort()
+        for event in keys:
+            acceptorDict = self.dict[event]
+            str = str + 'Event: ' + event + '\n'
+            for object in acceptorDict.keys():
+                method, extraArgs, persistent = acceptorDict[object]
+                className = object.__class__.__name__
+                methodName = method.__name__
+                str = (str + '\t' +
+                       'Acceptor:   ' + className + ' instance' + '\n\t' +
+                       'Method:     ' + methodName + '\n\t' +
+                       'Extra Args: ' + `extraArgs` + '\n\t' +
+                       'Persistent: ' + `persistent` + '\n\n'
+                       )
+        str = str + '='*50 + '\n'
+        return str
+
+    def __reprehensible__(self):
+        """__repr__(self)
+        Old way to print out the table in a readable format
+        """
+        str = 'Messenger\n'
+        str = str + '='*50 + '\n'
         for event in self.dict.keys():
             acceptorDict = self.dict[event]
             str = str + event + '\n'
@@ -129,3 +163,7 @@ class Messenger:
         return str
 
 
+
+
+
+