#!/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. import sys import math from PyOSG import * from OpenGL.GL import * #/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield # * # * This library is open source and may be redistributed and/or modified under # * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or # * (at your option) any later version. The full license is in LICENSE file # * included with this distribution, and on the openscenegraph.org website. # * # * This library 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. See the # * OpenSceneGraph Public License for more details. #*/ class SlideEventHandler( osgGA.GUIEventHandler, osg.NodeCallback): px = -1 py = -1 def __init__(self): osgGA.GUIEventHandler.__init__(self) osg.NodeCallback.__init__(self) self._switch = 0 self._texmatLeft = None self._texmatRight = None self._firstTraversal = True self._activeSlide = 0 self._previousTime = -1.0 self._timePerSlide = 5.0 self._autoSteppingActive=False self._initSeperationX = 0 self._currentSeperationX = 0 self._initSeperationY = 0 self._currentSeperationY = 0 def accept(self, v): v.visit(self) def set(self, sw, offsetX, offsetY, texmatLeft, texmatRight, timePerSlide, autoSteppingActive): self._switch = sw self._switch.setUpdateCallback(self) self._texmatLeft = texmatLeft self._texmatRight = texmatRight self._timePerSlide = timePerSlide self._autoSteppingActive = autoSteppingActive; self._initSeperationX = offsetX self._currentSeperationX = self._initSeperationX self._initSeperationY = offsetY self._currentSeperationY = self._initSeperationY self.initTexMatrices() def handle(self, ea, ga): etype = ea.getEventType() if etype == osgGA.GUIEventAdapter.KEYDOWN: if ea.getKey()==ord('a'): self._autoSteppingActive = not self._autoSteppingActive self._previousTime = ea.time() return True elif ea.getKey()==ord('n'): self.nextSlide() return True elif ea.getKey()==ord('p'): self.previousSlide() return True elif ea.getKey()==ord('w'): self.scaleImage(0.99) return True elif ea.getKey()==ord('s'): self.scaleImage(1.01) return True elif ea.getKey()==ord('j'): self.offsetImage(-0.001,0.0) return True elif ea.getKey()==ord('k'): self.offsetImage(0.001,0.0) return True elif ea.getKey()==ord('i'): self.offsetImage(0.0,-0.001) return True elif ea.getKey()==ord('m'): self.offsetImage(0.0,0.001) return True elif ea.getKey()==ord(' '): self.initTexMatrices() return True return False elif etype == osgGA.GUIEventAdapter.DRAG or etype == osgGA.GUIEventAdapter.MOVE: if SlideEventHandler.px == -1: SlideEventHandler.px = ea.getXnormalized() SlideEventHandler.py = ea.getYnormalized() dx = ea.getXnormalized()-SlideEventHandler.px dy = ea.getYnormalized()-SlideEventHandler.py SlideEventHandler.px = ea.getXnormalized() SlideEventHandler.py = ea.getYnormalized() self.rotateImage(dx,dy) return True else: return False def getUsage(self, usage): usage.addKeyboardMouseBinding("Space","Reset the image position to center") usage.addKeyboardMouseBinding("a","Toggle on/off the automatic advancement for image to image") usage.addKeyboardMouseBinding("n","Advance to next image") usage.addKeyboardMouseBinding("p","Move to previous image") usage.addKeyboardMouseBinding("q","Zoom into the image") usage.addKeyboardMouseBinding("a","Zoom out of the image") usage.addKeyboardMouseBinding("j","Reduce horizontal offset") usage.addKeyboardMouseBinding("k","Increase horizontal offset") usage.addKeyboardMouseBinding("m","Reduce vertical offset") usage.addKeyboardMouseBinding("i","Increase vertical offset") def apply(self, node, nv): if self._autoSteppingActive and nv.getFrameStamp(): time = nv.getFrameStamp().getReferenceTime() if self._firstTraversal: self._firstTraversal = False self._previousTime = time elif time-self._previousTime>self._timePerSlide: self._previousTime = time self.nextSlide() self.traverse(node,nv) def nextSlide(self): if _switch.getNumChildren()==0: return self._activeSlide += 1 if self._activeSlide>=self._switch.getNumChildren(): self._activeSlide = 0 self._switch.setSingleChildOn(self._activeSlide) def previousSlide(self): if self._switch.getNumChildren()==0: return if self._activeSlide==0: self._activeSlide = self._switch.getNumChildren()-1 else: self._activeSlide -= 1 self._switch.setSingleChildOn(self._activeSlide) def scaleImage(self, s): self._texmatLeft.setMatrix(self._texmatLeft.getMatrix()*osg.Matrix.translate(-0.5,-0.5,0.0)*osg.Matrix.scale(s,s,1.0)*osg.Matrix.translate(0.5,0.5,0.0)) self._texmatRight.setMatrix(self._texmatRight.getMatrix()*osg.Matrix.translate(-0.5,-0.5,0.0)*osg.Matrix.scale(s,s,1.0)*osg.Matrix.translate(0.5,0.5,0.0)) def offsetImage(self, ds, dt): self._currentSeperationX+=ds self._currentSeperationY+=dt self._texmatLeft.setMatrix(self._texmatLeft.getMatrix()*osg.Matrix.translate(ds,dt,0.0)) self._texmatRight.setMatrix(self._texmatRight.getMatrix()*osg.Matrix.translate(-ds,-dt,0.0)) #-- osgDB.writeNodeFile(self._switch,"sw.osg") def rotateImage(self, rx, ry): scale = 0.5 self._texmatLeft.setMatrix(self._texmatLeft.getMatrix()*osg.Matrix.translate(-rx*scale,-ry*scale,0.0)) self._texmatRight.setMatrix(self._texmatRight.getMatrix()*osg.Matrix.translate(-rx*scale,-ry*scale,0.0)) def initTexMatrices(self): self._texmatLeft.setMatrix(osg.Matrix.translate(self._initSeperationX,self._initSeperationY,0.0)) self._texmatRight.setMatrix(osg.Matrix.translate(-self._initSeperationX,-self._initSeperationY,0.0)) def createSectorForImage(image, texmat, s, t, radius, height, length): numSegments = 20 Theta = length/radius dTheta = Theta/float(numSegments-1) ThetaZero = height*s/(t*radius) # set up the texture. texture = osg.Texture2D() texture.setFilter(osg.Texture2D.MIN_FILTER,osg.Texture2D.LINEAR) texture.setFilter(osg.Texture2D.MAG_FILTER,osg.Texture2D.LINEAR) texture.setImage(image) # set up the drawstate. dstate = osg.StateSet() dstate.setMode(GL_CULL_FACE,osg.StateAttribute.OFF) dstate.setMode(GL_LIGHTING,osg.StateAttribute.OFF) dstate.setTextureAttributeAndModes(0, texture,osg.StateAttribute.ON) dstate.setTextureAttribute(0, texmat) # set up the geoset. geom = osg.Geometry() geom.setStateSet(dstate) coords = osg.Vec3Array() tcoords = osg.Vec2Array() angle = -Theta/2.0 for i in range(numSegments): angle+=dTheta coords.push_back(osg.Vec3(math.sin(angle)*radius,math.cos(angle)*radius,height*0.5)) coords.push_back(osg.Vec3(math.sin(angle)*radius,math.cos(angle)*radius,-height*0.5)) tcoords.push_back(osg.Vec2(angle/ThetaZero+0.5,1.0)) tcoords.push_back(osg.Vec2(angle/ThetaZero+0.5,0.0)) colors = osg.Vec4Array() colors.push_back(osg.Vec4(1.0,1.0,1.0,1.0)) elements = osg.DrawArrays(osg.PrimitiveSet.QUAD_STRIP,0,coords.size()) geom.setVertexArray(coords) geom.setTexCoordArray(0,tcoords) geom.setColorArray(colors) geom.setColorBinding(osg.Geometry.BIND_OVERALL) geom.addPrimitiveSet(elements) # set up the geode. geode = osg.Geode() geode.addDrawable(geom) return geode # create a switch containing a set of child each containing a # stereo image pair. def createScene(fileList, texmatLeft, texmatRight, radius, height, length): sw = osg.Switch() # load the images. for i in range(0, len(fileList), 2): imageLeft = osgDB.readImageFile(fileList[i]) imageRight = osgDB.readImageFile(fileList[i+1]) if imageLeft and imageRight: average_s = (imageLeft.s()+imageRight.s())*0.5 average_t = (imageLeft.t()+imageRight.t())*0.5 geodeLeft = createSectorForImage(imageLeft,texmatLeft,average_s,average_t, radius, height, length) geodeLeft.setNodeMask(0x01) geodeRight = createSectorForImage(imageRight,texmatRight,average_s,average_t, radius, height, length) geodeRight.setNodeMask(0x02) imageGroup = osg.Group() imageGroup.addChild(geodeLeft) imageGroup.addChild(geodeRight) sw.addChild(imageGroup) else: print "Warning: Unable to load both image files, '", fileList[i], "' & '", fileList[i+1], "', required for stereo imaging." if sw.getNumChildren()>0: # select first child. sw.setSingleChildOn(0) return sw 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 node masks to create stereo images.") arguments.getApplicationUsage().setCommandLineUsage(arguments.getApplicationName()+" [options] image_file_left_eye image_file_right_eye") arguments.getApplicationUsage().addCommandLineOption("-d ","Time delay in sceonds between the display of successive image pairs when in auto advance mode.") arguments.getApplicationUsage().addCommandLineOption("-a","Enter auto advance of image pairs on start up.") arguments.getApplicationUsage().addCommandLineOption("-x ","Horizontal offset of left and right images.") arguments.getApplicationUsage().addCommandLineOption("-y ","Vertical offset of left and right images.") 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.ESCAPE_SETS_DONE) # register the handler to add keyboard and mosue handling. seh = SlideEventHandler() viewer.getEventHandlerList().push_front(seh) # get details on keyboard and mouse bindings used by the viewer. viewer.getUsage(arguments.getApplicationUsage()) # read any time delay argument. timeDelayBetweenSlides = 5.0 #while (arguments.read("-d",timeDelayBetweenSlides)) {} autoSteppingActive = False #while (arguments.read("-a")) autoSteppingActive = True offsetX=0.0 #while (arguments.read("-x",offsetX)) {} offsetY=0.0 #while (arguments.read("-y",offsetY)) {} # if user request help write it out to cout. if arguments.read("-h") or arguments.read("--help"): arguments.getApplicationUsage().write(std.cout) 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(std.cout) return 1 if arguments.argc()<=1: arguments.getApplicationUsage().write(osg.ApplicationUsage.COMMAND_LINE_OPTION) return 1 # extract the filenames from the arguments list. fileList = [] for pos in range(1, arguments.argc()): if arguments.isString(pos): fileList.append(arguments[pos]) if len(fileList)<2: return 1 # set up the use of stereo by default. ds = viewer.getDisplaySettings() if not ds: ds = osg.DisplaySettings.instance() if ds: ds.setStereo(True) # create the windows and run the threads. viewer.realize() # now the windows have been realized we switch off the cursor to prevent it # distracting the people seeing the stereo images. fovy = 1.0 for i in range(viewer.getCameraConfig().getNumberOfCameras()): cam = viewer.getCameraConfig().getCamera(i) rs = cam.getRenderSurface() rs.useCursor(False) fovy = osg.DegreesToRadians(cam.getLensVerticalFov()) radius = 1.0 height = 2*radius*math.tan(fovy*0.5) length = osg.PI*radius; # half a cylinder. # use a texure matrix to control the placement of the image. texmatLeft = osg.TexMat() texmatRight = osg.TexMat() # creat the scene from the file list. rootNode = createScene(fileList,texmatLeft,texmatRight,radius,height,length) #-- osgDB.writeNodeFile(rootNode,"test.osg") # set the scene to render viewer.setSceneData(rootNode) # set all the sceneview's up so that their left and right add cull masks are set up. for itr in viewer.getSceneHandlerList(): sceneview = itr.getSceneView() sceneview.setCullMask(0xfffffff) sceneview.setCullMaskLeft(0x00000001) sceneview.setCullMaskRight(0x00000002) sceneview.setFusionDistance(osgUtil.SceneView.USE_FUSION_DISTANCE_VALUE,radius) # set up the SlideEventHandler. seh.set(rootNode,offsetX,offsetY,texmatLeft,texmatRight,timeDelayBetweenSlides,autoSteppingActive) homePosition = osg.Matrix() homePosition.makeLookAt(osg.Vec3(0.0,0.0,0.0),osg.Vec3(0.0,1.0,0.0),osg.Vec3(0.0,0.0,1.0)) 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() viewer.setView(homePosition) # 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)