#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
#include <Performer/pf.h>
#include <Performer/pf/pfChannel.h>
#include <Performer/pf/pfEarthSky.h>
#include <Performer/pf/pfLightSource.h>
#include <Performer/pf/pfDCS.h>
#include <Performer/pf/pfSwitch.h>
#include <Performer/pf/pfPartition.h>
#include <Performer/pfdu.h>
#include <Performer/pfutil.h>
#include <cave.h>
#include <pfcave.h>
#ifdef SOUND
#include <vssClient.h>
#include "AudioEnv.h"
#endif

typedef struct
	{
	pfVec3 prevPos,pos;
	pfVec3 dir;
	float speed;
	float height;
	pfDCS *dcs;
	} ball_t;

void update_ball(ball_t *ball,pfNode *hole);
void move_ball(ball_t *ball,pfNode *scene);
void bounce_ball(ball_t *ball,pfNode *scene);
void initGraphics(void);
ball_t * init_ball(char *objFile);
void do_navigation(void);
void create_scene(pfScene **scene,pfDCS **nav_dcs);
void define_earthsky(pfChannel *chan);


main(int argc,char **argv)
{
 char *sounddata=NULL;
 int opt;
#ifdef SOUND
 AudioEnv *audio_env = NULL;
#endif
 pfScene *scene;
 pfNode *hole;
 pfDCS *nav_dcs;
 pfChannel *masterChan;
 ball_t *ball=NULL;
 pfInit();
 pfCAVEConfig(&argc,argv,NULL);
 while ((opt = getopt(argc,argv,"s:")) != EOF)
	switch (opt)
		{
		case 's': sounddata = strdup(optarg); break;
		}
 if (optind >= argc-1)
	{
	fprintf(stderr,"\nUsage: %s [-s sound] hole ball\n",argv[0]);
	exit(1);
	}
 pfConfig();
 create_scene(&scene,&nav_dcs);
 nav_dcs->addChild(hole = pfdLoadFile(argv[optind]));
 ball = init_ball(argv[optind+1]);
 nav_dcs->addChild(ball->dcs);
{ pfSphere bsphere;
 scene->getBound(&bsphere);
 CAVENavTranslate(bsphere.center.vec[0],bsphere.center.vec[1],bsphere.center.vec[2]);
}
 scene->setTravMask(PFTRAV_ISECT, 0xffffffff,
			PFTRAV_SELF|PFTRAV_DESCEND|PFTRAV_IS_CACHE, PF_OR);
/* nav_dcs->addChild(ball->dcs); */
 pfCAVEInitChannels();
 masterChan = pfCAVEMasterChan();
 masterChan->setScene(scene);
/* define_earthsky(masterChan); */
/* pfCAVEPreDrawFunc((pfChanFuncType)initGraphics); */
#ifdef SOUND
 if ((sounddata) && (CAVEDistribMaster()))
	{
	if (!BeginSoundServer())
	        printf("UDP connection to sound server failed\n");
	else
		audio_env = new AudioEnv(sounddata);
	}
#endif
 while (!CAVEgetbutton(CAVE_ESCKEY))
	{
	pfSync();
	pfCAVEPreFrame();
	pfFrame();
	pfCAVEPostFrame();
	do_navigation();
	pfCAVEDCSNavTransform(nav_dcs);
#ifdef SOUND
	if (audio_env)
		audio_env->Update(1);
#endif
	update_ball(ball,hole);
	}
#ifdef SOUND
 if (audio_env)
	{
	delete audio_env;
	EndSoundServer();
	}
#endif
 pfCAVEHalt();
 pfExit();
 return 0;
}

void update_ball(ball_t *ball,pfNode *hole)
{
 if (CAVEButtonChange(2) == -1)
	{
	pfCAVEGetVector(CAVE_WAND_FRONT_NAV,ball->dir.vec);
	ball->dir[PF_Z] = 0;
	ball->dir.normalize();
	ball->speed = 3.0f;
	}
 move_ball(ball,hole);
}

