Eng 591 - VR Programming - Class Outline 10

General Topics: Accessing and Acting Upon Mouse Data

The Mouse as a Sensor

WorldToolKit treats the mouse as one member of the general family of "sensors". In general, sensors include any attached device that delivers information into the computer, including keyboards, mice, joysticks, head trackers, wands, and any other type of feedback device that may be available.

Changing the Mouse Update Function

Every sensor has an associated "update function", that updates the 3-D position and orientation associated with that particular sensor. If the sensor is motion-linked to an object or viewpoint, then whenever the sensor's position and orientation are updated by the update function it also moves the linked object or viewpoint.

The default update function for the mouse ( when the sensor is created with WTmouse_new() ) is WTmouse_moveview2. However this update function can be changed using the following WTK functions:

  • WTsensor_setupdatefn - Used to change the update function associated with a sensor.
  • WTmouse_moveview2 - The default update function for mice.
  • WTmouse_moveview1 - An update function that moves in 3-D space, without pitch or roll. Note that this update function causes movement whenever the mouse is away from the center of the screen, whether buttons are pressed or not.
  • WTmouse_move2D - An update function that allows movement along the Z axis and rotation about the Y axis only. Useful for driving simulators on a two-dimensional surface.
  • WTmouse_drawcursor - An update function that moves the mouse cursor around the screen, but does nothing else. Useful when using the mouse to "pick" objects as opposed to flying.
  • User written - It is also possible to write your own update function, as described on page13-32 and in appendix E

Accessing and Utilizing Mouse Miscellaneous Data ( Buttons )

The WTK function WTsensor_getmiscdata returns an integer with individual bits set to indicate relevant miscellaneous data. The integer that is returned can be bitwise "and"ed with #defined constants to determine which miscellaneous event occured. In the case of the mouse, these constants are:
  • WTMOUSE_LEFTDOWN, WTMOUSE_RIGHTDOWN, and WTMOUSE_MIDDLEDOWN indicate whether a particular button is currently down.
  • WTMOUSE_LEFTBUTTON, WTMOUSE_RIGHTBUTTON, and WTMOUSE_MIDDLEBUTTON indicate whether a particular button has just been pressed ( transitioned from up to down ).
  • WTMOUSE_LEFTUP, WTMOUSE_RIGHTUP, and WTMOUSE_MIDDLEUP indicate whether a particular button has just been released ( transitioned from down to up ).
  • WTMOUSE_LEFTDBLCLK, WTMOUSE_RIGHTDBLCLK, and WTMOUSE_MIDDLEDBLCLK indicate whether a particular button has just been double-clicked.
  • Example:
             if( WTsensor_getmiscdata( Mouse ) & WTMOUSE_LEFTDOWN )
                 WTmessage( "The left mouse button is currently down.\n" );

Accessing and Utilizing Mouse Raw Data ( X-Y Cursor Position on Screen )

The WTK function WTsensor_getrawdata returns a pointer to a structure containing sensor-specific raw data. In the case of the mouse, this structure contains a single field, which is a WTp2 containing the X and Y location of the cursor on the screen. Screen coordinates are floating point values, with ( 0.0, 0.0 ) representing the upper left corner of the screen, and ( screen_width_in_pixels, screen_height_in_pixels ) representing the lower right corner. Note carefully the distinction between screen coordinates and window coordinates - It is not guaranteed that the mouse is in any window at any given time.

The position returned by WTsensor_getrawdata can be used in conjunction with WTscreen_pickpoly to determine which polygon ( if any ) is pointed to by the mouse. Perhaps more useful is that WTscreen_pickpoly also returns a pointer to a nodepath, as well as the location in 3-D space at which the polygon was picked by the mouse.

WTnodepaths

A WTnodepath is a sequence of nodes through the scene graph from a parent node to a descendant node. Nodepaths are particularly useful for distinguishing between multiple instances of a common geometry. WTK functions relative to the current discussion include:
  • WTnodepath_numnodes
  • WTnodepath_getnode
  • WTnodepath_gettraversal
  • WTnodepath_gettransform
  • WTnodepath_gettranslation
  • WTnodepath_getorientation
  • WTnodepath_delete

For example:

int nNodes
WTp3 position;
WTnode *node;
WTnodepath *nodepath;
WTmouse_rawdata *raw;     /* Pointer to structure */
         
