looploop.c

( cokecan.nff, cokecan.mat )


/* looploop.c - A simple VR program to illustrate the
        use of paths and motion linls.
        for Eng 591, Virtual Reality Programming.

        Written October 1999 by John T. Bell

        Last modified 19 October 1999 by John Bell
*/


#include <stdlib.h>
#include <stdio.h>
#include "wt.h"

#define NPOINTS  11
#define NSPHERES 11
#define RADIUS 10.0f
#define HEIGHT 37.0f
#define HEIGHT2 18.5f

/* Function Prototypes */

void UserActions( void );
WTgeometry *buildMarker( void );
WTpath *randomPath( int npoints, float minX, float maxX, float minY, 
        float maxY, float minZ, float maxZ );
void randomSpheres( WTnode *Parent, int nspheres, float minX, float maxX, 
        float minY, float maxY, float minZ, float maxZ, float minRadius, 
        float maxRadius );

float cokeU( WTp3 );
float cokeV( WTp3 );

/* Global Variables - Use only when NECESSARY */

WTpq InitialView = { { -150.0f, -50.0f, -150.0f }, 
        { 0.0f, -0.3827f, 0.0f, 0.9239f } };
WTnode *Root = NULL;
WTviewpoint *Viewpoint = NULL;
WTsensor *Mouse = NULL;

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

int main ( int argc, char **argv )

