mirror of
https://github.com/panda3d/panda3d.git
synced 2026-04-25 16:09:05 -05:00
better support for multiple windows, multiple trackballs, one camera
This commit is contained in:
@@ -1184,7 +1184,7 @@ class DisplayRegionContext(DirectObject):
|
||||
|
||||
# Values for this frame
|
||||
# This ranges from -1 to 1
|
||||
if (base.mouseWatcherNode.hasMouse()):
|
||||
if base.mouseWatcherNode and base.mouseWatcherNode.hasMouse():
|
||||
self.mouseX = base.mouseWatcherNode.getMouseX()
|
||||
self.mouseY = base.mouseWatcherNode.getMouseY()
|
||||
self.mouseX = (self.mouseX-self.originX)*self.scaleX
|
||||
|
||||
@@ -14,7 +14,7 @@ from ObjectPaletteBase import ObjectGen
|
||||
# python wrapper around a panda.NodePath object
|
||||
class PythonNodePath(NodePath):
|
||||
def __init__(self,node):
|
||||
NodePath.NodePath.__init__(self,node)
|
||||
NodePath.__init__(self,node)
|
||||
|
||||
class ObjectMgrBase:
|
||||
""" ObjectMgr will create, manage, update objects in the scene """
|
||||
|
||||
+123
-79
@@ -246,6 +246,13 @@ class ShowBase(DirectObject.DirectObject):
|
||||
props.setRawMice(1)
|
||||
self.openDefaultWindow(startDirect = False, props=props)
|
||||
|
||||
# The default is trackball mode, which is more convenient for
|
||||
# ad-hoc development in Python using ShowBase. Applications
|
||||
# can explicitly call base.useDrive() if they prefer a drive
|
||||
# interface.
|
||||
self.mouseInterface = self.trackball
|
||||
self.useTrackball()
|
||||
|
||||
self.loader = Loader.Loader(self)
|
||||
self.graphicsEngine.setDefaultLoader(self.loader.loader)
|
||||
|
||||
@@ -566,19 +573,102 @@ class ShowBase(DirectObject.DirectObject):
|
||||
|
||||
def openWindow(self, props = None, pipe = None, gsg = None,
|
||||
type = None, name = None, size = None, aspectRatio = None,
|
||||
makeCamera = 1, keepCamera = 0,
|
||||
scene = None, stereo = None, rawmice = 0,
|
||||
callbackWindowDict = None):
|
||||
makeCamera = True, keepCamera = False,
|
||||
scene = None, stereo = None,
|
||||
callbackWindowDict = None, requireWindow = None):
|
||||
"""
|
||||
Creates a window and adds it to the list of windows that are
|
||||
to be updated every frame.
|
||||
|
||||
props is the WindowProperties that describes the window.
|
||||
|
||||
type is either 'onscreen', 'offscreen', or 'none'.
|
||||
|
||||
If keepCamera is true, the existing base.cam is set up to
|
||||
render into the new window.
|
||||
|
||||
If keepCamera is false but makeCamera is true, a new camera is
|
||||
set up to render into the new window.
|
||||
|
||||
If callbackWindowDict is not None, a CallbackGraphicWindow is
|
||||
created instead, which allows the caller to create the actual
|
||||
window with its own OpenGL context, and direct Panda's
|
||||
rendering into that window.
|
||||
|
||||
If requireWindow is true, it means that the function should
|
||||
raise an exception if the window fails to open correctly.
|
||||
|
||||
"""
|
||||
|
||||
# Save this lambda here for convenience; we'll use it to call
|
||||
# down to the underlying _doOpenWindow() with all of the above
|
||||
# parameters.
|
||||
func = lambda : self._doOpenWindow(
|
||||
props = props, pipe = pipe, gsg = gsg,
|
||||
type = type, name = name, size = size, aspectRatio = aspectRatio,
|
||||
makeCamera = makeCamera, keepCamera = keepCamera,
|
||||
scene = scene, stereo = stereo,
|
||||
callbackWindowDict = callbackWindowDict)
|
||||
|
||||
if self.win:
|
||||
# If we've already opened a window before, this is just a
|
||||
# pass-through to _doOpenWindow().
|
||||
win = func()
|
||||
self.graphicsEngine.openWindows()
|
||||
return win
|
||||
|
||||
if type is None:
|
||||
type = self.windowType
|
||||
if requireWindow is None:
|
||||
requireWindow = self.requireWindow
|
||||
|
||||
win = func()
|
||||
|
||||
# Give the window a chance to truly open.
|
||||
self.graphicsEngine.openWindows()
|
||||
if win != None and not win.isValid():
|
||||
self.notify.info("Window did not open, removing.")
|
||||
self.closeWindow(win)
|
||||
win = None
|
||||
|
||||
if win == None and pipe == None:
|
||||
# Try a little harder if the window wouldn't open.
|
||||
self.makeAllPipes()
|
||||
try:
|
||||
self.pipeList.remove(self.pipe)
|
||||
except ValueError:
|
||||
pass
|
||||
while self.win == None and self.pipeList:
|
||||
self.pipe = self.pipeList[0]
|
||||
self.notify.info("Trying pipe type %s (%s)" % (
|
||||
self.pipe.getType(), self.pipe.getInterfaceName()))
|
||||
win = func()
|
||||
|
||||
self.graphicsEngine.openWindows()
|
||||
if win != None and not win.isValid():
|
||||
self.notify.info("Window did not open, removing.")
|
||||
self.closeWindow(win)
|
||||
win = None
|
||||
if win == None:
|
||||
self.pipeList.remove(self.pipe)
|
||||
|
||||
if win == None:
|
||||
self.notify.warning("Unable to open '%s' window." % (type))
|
||||
if requireWindow:
|
||||
# Unless require-window is set to false, it is an
|
||||
# error not to open a window.
|
||||
raise StandardError, 'Could not open window.'
|
||||
else:
|
||||
self.notify.info("Successfully opened window of type %s (%s)" % (
|
||||
win.getType(), win.getPipe().getInterfaceName()))
|
||||
|
||||
return win
|
||||
|
||||
def _doOpenWindow(self, props = None, pipe = None, gsg = None,
|
||||
type = None, name = None, size = None, aspectRatio = None,
|
||||
makeCamera = True, keepCamera = False,
|
||||
scene = None, stereo = None,
|
||||
callbackWindowDict = None):
|
||||
if pipe == None:
|
||||
pipe = self.pipe
|
||||
|
||||
@@ -769,69 +859,13 @@ class ShowBase(DirectObject.DirectObject):
|
||||
if 'startDirect' in kw:
|
||||
del kw['startDirect']
|
||||
|
||||
if self.win:
|
||||
# If we've already opened a window before, this does
|
||||
# little more work than openMainWindow() alone.
|
||||
self.openMainWindow(*args, **kw)
|
||||
self.graphicsEngine.openWindows()
|
||||
return
|
||||
|
||||
self.openMainWindow(*args, **kw)
|
||||
|
||||
# Give the window a chance to truly open.
|
||||
self.graphicsEngine.openWindows()
|
||||
if self.win != None and not self.isMainWindowOpen():
|
||||
self.notify.info("Window did not open, removing.")
|
||||
self.closeWindow(self.win)
|
||||
|
||||
if self.win == None and not kw.get('pipe', None):
|
||||
# Try a little harder if the window wouldn't open.
|
||||
self.makeAllPipes()
|
||||
try:
|
||||
self.pipeList.remove(self.pipe)
|
||||
except ValueError:
|
||||
pass
|
||||
while self.win == None and self.pipeList:
|
||||
self.pipe = self.pipeList[0]
|
||||
self.notify.info("Trying pipe type %s (%s)" % (
|
||||
self.pipe.getType(), self.pipe.getInterfaceName()))
|
||||
self.openMainWindow(*args, **kw)
|
||||
|
||||
self.graphicsEngine.openWindows()
|
||||
if self.win != None and not self.isMainWindowOpen():
|
||||
self.notify.info("Window did not open, removing.")
|
||||
self.closeWindow(self.win)
|
||||
if self.win == None:
|
||||
self.pipeList.remove(self.pipe)
|
||||
|
||||
if self.win == None:
|
||||
self.notify.warning("Unable to open '%s' window." % (
|
||||
self.windowType))
|
||||
if self.requireWindow:
|
||||
# Unless require-window is set to false, it is an
|
||||
# error not to open a window.
|
||||
raise StandardError, 'Could not open window.'
|
||||
else:
|
||||
self.notify.info("Successfully opened window of type %s (%s)" % (
|
||||
self.win.getType(), self.win.getPipe().getInterfaceName()))
|
||||
|
||||
# The default is trackball mode, which is more convenient for
|
||||
# ad-hoc development in Python using ShowBase. Applications
|
||||
# can explicitly call base.useDrive() if they prefer a drive
|
||||
# interface.
|
||||
self.mouseInterface = self.trackball
|
||||
self.useTrackball()
|
||||
|
||||
if startDirect:
|
||||
self.__doStartDirect()
|
||||
|
||||
return self.win != None
|
||||
|
||||
def isMainWindowOpen(self):
|
||||
if self.win != None:
|
||||
return self.win.isValid()
|
||||
return 0
|
||||
|
||||
def openMainWindow(self, *args, **kw):
|
||||
"""
|
||||
Creates the initial, main window for the application, and sets
|
||||
@@ -1148,11 +1182,7 @@ class ShowBase(DirectObject.DirectObject):
|
||||
win = self.win
|
||||
|
||||
if win != None and win.hasSize():
|
||||
# Temporary hasattr for old Pandas
|
||||
if not hasattr(win, 'getSbsLeftXSize'):
|
||||
aspectRatio = float(win.getXSize()) / float(win.getYSize())
|
||||
else:
|
||||
aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
|
||||
aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
|
||||
|
||||
else:
|
||||
if win == None or not hasattr(win, "getRequestedProperties"):
|
||||
@@ -1216,6 +1246,8 @@ class ShowBase(DirectObject.DirectObject):
|
||||
self.camera.node().setPreserveTransform(ModelNode.PTLocal)
|
||||
__builtin__.camera = self.camera
|
||||
|
||||
self.mouse2cam.node().setNode(self.camera.node())
|
||||
|
||||
if useCamera:
|
||||
# Use the existing camera node.
|
||||
cam = useCamera
|
||||
@@ -1381,7 +1413,13 @@ class ShowBase(DirectObject.DirectObject):
|
||||
self.dataRoot = NodePath('dataRoot')
|
||||
# Cache the node so we do not ask for it every frame
|
||||
self.dataRootNode = self.dataRoot.node()
|
||||
self.dataUnused = NodePath('dataUnused')
|
||||
|
||||
# Now we have the main trackball & drive interfaces.
|
||||
# useTrackball() and useDrive() switch these in and out; only
|
||||
# one is in use at a given time.
|
||||
self.trackball = NodePath(Trackball('trackball'))
|
||||
self.drive = NodePath(DriveInterface('drive'))
|
||||
self.mouse2cam = NodePath(Transform2SG('mouse2cam'))
|
||||
|
||||
# [gjeon] now you can create multiple mouse watchers to support multiple windows
|
||||
def setupMouse(self, win, fMultiWin=False):
|
||||
@@ -1389,6 +1427,15 @@ class ShowBase(DirectObject.DirectObject):
|
||||
Creates the structures necessary to monitor the mouse input,
|
||||
using the indicated window. If the mouse has already been set
|
||||
up for a different window, those structures are deleted first.
|
||||
|
||||
The return value is the ButtonThrower NodePath created for
|
||||
this window.
|
||||
|
||||
If fMultiWin is true, then the previous mouse structures are
|
||||
not deleted; instead, multiple windows are allowed to monitor
|
||||
the mouse input. However, in this case, the trackball
|
||||
controls are not set up, and must be set up by hand if
|
||||
desired.
|
||||
"""
|
||||
if not fMultiWin and self.buttonThrowers != None:
|
||||
for bt in self.buttonThrowers:
|
||||
@@ -1409,6 +1456,9 @@ class ShowBase(DirectObject.DirectObject):
|
||||
self.mouseWatcher = self.buttonThrowers[0].getParent()
|
||||
self.mouseWatcherNode = self.mouseWatcher.node()
|
||||
|
||||
if self.mouseInterface:
|
||||
self.mouseInterface.reparentTo(self.mouseWatcher)
|
||||
|
||||
if self.recorder:
|
||||
# If we have a recorder, the mouseWatcher belongs under a
|
||||
# special MouseRecorder node, which may intercept the
|
||||
@@ -1420,14 +1470,6 @@ class ShowBase(DirectObject.DirectObject):
|
||||
np = mw.getParent().attachNewNode(mouseRecorder)
|
||||
mw.reparentTo(np)
|
||||
|
||||
# Now we have the main trackball & drive interfaces.
|
||||
# useTrackball() and useDrive() switch these in and out; only
|
||||
# one is in use at a given time.
|
||||
self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
|
||||
self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
|
||||
self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
|
||||
self.mouse2cam.node().setNode(self.camera.node())
|
||||
|
||||
# A special ButtonThrower to generate keyboard events and
|
||||
# include the time from the OS. This is separate only to
|
||||
# support legacy code that did not expect a time parameter; it
|
||||
@@ -1445,6 +1487,8 @@ class ShowBase(DirectObject.DirectObject):
|
||||
self.pixel2dp.node().setMouseWatcher(mw.node())
|
||||
mw.node().addRegion(PGMouseWatcherBackground())
|
||||
|
||||
return self.buttonThrowers[0]
|
||||
|
||||
# [gjeon] this function is seperated from setupMouse to allow multiple mouse watchers
|
||||
def setupMouseCB(self, win):
|
||||
# For each mouse/keyboard device, we create
|
||||
@@ -1471,8 +1515,7 @@ class ShowBase(DirectObject.DirectObject):
|
||||
mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
|
||||
mw = mk.attachNewNode(MouseWatcher("watcher%s" % (i)))
|
||||
|
||||
# Temporary hasattr for old Pandas
|
||||
if hasattr(win, 'getSideBySideStereo') and win.getSideBySideStereo():
|
||||
if win.getSideBySideStereo():
|
||||
# If the window has side-by-side stereo enabled, then
|
||||
# we should constrain the MouseWatcher to the window's
|
||||
# DisplayRegion. This will enable the MouseWatcher to
|
||||
@@ -1991,7 +2034,7 @@ class ShowBase(DirectObject.DirectObject):
|
||||
# object out of there, so we won't be updating the camera any
|
||||
# more.
|
||||
if self.mouse2cam:
|
||||
self.mouse2cam.reparentTo(self.dataUnused)
|
||||
self.mouse2cam.detachNode()
|
||||
|
||||
def enableMouse(self):
|
||||
"""
|
||||
@@ -2029,12 +2072,13 @@ class ShowBase(DirectObject.DirectObject):
|
||||
Switch mouse action
|
||||
"""
|
||||
# Get rid of the prior interface:
|
||||
self.mouseInterface.reparentTo(self.dataUnused)
|
||||
self.mouseInterface.detachNode()
|
||||
# Update the mouseInterface to point to the drive
|
||||
self.mouseInterface = changeTo
|
||||
self.mouseInterfaceNode = self.mouseInterface.node()
|
||||
# Hookup the drive to the camera.
|
||||
self.mouseInterface.reparentTo(self.mouseWatcher)
|
||||
if self.mouseWatcher:
|
||||
self.mouseInterface.reparentTo(self.mouseWatcher)
|
||||
if self.mouse2cam:
|
||||
self.mouse2cam.reparentTo(self.mouseInterface)
|
||||
|
||||
@@ -2157,7 +2201,7 @@ class ShowBase(DirectObject.DirectObject):
|
||||
self.oobeLens.setNearFar(0.1, 10000.0)
|
||||
self.oobeLens.setMinFov(40)
|
||||
|
||||
self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
|
||||
self.oobeTrackball = NodePath(Trackball('oobeTrackball'), 1)
|
||||
self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
|
||||
self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
|
||||
|
||||
|
||||
@@ -35,12 +35,7 @@ class EmbeddedPandaWindow(wx.Window):
|
||||
if platform.system() != 'Darwin':
|
||||
wp.setParentWindow(self.GetHandle())
|
||||
|
||||
if base.win:
|
||||
self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen')
|
||||
else:
|
||||
base.openDefaultWindow(props = wp, gsg = gsg, type = 'onscreen')
|
||||
self.win = base.win
|
||||
|
||||
self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen')
|
||||
self.Bind(wx.EVT_SIZE, self.onSize)
|
||||
|
||||
# This doesn't actually do anything, since wx won't call
|
||||
|
||||
@@ -52,7 +52,9 @@ transmit_data(DataGraphTraverser *trav,
|
||||
const DataConnection &connect = (*ci);
|
||||
const EventParameter &data =
|
||||
inputs[connect._parent_index].get_data(connect._output_index);
|
||||
new_input.set_data(connect._input_index, data);
|
||||
if (!data.is_empty()) {
|
||||
new_input.set_data(connect._input_index, data);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -308,7 +310,7 @@ reconnect() {
|
||||
dgraph_cat.warning()
|
||||
<< "Ignoring mismatched type for connection " << name
|
||||
<< " between " << *data_node << " and " << *this << "\n";
|
||||
} else if (num_found == 1) {
|
||||
} else {
|
||||
DataConnection dc;
|
||||
dc._parent_index = i;
|
||||
dc._output_index = output_def._index;
|
||||
@@ -320,9 +322,11 @@ reconnect() {
|
||||
}
|
||||
|
||||
if (num_found > 1) {
|
||||
dgraph_cat.warning()
|
||||
<< "Multiple connections found for " << name << " into " << *this
|
||||
<< "\n";
|
||||
if (dgraph_cat.is_debug()) {
|
||||
dgraph_cat.debug()
|
||||
<< "Multiple connections found for " << name << " into " << *this
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user