2005-07-20 00:30:55 +08:00
# include <osgProducer/Viewer>
# include <osg/Projection>
# include <osg/Geometry>
# include <osg/Texture>
# include <osg/TexGen>
2003-06-25 05:57:13 +08:00
# include <osg/Geode>
2005-07-20 00:30:55 +08:00
# include <osg/ShapeDrawable>
# include <osg/PolygonOffset>
# include <osg/CullFace>
2003-06-25 05:57:13 +08:00
# include <osg/TextureCubeMap>
# include <osg/TexMat>
2004-06-04 16:40:15 +08:00
# include <osg/MatrixTransform>
2005-07-20 00:30:55 +08:00
# include <osg/Light>
# include <osg/LightSource>
# include <osg/PolygonOffset>
# include <osg/CullFace>
# include <osg/Material>
2005-10-07 04:02:18 +08:00
# include <osg/PositionAttitudeTransform>
2004-06-04 16:40:15 +08:00
2006-11-27 22:52:07 +08:00
# include <osg/Camera>
2005-07-20 00:30:55 +08:00
# include <osg/TexGenNode>
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
using namespace osg ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
ref_ptr < Group > _create_scene ( )
{
ref_ptr < Group > scene = new Group ;
ref_ptr < Geode > geode_1 = new Geode ;
scene - > addChild ( geode_1 . get ( ) ) ;
ref_ptr < Geode > geode_2 = new Geode ;
ref_ptr < MatrixTransform > transform_2 = new MatrixTransform ;
transform_2 - > addChild ( geode_2 . get ( ) ) ;
2005-11-09 23:11:22 +08:00
transform_2 - > setUpdateCallback ( new osg : : AnimationPathCallback ( Vec3 ( 0 , 0 , 0 ) , Y_AXIS , inDegrees ( 45.0f ) ) ) ;
2005-07-20 00:30:55 +08:00
scene - > addChild ( transform_2 . get ( ) ) ;
ref_ptr < Geode > geode_3 = new Geode ;
ref_ptr < MatrixTransform > transform_3 = new MatrixTransform ;
transform_3 - > addChild ( geode_3 . get ( ) ) ;
2005-11-09 23:11:22 +08:00
transform_3 - > setUpdateCallback ( new osg : : AnimationPathCallback ( Vec3 ( 0 , 0 , 0 ) , Y_AXIS , inDegrees ( - 22.5f ) ) ) ;
2005-07-20 00:30:55 +08:00
scene - > addChild ( transform_3 . get ( ) ) ;
const float radius = 0.8f ;
const float height = 1.0f ;
ref_ptr < TessellationHints > hints = new TessellationHints ;
hints - > setDetailRatio ( 2.0f ) ;
ref_ptr < ShapeDrawable > shape ;
shape = new ShapeDrawable ( new Box ( Vec3 ( 0.0f , - 2.0f , 0.0f ) , 10 , 0.1f , 10 ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 0.5f , 0.5f , 0.7f , 1.0f ) ) ;
geode_1 - > addDrawable ( shape . get ( ) ) ;
shape = new ShapeDrawable ( new Sphere ( Vec3 ( - 3.0f , 0.0f , 0.0f ) , radius ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 0.6f , 0.8f , 0.8f , 1.0f ) ) ;
geode_2 - > addDrawable ( shape . get ( ) ) ;
shape = new ShapeDrawable ( new Box ( Vec3 ( 3.0f , 0.0f , 0.0f ) , 2 * radius ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 0.4f , 0.9f , 0.3f , 1.0f ) ) ;
geode_2 - > addDrawable ( shape . get ( ) ) ;
shape = new ShapeDrawable ( new Cone ( Vec3 ( 0.0f , 0.0f , - 3.0f ) , radius , height ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 0.2f , 0.5f , 0.7f , 1.0f ) ) ;
geode_2 - > addDrawable ( shape . get ( ) ) ;
shape = new ShapeDrawable ( new Cylinder ( Vec3 ( 0.0f , 0.0f , 3.0f ) , radius , height ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 1.0f , 0.3f , 0.3f , 1.0f ) ) ;
geode_2 - > addDrawable ( shape . get ( ) ) ;
shape = new ShapeDrawable ( new Box ( Vec3 ( 0.0f , 3.0f , 0.0f ) , 2 , 0.1f , 2 ) , hints . get ( ) ) ;
shape - > setColor ( Vec4 ( 0.8f , 0.8f , 0.4f , 1.0f ) ) ;
geode_3 - > addDrawable ( shape . get ( ) ) ;
// material
ref_ptr < Material > matirial = new Material ;
matirial - > setColorMode ( Material : : DIFFUSE ) ;
matirial - > setAmbient ( Material : : FRONT_AND_BACK , Vec4 ( 0 , 0 , 0 , 1 ) ) ;
matirial - > setSpecular ( Material : : FRONT_AND_BACK , Vec4 ( 1 , 1 , 1 , 1 ) ) ;
matirial - > setShininess ( Material : : FRONT_AND_BACK , 64.0f ) ;
scene - > getOrCreateStateSet ( ) - > setAttributeAndModes ( matirial . get ( ) , StateAttribute : : ON ) ;
return scene ;
}
2003-06-25 05:57:13 +08:00
2006-03-01 03:36:18 +08:00
osg : : NodePath createReflector ( )
2005-07-20 00:30:55 +08:00
{
2006-03-01 03:36:18 +08:00
osg : : PositionAttitudeTransform * pat = new osg : : PositionAttitudeTransform ;
2005-10-07 04:02:18 +08:00
pat - > setPosition ( osg : : Vec3 ( 0.0f , 0.0f , 0.0f ) ) ;
pat - > setAttitude ( osg : : Quat ( osg : : inDegrees ( 0.0f ) , osg : : Vec3 ( 0.0f , 0.0f , 1.0f ) ) ) ;
2006-03-01 03:36:18 +08:00
Geode * geode_1 = new Geode ;
pat - > addChild ( geode_1 ) ;
2005-07-20 00:30:55 +08:00
const float radius = 0.8f ;
ref_ptr < TessellationHints > hints = new TessellationHints ;
hints - > setDetailRatio ( 2.0f ) ;
2006-03-01 03:36:18 +08:00
ShapeDrawable * shape = new ShapeDrawable ( new Sphere ( Vec3 ( 0.0f , 0.0f , 0.0f ) , radius * 1.5f ) , hints . get ( ) ) ;
2005-07-20 00:30:55 +08:00
shape - > setColor ( Vec4 ( 0.8f , 0.8f , 0.8f , 1.0f ) ) ;
2006-03-01 03:36:18 +08:00
geode_1 - > addDrawable ( shape ) ;
2005-07-20 00:30:55 +08:00
2006-03-01 03:36:18 +08:00
osg : : NodePath nodeList ;
nodeList . push_back ( pat ) ;
nodeList . push_back ( geode_1 ) ;
2005-07-20 00:30:55 +08:00
2006-03-01 03:36:18 +08:00
return nodeList ;
2005-07-20 00:30:55 +08:00
}
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
class UpdateCameraAndTexGenCallback : public osg : : NodeCallback
2003-06-25 05:57:13 +08:00
{
public :
2006-11-27 22:52:07 +08:00
typedef std : : vector < osg : : ref_ptr < osg : : Camera > > CameraList ;
2003-06-25 05:57:13 +08:00
2006-11-27 22:52:07 +08:00
UpdateCameraAndTexGenCallback ( osg : : NodePath & reflectorNodePath , CameraList & Cameras ) :
2005-07-20 00:30:55 +08:00
_reflectorNodePath ( reflectorNodePath ) ,
2006-11-27 22:52:07 +08:00
_Cameras ( Cameras )
2005-07-20 00:30:55 +08:00
{
}
2003-06-25 05:57:13 +08:00
virtual void operator ( ) ( osg : : Node * node , osg : : NodeVisitor * nv )
{
2005-07-20 00:30:55 +08:00
// first update subgraph to make sure objects are all moved into postion
2003-06-25 05:57:13 +08:00
traverse ( node , nv ) ;
2005-10-07 04:02:18 +08:00
2005-07-20 00:30:55 +08:00
// compute the position of the center of the reflector subgraph
2005-10-07 04:02:18 +08:00
osg : : Matrixd worldToLocal = osg : : computeWorldToLocal ( _reflectorNodePath ) ;
2005-07-20 00:30:55 +08:00
osg : : BoundingSphere bs = _reflectorNodePath . back ( ) - > getBound ( ) ;
2005-10-07 04:02:18 +08:00
osg : : Vec3 position = bs . center ( ) ;
2005-07-20 00:30:55 +08:00
typedef std : : pair < osg : : Vec3 , osg : : Vec3 > ImageData ;
const ImageData id [ ] =
{
ImageData ( osg : : Vec3 ( 1 , 0 , 0 ) , osg : : Vec3 ( 0 , - 1 , 0 ) ) , // +X
ImageData ( osg : : Vec3 ( - 1 , 0 , 0 ) , osg : : Vec3 ( 0 , - 1 , 0 ) ) , // -X
ImageData ( osg : : Vec3 ( 0 , 1 , 0 ) , osg : : Vec3 ( 0 , 0 , 1 ) ) , // +Y
ImageData ( osg : : Vec3 ( 0 , - 1 , 0 ) , osg : : Vec3 ( 0 , 0 , - 1 ) ) , // -Y
ImageData ( osg : : Vec3 ( 0 , 0 , 1 ) , osg : : Vec3 ( 0 , - 1 , 0 ) ) , // +Z
ImageData ( osg : : Vec3 ( 0 , 0 , - 1 ) , osg : : Vec3 ( 0 , - 1 , 0 ) ) // -Z
} ;
for ( unsigned int i = 0 ;
2006-11-27 22:52:07 +08:00
i < 6 & & i < _Cameras . size ( ) ;
2005-07-20 00:30:55 +08:00
+ + i )
{
2005-10-07 04:02:18 +08:00
osg : : Matrix localOffset ;
localOffset . makeLookAt ( position , position + id [ i ] . first , id [ i ] . second ) ;
osg : : Matrix viewMatrix = worldToLocal * localOffset ;
2006-11-27 22:52:07 +08:00
_Cameras [ i ] - > setReferenceFrame ( osg : : Camera : : ABSOLUTE_RF ) ;
_Cameras [ i ] - > setProjectionMatrixAsFrustum ( - 1.0 , 1.0 , - 1.0 , 1.0 , 1.0 , 10000.0 ) ;
_Cameras [ i ] - > setViewMatrix ( viewMatrix ) ;
2005-07-20 00:30:55 +08:00
}
2003-06-25 05:57:13 +08:00
}
2005-07-20 00:30:55 +08:00
protected :
virtual ~ UpdateCameraAndTexGenCallback ( ) { }
2006-03-01 03:36:18 +08:00
osg : : NodePath _reflectorNodePath ;
2006-11-27 22:52:07 +08:00
CameraList _Cameras ;
2003-06-25 05:57:13 +08:00
} ;
2005-07-20 00:30:55 +08:00
class TexMatCullCallback : public osg : : NodeCallback
2003-06-25 05:57:13 +08:00
{
public :
2005-07-20 00:30:55 +08:00
TexMatCullCallback ( osg : : TexMat * texmat ) :
2003-06-25 05:57:13 +08:00
_texmat ( texmat )
2005-07-20 00:30:55 +08:00
{
}
2003-06-25 05:57:13 +08:00
virtual void operator ( ) ( osg : : Node * node , osg : : NodeVisitor * nv )
{
2005-07-20 00:30:55 +08:00
// first update subgraph to make sure objects are all moved into postion
traverse ( node , nv ) ;
2003-06-25 05:57:13 +08:00
osgUtil : : CullVisitor * cv = dynamic_cast < osgUtil : : CullVisitor * > ( nv ) ;
2005-07-20 00:30:55 +08:00
if ( cv )
2003-06-25 05:57:13 +08:00
{
2006-08-01 01:31:21 +08:00
osg : : Quat quat = cv - > getModelViewMatrix ( ) . getRotate ( ) ;
2005-07-20 00:30:55 +08:00
_texmat - > setMatrix ( osg : : Matrix : : rotate ( quat . inverse ( ) ) ) ;
2003-06-25 05:57:13 +08:00
}
}
2005-07-20 00:30:55 +08:00
protected :
osg : : ref_ptr < TexMat > _texmat ;
2003-06-25 05:57:13 +08:00
} ;
2006-11-27 22:52:07 +08:00
osg : : Group * createShadowedScene ( osg : : Node * reflectedSubgraph , osg : : NodePath reflectorNodePath , unsigned int unit , const osg : : Vec4 & clearColor , unsigned tex_width , unsigned tex_height , osg : : Camera : : RenderTargetImplementation renderImplementation )
2003-06-25 05:57:13 +08:00
{
2005-07-20 00:30:55 +08:00
osg : : Group * group = new osg : : Group ;
osg : : TextureCubeMap * texture = new osg : : TextureCubeMap ;
texture - > setTextureSize ( tex_width , tex_height ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
texture - > setInternalFormat ( GL_RGB ) ;
2005-11-02 23:56:29 +08:00
texture - > setWrap ( osg : : Texture : : WRAP_S , osg : : Texture : : CLAMP_TO_EDGE ) ;
texture - > setWrap ( osg : : Texture : : WRAP_T , osg : : Texture : : CLAMP_TO_EDGE ) ;
texture - > setWrap ( osg : : Texture : : WRAP_R , osg : : Texture : : CLAMP_TO_EDGE ) ;
2005-07-20 00:30:55 +08:00
texture - > setFilter ( osg : : TextureCubeMap : : MIN_FILTER , osg : : TextureCubeMap : : LINEAR ) ;
texture - > setFilter ( osg : : TextureCubeMap : : MAG_FILTER , osg : : TextureCubeMap : : LINEAR ) ;
// set up the render to texture cameras.
2006-11-27 22:52:07 +08:00
UpdateCameraAndTexGenCallback : : CameraList Cameras ;
2005-07-20 00:30:55 +08:00
for ( unsigned int i = 0 ; i < 6 ; + + i )
{
// create the camera
2006-11-27 22:52:07 +08:00
osg : : Camera * camera = new osg : : Camera ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
camera - > setClearMask ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;
camera - > setClearColor ( clearColor ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// set viewport
camera - > setViewport ( 0 , 0 , tex_width , tex_height ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// set the camera to render before the main camera.
2006-11-27 22:52:07 +08:00
camera - > setRenderOrder ( osg : : Camera : : PRE_RENDER ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// tell the camera to use OpenGL frame buffer object where supported.
2005-09-29 17:36:51 +08:00
camera - > setRenderTargetImplementation ( renderImplementation ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// attach the texture and use it as the color buffer.
2006-11-27 22:52:07 +08:00
camera - > attach ( osg : : Camera : : COLOR_BUFFER , texture , 0 , i ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// add subgraph to render
camera - > addChild ( reflectedSubgraph ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
group - > addChild ( camera ) ;
2006-11-27 22:52:07 +08:00
Cameras . push_back ( camera ) ;
2003-06-25 05:57:13 +08:00
}
2005-07-20 00:30:55 +08:00
// create the texgen node to project the tex coords onto the subgraph
osg : : TexGenNode * texgenNode = new osg : : TexGenNode ;
texgenNode - > getTexGen ( ) - > setMode ( osg : : TexGen : : REFLECTION_MAP ) ;
texgenNode - > setTextureUnit ( unit ) ;
group - > addChild ( texgenNode ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// set the reflected subgraph so that it uses the texture and tex gen settings.
2003-06-25 05:57:13 +08:00
{
2006-03-01 04:10:25 +08:00
osg : : Node * reflectorNode = reflectorNodePath . front ( ) ;
2005-07-20 00:30:55 +08:00
group - > addChild ( reflectorNode ) ;
osg : : StateSet * stateset = reflectorNode - > getOrCreateStateSet ( ) ;
stateset - > setTextureAttributeAndModes ( unit , texture , osg : : StateAttribute : : ON ) ;
stateset - > setTextureMode ( unit , GL_TEXTURE_GEN_S , osg : : StateAttribute : : ON ) ;
stateset - > setTextureMode ( unit , GL_TEXTURE_GEN_T , osg : : StateAttribute : : ON ) ;
stateset - > setTextureMode ( unit , GL_TEXTURE_GEN_R , osg : : StateAttribute : : ON ) ;
stateset - > setTextureMode ( unit , GL_TEXTURE_GEN_Q , osg : : StateAttribute : : ON ) ;
osg : : TexMat * texmat = new osg : : TexMat ;
stateset - > setTextureAttributeAndModes ( unit , texmat , osg : : StateAttribute : : ON ) ;
reflectorNode - > setCullCallback ( new TexMatCullCallback ( texmat ) ) ;
2003-06-25 05:57:13 +08:00
}
2005-07-20 00:30:55 +08:00
// add the reflector scene to draw just as normal
group - > addChild ( reflectedSubgraph ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// set an update callback to keep moving the camera and tex gen in the right direction.
2006-11-27 22:52:07 +08:00
group - > setUpdateCallback ( new UpdateCameraAndTexGenCallback ( reflectorNodePath , Cameras ) ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
return group ;
2003-06-25 05:57:13 +08:00
}
2005-07-20 00:30:55 +08:00
int main ( int argc , char * * argv )
2003-06-25 05:57:13 +08:00
{
// use an ArgumentParser object to manage the program arguments.
2005-07-20 00:30:55 +08:00
ArgumentParser arguments ( & argc , argv ) ;
2003-06-25 05:57:13 +08:00
// set up the usage document, in case we need to print out how to use this program.
2005-07-20 00:30:55 +08:00
arguments . getApplicationUsage ( ) - > setDescription ( arguments . getApplicationName ( ) + " is the example which demonstrates using of GL_ARB_shadow extension implemented in osg::Texture class " ) ;
arguments . getApplicationUsage ( ) - > setCommandLineUsage ( arguments . getApplicationName ( ) ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " -h or --help " , " Display this information " ) ;
2005-07-25 21:05:57 +08:00
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --fbo " , " Use Frame Buffer Object for render to texture, where supported. " ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --fb " , " Use FrameBuffer for render to texture. " ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --pbuffer " , " Use Pixel Buffer for render to texture, where supported. " ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --window " , " Use a seperate Window for render to texture. " ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --width " , " Set the width of the render to texture " ) ;
arguments . getApplicationUsage ( ) - > addCommandLineOption ( " --height " , " Set the height of the render to texture " ) ;
2005-07-20 00:30:55 +08:00
2003-06-25 05:57:13 +08:00
// construct the viewer.
osgProducer : : Viewer 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.
2005-07-20 00:30:55 +08:00
viewer . getUsage ( * arguments . getApplicationUsage ( ) ) ;
2003-06-25 05:57:13 +08:00
// if user request help write it out to cout.
if ( arguments . read ( " -h " ) | | arguments . read ( " --help " ) )
{
arguments . getApplicationUsage ( ) - > write ( std : : cout ) ;
return 1 ;
}
2005-07-25 21:05:57 +08:00
unsigned tex_width = 512 ;
unsigned tex_height = 512 ;
while ( arguments . read ( " --width " , tex_width ) ) { }
while ( arguments . read ( " --height " , tex_height ) ) { }
2006-11-27 22:52:07 +08:00
osg : : Camera : : RenderTargetImplementation renderImplementation = osg : : Camera : : FRAME_BUFFER_OBJECT ;
2005-07-25 21:05:57 +08:00
2006-11-27 22:52:07 +08:00
while ( arguments . read ( " --fbo " ) ) { renderImplementation = osg : : Camera : : FRAME_BUFFER_OBJECT ; }
while ( arguments . read ( " --pbuffer " ) ) { renderImplementation = osg : : Camera : : PIXEL_BUFFER ; }
while ( arguments . read ( " --fb " ) ) { renderImplementation = osg : : Camera : : FRAME_BUFFER ; }
while ( arguments . read ( " --window " ) ) { renderImplementation = osg : : Camera : : SEPERATE_WINDOW ; }
2005-07-25 21:05:57 +08:00
2003-06-25 05:57:13 +08:00
// 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 ( ) )
{
2005-07-20 00:30:55 +08:00
arguments . writeErrorMessages ( std : : cout ) ;
return 1 ;
2003-06-25 05:57:13 +08:00
}
2005-07-20 00:30:55 +08:00
ref_ptr < MatrixTransform > scene = new MatrixTransform ;
scene - > setMatrix ( osg : : Matrix : : rotate ( osg : : DegreesToRadians ( 125.0 ) , 1.0 , 0.0 , 0.0 ) ) ;
2004-06-04 16:40:15 +08:00
2005-07-20 00:30:55 +08:00
ref_ptr < Group > reflectedSubgraph = _create_scene ( ) ;
if ( ! reflectedSubgraph . valid ( ) ) return 1 ;
2004-06-04 16:40:15 +08:00
2005-07-25 21:05:57 +08:00
ref_ptr < Group > reflectedScene = createShadowedScene ( reflectedSubgraph . get ( ) , createReflector ( ) , 0 , viewer . getClearColor ( ) ,
tex_width , tex_height , renderImplementation ) ;
2004-06-04 16:40:15 +08:00
2005-07-20 00:30:55 +08:00
scene - > addChild ( reflectedScene . get ( ) ) ;
2004-06-04 16:40:15 +08:00
2005-07-20 00:30:55 +08:00
viewer . setSceneData ( scene . get ( ) ) ;
2003-06-25 05:57:13 +08:00
// create the windows and run the threads.
viewer . realize ( ) ;
2005-07-20 00:30:55 +08:00
while ( ! viewer . done ( ) )
2003-06-25 05:57:13 +08:00
{
2005-07-20 00:30:55 +08:00
// wait for all cull and draw threads to complete.
viewer . sync ( ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// update the scene by traversing it with the the update visitor which will
// call all node update callbacks and animations.
viewer . update ( ) ;
2003-06-25 05:57:13 +08:00
2005-07-20 00:30:55 +08:00
// fire off the cull and draw traversals of the scene.
viewer . frame ( ) ;
2003-06-25 05:57:13 +08:00
}
2006-08-03 03:55:03 +08:00
// wait for all cull and draw threads to complete.
viewer . sync ( ) ;
// run a clean up frame to delete all OpenGL objects.
viewer . cleanup_frame ( ) ;
// wait for all the clean up frame to complete.
2003-06-25 05:57:13 +08:00
viewer . sync ( ) ;
return 0 ;
}