Eng 591 - VR Programming - Class Outline 19

General Topics: Sound, Drawing Functions

Sound

Recall that the goal of VR is to produce a highly realistic simulation, to the point where users forget that it is just a simulation and begin to believe that they are really "there". The use of sound can go a long way to improve the realism and believability of a simulation, even if it is just the sound of a door closing or footsteps when the user walks. For other simulations, it may be appropriate to have a narrator at certain times.

Sound is implemented in WTK through a series of steps:

  1. Open a sound device. Typically this is the standard sound card that comes with the computer, but it is also possible to get highly specialized devices that produce highly believeable 3-D spatialized sound effects.
    • WTsounddevice_open
  2. ( Optional ) Configure the sound device parameters, such as rolloff distance.
    • WTsounddevice_setparam, WTsounddevice_getparam
  3. Load a sound from a file. On MS Windows platforms these are typically ".WAV" format files, and on SGI they are typically in "AIF" format. ( at one time there were more specific restrictions on the file formats, such as allowable frequencies and mono versus stereo, but those restrictions may have been removed. See the current version of the hardware guide for full details. )
    • WTsound_load
  4. Adjust the sound parameters, such as the volume and whether or not the sound loops continuously.
    • WTsound_setparam, WTsound_getparam
  5. Play the sound, generally in response to some action by the user.
    • WTsound_play
    • WTsound_stop
  • Other useful sound functions:
    • WTsounddevice_setlistener associates a Viewpoint with a sound device ( used for rolloff. )
    • WTsound_delete deletes a sound
    • WTsound_next is used to iterate through all the sounds for a device.
    • WTsound_isplaying determines whether a sound is currently playing.
    • WTsound_setdonefn sets a function to be called automatically when the sound completes.
    • WTsound_setposition, WTsound_getposition - Used for 3-D spatialized sound.
    • WTsound_setnodepath associates a sound with a nodepath ( specific instance of an object. )
Example: ( Extraction from PVT.C )
/* Defining Global Varibles */
      
int P = 5, V = 5; /* 0 to 10 */
float T[11][11], SS[11][11];
WTsound *EntropySounds [NENTROPY];
WTsounddevice *SoundDevice;			
FLAG SoundON = TRUE;
      
/*********************************************************************/
      
/* MAIN ROUTINE */
      
 main( int argc, char *argv[] ){
char buffer[20];
int i;   
         
WTinit_setmodels( "./Models" );
         
VCR_ReadArgs( argc, argv );
WTuniverse_new( VCR_DisplayType, VCR_WindowType );
Root = WTuniverse_getrootnodes();
         
Viewpoint = WTuniverse_getviewpoints();
         
WTviewpoint_moveto( Viewpoint, &InitialView );
         
/* Turn Entropy sound ON */
         
#ifdef SGI
SoundDevice = WTsounddevice_open( WTSOUNDDEVICE_SGI, 1, Viewpoint );
#else
SoundDevice = WTsounddevice_open( WTSOUNDDEVICE_WINMM, 1, Viewpoint );
#endif
		
if ( SoundDevice ) for( i=0; i<NENTROPY; i++ ){
         
#ifdef SGI
sprintf( buffer, "Waves/PVT%d.AIF", i+1 );
#else
sprintf( buffer, "Waves\\PVT%d.WAV", i+1 );
	#endif
EntropySounds[i] = WTsound_load( SoundDevice, buffer );
WTsound_setparam( EntropySounds[i], WTSOUND_LOOPS, -1.0 );
} /*End of "for" loop for loading the sound files */
         
         /* MAJORITY OF MAIN DELETED FROM EXAMPLE */
         
         WTuniverse_setactions( PVT_Actions );
		
WTuniverse_ready();
         
WTuniverse_go();
         
WTuniverse_delete();
         
return 0;
         
} /* End of Main Routine */
      
/*********************************************************************/
      
void PVT_Actions( void ) {
short key;
         
/* Process Keyboard Input */
         
key = WTkeyboard_getlastkey();
if( !key ) return;
         
switch( key ) {
/* Arrow keys move Cylinder */
            
case WTKEY_RIGHTARROW:              /* Increasing Pressure */
if( P < 10 ) P++;
break;
case WTKEY_LEFTARROW:               /* Decreasing Pressure */
if( P > 0 ) P--;
break;
case WTKEY_UPARROW:                 /* Decreasing Volume */
if( V > 0 ) V--;
break;
case WTKEY_DOWNARROW:               /* Increasing Volume */
if ( V < 10 ) V++;
break;
case 'S':                   
case 's':  
SoundON = !SoundON;
if( !SoundON && SoundDevice )
for( i=0; i<NENTROPY; i++ )
if ( WTsound_isplaying( EntropySounds[i] ) ){
WTsound_stop( EntropySounds[i] );
break;
}
break;
}  /* End switch */
         
DisplayPVT();
         
return;
} /* End Action Function */
      
      
/*********************************************************************/
      
void DisplayPVT( void ){
	
	int i, j;
	WTp3 midpoint, extents, translation;
	static  WTp3 stretchFactors = { 1.0, 1.0, 1.0 },
			washerTranslations[3] = { {   0.0, -500.0, -500.0 },
									  { 500.0,    0.0, -500.0 },
									  { 500.0, -500.0,    0.0 } };
	WTgeometry *geometry;
	unsigned char red, green, blue;
	float t, entropy, entropyLimits [NENTROPY] = { 5.5, 11.0, 16.5, 22.0,
		27.5, 33.0, 38.5, 44.0, 49.5, 55.0 };
      
      
	geometry = WTnode_getgeometry( PVTnodes[P] );
	
	/* Set shape based on pressure */
	
	WTswitchnode_setwhichchild( PVTswitch, P );
	
	/* Set size based on volume */
	/* Extents [Y] should be 50 + 10 * V */
	
	WTgeometry_getmidpoint( geometry, midpoint );
	WTgeometry_getextents( geometry, extents );
	
	stretchFactors[Y] = ( 50.0 + 10.0 * V ) / extents[Y];
	
	WTgeometry_stretch( geometry, stretchFactors, midpoint );
	
	/* Set color based on temperature */
	
	t = T[P][V];
	red   = ( t > MIDTEMP )? 255 : 255*( t-MINTEMP )/( MIDTEMP-MINTEMP );
	blue  = ( t < MIDTEMP )? 255 : 255*( 1.0-( t-MIDTEMP )/( MAXTEMP-MIDTEMP ) );
	green = MIN( red, blue );
	
	WTgeometry_setrgb( geometry, red, green, blue );
	
	
	/* Set position based on P,V, T */
	
	translation[X] =  P * 100.0;
	translation[Z] = -V * 100.0;
	translation[Y] = -1000.0 * ( t-MINTEMP ) / ( MAXTEMP-MINTEMP );
	
	WTnode_settranslation( PVTswitch, translation );	
      
	
	/* Set position of washers */
      
	for( i=0; i<3; i++ ){
		washerTranslations[i][i] = translation[i];
		WTnode_settranslation( Washer[i], washerTranslations[i] );
	}/* End of "for" loop for positioning washers */
      
	/* Play entropy sound track */
	
	if ( !SoundON || !SoundDevice ) return;
	entropy = SS[P][V];
	for ( i=0; i<NENTROPY; i++ ){
		if( entropy>entropyLimits[i] ) continue;
		if( WTsound_isplaying( EntropySounds[i] ) ) return;
		for( j=0; j<NENTROPY; j++ ){
			if ( WTsound_isplaying( EntropySounds[j] ) ){
				WTsound_stop( EntropySounds[j] );
				break;
			} /* End of "if" block to stop sounds */
		}/* End of "for" loop for checking whether the wrong sound is playing */
		WTsound_play( EntropySounds[i] );
		return;
	} /* End of "for" loop for playing entropy sound track */
	
	WTmessage( "Error playing sound.\n");
	
	return;
	
	
} /* End of DisplayPVT */

Drawing Functions

WorldToolKit normally handles all of the necessary graphics display issues for rendering the objects in the simulation, taking into account the user's viewpoint, lighting, material properties, etc. However there are also times when it is desirable to add additional graphics to the screen, which do not correspond to objects in the scene graph. A classic example would be a control panel at the bottom of the screen to simulate an automobile dashboard, or two-dimensional text overlaid on the 3-D imagery. These graphics are typically handled using 2-D and 3-D drawing functions. ( A more recent mechanism, which will not be covered here, involves the use of OpenGL nodes on the scene graph, allowing the programmer to make OpenGL calls at any point during the scene graph traversal. )

2-D Drawing Functions

2-D drawing functions are used to draw overlays that appear in front of the scene, such as a heads-up display or text annotations.

The first step is to create a function prototype of the 2-D drawing function, which must take a WTwindow pointer and a FLAG as its arguments and return void, such as:

void myDrawFunction( WTwindow *win, FLAG eye );

Then the second step is to tell WorldToolKit that you have a 2-D drawing function. This will then cause your function to be called once each simulation loop for monoscopic displays or twice for stereoscopic displays. ( The FLAG argument will be WTEYE_LEFT for the first call, and WTEYE_RIGHT for the second call, if applicable. ) For example:

WTwindow *mainWindow;

mainWindow = WTuniverse_getwindows();
WTwindow_setfgactions( mainWindow, myDrawFunction );

