Browse Source

*** empty log message ***

Mark Mine 24 years ago
parent
commit
11cdb8a6be

+ 4 - 1
direct/src/directdevices/DirectJoybox.py

@@ -55,7 +55,10 @@ class DirectJoybox(PandaObject):
         self.refCS = direct.cameraControl.coaMarker
         self.tempCS = direct.group.attachNewNode('JoyboxTempCS')
         # Text object to display current mode
-        self.readout = OnscreenText.OnscreenText( pos = (-0.9, 0.95) )
+        self.readout = OnscreenText.OnscreenText(
+            pos = (-0.9, 0.95),
+            font = direct.font,
+            mayChange = 1)
         # List of functions to cycle through
         self.modeList = [self.joeMode, self.driveMode, self.orbitMode]
         # Pick initial mode

+ 96 - 43
direct/src/directtools/DirectCameraControl.py

@@ -8,10 +8,11 @@ Y_AXIS = Vec3(0,1,0)
 class DirectCameraControl(PandaObject):
     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.coaDist = 100
         self.coaMarker = loader.loadModel('models/misc/sphere')
         self.coaMarker.setName('DirectCameraCOAMarker')
         self.coaMarker.setTransparency(1)
@@ -19,7 +20,10 @@ class DirectCameraControl(PandaObject):
         self.coaMarker.setPos(0,100,0)
         useDirectRenderStyle(self.coaMarker)
         self.coaMarkerPos = Point3(0)
