/* Program to create a CAVE maze with OpenGL in X Windows
 * maze.c
 *
 * Written by:    Juliet Bishop 
 * Last modified: May 16, 1996
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "image.h"
#include "cave_ogl.h"


#define  MAX	      15    /* Controls size of maze */	
#define  NUM_ITER     100
#define  HT           15.0  /* Controls dimensions (height of walls, etc) */
#define  DS           5.0
#define	 X	      0
#define	 Y	      1
#define  Z	      2

/* Global variables */
GLuint   MAZE_WALLS_LIST;
int      twoD_array [MAX][MAX];
int      start_i, start_j, exit_i, exit_j;
float    old_azimuth;
int      ceiling;

void Setup_Light(void);

/* Function that determines where walls should go 
   and puts each polygon into the display list */
void Make_Maze(void) {
  int i, j, num_walls;
  int floor_width, floor_height, ceiling_width, ceiling_height;
  int start_width, start_height, wall_width, wall_height;
  float x_pos, y_pos, z_pos;
  GLfloat material_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
  GLfloat material_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
  GLfloat material_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat material_shininess[] = { 40.0 };
  GLfloat material_emission[] = { 0.3, 0.2, 0.2, 0.0 };
  unsigned char *start_tex, *wall_tex, *floor_tex, *ceiling_tex;
  
  glClearColor(0.0, 0.0, 0.0, 0.0);
  Setup_Light();   /* Sets up and turns on light sources */

  /* Call to load_image puts the texture map into the last argument.
   * load_image can be found in image.c */
  if (!load_image("marble.rgb",
		  &floor_width, &floor_height, &floor_tex))
    printf("floor image didn't load\n");
  
  if (!load_image("sky.rgb",
		  &ceiling_width, &ceiling_height, &ceiling_tex))
    printf("ceiling image didn't load\n");
 
  if (!load_image("church_marble5.rgb",
		  &start_width, &start_height, &start_tex))
    printf("start image didn't load\n");
  
  if (!load_image("brick7.rgb",
		  &wall_width, &wall_height, &wall_tex))
    printf("wall image didn't load\n");

  /* The following code sets up texture mapping */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glEnable(GL_TEXTURE_2D);
  glShadeModel(GL_SMOOTH);
  
  /* Start of display list */
  MAZE_WALLS_LIST = glGenLists(1);
  glNewList(MAZE_WALLS_LIST, GL_COMPILE);

   /* Polygon for maze floor */
    glColor3f(0.7, 0.7, 0.7);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, floor_width, floor_height, 0, GL_RGB, GL_UNSIGNED_BYTE, floor_tex);
    glBegin(GL_POLYGON);
      glNormal3f(0.0, 1.0, 0.0);
      glTexCoord2f(0.0, 0.0); glVertex3f(-DS*HT, 0.0, -DS*HT);
      glTexCoord2f(7.0, 0.0); glVertex3f((MAX-DS)*HT, 0.0, -DS*HT);
      glTexCoord2f(7.0, 7.0); glVertex3f((MAX-DS)*HT, 0.0, (MAX-DS)*HT);
      glTexCoord2f(0.0, 7.0); glVertex3f(-DS*HT, 0.0, (MAX-DS)*HT);
    glEnd();
    
 /* Polygon for maze ceiling */
    if (ceiling) {  /* Only add ceiling polygon if user wants it */
      glColor3f(1.0, 1.0, 1.0);
      glTexImage2D(GL_TEXTURE_2D, 0, 3, ceiling_width, ceiling_height, 0, GL_RGB, GL_UNSIGNED_BYTE, ceiling_tex);
      glBegin(GL_POLYGON);
        glNormal3f(0.0, -1.0, 0.0);
	glTexCoord2f(0.0, 0.0); glVertex3f(-DS*HT, HT, -DS*HT);
	glTexCoord2f(4.0, 0.0); glVertex3f((MAX-DS)*HT, HT, -DS*HT);
	glTexCoord2f(4.0, 4.0); glVertex3f((MAX-DS)*HT, HT, (MAX-DS)*HT);
	glTexCoord2f(0.0, 4.0); glVertex3f(-DS*HT, HT, (MAX-DS)*HT);
      glEnd();
    }
    
   /* Polygon to mark start position of maze */
    glColor3f(1.0, 1.0, 1.0);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, start_width, start_height, 0, GL_RGB, GL_UNSIGNED_BYTE, start_tex); 
    glBegin(GL_POLYGON);
      glNormal3f(0.0, 1.0, 0.0);
      glTexCoord2f(0.0, 0.0); glVertex3f((start_j-DS)*HT, 0.1, (start_i-DS)*HT);
      glTexCoord2f(1.0, 0.0); glVertex3f((start_j-DS)*HT, 0.1, (start_i-DS)*HT+HT*2);
      glTexCoord2f(1.0, 1.0); glVertex3f((start_j-DS)*HT+HT*2, 0.1, (start_i-DS)*HT+HT*2);
      glTexCoord2f(0.0, 1.0); glVertex3f((start_j-DS)*HT+HT*2, 0.1, (start_i-DS)*HT);
    glEnd();
   
    /* Material properties setup for walls */
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, material_shininess);
    glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_emission);
 
  /* Using the array twoD_array, the positions of the walls
   * are determined  */
    glColor3f(1.0, 1.0, 1.0);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, wall_width, wall_height, 0, GL_RGB, GL_UNSIGNED_BYTE, wall_tex);
    num_walls = 0;
    for (i=0; i < MAX; i++) {
      for (j=0; j < MAX; j++) {
	if (twoD_array[i][j] == 0) {  /* Finds 0's in array */
	  if (j < MAX-1) {
	    if (twoD_array[i][j+1] == 1){  /* Checks if cell to the right is 1 */
	      glBegin(GL_POLYGON);
	        glNormal3f(1.0, 0.0, 0.0); 
	        glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i-DS));
		glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i-DS));
		glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i+1-DS));
		glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i+1-DS));
	      glEnd();
	      num_walls++; 
	    }
	  }
	  if (j > 0) {
	    if (twoD_array[i][j-1] == 1) { /* Checks if cell to the left is 1 */
	      glBegin(GL_POLYGON);
	        glNormal3f(0.0, 0.0, 1.0);
	        glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i-DS));
		glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i-DS));
		glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i+1-DS));
		glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i+1-DS));
	      glEnd();
	      num_walls++; 
	    }
	  }
	  if (i < MAX-1) {
	    if (twoD_array[i+1][j] == 1) { /* Checks if cell below is 1 */
	      glBegin(GL_POLYGON);
	        glNormal3f(0.0, 0.0, -1.0);
	        glNormal3f(1.0, 0.0, 0.0);
	        glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i+1-DS));
		glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i+1-DS));
		glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i+1-DS));
		glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i+1-DS));
	      glEnd();
	      num_walls++; 
	    }
	  }
	  if (i > 0) {
	    if (twoD_array[i-1][j] == 1) { /* Checks if cell above is 1 */
	      glBegin(GL_POLYGON);
	        glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i-DS));
		glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i-DS));
		glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i-DS));
		glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i-DS));
	      glEnd();
	      num_walls++; 
	    }
	  }
	}  /* End of check for particular cell */

      } /* End of for j loop */
    }  /* End of for i loop */