And finally the last step is to actually write the 2-D drawing function. This function will be called once or twice per simulation loop, and whatever it draws will appear in front of the 3-D scene. Some of the relevant function calls that normally appear in 2-D drawing functions include:

  • WTwindow_draw2Dtexture - Perhaps the most useful, this places a texture map in the foreground of the scene. It can be placed in any portion of the screen, and can be a transparent texture map ( for irregular shapes, such as a steering wheel or dashboard. )
     
  • WTwindow_set2Dcolor
  • WTwindow_set2Dlinestyle ( solid or dashed )
  • WTwindow_set2Dlinewidth
     
  • WTwindow_set2Dfont
  • WTwindow_draw2Dtext
  • WTwindow_get2Dtextextents
     
  • WTwindow_draw2Dcircle
  • WTwindow_draw2Drectangle
  • WTwindow_draw2Dpoint
  • WTwindow_draw2Dline
A Complete Example, extracted from Vicher 1:
/* Global Definitions: */
   
   #define TWO_D_WIDTH 0.25
#define TWO_D_HEIGHT 0.33
   
WTwindow *VCR_Window = NULL;
   
void StagedDraw2D_Fcn( WTwindow *, FLAG );
   
#define N_TRACER_POS 128
   
/* defining the variables
   
    tracerstructure     structure holding four variables pertaining to each
                        tracer position:
        WTp3    tracerLocation      3-D tracer location
        float   tracerxlocation     2-D tracer x position
        float   tracerylocation     2-D tracer y location
        int     tracertype      0 = sphere
                                1 = disk
                               -1 = no change
*/
   
   
static struct tracerstructure {WTp3 tracerLocation;
                               float tracerxlocation;
                               float tracerylocation;
                               int tracerType;
   
    } TracerData[N_TRACER_POS] =  /* Data omitted from handout */
   
/* The following line appears in main: */
   
   WTwindow_setfgactions( VCR_Window, StagedDraw2D_Fcn );
   
/******************************************************************************/
   
   void StagedDraw2D_Fcn( WTwindow *window, FLAG eye ) {
int x0, y0, width, height;
      
WTp2 point;
      
WTp2 xyarray[4] = {{0.0,0.0},{TWO_D_WIDTH,0.0},
    {TWO_D_WIDTH,TWO_D_HEIGHT},{0.0,TWO_D_HEIGHT}},
    uvarray[4] = {{0.0,0.0},{1.0,0.0},{1.0,1.0},{0.0,1.0}};
      
WTp2 xyarray2[ 4 ] = { { 0.875, 0.0 }, { 1.0, 0.0 }, { 1.0, 0.125 },
    { 0.875, 0.125 } },
    uvarray2[ 4 ] = { { 0.0, 0.0 }, { 1.0, 0.0 }, { 1.0, 1.0 }, { 0.0, 1.0 } };
      
/* First draw the equilibrium diagram in the lower left corner, and
   place the tracer in the appropriate spot */
      
WTwindow_draw2Dtexture( window, "2DGRAPH.TGA", FALSE, xyarray, uvarray );
WTwindow_set2Dcolor( window, 0, 0, 0xFF );
WTwindow_draw2Dcircle( VCR_Window,
    TracerData[TracerIndex].tracerxlocation*TWO_D_WIDTH,
    TracerData[TracerIndex].tracerylocation*TWO_D_HEIGHT, 
    0.01, WTDRAW2D_SOLID );
      
/* Draw a line from the 2-D graph to the reactor, if reactor is in view */
      
if( WTwindow_projectpoint( window, eye, TracerData[TracerIndex].tracerLocation,
    point ) ) {
WTwindow_getposition( window, &x0, &y0, &width, &height );
         
WTwindow_draw2Dline( window, point[ X ] / width, point[ Y ] / height,
    TracerData[TracerIndex].tracerxlocation*TWO_D_WIDTH,
    TracerData[TracerIndex].tracerylocation*TWO_D_HEIGHT ); 
}
      
/* Then put an exit in the lower right corner */
      
WTwindow_draw2Dtexture( window, "WELCOME.TGA", FALSE, xyarray2, uvarray2 );
      
return;
} /* End of StagedDraw2D_Fcn */
   

3-D Drawing Functions

3-D Drawing functions work pretty much like their 2-D counterparts, except that the lines, points, etc. are drawn in 3-D space as opposed to 2-D overlays. The function prototype for the 3-D drawing function is identical to its 2-D counterpart, and is set up using WTwindow_setdrawfn ( as opposed to WTwindow_setfgactions ):
WTwindow_setdrawfn( mainWindow, myDrawFunction );

It is not possible to set textures or text in 3-D drawing functions; However the following are available:

  • WTwindow_set3dcolor
  • WTwindow_set3dlinestyle
  • WTwindow_set3Dlinewidth
  • WTwindow_set3Dpointsize
  • WTwindow_draw3Dpoints
  • WTwindow_draw3Dlines