From 11cdb8a6beecf10414fa237b778363e11233801d Mon Sep 17 00:00:00 2001 From: Mark Mine Date: Fri, 1 Jun 2001 03:46:46 +0000 Subject: [PATCH] *** empty log message *** --- direct/src/directdevices/DirectJoybox.py | 5 +- direct/src/directtools/DirectCameraControl.py | 149 ++++++++++++------ direct/src/directtools/DirectManipulation.py | 33 ++-- direct/src/directtools/DirectSelection.py | 30 ++-- direct/src/directtools/DirectSession.py | 38 ++++- direct/src/doc/howto.DIRECT | 42 +++-- 6 files changed, 214 insertions(+), 83 deletions(-) diff --git a/direct/src/directdevices/DirectJoybox.py b/direct/src/directdevices/DirectJoybox.py index 4db2e147d2..9b9b1bc092 100644 --- a/direct/src/directdevices/DirectJoybox.py +++ b/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 diff --git a/direct/src/directtools/DirectCameraControl.py b/direct/src/directtools/DirectCameraControl.py index 74d54bd8ee..6f57847e18 100644 --- a/direct/src/directtools/DirectCameraControl.py +++ b/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: - # 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)): - # 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 - else: - # If no intersection point or COA is locked: + if self.fLockCOA: + # COA is locked, use existing 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) - - def updateCoa(self, cam2point, coaDist = None): - self.coa.set(cam2point[0], cam2point[1], cam2point[2]) - if coaDist: - self.coaDist = coaDist + # 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) + # Handle case of bad coa point (too close or too far) + if ((hitPtDist < (1.1 * direct.dr.near)) or + (hitPtDist > direct.dr.far)): + # Just use existing point + coa.assign(self.coaMarker.getPos(direct.camera)) + # Reset hit point count + self.nullHitPointCount = 0 else: - self.coaDist = Vec3(self.coa - ZERO_POINT).length() + # 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 = coaDist) + + 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 diff --git a/direct/src/directtools/DirectManipulation.py b/direct/src/directtools/DirectManipulation.py index edeac68c7b..2b36a513c1 100644 --- a/direct/src/directtools/DirectManipulation.py +++ b/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 """ diff --git a/direct/src/directtools/DirectSelection.py b/direct/src/directtools/DirectSelection.py index ab5236e329..13cb4b4d1b 100644 --- a/direct/src/directtools/DirectSelection.py +++ b/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) diff --git a/direct/src/directtools/DirectSession.py b/direct/src/directtools/DirectSession.py index 6ed532db8f..bef24485e1 100644 --- a/direct/src/directtools/DirectSession.py +++ b/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) diff --git a/direct/src/doc/howto.DIRECT b/direct/src/doc/howto.DIRECT index bd5f4272c0..9f18fd221d 100644 --- a/direct/src/doc/howto.DIRECT +++ b/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: