Another way to do wxOgre

Istari

18-06-2006 14:14:10

Here is a little demo that I'm thinking of putting in the demos folder, so I would like to ask for your comments before I commit it.
It should enable you to change any wx.Window subclass into a render window, handle resizing and rendering.
The rendering will stop when wx is processing other events, but I like this better than overtaking App.MainLoop and having to always listen for the EVT_CLOSE event.
I know this works in Windows but haven't tested it in Linux, I'm pretty sure that it will not work correctly with SDL.

wxogre.pyimport atexit
import wx
from pyogre import ogre

class OgreManager(object):
_borgDict = None

def __init__(self):
if self.__class__._borgDict is None:
self.root = ogre.Root(ogre.getPluginPath())
self._configured = False
self._cameraIdCounter = 0

self.__class__._borgDict = self.__dict__
else:
self.__dict__ = self.__class__._borgDict

def _cleanup(self):
del self.root

def _bindWindowEvents(self, window, renderWindow):
def handleClose(event):
renderWindow.destroy()
event.Skip()
def handleIdle(event):
self.root.renderOneFrame()
event.RequestMore()
event.Skip()
def handleBackground(event):
self.root.renderOneFrame()
event.Skip()
def handleResize(event):
renderWindow.windowMovedOrResized()
self.root.renderOneFrame()
event.Skip()

window.Bind(wx.EVT_CLOSE, handleClose)
window.Bind(wx.EVT_IDLE, handleIdle)
window.Bind(wx.EVT_ERASE_BACKGROUND, handleBackground)
window.Bind(wx.EVT_SIZE, handleResize)

def configure(self):
return self.root.showConfigDialog()

def setupResources(self):
config = ogre.ConfigFile()
config.loadFromFile('resources.cfg')
for section, key, path in config.values:
ogre.ResourceGroupManager.getSingleton().addResourceLocation(path, key, section)

def chooseSceneManager(self):
self.sceneManager = self.root.createSceneManager(ogre.ST_GENERIC, "Scene Manager")

def createCamera(self, cameraName):
camera = self.sceneManager.createCamera(cameraName)
camera.position = (0, 0, 500)
camera.lookAt(ogre.Vector3(0, 0, -300))
camera.nearClipDistance = 5

return camera

def createViewport(self, renderWindow, camera):
viewport = renderWindow.addViewport(camera, 0, 0.0, 0.0, 1.0, 1.0)
viewport.backgroundColour = 0, 0, 0

return viewport

def initWindow(self, window, cameraName=None):
shouldInitResources = False

if not self._configured:
self.setupResources()
if not self.configure():
raise RuntimeError('Ogre was not configured')

self.root.initialise(False)
self.chooseSceneManager()
self._configured = True
shouldInitResources = True

renderParameters = ogre._StringStringMap()
renderParameters['externalWindowHandle'] = str(window.GetHandle())
renderWindow = self.root.createRenderWindow('RenderWindow_%i' % id(window), 640, 480, False, renderParameters)

if shouldInitResources:
ogre.ResourceGroupManager.getSingleton().initialiseAllResourceGroups()

if cameraName is None:
cameraName = 'Camera%i' % self._cameraIdCounter
self._cameraIdCounter += 1

camera = self.createCamera(cameraName)
viewport = self.createViewport(renderWindow, camera)
self._bindWindowEvents(window, renderWindow)

return (renderWindow, camera, viewport)

atexit.register(OgreManager()._cleanup)


test.pyimport wx
import wxogre
from pyogre import ogre

class MyFrame(wx.Frame):
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
self.ogreMgr = wxogre.OgreManager()

self.window1 = wx.Window(self, -1)
self.window2 = wx.Window(self, -1)
self.window3 = wx.Window(self, -1)
self.window4 = wx.Window(self, -1)
self.frame2 = wx.Frame(self, -1, 'Another wxOgre', size=(800,600))
self.frame2.Show(True)

sizer = wx.GridSizer(2, 2)
sizer.Add(self.window1, 0, wx.EXPAND)
sizer.Add(self.window2, 0, wx.EXPAND)
sizer.Add(self.window3, 0, wx.EXPAND)
sizer.Add(self.window4, 0, wx.EXPAND)
self.SetSizer(sizer)

(renderWindow1, camera1, viewport1) = self.ogreMgr.initWindow(self.window1)
(renderWindow2, camera2, viewport2) = self.ogreMgr.initWindow(self.window2)
(renderWindow3, camera3, viewport3) = self.ogreMgr.initWindow(self.window3)
(renderWindow4, camera4, viewport4) = self.ogreMgr.initWindow(self.window4)
(renderWindow5, camera5, viewport5) = self.ogreMgr.initWindow(self.frame2)