raw = ( WTmouse_rawdata *  ) WTsensor_getrawdata( Mouse );
if( WTscreen_pickpoly( 0, raw -> pos, &nodepath, position ) ) {
nNodes = WTnodepath_numnodes( nodepath );
node = WTnodepath_getnode( nNodes - 1 );
WTmessage( "The node %s was picked, at 3-D location ( %f, %f, %f )\n",
     WTnode_getname( node ), position[ X ], position[ Y ], position[ Z ] );
}
         

Note: In the above example, once a pointer to the node was obtained, it could be compared against a node pointer stored in a variable, to determine if some special node had been picked. An alternative ( and more complicated ) approach is shown in several examples below.

Example: Use of nodepaths in safety ( question marks & stop light ) program.

Recall the following from outline 6:

Example 3: Questions and Answers

For this example it is desired to set up a series of "question mark" icons for users to find and select. When a question has been selected, then three lights appear - red, yellow, and green in the standard "stop light" configuration. Users "answer" the question by selecting one of the lights. If their answer is correct, then the other two lights turn grey; If their answer is incorrect, then their selection turns grey, and they can choose again from the remaining two lights.

The scene graph is as follows:

The original code to generate this scene graph looked something like:

        /* Load up the questions */
        /* Eventually these will be read from a file */
   
        NQuestions = NQUESTIONS;        /* Integer */
        Questions = localQuestions;     /* Array of structures, with position, etc. */
   
        /* First load up the master copies.  Note that they are not
           connected to the scene graph. */
   
        QuestionMark = WTnode_load( NULL, "QUESTION.NFF", 1.0 );
   
        RedLight = WTnode_load( NULL, "RED.NFF", 1.0 );
        GreyRedLight = WTnode_load( NULL, "RED.NFF", 1.0 );
        WTgeometry_setrgb( WTnode_getgeometry( GreyRedLight ), 191, 127, 127 );
   
        YellowLight = WTnode_load( NULL, "YELLOW.NFF", 1.0 );
        GreyYellowLight = WTnode_load( NULL, "YELLOW.NFF", 1.0 );
        WTgeometry_setrgb( WTnode_getgeometry( GreyYellowLight ), 191, 191,127 );
   
        GreenLight = WTnode_load( NULL, "GREEN.NFF", 1.0 );
        GreyGreenLight = WTnode_load( NULL, "GREEN.NFF", 1.0 );
        WTgeometry_setrgb( WTnode_getgeometry( GreyGreenLight ), 127, 191, 127 );
   
        /* Now to build the scene graph. */
   
        QuestionsNode = WTgroupnode_new( root ); /* Global pointer to group node */
   
        for( i = 0; i < NQuestions; i++ ) {    /* Loop through array of Questions */
   
                /* First the separator and transform nodes */
   
                sep = WTsepnode_new( QuestionsNode );
                node = WTxformnode_new( sep );
                WTnode_settranslation( node, Questions[ i ].translation );
                WTnode_setorientation( node, Questions[ i ].orientation );
   
                /* Then the question mark geometry */
   
                WTnode_addchild( sep, QuestionMark );
   
                /* And then the red, yellow, and green lights */
   
                node = WTswitchnode_new( sep );
   
                WTnode_addchild( node, RedLight );
                WTnode_addchild( node, GreyRedLight );
                /* WTswitchnode_setwhichchild( node, GREY ); */
                /* Default switch when switchnode created is NONE */
                /* GREY is #defined as 1, BRIGHT as 0 */
   
                node = WTswitchnode_new( sep );
                WTnode_addchild( node, YellowLight );
                WTnode_addchild( node, GreyYellowLight );
                /* WTswitchnode_setwhichchild( node, GREY ); */
   
                node = WTswitchnode_new( sep );
                WTnode_addchild( node, GreenLight );
                WTnode_addchild( node, GreyGreenLight );
                /* WTswitchnode_setwhichchild( node, GREY ); */
   
        }

Now let's see how the questions are handled at run time.

#include "SAFETY.H"