void move_ball(ball_t *ball,pfNode *scene)
{
 static float prevTime=-1;
 float time,dt;
 if (prevTime < 0)
	prevTime = pfGetTime();
 time = pfGetTime();
 dt = time - prevTime;
 prevTime = time;
 ball->prevPos.copy(ball->pos);
 ball->pos.addScaled(ball->pos,dt*ball->speed,ball->dir);
 ball->dcs->setTrans(ball->pos.vec[PF_X],ball->pos.vec[PF_Y],ball->pos.vec[PF_Z]);
 bounce_ball(ball,scene);
/* ball->speed *= .995f; */
}

void bounce_ball(ball_t *ball,pfNode *scene)
{
 pfSegSet segset;
 pfHit **hits[32];
 segset.activeMask = 1;
 segset.isectMask = 0xFFFF;
 segset.discFunc = NULL;
 segset.bound = NULL;
 segset.mode = PFTRAV_IS_PRIM|PFTRAV_IS_NORM|PFTRAV_IS_CULL_BACK;
 segset.segs[0].makePts(ball->prevPos,ball->pos);
 if (scene->isect(&segset, hits))
	{
	pfVec3 norm,negdir;
	pfMatrix xmat,rotmat;
	(*hits[0])->query(PFQHIT_XFORM, (float*)xmat.mat);
	(*hits[0])->query(PFQHIT_NORM, norm.vec);
	norm.xformVec(norm, xmat);
	negdir.scale(-1.0f,ball->dir);
	rotmat.makeVecRotVec(negdir,norm);
	norm.xformVec(norm, rotmat);
	ball->dir.copy(norm);
	ball->dir[PF_Z] = 0;
	ball->dir.normalize();
	ball->pos.copy(ball->prevPos); /* cheap hack */
	}
}

void initGraphics(void)
{
 pfCullFace(PFCF_OFF);
 pfOverride(PFSTATE_CULLFACE, PF_ON);
 pfCAVEPreDrawFunc(NULL);
}


ball_t * init_ball(char *objFile)
{
 ball_t *ball;
 pfNode *obj;
 pfBox bbox;
 if ((obj = pfdLoadFile(objFile)) == NULL)
	{
	pfCAVEHalt();
	pfExit();
	}
 ball = (ball_t *) pfMalloc(sizeof(ball_t),pfGetSharedArena());
 ball->dcs = new pfDCS;
 ball->dcs->addChild(obj);
 pfuTravCalcBBox(ball->dcs, &bbox);
 ball->height = -bbox.min[PF_Z];
 ball->pos.set(0,0,ball->height);
 return ball;
}


void do_navigation(void)
{
 float jx=CAVE_JOYSTICK_X,jy=CAVE_JOYSTICK_Y;
 if (fabs(jx) > 0.2) CAVENavRot(2.5f*jx,'z');
 if (fabs(jy) > 0.2)
	{
	float w[3];
	pfCAVEGetVector(CAVE_WAND_FRONT,w);
	CAVENavTranslate(w[0]*jy/5.0f,w[1]*jy/5.0f,0.0f);
	}
}

void create_scene(pfScene **scene,pfDCS **nav_dcs)
{
 pfLightSource *light;
 *scene = new pfScene;
 *nav_dcs = new pfDCS;
 (*scene)->addChild(*nav_dcs);
 pfGeoState *gstate = new pfGeoState;
 gstate->setMode(PFSTATE_ENLIGHTING, PF_ON);
 (*scene)->setGState(gstate);
 light = new pfLightSource;
 light->setPos(1.0f,1.0f,1.0f,0.0f);
 (*scene)->addChild(light);
 light = new pfLightSource;
 light->setPos(-1.0f,-0.2f,-0.2f,0.0f);
 (*scene)->addChild(light);
}

void define_earthsky(pfChannel *chan)
{
 pfEarthSky *esky = new pfEarthSky();
 esky->setMode(PFES_BUFFER_CLEAR, PFES_SKY_GRND);
 esky->setAttr(PFES_GRND_HT, 0.0f);
 esky->setColor(PFES_GRND_FAR, 0.3f, 0.1f, 0.0f, 1.0f);
 esky->setColor(PFES_GRND_NEAR, 0.5f, 0.3f, 0.1f, 1.0f);
 chan->setESky(esky);
}