camera2.position = (500, 0, 0)
camera2.lookAt(ogre.Vector3(-300, 0, 0))
camera3.position = (0, 0, -500)
camera3.lookAt(ogre.Vector3(0, 0, 300))
camera4.position = (-500, 0, 0)
camera4.lookAt(ogre.Vector3(300, 0, 0))
camera5.position = (-500, 500, 0)
camera5.lookAt(ogre.Vector3(300, -300, 0))

self.createScene()

def createScene(self):
sceneManager = self.ogreMgr.sceneManager

sceneManager.ambientLight = 0.2, 0.2, 0.2
sceneManager.setSkyDome(True, 'Examples/CloudySky', 4.0, 8.0)

light = sceneManager.createLight('MainLight')
light.position = (20, 80, 50)

plane = ogre.Plane()
plane.normal = ogre.Vector3(0, 1, 0)
plane.d = 200
ogre.MeshManager.getSingleton().createPlane('FloorPlane', "General",
plane, 200000, 200000,
20, 20, True, 1, 50, 50,
(0, 0, 1))

# create floor entity
entity = sceneManager.createEntity('floor', 'FloorPlane')
entity.setMaterialName('Examples/RustySteel')
sceneManager.rootSceneNode.createChildSceneNode().attachObject(entity)

# create head entity
headNode = sceneManager.rootSceneNode.createChildSceneNode()
entity = sceneManager.createEntity('head', 'ogrehead.mesh')
headNode.attachObject(entity)

# add some fog
sceneManager.setFog(ogre.FOG_EXP, ogre.ColourValue.White, 0.0002)

class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1, 'wxOgre', size=(800,600))
self.frame.Show(True)
return True

if __name__ == '__main__':
app = MyApp(False)
app.MainLoop()

dermont

19-06-2006 09:09:37

Looks good, nice job.

alexjc

21-06-2006 22:46:24

Yeah, it's nice being able to have any number of windows completely separate from the OgreManager.

To get smoother rendering (not relying on EVT_IDLE), you could use a wx.FutureCall (or wx.Timer directly) to requests an update at an acceptable framerate.

To keep wx responsive though, it might be worthwhile considering using a rendering thread? Has anyone had any luck with this in PyOgre?

Alex

Istari

11-08-2006 17:27:40

This is my newest version. The Ogre render loop is in it's own thread and seems to like it just fine.
That is until the render loop should stop, when it seems to raise an exception unconditionally. Does anyone know of a better way to do this (I'm fairly new to using threads in Python)?

threadedwxogre.pyimport atexit
import wx
import time
import sys
import threading
import Queue
from pyogre import ogre

CONFIGURE_CHECK_INTERVAL = 0.1
FRAME_SLEEP_DURATION = 0.001

class OgreManager(object):
def __init__(self):
self.root = ogre.Root(ogre.getPluginPath())
self._configured = False
self._cameraIdCounter = 0

def cleanup(self):
del self.root

def _bindWindowEvents(self, window, renderWindow):
def handleClose(event):
renderWindow.destroy()
event.Skip()
def handleBackground(event):
self.root.renderOneFrame()
event.Skip()
def handleResize(event):
renderWindow.windowMovedOrResized()
self.root.renderOneFrame()
event.Skip()

window.Bind(wx.EVT_CLOSE, handleClose)
window.Bind(wx.EVT_ERASE_BACKGROUND, handleBackground)
window.Bind(wx.EVT_SIZE, handleResize)

def configure(self):
return self.root.showConfigDialog()

def setupResources(self):
config = ogre.ConfigFile()
config.loadFromFile('resources.cfg')
for section, key, path in config.values:
ogre.ResourceGroupManager.getSingleton().addResourceLocation(path, key, section)

def chooseSceneManager(self):
self.sceneManager = self.root.createSceneManager(ogre.ST_GENERIC, "Scene Manager")

def createCamera(self, cameraName):
camera = self.sceneManager.createCamera(cameraName)
camera.position = (0, 0, 500)
camera.lookAt(ogre.Vector3(0, 0, -300))
camera.nearClipDistance = 5

return camera

def createViewport(self, renderWindow, camera):
viewport = renderWindow.addViewport(camera, 0, 0.0, 0.0, 1.0, 1.0)
viewport.backgroundColour = 0, 0, 0

return viewport

def initWindow(self, window, cameraName=None):
shouldInitResources = False

if not self._configured:
self.setupResources()
if not self.configure():
raise RuntimeError('Ogre was not configured')

self.root.initialise(False)
self.chooseSceneManager()
self._configured = True
shouldInitResources = True

renderParameters = ogre._StringStringMap()
renderParameters['externalWindowHandle'] = str(window.GetHandle())
renderWindow = self.root.createRenderWindow('RenderWindow_%i' % id(window), 640, 480, False, renderParameters)

if shouldInitResources:
ogre.ResourceGroupManager.getSingleton().initialiseAllResourceGroups()

if cameraName is None:
cameraName = 'Camera%i' % self._cameraIdCounter
self._cameraIdCounter += 1

camera = self.createCamera(cameraName)
viewport = self.createViewport(renderWindow, camera)
self._bindWindowEvents(window, renderWindow)

return (renderWindow, camera, viewport)

class SampleFrame(wx.Frame):
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
self.ogreMgr = wx.GetApp().ogreMgr

self.window1 = wx.Window(self, -1)
self.window2 = wx.Window(self, -1)
self.window3 = wx.Window(self, -1)
self.window4 = wx.Window(self, -1)
self.frame2 = wx.Frame(self, -1, 'Another wxOgre', size=(800,600))
self.frame2.Show(True)

sizer = wx.GridSizer(2, 2)
sizer.Add(self.window1, 0, wx.EXPAND)
sizer.Add(self.window2, 0, wx.EXPAND)
sizer.Add(self.window3, 0, wx.EXPAND)
sizer.Add(self.window4, 0, wx.EXPAND)
self.SetSizer(sizer)

(renderWindow1, camera1, viewport1) = self.ogreMgr.initWindow(self.window1)
(renderWindow2, camera2, viewport2) = self.ogreMgr.initWindow(self.window2)
(renderWindow3, camera3, viewport3) = self.ogreMgr.initWindow(self.window3)
(renderWindow4, camera4, viewport4) = self.ogreMgr.initWindow(self.window4)
(renderWindow5, camera5, viewport5) = self.ogreMgr.initWindow(self.frame2)

camera2.position = (500, 0, 0)
camera2.lookAt(ogre.Vector3(-300, 0, 0))
camera3.position = (0, 0, -500)
camera3.lookAt(ogre.Vector3(0, 0, 300))
camera4.position = (-500, 0, 0)
camera4.lookAt(ogre.Vector3(300, 0, 0))
camera5.position = (-500, 500, 0)
camera5.lookAt(ogre.Vector3(300, -300, 0))

self.createScene()

def createScene(self):
sceneManager = self.ogreMgr.sceneManager

sceneManager.ambientLight = 0.2, 0.2, 0.2
sceneManager.setSkyDome(True, 'Examples/CloudySky', 4.0, 8.0)

light = sceneManager.createLight('MainLight')
light.position = (20, 80, 50)

plane = ogre.Plane()
plane.normal = ogre.Vector3(0, 1, 0)
plane.d = 200
ogre.MeshManager.getSingleton().createPlane('FloorPlane', "General",
plane, 200000, 200000,
20, 20, True, 1, 50, 50,
(0, 0, 1))

# create floor entity
entity = sceneManager.createEntity('floor', 'FloorPlane')
entity.setMaterialName('Examples/RustySteel')
sceneManager.rootSceneNode.createChildSceneNode().attachObject(entity)

# create head entity
headNode = sceneManager.rootSceneNode.createChildSceneNode()
entity = sceneManager.createEntity('head', 'ogrehead.mesh')
headNode.attachObject(entity)

# add some fog
sceneManager.setFog(ogre.FOG_EXP, ogre.ColourValue.White, 0.0002)

class SampleApp(wx.App):
def renderThread(self):
while not self.ogreMgr._configured:
time.sleep(CONFIGURE_CHECK_INTERVAL)

time.sleep(CONFIGURE_CHECK_INTERVAL)
while True:
if self.shouldQuitRendering:
self.ogreMgr.cleanup()
return

try:
self.ogreMgr.root.renderOneFrame()
except ogre.OgreException:
# The render windows could have been destroyed.
# If that is the case renderOneFrame will raise this exception.
# This could possibly be masking other exceptions, so we print
# out the exception information instead of discarding it
sys.excepthook(*sys.exc_info())
time.sleep(FRAME_SLEEP_DURATION)

def OnInit(self):
self.ogreMgr = OgreManager()
self.shouldQuitRendering = False
self.renderThread = threading.Thread(target=self.renderThread)
self.renderThread.start()

self.frame = SampleFrame(None, -1, 'wxOgre - threaded', size=(800,600))
self.frame.Show(True)
return True

def OnExit(self):
self.shouldQuitRendering = True
self.renderThread.join()

if __name__ == '__main__':
app = SampleApp(False)
app.MainLoop()

mthorn

03-01-2007 22:31:00

I've had problems using separate threads for rendering. Updating the scene with wxWidgets while a different Python thread renders at the same time seems to cause problems. To fix it I had to use a thread lock and get it when I needed to update the scene in a case where it would become unstable then release the lock. This was too much overhead.

Instead I stettled on wxTimer because it doesn't actually run in it's own thread and will wait while other wxWidgets events fire. If you have some event that will take a while to process, just fire wxYield as often as possible. The advatage of this is that you can control the framerate. Why use up resources for 100+ FPS when a typical CAD type application only needs 30 FPS? You can also intelligently increase or decrease the timer to adjust for system load.

griminventions

04-01-2007 00:37:08

For something like a level editor I would imagine even 20 fps would be enough, but having a smart fps throttle would be ideal. I'm just happy to see that there is this option at all! :) Nice work, and thanks for sharing.

bharling

04-01-2007 10:42:43

what about wx.lib.delayedresult ?

That is a wx method to execute a process seperate to the main gui thread. You pass a callback function, which is started, then control is immediately given back to the gui thread, with the callback performed in the background. I dont know whether it would work for rendering the ogre window though.

totalknowledge

05-01-2007 18:49:59

I am still learning all of this stuff so bear with me if these are simplistic questions.

I have been playing around with mixing Ogre with other toolkits (wx, pygame) and the above method seems to work pretty good on pygame, and I am assuming wx as well.

My first question: I have been moving my program over to python-ogre and this method doesn't seem to be accessable anymore. Am I just missing a way to call it?

Second question: Does the string act as an image? can I just paint different layers to the same display; or use double buffering with it?

Third question: If the answer to the second question is no, is there another way to render the ogre frame to an image that can then be double buffered with pygame?

andy

06-01-2007 00:23:46

If you have some sample code that doesn't work with Python-Ogre then post it to the Python-Ogre mailing list and we'll help out.

Secondly, what functionality are you looking for from these 3rd party libraries that's missing in Ogre??

I understand WX as it's got all the interface stuff, so if you want a 'Windows' app that has 3D rendering/graphics then an ideal solution

I'm less sure I understand the need for pygame.......

BTW, I'm really asking in the context of what we might add to the Python-Ogre package in the future :)

Cheers
Andy

saluk

06-01-2007 02:29:28

I can see several reasons for pygame: familiarity, ease of use, and a nice package to be able to handle sound and input easily. There are also some nice gui toolkits in pygame that would be a lot easier to use than CEGUI, but I think it would be pretty hard to get them to layer over an ogre scene.

I think the layering is what totalknowedge was asking about.

People in c++ land use sdl often with ogre, pygame is just pysdl really.

totalknowledge

06-01-2007 23:16:10

Well ok ejecting the cd is kind of trivial, but I was really just doing it to have fun lol...

I am not sure I am all that crazy about crazy eddies gui. That might just be the name, I haven't played with it a whole whole lot.

Ultimately there may not be much. However the stuff I am really looking for is....

drum roll please....

Being able to name the window
Being able to have an icon in the window/minimized bar
Being able to toggle between window and fullscreen

:)

totalknowledge

06-01-2007 23:22:24

Oh and input...

Maybe you can wrap GGI/GII and then make some mega widgets and have a compleat game development soup :))

pjcast

07-01-2007 03:14:53

Oh and input...

Maybe you can wrap GGI/GII and then make some mega widgets and have a compleat game development soup :))


Why wrap GGI when there is already a perfectly good wrapped OIS?

jintal

07-01-2007 09:29:29

pygame for me has no use except that i'm using it's window to render on. i have this feeling that wx will render slower than pygame's window. not sure though.

just my two cents :D

Game_Ender

08-01-2007 03:12:32

Do you have any facts to back that up? There is very little to reason that rendering into a window created by wxWidgets is any slower than on a window pygame creates. They are both using the same OS calls. Besides I think you wouldn't use pygame and wxOgre for the same thing. wxOgre lets you create wxPython and PyOgre based tools, not really useful for games.

jintal

08-01-2007 09:20:17

yup i don't have facts to back my point up. but as i said, i just had a feeling :)

everything said, i do agree that a whole wx window with buttons and all would be helpful indeed for game development (i.e. map making, etc). i also agree that a game wouldn't need those elements that much. :D