void SafetyActions( void ) {

	/* Local variables, in order of increasing complexity */

	unsigned char red, green, blue;
	short key;
	int i, numnodes, path[ 10 ];
	long mouseMiscData;
	static float simulationTime, ElapsedTime;

	WTp3 p3, pickPosition, translation;
	WTq q, orientation;

	WTvertex *vertex;
	WTpoly *polygon;
	WTmouse_rawdata *mouseRawData;
	WTgeometry *geometry;
	WTnode *node;
	WTnodepath *nodePath;

	/* Update the timekeeping variables */


     ElapsedTime = WTuniverse_time() - simulationTime;
     simulationTime = WTuniverse_time();

	/* Keep view level upon demand */

	if( LevelView ) {
		WTviewpoint_getdirection( Viewpoint, p3 );
		WTdir_2q( p3, q );
		WTviewpoint_setorientation( Viewpoint, q );
	}

	/* Process Keyboard Input */

	key = WTkeyboard_getlastkey();

	if( key ) {

	    switch( key ) {

	        case ' ':	/* Activate Questions */

			  mouseRawData = ( WTmouse_rawdata * )
				 WTsensor_getrawdata( Mouse );

			  WTscreen_pickpoly( 0, mouseRawData -> pos, &nodePath, p3 );

			  if( nodePath ) { 

				 if( WTnodepath_getnode( nodePath, 0 ) == QuestionsNode )
					handleQuestion( nodePath );

				 WTnodepath_delete( nodePath );

			  }

			  break;

		case 'l':	/* Level view momentarily */
			WTviewpoint_getdirection( Viewpoint, p3 );
			WTdir_2q( p3, q );
			WTviewpoint_setorientation( Viewpoint, q );
			break;

		case 'L':	/* Toggle Level View Flag */
			LevelView = !LevelView;
			printf( LevelView ? "Level view on\n" :
				"Level view off\n" );
			break;


		case '1':
		case '2':
		case '3':

			CurrentView = key - '1';
			newViewpoint = Viewpoints[ CurrentView ];

               /* The following global variables are pointers to
                  different sensor devices, which may or may not exist. */

			if( Mouse ) {
				WTviewpoint_removesensor( Viewpoint, Mouse );
				WTviewpoint_addsensor( newViewpoint, Mouse );
			}


			if( Joystick ) {
				WTviewpoint_removesensor( Viewpoint, Joystick );
				WTviewpoint_addsensor( newViewpoint, Joystick );
			}


			if( Logitech ) {
				WTviewpoint_removesensor( Viewpoint, Logitech );
				WTviewpoint_addsensor( newViewpoint, Logitech );
			}


			if( IGlasses ) {
				WTviewpoint_removesensor( Viewpoint, IGlasses );
				WTviewpoint_addsensor( newViewpoint, IGlasses );
			}


			if( Boom ) {
				WTviewpoint_removesensor( Viewpoint, Boom );
				WTviewpoint_addsensor( newViewpoint, Boom );
			}

			Viewpoint = newViewpoint;
			WTwindow_setviewpoint( VCR_Window, Viewpoint );

			break;

		case 'R':
		case 'r': /* Reset Viewpoint to original view */
			WTviewpoint_moveto( Viewpoint, &InitialViews[ CurrentView ] );
			break;

		case 'B':
		case 'b': /* Reset Viewpoint "Backwards" 180 degrees */
			WTviewpoint_rotate( Viewpoint, Y, PI, WTFRAME_VPOINT );
			break;

		case 'p':
			WTviewpoint_setparallax ( Viewpoint,
				WTviewpoint_getparallax ( Viewpoint ) - 0.1 );
			break;

		case 'P':
			WTviewpoint_setparallax ( Viewpoint,
				WTviewpoint_getparallax ( Viewpoint ) + 0.1 );
		    	break;

		case 'c':
			WTviewpoint_setconvergence ( Viewpoint,
				WTviewpoint_getconvergence ( Viewpoint ) - 1 );
			break;

		case 'C':
			WTviewpoint_setconvergence ( Viewpoint,
				WTviewpoint_getconvergence( Viewpoint ) + 1 );
			break;

		case 'y': /* Decrease y-blank */
			WTscreen_setyblank( WTscreen_getyblank() - 1 );
			break;

		case 'Y': /* Increase y-blank */
			WTscreen_setyblank( WTscreen_getyblank() + 1 );
			break;

		case 'M': /* Increase mouse sensitivity */

			WTsensor_setsensitivity( Mouse,
			WTsensor_getsensitivity( Mouse ) * 1.1 );
			break;

		case 'm': /* Decrease mouse sensitivity */

			WTsensor_setsensitivity( Mouse,
			WTsensor_getsensitivity( Mouse ) / 1.1 );
			break;

		case 'J': /* Increase joystick sensitivity */

			WTsensor_setsensitivity( Joystick,
			WTsensor_getsensitivity( Joystick ) * 1.1);
			break;

		case 'j': /* Decrease joystick sensitivity */

			WTsensor_setsensitivity( Joystick,
			WTsensor_getsensitivity( Joystick ) /1.1);
			break;

#ifdef DEVELOPMENT

		case 'N':
		case 'n': /* Node information */
			mouseRawData = ( WTmouse_rawdata * )
			WTsensor_getrawdata( Mouse );
			polygon = WTscreen_pickpoly( 0, mouseRawData -> pos, 
				&nodePath, pickPosition );

			if( !nodePath ) {
				WTmessage( "No node chosen.\n"
				    "Use mouse to point to node of your desire"
				    " and try 'N' again.\n" );
				break;
			} /* End of if no node picked */

			numnodes = WTnodepath_numnodes( nodePath );
			node = WTnodepath_getnode( nodePath, numnodes - 1 );
			WTnode_boundingbox( node, TRUE );
			WTmessage( "\n\nNode %s chosen,", WTnode_getname( node ) );
			WTmessage( " contains %d polygons, \n", WTnode_numpolys( node ) );
			WTp3_print( pickPosition, "and was intersected at: " );
			WTnode_getextents( node, extents );
			WTp3_print( extents, "Node extents:" );
			WTmessage( "Node radius: %f\n", WTnode_getradius( node ) );
			WTnodepath_gettranslation( nodePath, translation );
			WTp3_print( translation, "Node translation:" );

			WTnode_getmidpoint( node, p3 );

			WTp3_print( p3, "Node midpoint:" );
			WTnodepath_getorientation( nodePath, orientation );
			WTq_print( orientation, "Node orientation:" );

			boxNode = ( struct boxNodeStruct * ) malloc( sizeof( struct boxNodeStruct ) );
			if( boxNode ) {
				boxNode->next = BoxList;
				boxNode->node = node;
				BoxList = boxNode;
			}

			WTnodepath_delete( nodePath ); /* Created by WTscreen_pickpoly */
			break;

		case 'U':      /* Undo bounding box */
		case 'u':
			if( BoxList ) {
				boxNode = BoxList;
				BoxList = BoxList->next;
				WTnode_boundingbox( boxNode->node, FALSE );
				free( boxNode );
			}
			break;

		case 'G':
		case 'g':	      /* Polygon information */
			mouseRawData = ( WTmouse_rawdata * ) 
				WTsensor_getrawdata( Mouse );

			polygon = WTscreen_pickpoly( 0, mouseRawData->pos, 
				&nodePath, pickPosition );

			if( !polygon ) {
			    WTmessage( "No polygon chosen.\n"
				"Use mouse to point to polygon of your desire"
				" and try 'G' again.\n" );
			    break;
			} /* End of if no polygon picked */

			WTmessage( "\n\nPolygon id # %hd picked has %hd vertices"
				" and has color ", WTpoly_getid( polygon ),
				( nvertex = WTpoly_numvertices( polygon ) ) );
			WTpoly_getrgb( polygon, &red, &green, &blue );
			WTmessage( "#X%#X%#X.\n", red, green, blue );

			if( ( geometry = WTpoly_getgeometry( polygon ) ) != NULL )
				WTmessage( "This polygon is part of the geometry"
					" \"%s\".\n", WTgeometry_getname( geometry ) );
			else
				WTmessage( "This polygon is part of the stationary"
					" universe.\n" );

			if( ( portal = WTpoly_getportal( polygon ) ) != NULL )
				WTmessage( "This polygon contains a portal to the"
					" \"%s\" universe.\n",
				WTportal_getuniverse( portal ) );
			else WTmessage( "This polygon does not contain a"
				" portal.\n" );

			WTpoly_getcg( polygon, p3 );
			WTp3_print( p3, "Polygon center of gravity:" );
			WTpoly_getnormal( polygon, p3 );
			WTp3_print( p3, "Polygon normal:" );
			WTdir_2q( p3, q );
			WTq_print( q, "Polygon orientation:" );

			WTmessage( "\nPolygon vertex coordinates are: \n" );
			for( i = 0; i < nvertex; i++ ) {
			vertex = WTpoly_getvertex( polygon, i );
			WTvertex_getposition( vertex, p3 );
			WTp3_print( p3, "" );
			}

			break;

#endif

		case 'H':
		case 'h':
		case '?':   /* Help - Fall through to default */

		default:
			WTmessage( "\nThe following keys are active:\n\n" );
			WTmessage( "Q or q: Quit\n"
				"R or r: Reset viewpoint to original view\n"
				"B or b: Backup. ( 180 degree turn )\n"
				"M:      Increase mouse sensitivity\n"
				"m:      Decrease mouse sensitivity\n"
				"J:      Increase joystick sensitivity\n"
				"j:      Decrease joystick sensitivity\n");
			WTmessage( "P:      Increase parallax ( 3D only )\n"
				"p:      Decrease parallax ( 3D only )\n"
				"C:      Increase convergence"
				" ( Crystal Eyes only )\n"
				"c:      Decrease convergence"
				" ( Crystal Eyes only )\n" );
			WTmessage( "Y:      Increase Y-blank"
				" ( Crystal Eyes only )\n"
				"y:      Decrease y-blank"
				" ( Crystal Eyes only )\n"
				"I or i: Information\n" );
			#ifdef DEVELOPMENT
				WTmessage( "N or n:	Node information\n"
					"G or g:	Polygon information\n" 
					"U or u:	Undo bounding box\n" );
			#endif
			break;

	    }  /* end switch( key ) */
	}  /* end if( key ) */


	/* Keep viewpoint above ground */
	if( p3[ Y ] > -0.5 ) {
		p3[ Y ] = -0.5;
		WTviewpoint_setposition( Viewpoint, p3 );
	}

	return;

} /* End SAFETY_Actions Function */