{
        /* Local Variables */

        WTp3 canpos1 = { -50.0f, 0.0f, 50.0f }, canpos2 = { 50.0f, 0.0f, 50.0f },
                canOffset = { 0.0f, -HEIGHT2, 0.0f };
        WTpq ground = { { 0.0f,  0.0f, 0.0f }, { 0.7071f, 0.0f, 0.0f, 0.7071f } };
        WTpq roads =  { { 0.0f, -0.1f, 0.0f }, { 0.7071f, 0.0f, 0.0f, 0.7071f } };
        WTpoly *poly;
        WTgeometry *geo;
        WTnode *node;

        /* Intialize the Universe */
        
        WTuniverse_new( WTDISPLAY_DEFAULT, WTWINDOW_DEFAULT );
        
        /* Set some global variables */
        
        Root = WTuniverse_getrootnodes();
        Viewpoint = WTuniverse_getviewpoints();

        /* Set up devices */

        WTkeyboard_open();

        Mouse = WTmouse_new();
        if( !Mouse )
            WTerror( "Sorry;  A mouse is required to run"
                " this program.\n" );
        else {
            WTmotionlink_new( Mouse, Viewpoint, WTSOURCE_SENSOR, 
                    WTTARGET_VIEWPOINT );
        }

        /* Build the scene graph, starting with a light */

        WTlightnode_newdirected( Root );
        
        /* Build Ground from primatives ( Rotate from vertical to horizontal */

        geo = WTgeometry_newrectangle(1000.0f, 1000.0f, TRUE);

        WTgeometry_transform(geo, &ground);
        WTgeometry_setrgb(geo, 0, 127, 0);
        WTgeometrynode_new(Root, geo);

        /* Build Roads from primatives */

        geo = WTgeometry_newrectangle(1000.0f,20.0f, TRUE);
        WTgeometry_transform(geo, &roads);
        WTgeometry_setrgb(geo, 0, 0, 0);
        WTgeometrynode_new(Root, geo);

        geo = WTgeometry_newrectangle(20.0f,1000.0f, TRUE);
        WTgeometry_transform(geo, &roads);
        WTgeometry_setrgb(geo, 0, 0, 0);
        WTgeometrynode_new(Root, geo);

        /* Put some spheres in the sky, just to make things more interesting */

        randomSpheres(Root, NSPHERES, -200, 200, -200, -300, -200, 200, 10, 20);

        /* Add some coke cans for texture demonstration
                The first can is just loaded from an nff file */


        node = WTmovnode_load( Root, "cokecan.nff", 1.0 );
        WTnode_settranslation( node, canpos1 );

        /* The second can is built on the fly.  The two functions
                cokeU and cokeV calculate the uv coordinates for the textures */


        geo = WTgeometry_newcylinder( HEIGHT, RADIUS, 42, TRUE, FALSE );
        WTgeometry_translate( geo, canOffset );
        
        WTgeometry_settextureuv( geo, "cokelabl.tga", cokeU, cokeV, FALSE, FALSE );
        
        for( poly = WTgeometry_getpolys( geo ); poly; poly = WTpoly_next( poly ) )
                if( WTpoly_numvertices( poly ) > 4 )
                        WTpoly_deletetexture( poly );

        node = WTmovgeometrynode_new( Root, geo );
        WTnode_settranslation( node, canpos2 );

        /* Everything else ( the paths & their markers ) will be built from
           the action function */


        /* Initialize the viewpoint */

        /*WTwindow_zoomviewpoint( WTuniverse_getcurrwindow() );*/
        WTviewpoint_moveto( Viewpoint, &InitialView );
        
        /* Setup the action function */
        
        WTuniverse_setactions( UserActions );

        /* The "go" function runs the simulation.  
           It doesn't return until we quit. */

           
        WTuniverse_ready();
        WTuniverse_go();
        
        /* And now to clean up our toys and go home. */

        WTuniverse_delete();

        return 0;

/* End of Main Routine */

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

/* Special keys added to the action function for this simulation:

        C or c:        (Re)Create a new path, with NPOINTS random points.
                ( Also attach Viewpoint to path, & set path parameters. )
        D or d: Delete the current path.
        L or l: Interpolate the path, using Linear fitting.
        S or s: Interpolate the path, using B-Spline fitting.
        B or b: Interpolate the path, using Bezier fitting.
        V or v: Toggle path marker visibility.
        1 to 9: Play the path at varying speeds.
        0:      Stop playing the path.
*/


void UserActions( void ) {

        short key;
        WTp3 p3;
        WTq q;
        static WTpath *path = NULL;
        WTpath *newpath;
        static WTgeometry *marker = NULL;

        /* Process Keyboard Input */

        key = WTkeyboard_getlastkey();

        if( key ) switch( key ) {

                case 'c':
                case 'C':        /*create a path*/

                        if( path ) 
                                WTpath_delete( path );
                        path = randomPath( NPOINTS, -100, 100, -10, -100, -100, 100 );
                        if( !marker )
                                marker = buildMarker();
                        WTpath_setmarker( path, marker );
                        WTpath_setvisibility( path, TRUE );
                        WTpath_setmode( path, WTPLAY_CONTINUOUS );
                        WTmotionlink_new( path, Viewpoint, WTSOURCE_PATH, 
                                WTTARGET_VIEWPOINT );
                        break;

                case 'd':
                case 'D':        /*Delete the path*/

                        WTpath_delete( path );
                        path = NULL;
                        break;

                case 'l':
                case 'L':        /* Interpolate linearly */

                        if(!path) break;

                        newpath = WTpath_interpolate( path, 2, WTPATH_LINEAR );
                        WTpath_setmarker( newpath, marker );
                        WTpath_setmode( newpath, WTPLAY_CONTINUOUS );
                        WTmotionlink_new( newpath, Viewpoint, WTSOURCE_PATH, 
                                WTTARGET_VIEWPOINT );
                        WTpath_setvisibility( newpath, TRUE );
                        if( WTpath_isplaying( path ) ) WTpath_play( newpath );
                        WTpath_delete( path );
                        path = newpath;
                        newpath = NULL;
                        break;

                case 'b':
                case 'B':        /* Interpolate using Bezier fitting */

                        if(!path) break;

                        newpath = WTpath_interpolate( path, 2, WTPATH_BEZIER );
                        WTpath_setmarker( newpath, marker );
                        WTpath_setmode( newpath, WTPLAY_CONTINUOUS );
                        WTmotionlink_new( newpath, Viewpoint, WTSOURCE_PATH, 
                                WTTARGET_VIEWPOINT );
                        WTpath_setvisibility( newpath, TRUE );
                        if( WTpath_isplaying( path ) ) WTpath_play( newpath );
                        WTpath_delete( path );
                        path = newpath;
                        newpath = NULL;
                        break;

                case 's':
                case 'S':        /* Interpolate using B-Splines */

                        if(!path) break;
                        newpath = WTpath_interpolate( path, 2, WTPATH_BSPLINE );
                        WTpath_setmarker( newpath, marker );
                        WTpath_setmode( newpath, WTPLAY_CONTINUOUS );
                        WTmotionlink_new( newpath, Viewpoint, WTSOURCE_PATH, 
                                WTTARGET_VIEWPOINT );
                        WTpath_setvisibility( newpath, TRUE );
                        if( WTpath_isplaying( path ) ) WTpath_play( newpath );
                        WTpath_delete( path );
                        path = newpath;
                        newpath = NULL;
                        break;

                case '0':        /* Stop playing the path */

                        if( path )
                                WTpath_stop(path);
                        break;

                case '1':
                case '2':
                case '3':
                case '4':
                case '5':        /* Play the path at various speeds */
                case '6':
                case '7':
                case '8':
                case '9':

                        if(!path) break;

                        WTpath_setplayspeed( path, key-'0' );
                        if( !WTpath_isplaying( path ) ) 
                                WTpath_play( path );
                        break;

                case 'n':
                case 'N':        /* Play to the next element of the path */

                        if( path && !WTpath_isplaying( path ) )
                                WTpath_play1( path );
                        break;

        
                case 'v':
                case 'V':        /* Toggle visibility of the path */

                        if( path )
                                WTpath_setvisibility( path, !WTpath_getvisibility( path ) );
                        break;

                case 'i':
                case 'I':        /* Print out information */

                        WTviewpoint_getposition( Viewpoint, p3 );
                        WTviewpoint_getorientation( Viewpoint, q );
                        WTp3_print( p3, "Current Position: " );
                        WTq_print( q, "Current Orientation: " );
                        break;

                case 'q':
                case 'Q':        /* Quit the simulation */

                        WTuniverse_stop();
                        break;

                case 'p':
                case 'P':        /* Print out the scene graph */

                        WTnode_print( Root );
                        break;

                case 'r':
                case 'R':        /* Reset the viewpoint */

                        WTviewpoint_moveto( Viewpoint, &InitialView );
                        break;

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

                default:
                        WTmessage( "\nThe following keys are active:\n\n" );
                            WTmessage( "Q or q: Quit.\n" );
                            WTmessage( "R or r: Reset\n" );
                            WTmessage( "C or c: Create a path.\n" );
                            WTmessage( "L or l: Interpolate the path linearly.\n" );
                            WTmessage( "B or b: Interpolate the path using Bezier fitting.\n" );
                            WTmessage( "S or s: Interpolate the path using B-splines.\n" );
                            WTmessage( "V or v: Toggle path visibility.\n" );
                            WTmessage( "1 to 9: Follow the path at various speeds.\n" );
                            WTmessage( "0 ( zero ): Stop the path.\n" );
                            WTmessage( "N or n: Step to the next point on the path.\n" );
                        WTmessage( "P or p: Print scene graph.\n\n" );
                        break;

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

        return;

/* End of UserActions */

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

/* The following function creates a path containing npoints
        randomly assorted over the range minX to maxX, etc.
        Each point is also given a random orientation.
*/


WTpath *randomPath(int npoints, float minX, float maxX, float minY, float maxY,
                                   float minZ, float maxZ) {

        float deltaX = maxX-minX;
        float deltaY = maxY-minY;
        float deltaZ = maxZ-minZ;
        int i;
        WTpath *path;
        WTpathelement *element;
        WTpq pq, pq0;
        double angle, sine;

        srand(time(NULL));

        path = WTpath_new(NULL);

        for(i=0; i < npoints; i++) {

                /* First generate a random p3 within the range */

                pq.p[ X ] = minX + deltaX * rand() / RAND_MAX;
                pq.p[ Y ] = minY + deltaY * rand() / RAND_MAX;
                pq.p[ Z ] = minZ + deltaZ * rand() / RAND_MAX;

                /* Generate 3 random components for the q, and normalize */

                pq.q[ X ] = -10.0f + rand() % 20;
                pq.q[ Y ] = -10.0f + rand() % 20;
                pq.q[ Z ] = -10.0f + rand() % 20;

                WTp3_norm(pq.q);

                /* Pick a random angle and handle the sine & cosine */

                angle = PI * rand() / RAND_MAX;

                sine = sin( angle );

                WTp3_mults( pq.q, sine );
                pq.q[ W ] = cos( angle );

                /* Save the first point, to re-use as the last point */

                if( i == 0 ) WTpq_copy( &pq, &pq0 );

                /* And finally add the new pathelement to the path */

                element = WTpathelement_new( &pq );
                WTpath_appendelement( path, element );
        }

        element = WTpathelement_new( &pq0 );
        WTpath_appendelement( path, element );
        
        return path;
}

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

/* The following routine generates a path marker consisting of two
   triangles.  The intersection of the triangles is at the origin,
   and the points of the triangles point along the positive Z axis,
   with one triangle flat in the y = 0 plane, and the other in the 
   X = 0 plane. */


WTgeometry *buildMarker(void) {
        
        int i;
        WTgeometry *geo;
        WTpoly *poly;
        WTp3 points[5] = { { -1, 0, 0 }, { 1, 0, 0 }, { 0, 0, 3 }, 
                        { 0, -1, 0 }, { 0, 0, 0 } };

        /* Start the geometry, and put in all the vertices */

        geo = WTgeometry_begin();

        for(i= 0; i < 5; i++)
                WTgeometry_newvertex( geo, points[ i ] );

        /* Build the first triangle, using the 1st 3 vertices. */

        poly = WTgeometry_beginpoly(geo);

        for ( i = 0; i < 3; i++ )
                WTpoly_addvertex( poly, i );
        WTpoly_close( poly );

        WTpoly_setrgb( poly, 255, 0, 255 ); /* Magenta */
        WTpoly_setbothsides( poly, TRUE );

        /* Build the second triangle from the last 3 vertices. */

        poly = WTgeometry_beginpoly(geo);

        for (i=2; i < 5; i++)
                WTpoly_addvertex(poly, i);
        WTpoly_close(poly);

        WTpoly_setrgb(poly, 0, 0, 0);        /* Black */
        WTpoly_setbothsides(poly, TRUE);

        /* Tha, tha, tha, that's all folks */

        WTgeometry_close(geo);
        
        return geo;
}

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

/* The following routine puts some randomly located spheres of random size and
   color into the sky, so we have something to look at when we look up. */


void randomSpheres( WTnode *parent, int nspheres, float minX, float maxX, 
        float minY, float maxY, float minZ, float maxZ, float minRadius, 
        float maxRadius ) {
        
        int i;
        float deltaX, deltaY, deltaZ, deltaR, radius;

        WTp3 p3;
        WTgeometry *geo;

        deltaX = maxX-minX;
        deltaY = maxY-minY;
        deltaZ = maxZ-minZ;
        deltaR = maxRadius-minRadius;

        srand(time(NULL));
        
        for ( i = 0; i < nspheres; i++ ) {
                
                /* Pick a random location and radius */

                p3[ X ] = minX + deltaX * rand() / RAND_MAX;
                p3[ Y ] = minY + deltaY * rand() / RAND_MAX;
                p3[ Z ] = minZ + deltaZ * rand() / RAND_MAX;
                radius = minRadius + deltaR * rand() / RAND_MAX;

                /* Build the sphere, put it in place, and colorize it */

                geo = WTgeometry_newsphere( radius, 7, 7, FALSE, TRUE );
                WTgeometry_translate( geo, p3 );
                WTgeometry_setrgb( geo, rand() % 255, rand() % 255, 
                        rand() % 255 );
                WTgeometrynode_new( parent, geo );

        }

        return;

}

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

/* The following two routines calculate the uv texture coordinates for the coke
        can. */


float cokeU( WTp3 p ) {

        double x;

        x = p[ X ] / RADIUS;        /* Beware of round-off errors */
        if( x > 1.0 ) x = 1.0;
        if( x < -1.0 ) x = -1.0;

        if( p[ Z ] > 0.0f )
                return acos( x ) / PIT2;
        else
                return 1.0f - acos( p[ X ] / RADIUS ) / PIT2;
}

float cokeV( WTp3 p ) {

        if( p[ Y ] < -HEIGHT2 )
                return 1.0f;
        else
                return 0.0f;
}