/* The following code generates the maze outside walls, 
   leaving a space for the exit */
    i=0;
    for (j=0; j < MAX; j++) 
      if ((i != exit_i) || (j != exit_j)) { /* Check for exit location */
	glBegin(GL_POLYGON);
	  glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i-DS));
	  glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i-DS));
	  glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i-DS));
	  glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i-DS));
	glEnd();
      } 
    j=0;
    for (i=0; i < MAX; i++) 
      if ((i != exit_i) || (j != exit_j)) { /* Check for exit location */
	glBegin(GL_POLYGON);
	  glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i-DS));
	  glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i-DS));
	  glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i+1-DS));
	  glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i+1-DS));
	glEnd();
      }
    i=MAX-1;
    for (j=0; j < MAX; j++) 
      if ((i != exit_i) || (j != exit_j)) { /* Check for exit location */
	glBegin(GL_POLYGON);
	  glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j-DS), 0.0, HT*(i+1-DS));
	  glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j-DS), HT, HT*(i+1-DS));
	  glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i+1-DS));
	  glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i+1-DS));
	glEnd();
      }
    j=MAX-1;
    for (i=0; i < MAX; i++) 
      if ((i != exit_i) || (j != exit_j)) { /* Check for exit location */
	glBegin(GL_POLYGON);
	  glTexCoord2f(1.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i-DS));
	  glTexCoord2f(1.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i-DS));
	  glTexCoord2f(0.0, 2.0); glVertex3f(HT*(j+1-DS), HT, HT*(i+1-DS));
	  glTexCoord2f(0.0, 0.0); glVertex3f(HT*(j+1-DS), 0.0, HT*(i+1-DS));
	glEnd();
      } 

   glEndList(); /* End of display list for maze */

}  /* End of function make_maze()  */


/* Set up and initialize light */
void Setup_Light(void) {
  GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
  GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat light_position[] = { 10.0, 20.0, 10.0, 0.0 };
  GLfloat light_spot_dir[] = { 0.0, -1.0, 0.0 };
  
  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light_spot_dir);
  glEnable(GL_LIGHT0);
 
}


/* Display loop function, clears buffers and draws maze
 * from display list */
void Display_Maze(void) {
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  CAVENavTransform();
  glCallList(MAZE_WALLS_LIST);

}


/* Generates MAX-1 by MAX-1 array of 1's and 0's for maze */
int Calculate_Maze_Array(void) {
  int visited [MAX][MAX];
  int i,j, dir, num_visit;
  int sum, num, rand_seed;
  int exit_set;
  
/* Initialize arrays */ 
  for (i=0; i < MAX; i++) {
    for (j=0; j < MAX; j++) {
      twoD_array[i][j] = 0;
      visited[i][j] = 0;   
    }
  }

  /* Prompt for user input for the random number generator seed */
  printf("Enter a number from 0 to 1000: ");
  scanf("%i", &rand_seed);
  if ((rand_seed < 0) || (rand_seed > 1000)) {
     printf("Try again: Enter a number from 0 to 1000\n");
     scanf("%i", &rand_seed);
  }
  if ((rand_seed < 0) || (rand_seed > 1000))  rand_seed = 1;
  
  i=0; j=0; sum=0;
  /* Set start position of maze */
  start_i = MAX/2; start_j = MAX/2;
  /* Initialize a square of four cells to 1 */
  twoD_array[MAX/2][MAX/2] = 1;
  twoD_array[MAX/2][MAX/2+1] = 1;
  twoD_array[MAX/2+1][MAX/2] = 1;
  twoD_array[MAX/2+1][MAX/2+1] = 1;
  
  visited[MAX/2][MAX/2] = 1;
  visited[MAX/2][MAX/2+1] = 1;
  visited[MAX/2+1][MAX/2] = 1;
  visited[MAX/2+1][MAX/2+1] = 1;

  srand(rand_seed);  /* rand_seed from user input */
  num = 0;
  exit_set = 0;     /* Set to 1 when maze reaches an outside wall */
  num_visit = 4;
  while ((num_visit < (MAX*MAX) || (exit_set == 0)) && (num <= NUM_ITER)) {
    num++; 
    for (i = 0; i < MAX; i++) {
      for (j = 0; j < MAX; j++) {
	/* Calculate sum of neighbor cells that have been set to 1 */
	sum = 0;
	if (visited[i][j] == 0) {
	  if (j < MAX-1)  sum = sum + twoD_array[i][j+1];
	  if (i < MAX-1)  sum = sum + twoD_array[i+1][j];
	  if (i > 0)      sum = sum + twoD_array[i-1][j];
	  if (j > 0)      sum = sum + twoD_array[i][j-1];
	  

	  
	  dir = rand();			 /* Get a random number */
	  if ((sum > 0) && (sum < 3)) {  /* 1 or 2 neighbors cells set to 1 */
	    if (dir < 21830) {		 /* Test random number */
	      twoD_array[i][j] = 1;	 /* Set cell to 1 */
	      if ((j == MAX-1) || (i == MAX-1)) {   /* If cell that is set to
						     * 1 is on the outside edge
						     * of the array, */
		exit_i = i; exit_j = j;	            /* set the exit position */
		exit_set = 1;
	      }
	    }
	    visited[i][j] = 1;		  /* Set visited flag to 1 */
	    num_visit++;
	  }
	  if (sum >= 3) visited[i][j] = 1; /* If the cell already has at least
					    * 3 neigbors, keep it as 0 */
	  
	}  /* End of if (visited) */
	
      } /* End of for j loop */
    }  /* End of for i loop */
  } /* End of while num_visit loop */
  
  /* Prints out twoD_array of 1's and 0's */ 
  /*   printf("%i,i=%i, j=%i\n", dir, i, j);
   for (i=0; i < MAX; i++) {
     for (j=0; j < MAX; j++) {
       printf("%i ", twoD_array[i][j]);
     }
    printf("\n");
  }
  */
 
  return(exit_set);
}


/* Controls navigation of user through maze.
 * Avoids walking through walls.  */
void Maze_Navigate(float speed) {
  int   maze_x, maze_z, i;
  float x_pos, y_pos, z_pos;
  float head_x, head_y, head_z;
  float azimuth, elevation, roll;
  float maze_pos[3], head_pos[3]; 

  
  CAVEGetWandFront(x_pos, y_pos, z_pos);
  CAVEGetHead(head_x, head_y, head_z);
  head_pos[X] = head_x;
  head_pos[Y] = head_y;
  head_pos[Z] = head_z;
  CAVENavConvertCAVEToWorld(head_pos, maze_pos);

  maze_x = (int)((maze_pos[X]/HT)+DS);
  maze_z = (int)((maze_pos[Z]/HT)+DS);		     

  /* Check to see if wall is ahead.
   * If so, do not allow movement in that direction.
   * When the user changes the wand's orientation
   * to move away from the wall, movement is allowed again */
  if (maze_pos[X]/HT+DS - maze_x > 0.9) {
    if (x_pos > 0.0) {       /* Moving towards wall */
      if (maze_x == MAX-1)   /* Outer wall of maze */		    
	x_pos = 0.0;  /* Do not move in the x direction */
      else if (twoD_array[maze_z][maze_x+1] == 0) {
	for (i=0; i<100; i++)
	  x_pos = x_pos - i*0.1;  /* Do not move in the x direction */
      }
    }
  }
  if (maze_pos[X]/HT+DS - maze_x < 0.1) {
    if (x_pos < 0.0) {   /* Moving towards wall */
      if (maze_x == 0)   /* Outer wall of maze */
	x_pos = 0.0;  /* Do not move in the x direction */
      else if (twoD_array[maze_z][maze_x-1] == 0) 
	x_pos = 0.0;  /* Do not move in the x direction */
    }
  }   
  if (maze_pos[Z]/HT+DS - maze_z > 0.9) {
    if (z_pos > 0.0) {       /* Moving towards wall */
      if (maze_z == MAX-1)   /* Outer wall of maze */
	z_pos = 0.0;  /* Do not move in the z direction */
      else if (twoD_array[maze_z+1][maze_x] == 0)
	z_pos = 0.0;  /* Do not move in the z direction */
    }
  } 
  if (maze_pos[Z]/HT+DS - maze_z < 0.1) {
    if (z_pos < 0.0) {   /* Moving towards wall */
      if (maze_z == 0)   /* Outer wall of maze */
	z_pos = 0.0;  /* Do not move in the z direction */
      else if (twoD_array[maze_z-1][maze_x] == 0)
	z_pos = 0.0;  /* Do not move in the z direction */
    }
  } 

  /* Move user's position according to wand orientation
   * at a certain speed*/
  CAVENavTranslate(x_pos/speed, 0.0, z_pos/speed);

  /* If user has changed the wand's orientation,
   * change the user's orientation accordingly.
   * Otherwise, don't change orientation */
  CAVEGetWandOrientation(azimuth, elevation, roll);

  if (azimuth != old_azimuth) {
    old_azimuth = azimuth;
    CAVENavRot(azimuth/(speed*1.5), 'y');
  }

} /* End of Maze_Navigate */


void main(int argc, char **argv) {
  float speed;
  char show;

  /* Get user input for whether to draw ceiling polygon or not */
  printf("Do you want to show the ceiling? (y/n)\n");
  scanf("%c", &show);
  if ((show == 'y') || (show == 'Y'))
    ceiling = 1;      /* Draw ceiling polygon */
  else
    ceiling = 0;      /* Don't draw ceiling polygon */
  
  /* Call to calculate array of 1's and 0's for maze paths and walls */
  if (!Calculate_Maze_Array()) {
    printf("No exit for maze, please choose another number.");
    Calculate_Maze_Array();  /* If first maze has no exit,
			      * generate another array. */
  }
  
  /* Cave calls */
  CAVEConfigure(NULL, NULL, NULL);
  CAVEInit();
  CAVEInitApplication(Make_Maze, 0);
  CAVEDisplay(Display_Maze, 0);
  
  /* Move to start position of maze */
  CAVENavTranslate((start_j-DS+1.0)*HT, 0.0, (start_i-DS+1.0)*HT);

  speed = 200.0;      /* Speed of movement in maze */
  old_azimuth = 0.0;
  
  /* The event loop */
  while (!CAVEgetbutton(CAVE_ESCKEY)) {

    /* The user only moves while holding down wand button 1 */
    if (CAVEBUTTON1) {
      Maze_Navigate(speed);  /* Navigate at given speed */
    } /* End of if(CAVEBUTTON1) */
    
    /* Up arrow increases speed of movement in CAVE */
    /* Change KKEY to UPARROWKEY when not in simulator mode */
     if (CAVEgetbutton(CAVE_KKEY)) { 
       if (speed > 20.0)	      /* Upper limit on speed  */
	 speed = speed - 1.0;
       }
    /* Down arrow decreases speed of movement in CAVE */
    /* Change JKEY to DOWNARROWKEY when not in simulator mode */
     if (CAVEgetbutton(CAVE_JKEY)) {
       if (speed < 2000.0)           /* Lower limit on speed  */
	 speed = speed + 1.0;
       }
    
  } /* End of while (!CAVEgetbutton) */
  
  CAVEExit();
 	
}