/************************************************************************/

void handleQuestion( WTnodepath *nodepath ) {

	/* This routine handles user picking of questions
	   and answers, including updating score variables. */

	FLAG *picked;
	int path[ 4 ], whichQuestion, whichPicked, 
		rightAnswer, i;
	QuestionType *question;
	WTnode *parent;
	
	WTnodepath_gettraversal( nodepath, path, 4 );
	whichQuestion = path[ 1 ];
	whichPicked = path[ 2 ] - 1;
	question = &Questions[ whichQuestion ];
	rightAnswer = question->rightAnswer;
	picked = question->picked;

	if ( picked[ whichPicked ] ) { /* A repeat: display & exit */
		display( &( question->displays[ whichPicked ] ) );
		return;
	}

	if( whichPicked == 0 ) { /* A question mark was picked */

		picked[ 0 ] = TRUE;
		NFound++;
		Score++;
		parent = WTnodepath_getnode( nodepath, 1 );
		for( i = 2; i < 5; i++ )
			WTswitchnode_setwhichchild( 
				WTnode_getchild( parent, i ), BRIGHT );
		
	} else { /* An answer light was picked */

		if( !picked[ 0 ] ) return; /* Ask the question first! */

		picked[ whichPicked ] = TRUE;

		if( whichPicked != rightAnswer ) { /* wrong answer */

			NWrong++;
			if( ++question->nwrong > 1 ) 
				picked[ rightAnswer ] = TRUE;

			WTswitchnode_setwhichchild( 
				WTnodepath_getnode( nodepath, 2 ), GREY );

		} else { /* Else right answer */

			NRight++;
			Score += 4 - 2 * question->nwrong;
			parent = WTnodepath_getnode( nodepath, 1 );
			for( i = 1; i < 4; i++ ) {
				picked[ i ] = TRUE;
				if( i != rightAnswer )
					WTswitchnode_setwhichchild( 
						WTnode_getchild( parent, i + 1 ), GREY );
			}

		} /* End of else correct answer chosen */
 
	} /* End of else an answer light was picked. */
	
	display( &( question->displays[ whichPicked ] ) );
	return;

} /* End of handleQuestion */