-        self.fUpdateCOA = 1
+        self.fLockCOA = 1
+        self.nullHitPtCount = 0
+        self.cqEntries = []
+        self.coaMarkerRef = direct.group.attachNewNode('coaMarkerRef')
 	self.camManipRef = direct.group.attachNewNode('camManipRef')
         t = CAM_MOVE_DURATION
         self.actionEvents = [
@@ -64,13 +68,9 @@ class DirectCameraControl(PandaObject):
             # MOUSE IS IN CENTRAL REGION
             # Hide the marker for this kind of motion
             self.coaMarker.hide()
-            # Check for a hit point based on
-            # current mouse position
-            # Allow intersection with unpickable objects
-            # And then spawn task to determine mouse mode
-            node, hitPt, hitPtDist = direct.iRay.pickGeom(
-                fIntersectUnpickable = 1)
-            self.computeCOA(node, hitPt, hitPtDist)
+            # Record time of start of mouse interaction
+            self.startT= globalClock.getFrameTime()
+            self.startF = globalClock.getFrameCount()
             # Start manipulation
             self.spawnXZTranslateOrHPanYZoom()
             # END MOUSE IN CENTRAL REGION
@@ -85,6 +85,24 @@ class DirectCameraControl(PandaObject):
 
     def mouseFlyStop(self):
 	taskMgr.removeTasksNamed('manipulateCamera')
+        stopT = globalClock.getFrameTime()
+        deltaT = stopT - self.startT
+        stopF = globalClock.getFrameCount()
+        deltaF = stopF - self.startF
+        if (deltaT <= 0.25) or (deltaF <= 1):
+            # Check for a hit point based on
+            # current mouse position
+            # Allow intersection with unpickable objects
+            # And then spawn task to determine mouse mode
+            node, hitPt, hitPtDist = direct.iRay.pickGeom(
+                fIntersectUnpickable = 1, fIgnoreCamera = 1)
+            self.computeCOA(node, hitPt, hitPtDist)
+            # Record reference point
+            self.coaMarkerRef.iPosHprScale(direct.iRay.collisionRef)
+            # Record entries
+            self.cqEntries = []
+            for i in range(direct.iRay.cq.getNumEntries()):
+                self.cqEntries.append(direct.iRay.cq.getEntry(i))
         # Show the marker
         self.coaMarker.show()
         # Resize it
@@ -96,7 +114,8 @@ class DirectCameraControl(PandaObject):
         # Spawn the new task
         t = Task.Task(self.XZTranslateOrHPanYZoomTask)
         # For HPanYZoom
-        t.zoomSF = Vec3(self.coa).length()
+        coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
+        t.zoomSF = (coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     def spawnXZTranslateOrHPPan(self):
@@ -117,7 +136,8 @@ class DirectCameraControl(PandaObject):
 	taskMgr.removeTasksNamed('manipulateCamera')
         # Spawn new task
         t = Task.Task(self.HPanYZoomTask)
-        t.zoomSF = Vec3(self.coa).length()
+        coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
+        t.zoomSF = (coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     def spawnHPPan(self):
@@ -155,15 +175,17 @@ class DirectCameraControl(PandaObject):
 
     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()
+        else:
+            moveDir = Vec3(Y_AXIS)
         moveDir.assign(moveDir * (-2.0 * direct.dr.mouseDeltaY *
                                         state.zoomSF))
+        if direct.dr.mouseDeltaY > 0.0:
+            moveDir.setY(moveDir[1] * 1.0)
         direct.camera.setPosHpr(direct.camera,
                                 moveDir[0],
                                 moveDir[1],
@@ -247,54 +269,85 @@ class DirectCameraControl(PandaObject):
         return Task.cont
 
     def lockCOA(self):
-        self.fUpdateCOA = 0
-
+        self.fLockCOA = 1
+        direct.message('COA Lock On')
+            
     def unlockCOA(self):
-        self.fUpdateCOA = 1
+        self.fLockCOA = 0
+        direct.message('COA Lock Off')
 
     def toggleCOALock(self):
-        self.fUpdateCOA = 1 - self.fUpdateCOA
+        self.fLockCOA = 1 - self.fLockCOA
+        if self.fLockCOA:
+            direct.message('COA Lock On')
+        else:
+            direct.message('COA Lock Off')
 
     def pickNextCOA(self):
         """ Cycle through collision handler entries """
-        node, hitPt, hitPtDist = direct.iRay.pickNext()
-        self.computeCOA(node, hitPt, hitPtDist)
+        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
+            node = entry.getIntoNode()
+            nodePath = render.findPathDownTo(node)
+            if camera not in nodePath.getAncestry():
+                # Compute hit point
+                hitPt = direct.iRay.parentToHitPt(entry)
+                # Move coa marker to new point
+                self.updateCoa(hitPt, ref = self.coaMarkerRef)
+            else:
+                # Remove offending entry
+                self.cqEntries = self.cqEntries[:-1]
+                self.pickNextCOA()
 
     def computeCOA(self, node, hitPt, hitPtDist):
         coa = Point3(0)
-        if self.fUpdateCOA and node:
+        if self.fLockCOA:
+            # COA is locked, use existing point
+            # Use existing point
+            coa.assign(self.coaMarker.getPos(direct.camera))
+            # Reset hit point count
+            self.nullHitPointCount = 0
+        elif node:
+            # Got a hit point (hit point is in camera coordinates)
             # Set center of action
             coa.assign(hitPt)
-            coaDist = hitPtDist
             # Handle case of bad coa point (too close or too far)
-            if ((coaDist < (1.1 * direct.dr.near)) or
-                (coaDist > direct.dr.far)):
+            if ((hitPtDist < (1.1 * direct.dr.near)) or
+                (hitPtDist > direct.dr.far)):
                 # Just use existing point
                 coa.assign(self.coaMarker.getPos(direct.camera))
-                coaDist = Vec3(coa - ZERO_POINT).length()
-                if coaDist < (1.1 * direct.dr.near):
-                    coa.set(0,100,0)
-                    coaDist = 100
+            # Reset hit point count
+            self.nullHitPointCount = 0
         else:
-            # If no intersection point or COA is locked:
-            # 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
+            # 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)
+            direct.message('COA Distance: ' + `dist`)
+            coa.set(0,dist,0)
+        # Compute COA Dist
+        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)
+        self.updateCoa(coa, coaDist = coaDist)
 
-    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 - ZERO_POINT).length()
+    def updateCoa(self, ref2point, coaDist = None, ref = None):
+        self.coa.set(ref2point[0], ref2point[1], ref2point[2])
+        if not coaDist:
+            coaDist = Vec3(self.coa - ZERO_POINT).length()
         # Place the marker in render space
-        self.coaMarker.setPos(direct.camera,self.coa)
+        if ref == None:
+            ref = base.cam
+        self.coaMarker.setPos(ref, self.coa)
         # Resize it
         self.updateCoaMarkerSize(coaDist)
         # Record marker pos in render space

+ 24 - 9
direct/src/directtools/DirectManipulation.py

@@ -231,7 +231,7 @@ class DirectManipulationControl(PandaObject):
             else:
                 # Mouse started in central region, xlate
                 # Mode depends on shift key
-                if direct.fShift:
+                if direct.fShift or direct.fControl:
                     self.xlateCamXY(state)
                 else:
                     self.xlateCamXZ(state)
@@ -367,7 +367,11 @@ class DirectManipulationControl(PandaObject):
 
     def xlateCamXY(self, state):
         """Constrained 2D motion perpendicular to camera's image plane
-        This moves the object in the camera's XY plane"""
+        This moves the object in the camera's XY plane if shift is held
+        Moves object toward camera if control is held
+        """
+        # Reset scaling init flag
+        self.fScaleInit = 1
         # Now, where is the widget relative to current camera view
         vWidget2Camera = direct.widget.getPos(direct.camera)
         # If this is first time around, record initial y distance
@@ -378,15 +382,26 @@ class DirectManipulationControl(PandaObject):
             # Get widget's current xy coords in screen space
             coaCenter = getNearProjectionPoint(direct.widget)
             self.deltaNearX = coaCenter[0] - direct.dr.nearVec[0]
-        # Reset scaling init flag
-        self.fScaleInit = 1
+        # Which way do we move the object?
+        if direct.fControl:
+            moveDir = Vec3(vWidget2Camera)
+            # If widget is behind camera invert vector
+            if moveDir[1] < 0.0:
+                moveDir.assign(moveDir * -1)
+            moveDir.normalize()
+        else:
+            moveDir = Vec3(Y_AXIS)
         # Move selected objects
         dr = direct.dr
-        # Move object in y axis based on mouse motion
-        newY = vWidget2Camera[1] + self.xlateSF * dr.mouseDeltaY
-        # Put object at same relative point to mouse in X
-        newX = (direct.dr.nearVec[0] + self.deltaNearX) * (newY/dr.near)
-        direct.widget.setPos(direct.camera, newX, newY, vWidget2Camera[2])
+        # Scale move dir
+        moveDir.assign(moveDir * (2.0 * dr.mouseDeltaY * self.xlateSF))
+        # Add it to current widget offset
+        vWidget2Camera += moveDir
+        # The object, however, stays at the same relative point to mouse in X
+        vWidget2Camera.setX((dr.nearVec[0] + self.deltaNearX) *
+                            (vWidget2Camera[1]/dr.near))
+        # Move widget
+        direct.widget.setPos(direct.camera, vWidget2Camera)
 
     def rotate2D(self, state):
         """ Virtual trackball rotation of widget """

+ 21 - 9
direct/src/directtools/DirectSelection.py

@@ -403,6 +403,8 @@ class SelectionRay:
         self.ct = CollisionTraverser( RenderRelation.getClassType() )
         # Let the traverser know about the queue and the collision node
         self.ct.addCollider(self.rayCollisionNode, self.cq )
+        # Reference node path (for picking next)
+        self.collisionRef = direct.group.attachNewNode('collisionRef')
         # List of objects that can't be selected
         self.unpickable = UNPICKABLE
 
@@ -414,7 +416,8 @@ class SelectionRay:
         if item in self.unpickable:
             self.unpickable.remove(item)
 
-    def pickGeom(self, targetNodePath = render, fIntersectUnpickable = 0):
+    def pickGeom(self, targetNodePath = render, fIntersectUnpickable = 0,
+                 fIgnoreCamera = 0):
         self.collideWithGeom()
         self.pick(targetNodePath,
                   direct.dr.mouseX,
@@ -425,9 +428,13 @@ class SelectionRay:
         for i in range(0,self.numEntries):
             entry = self.cq.getEntry(i)
             node = entry.getIntoNode()
+            nodePath = render.findPathDownTo(node)
             # Don't pick hidden nodes
             if node.isHidden():
                 pass
+            elif fIgnoreCamera and (camera in nodePath.getAncestry()):
+                # This avoids things parented to a camera.  Good idea?
+                pass
             # Can pick unpickable, use the first visible node
             elif fIntersectUnpickable:
                 self.cqIndex = i
@@ -448,7 +455,8 @@ class SelectionRay:
         if(self.cqIndex >= 0):
             # Yes!
             # Find hit point in parent's space
-            hitPt = self.parentToHitPt(self.cqIndex)
+            entry = self.cq.getEntry(self.cqIndex)
+            hitPt = self.parentToHitPt(entry)
             hitPtDist = Vec3(hitPt - ZERO_POINT).length()
             return (node, hitPt, hitPtDist)
         else:
@@ -465,7 +473,8 @@ class SelectionRay:
             # Entry 0 is the closest hit point if multiple hits
             minPt = 0
             # Find hit point in parent's space
-            hitPt = self.parentToHitPt(minPt)
+            entry = self.cq.getEntry(minPt)
+            hitPt = self.parentToHitPt(entry)
             hitPtDist = Vec3(hitPt).length()
             # Get the associated collision queue object
             entry = self.cq.getEntry(minPt)
@@ -483,15 +492,18 @@ class SelectionRay:
         self.ct.traverse( targetNodePath.node() )
         self.numEntries = self.cq.getNumEntries()
         self.cq.sortEntries()
+        # Record cam's current position (used for cycling through
+        # other hit points)
+        self.collisionRef.iPosHprScale(base.cam)
         return self.numEntries
 
-    def pickNext(self):
+    def getHitPt(self, entry):
         if self.cqIndex >= 0:
             self.cqIndex = (self.cqIndex + 1) % self.numEntries
             entry = self.cq.getEntry(self.cqIndex)
             node = entry.getIntoNode()
             # Find hit point in parent's space
-            hitPt = self.parentToHitPt(self.cqIndex)
+            hitPt = self.parentToHitPt(entry)
             hitPtDist = Vec3(hitPt - ZERO_POINT).length()
             return (node, hitPt, hitPtDist)
         else:
@@ -530,7 +542,8 @@ class SelectionRay:
         if(self.cqIndex >= 0):
             # Yes!
             # Find hit point in parent's space
-            hitPt = self.parentToHitPt(self.cqIndex)
+            entry = self.cq.getEntry(self.cqIndex)
+            hitPt = self.parentToHitPt(entry)
             hitPtDist = Vec3(hitPt - ZERO_POINT).length()
             return (node, hitPt, hitPtDist)
         else:
@@ -561,9 +574,8 @@ class SelectionRay:
     def objectToHitPt(self, index):
         return self.cq.getEntry(index).getIntoIntersectionPoint()
 
-    def parentToHitPt(self, index):
-        # Get the specified entry
-        entry = self.cq.getEntry(index)
+    def parentToHitPt(self, entry):
+        # Get hit point
         hitPt = entry.getIntoIntersectionPoint()
         # Convert point from object local space to parent's space
         return entry.getInvWrtSpace().xformPoint(hitPt)

+ 35 - 3
direct/src/directtools/DirectSession.py

@@ -23,6 +23,10 @@ class DirectSession(PandaObject):
         # Establish a global pointer to the direct object early on
         # so dependant classes can access it in their code
         __builtin__.direct = self
+        # These come early since they are used later on
+        self.group = render.attachNewNode('DIRECT')
+        self.font = loader.loadFont("models/fonts/Comic")
+        
         self.fEnabled = 0
         self.drList = DisplayRegionList()
         self.iRayList = map(lambda x: x.iRay, self.drList)
@@ -30,7 +34,6 @@ class DirectSession(PandaObject):
         self.camera = self.dr.camera
         self.iRay = self.dr.iRay
 
-        self.group = render.attachNewNode('DIRECT')
         self.cameraControl = DirectCameraControl()
         self.manipulationControl = DirectManipulationControl()
         self.useObjectHandles()
@@ -51,18 +54,28 @@ class DirectSession(PandaObject):
 
         self.selectedNPReadout = OnscreenText.OnscreenText(
             pos = (-1.0, -0.9), bg=Vec4(1,1,1,1),
-            scale = 0.05, align = TMALIGNLEFT)
+            scale = 0.05, align = TMALIGNLEFT,
+            mayChange = 1, font = self.font)
         # Make sure readout is never lit or drawn in wireframe
         useDirectRenderStyle(self.selectedNPReadout)
         self.selectedNPReadout.reparentTo( hidden )
 
         self.activeParentReadout = OnscreenText.OnscreenText(
             pos = (-1.0, -0.975), bg=Vec4(1,1,1,1),
-            scale = 0.05, align = TMALIGNLEFT)
+            scale = 0.05, align = TMALIGNLEFT,
+            mayChange = 1, font = self.font)
         # Make sure readout is never lit or drawn in wireframe
         useDirectRenderStyle(self.activeParentReadout)
         self.activeParentReadout.reparentTo( hidden )
 
+        self.directMessageReadout = OnscreenText.OnscreenText(
+            pos = (-1.0, 0.9), bg=Vec4(1,1,1,1),
+            scale = 0.05, align = TMALIGNLEFT,
+            mayChange = 1, font = self.font)
+        # Make sure readout is never lit or drawn in wireframe
+        useDirectRenderStyle(self.directMessageReadout)
+        self.directMessageReadout.reparentTo( hidden )
+
         # Create a vrpn client vrpn-server or default
         if base.config.GetBool('want-vrpn', 0):
             from DirectDeviceManager import *
@@ -291,6 +304,7 @@ class DirectSession(PandaObject):
             self.selectedNPReadout.reparentTo(aspect2d)
             self.selectedNPReadout.setText(
                 'Selected:' + dnp.name)
+            self.selectedNPReadout.adjustAllPriorities(100)
             # Show the manipulation widget
             self.widget.showWidget()
             # Update camera controls coa to this point
@@ -346,6 +360,7 @@ class DirectSession(PandaObject):
         self.activeParentReadout.reparentTo(aspect2d)
         self.activeParentReadout.setText(
             'Active Parent:' + nodePath.getName())
+        self.activeParentReadout.adjustAllPriorities(100)
         # Alert everyone else
         messenger.send('DIRECT_activeParent', [self.activeParent])
         
@@ -577,6 +592,23 @@ class DirectSession(PandaObject):
             messenger.send('DIRECT_redo')
 
     # UTILITY FUNCTIONS
+    def message(self, text):
+        taskMgr.removeTasksNamed('hideDirectMessage')
+        taskMgr.removeTasksNamed('hideDirectMessageLater')
+        self.directMessageReadout.reparentTo(aspect2d)
+        self.directMessageReadout.setText(text)
+        self.directMessageReadout.adjustAllPriorities(100)
+        self.hideDirectMessageLater()
+
+    def hideDirectMessageLater(self):
+        seq = Task.doLater(3.0, Task.Task(self.hideDirectMessage),
+                           'hideDirectMessage')
+        t = taskMgr.spawnTaskNamed(seq, 'hideDirectMessageLater')
+
+    def hideDirectMessage(self, state):
+        self.directMessageReadout.reparentTo(hidden)
+        return Task.done
+
     def useObjectHandles(self):
         self.widget = self.manipulationControl.objectHandles
         self.widget.reparentTo(direct.group)

+ 29 - 13
direct/src/doc/howto.DIRECT

@@ -35,7 +35,10 @@ Central Region:
       Up/Down/Left/Right: Moves object in plane parallel to camera's image
          plane 
    Shift + LMB:
-      Up/Down:  Moves object's COA towards camera
+      Up/Down:  Moves object's COA in camera's XY plane
+      Left/Right: Moves object parallel to camera's X axis
+   Control + LMB:
+      Up/Down:  Moves object's COA toward Camera (in Y and Z)
       Left/Right: Moves object parallel to camera's X axis
    Alt + LMB (off of widget):
       Away from COA: scale object up
@@ -70,9 +73,25 @@ COA.  Hit 'Tab' again to return to normal object manipulation mode.
 
 All camera manipulation performed with middle mouse button (MMB).
 
-Window is divided up into three regions: an outer frame, central region, and
-the four corners.  Camera manipulation depends on where mouse interaction
-begins.
+Camera manipulation depends on where mouse interaction begins and the
+current center of action (COA). 
+
+The window is divided up into three regions: an outer frame, central region,
+and the four corners.  A different manipulation mode is defined for each
+region and is described below.
+
+The camera center of action (COA) determines the center of rotation of any
+rotation moves and the scale factor for any translation moves.  
+
+The COA is set by quickly clicking (less than .25 second or 1 frame) with
+the MMB in the central region.  It is defined as the intersection point of
+the ray from the camera's origin, through the mouse with the model.  If no
+intersection occurs, the COA is put out along the camera's Y axis.  Each
+time the MMB is clicked with no intersection the COA moves further out
+along the Y axis.
+
+Pressing 'L' toggles COA lock, when on, the COA is locked in its current
+location.
 
 Central Region:
    MMB:
@@ -85,15 +104,12 @@ Central Region:
       Up/Down/Left/Right: shifts camera in image plane
 Outer Region:
    MMB:
-      Up/Down/Left/Right: Rotates about current COA.  The COA is set every
-        time you press the MMB in the central region.  It is defined as the
-         intersection point of the ray from the camera's origin, through
-         the mouse with the model (if no intersection, no change in the COA
-         will result).  If mouse stays within outer frame, motion about COA
-         is constrained to a single axis. (parallel to camera's X axis when
-         in left and right part of the frame and parallel to the camera's Z
-         axis when in the top or bottom part of the frame)
-   Shift MMB:
+      Up/Down/Left/Right: Rotates about current COA.  If mouse stays within
+      outer frame, motion about COA is constrained to a single axis.
+      (parallel to camera's X axis when in left and right part of the frame
+      and parallel to the camera's Z axis when in the top or bottom part of
+      the frame)
+   Shift + MMB:
       Up/Down: Pitch about camera's X axis
       Left/Right: Yaw about camera's Z axis
 Four corners: