#!/usr/bin/env python # Copyright (C) 2002-2003 Gideon May (gideon@computer.org) # # Permission to copy, use, sell and distribute this software is granted # provided this copyright notice appears in all copies. # Permission to modify the code and to distribute modified code is granted # provided this copyright notice appears in all copies, and a notice # that the code was modified is included with the copyright notice. # # This software is provided "as is" without express or implied warranty, # and with no claim as to its suitability for any purpose. # -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield # # This application is open source and may be redistributed and/or modified # freely and without restriction, both in commericial and non commericial applications, # as long as this copyright notice is maintained. # # This application is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # import sys from PyOSG import Producer from PyOSG import osg from PyOSG import osgDB from PyOSG import osgUtil from PyOSG import osgGA from PyOSG import osgProducer from OpenGL.GL import * """ A simple demo demonstrating different texturing modes, including using of texture extensions. """ class ConstructStateCallback(osgProducer.OsgCameraGroup.RealizeCallback): def __init__(self, node): osgProducer.OsgCameraGroup.RealizeCallback.__init__(self) self._node = node self._initialized = False def constructState(self): # read 4 2d images image_0 = osgDB.readImageFile("Images/lz.rgb") image_1 = osgDB.readImageFile("Images/reflect.rgb") image_2 = osgDB.readImageFile("Images/tank.rgb") image_3 = osgDB.readImageFile("Images/skymap.jpg") if not image_0 or not image_1 or not image_2 or not image_3: print "Warning: could not open files." return osg.StateSet() if image_0.getPixelFormat() != image_1.getPixelFormat() or image_0.getPixelFormat()!=image_2.getPixelFormat() or image_0.getPixelFormat()!=image_3.getPixelFormat(): print "Warning: image pixel formats not compatible." return osg.StateSet() # get max 3D texture size textureSize = osg.Texture3D.getExtensions(0,True).maxTexture3DSize() if textureSize > 256: textureSize = 256 # scale them all to the same size. image_0.scaleImage(textureSize,textureSize,1) image_1.scaleImage(textureSize,textureSize,1) image_2.scaleImage(textureSize,textureSize,1) image_3.scaleImage(textureSize,textureSize,1) # then allocated a 3d image to use for texturing. image_3d = osg.Image() image_3d.allocateImage(textureSize,textureSize,4, image_0.getPixelFormat(),image_0.getDataType()) # copy the 2d images into the 3d image. image_3d.copySubImage(0,0,0,image_0) image_3d.copySubImage(0,0,1,image_1) image_3d.copySubImage(0,0,2,image_2) image_3d.copySubImage(0,0,3,image_3) image_3d.setInternalTextureFormat(image_0.getInternalTextureFormat()) # set up the 3d texture itself, # note, well set the filtering up so that mip mapping is disabled, # gluBuild3DMipsmaps doesn't do a very good job of handled the # inbalanced dimensions of the 256x256x4 texture. texture3D = osg.Texture3D() texture3D.setFilter(osg.Texture3D.MIN_FILTER,osg.Texture3D.LINEAR) texture3D.setFilter(osg.Texture3D.MAG_FILTER,osg.Texture3D.LINEAR) texture3D.setWrap(osg.Texture3D.WRAP_R,osg.Texture3D.REPEAT) texture3D.setImage(image_3d) # create a texgen to generate a R texture coordinate, the geometry # itself will supply the S & T texture coordinates. # in the animateStateSet callback well alter this R value to # move the texture through the 3d texture, 3d texture filtering # will do the blending for us. texgen = osg.TexGen() texgen.setMode(osg.TexGen.OBJECT_LINEAR) texgen.setPlane(osg.TexGen.R, osg.Plane(osg.Vec4(0.0,0.0,0.0,0.2))) # create the StateSet to store the texture data stateset = osg.StateSet() stateset.setTextureMode(0,GL_TEXTURE_GEN_R,osg.StateAttribute.ON) stateset.setTextureAttribute(0,texgen) stateset.setTextureAttributeAndModes(0,texture3D,osg.StateAttribute.ON) return stateset def apply(self, ocg, sh, rs): if not self._initialized: # only initialize state once, only need for cases where multiple graphics contexts are # if which case this callback can get called multiple times. self._initialized = True if self._node: self._node.setStateSet(self.constructState()) # now safe to con sh.init() class UpdateStateCallback(osg.NodeCallback): def __init__(self): osg.NodeCallback.__init__(self) def animateState(self, stateset): # here we simply get any existing texgen, and then increment its # plane, pushing the R coordinate through the texture. attribute = stateset.getTextureAttribute(0,osg.StateAttribute.TEXGEN) texgen = attribute.asTexGen() if texgen: texgen.getPlane(osg.TexGen.R)[3] += 0.001 def apply(self, node, nv): stateset = node.getStateSet() if stateset: # we have an exisitng stateset, so lets animate it. self.animateState(stateset) # note, callback is repsonsible for scenegraph traversal so # should always include call the traverse(node,nv) to ensure # that the rest of cullbacks and the scene graph are traversed. self.traverse(node,nv) # create 2,2 square with center at 0,0,0 and aligned along the XZ plan def createSquare(textureCoordMax=1.0): # set up the Geometry. geom = osg.Geometry() coords = osg.Vec3Array(4) coords[0].set(-1.0,0.0,1.0) coords[1].set(-1.0,0.0,-1.0) coords[2].set(1.0,0.0,-1.0) coords[3].set(1.0,0.0,1.0) geom.setVertexArray(coords) norms = osg.Vec3Array(1) norms[0].set(0.0,-1.0,0.0) geom.setNormalArray(norms) geom.setNormalBinding(osg.Geometry.BIND_OVERALL) tcoords = osg.Vec2Array(4) tcoords[0].set(0.0,textureCoordMax) tcoords[1].set(0.0,0.0) tcoords[2].set(textureCoordMax,0.0) tcoords[3].set(textureCoordMax,textureCoordMax) geom.setTexCoordArray(0,tcoords) geom.addPrimitiveSet(osg.DrawArrays(osg.PrimitiveSet.QUADS,0,4)) return geom def createModel(): # create the geometry of the model, just a simple 2d quad right now. geode = osg.Geode() geode.addDrawable(createSquare()) # normally we'd create the stateset's to contain all the textures # etc here, but, the above technique uses osg.Image.scaleImage and # osg.Image.copySubImage() which are implemented with OpenGL utility # library, which unfortunately can't be used until we have a valid # OpenGL context, and at this point in initilialization we don't have # a valid OpenGL context, so we have to delay creation of state until # there is a valid OpenGL context. I'll manage this by using an # app callback which will create the state during the first traversal. # A bit hacky, and my plan is to reimplement the osg.scaleImage and # osg.Image.copySubImage() without using GLU which will get round # this current limitation. geode.setUpdateCallback(UpdateStateCallback()) return geode def main(argv): # use an ArgumentParser object to manage the program arguments. arguments = osg.ArgumentParser(argv) # set up the usage document, in case we need to print out how to use this program. arguments.getApplicationUsage().setDescription(arguments.getApplicationName()+" is the example which demonstrates use of 3D textures.") arguments.getApplicationUsage().setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...") arguments.getApplicationUsage().addCommandLineOption("-h or --help","Display this information") # construct the viewer. viewer = osgProducer.Viewer(arguments) # set up the value with sensible default event handlers. viewer.setUpViewer(osgProducer.Viewer.STANDARD_SETTINGS) # get details on keyboard and mouse bindings used by the viewer. viewer.getUsage(arguments.getApplicationUsage()) # if user request help write it out to cout. if arguments.read("-h") or arguments.read("--help"): arguments.getApplicationUsage().write() return 1 # any option left unread are converted into errors to write out later. arguments.reportRemainingOptionsAsUnrecognized() # report any errors if they have occured when parsing the program aguments. if arguments.errors(): arguments.writeErrorMessages() return 1 # create a model from the images. rootNode = createModel() if rootNode: # set the scene to render viewer.setSceneData(rootNode) # the construct state uses gl commands to resize images so we are forced # to only call it once a valid graphics context has been established, # for that we use a realize callback. viewer.setRealizeCallback(ConstructStateCallback(rootNode)) # create the windows and run the threads. viewer.realize() while not viewer.done(): # wait for all cull and draw threads to complete. viewer.sync() # update the scene by traversing it with the the update visitor which will # call all node update callbacks and animations. viewer.update() # fire off the cull and draw traversals of the scene. viewer.frame() # wait for all cull and draw threads to complete before exit. viewer.sync() return 0 if __name__ == "__main__": main(sys.argv)