Example: "Activate Node" code from VRiChEL programs.

/* Global variables and type definitions: */

int ActivatedNode;

FLAG LevelView = TRUE;

typedef struct {
            char *helpCode;
            int actionCode;
        } VCR_NodeData;

#define REACTOR 100
#define WELCOME 110

static FLAG Transparent = FALSE;

static WTnode *ReactorSwitch;

void ( *VCR_USF ) ( void ); /* VCR_USF is a pointer to a function */

void VCR_MoveBedActionFcn( void );  /* Function prototype */

VCR_USF = VCR_MoveBedActionFcn;  /* VCR_USF points to VCR_MoveBedActionFcn */


/*************************************************************************/

void loadMoveBed( WTnode *root ) {

	/* This function loads up the Moving Bed Room Scene Graph. 
	   First the lights, then the room, then everything in it. */

     static WTpq signTransform = { { 14.5, -8.0, 0.05 }, { 0.0, 1.0, 0.0, 0.0 } };

	static VCR_NodeData
		ReactorData = { VCR_HELP_MOVING_BED, REACTOR },
             SignData = { VCR_HELP_WELCOME,    WELCOME };

	WTnode *node;

	WTlightnode_load( root, "LIGHTSR6" );

	WTnode_load( root, "MOVEBED.NFF", 1.0 );
	WTnode_load( root, "MBPILES.NFF", 1.0 );
	WTnode_load( root, "FRESHVAP.NFF", 1.0 );
	WTnode_load( root, "STINPIPE.NFF", 1.0 );
	WTnode_load( root, "CATIN.NFF", 1.0 );
	WTnode_load( root, "CRACKVAP.NFF", 1.0 );
	WTnode_load( root, "PURGEVAP.NFF", 1.0 );
	WTnode_load( root, "FLUE1.NFF", 1.0 );
	WTnode_load( root, "FLUE2.NFF", 1.0 );
	WTnode_load( root, "AIRINTOP.NFF", 1.0 );
	WTnode_load( root, "AIRINBOT.NFF", 1.0 );
	WTnode_load( root, "MBSTAIRS.NFF", 1.0 );
	WTnode_load( root, "RAIL1.NFF", 1.0 );
	WTnode_load( root, "SPIRAL1.NFF", 1.0 );
	WTnode_load( root, "FRAT1.NFF", 1.0 );
	WTnode_load( root, "FRAT2.NFF", 1.0 );
	WTnode_load( root, "FRAT3.NFF", 1.0 );
	WTnode_load( root, "FRAT4.NFF", 1.0 );


     node = WTgeometrynode_load( root, "SIGNWELC.NFF", 1.0 );
     geometry = WTnode_getgeometry( node );
     WTgeometry_transform( geometry, &signTransform );
	WTnode_setdata( node, ( void * ) &SignData );

	ReactorSwitch = WTswitchnode_new( root );

	node = WTnode_load( ReactorSwitch, "MBOPAQ.NFF", 1.0 );
	WTnode_setdata( node, ( void * ) &ReactorData );
	node = WTnode_load( ReactorSwitch, "MBTRANS.NFF", 1.0 );
	WTnode_setdata( node, ( void * ) &ReactorData );

	Transparent = TRUE;
	WTswitchnode_setwhichchild( ReactorSwitch, Transparent );

     /* Remainder of Moving Bed load function not shown */

     return;

} /* End of MoveBed Load Function */

/*************************************************************************/

void VCR_MainActions( void ) {

	   int i;

        static float simulationTime;

	   WTp3 p3;
	   WTq q;

	   WTnodepath *nodePath;
	
        long mouseMiscData;
        WTmouse_rawdata *mouseRawData;

	   ActivatedNode = 0;  /* initialize activated node */

        ElapsedTime = WTuniverse_time() - simulationTime;
	   if( ElapsedTime > 1.0 ) ElapsedTime = 0.0; /* Times > 1 are bogus */
        simulationTime = WTuniverse_time();

	   /* Keep view level upon demand */

	   if( LevelView ) {
		  WTviewpoint_getdirection( View, p3 );
		  WTdir_2q( p3, q );
		  WTviewpoint_setorientation( View, q );
	   }

        /* Process Keyboard Input */

        key = WTkeyboard_getlastkey();

        if( key ) switch( key ) {

		 case 'l':	/* Level view momentarily */
			WTviewpoint_getdirection( View, p3 );
			WTdir_2q( p3, q );
			WTviewpoint_setorientation( View, q );
			break;

		 case 'L':	/* Toggle Level View Flag */
			LevelView = !LevelView;
			break;

		 case 'M': WTsensor_setsensitivity( Mouse,
			WTsensor_getsensitivity( Mouse ) * 1.1 );
			break;

		 case 'm': WTsensor_setsensitivity( Mouse,
			WTsensor_getsensitivity( Mouse ) / 1.1 );
			break;

		 case ' ': /* Space bar */

               mouseRawData = ( WTmouse_rawdata * )
                       WTsensor_getrawdata( Mouse );
               WTscreen_pickpoly( 0, mouseRawData -> pos, &nodePath, p3 );

	          if( nodePath ) { 
			    VCR_ActivateNode( nodePath );
			    WTnodepath_delete( nodePath );
		    }

              break;


           /* Remainder of key switch removed */

        } /* End of if( key ) switch( key ) */


        /* Call Universe Specific Functions ( USFs ) as neccessary.
           VCR_USF is a global variable of type "pointer to function".
           If it is not NULL, then it contains the address of an action
           function that is specific to the particular current situation,
           as opposed to this action function that handles generic tasks
           such as reading the keyboard.

           The code above may have set the global variable "ActivatedNode",
           which may then be acted upon by a switch in the USF function. */

        if( VCR_USF ) VCR_USF();

        /* Remainder of main action function not shown */
}


/*********************************************************/

#define EXIT_SIGN 9999

void VCR_ActivateNode( WTnodepath *nodePath ) {

	/* This routine will "Activate" a node, as selected by mouse. When
	   a node that can be activated is clicked by mouse, a matching 
	   integer is stored in the global variable ActivatedNode.  
	   In each of the worlds, as well as here, a switch is run on 
	   ActivatedObject, with code in the respective cases   */

	char *nodename, buffer[ 80 ];
	int numnodes;
	WTgeometry *geometry;
	WTnode *node;
	VCR_NodeData *nodeData;

	numnodes = WTnodepath_numnodes( nodePath );
	node = WTnodepath_getnode( nodePath, numnodes - 1 ); /* leaf node */
	geometry = WTnode_getgeometry( node );
	nodename = WTnode_getname( node );

	if( ( nodeData = ( VCR_NodeData * )
	    WTnode_getdata( node ) ) != NULL ) {
		        
		ActivatedNode = nodeData -> actionCode;

		switch ( ActivatedNode ) { 
		    
		    case TV_ON_OFF:  /*turn on/off tv */

			TVIsOn = !TVIsOn;

			if ( TVIsOn )   {
			    VCR_Log( "TV turned on." );
			    WTgeometry_settexture ( geometry, "TVON.TGA",
				   FALSE, FALSE );
			} else {
			    VCR_Log( "TV turned off." );
			    WTgeometry_settexture ( geometry, "TVOFF.TGA",
				   FALSE, FALSE );
			}
			break;

		    case TV_CHANNEL_UP:      /* change channel up on tv */
		    case TV_CHANNEL_DOWN:    /* change channel down on tv */

			if ( !TVIsOn )  {
			    printf("\nTurn on TV before changing channel\n");
			    ActivatedNode = 0;
			}
			break;

		    case EXIT_SIGN:  /*Exit simulation by activating exit sign*/

			WTuniverse_stop ();
			return;
			/* break; */

		    case 0:	/* No action code in data structure */
			sprintf( buffer, "Attempted to activate \"%s\".",
			    nodename );
			VCR_Log( buffer );
			break; 

		    default: /* All other actions handled in USFs */
		        break;
	
		} /* End of action code switch */

	} else {  /* Object has no associated data structure. */

		sprintf( buffer, "Attempted to activate \"%s\".",
	            nodename );
		VCR_Log( buffer );

	} /* End of if object has data */

} /* End of VCR_activateNode */

/*************************************************************************/

void VCR_MoveBedActionFcn( void ) {

	switch( ActivatedNode ) {

		case REACTOR:
			Transparent = !Transparent;
			WTswitchnode_setwhichchild( ReactorSwitch, Transparent );
			break;

         case WELCOME:
		    VCR_Log ("Entering Welcome Center. ");
		    if( VCR_ExitFcn ) VCR_ExitFcn();
		    WTwindow_setrootnode( VCR_Window, WelcomeRoot );
		    VCR_USF = VCR_WelcomeActionFcn;
		    VCR_WelcomeEntryFcn();
		    return;
		    /* break; not valid after return */

	}

     /* Remainder of Moving Bed Action Function not shown */

	return;
}