/* *********************************************************************** */
/* Filename: obstacle.c                                                    */
/*                                                                         */
/* Description:  Obstacle Detection Application Program                    */ 
/*                                                                         */
/* This application is designed to demonstrate the image processing        */
/* capability of the EyeBot Mobile Robot Platform with its QuickCam        */  
/* digital greyscale camera.  It uses image data to detect obstacles,      */
/* avoid collisions, and navigate in a dynamically changing environment.   */
/*                                                                         */
/* Author:          Barbara Linn    (bsl0560@cs.rit.edu)                   */
/*                                                                         */
/* Last changes:     26-Apr-97                                             */
/*                   20-Aug-97 Thomas Braunl                               */
/*                   28-Feb-00 Thomas Braunl, adapt for V3.0               */
/* Version:          1.1                                                   */
/* *********************************************************************** */

#define REALTIME        0
#define SLOWMODE        1

#include "eyebot.h"

/* global variables */

int version;  
char z;
image img1, img2, img3, img4;
image *ip1, *ip2, *ip3, *ip4;
int angle, speed;
int mymaxrow, mymaxcol;
int half, third;
IPL_CONFIG _IPLConfig;

int main(void)
/* ********************************************************************* */
/*                                                                       */
/* This is the application's top level routine.  It controls the endless */
/* loop that offers users the choice of running in either Realtime or    */
/* Slow mode.  Control returns to this routine after the user requests   */
/* END inside the application                                            */
/*                                                                       */
/* ********************************************************************* */
{
int initob(void);
int mainmenu(void);
int obstacle(int);

	initob();

	/* Endless main application loop */

	mainmenu();

	while (1==1)
	{
		z = KEYGet();
		switch (z)
		{
			case KEY1:
				LCDMode(NONSCROLLING);
				obstacle(REALTIME);
				mainmenu();
				break;

			case KEY2:      
				LCDMode(NONSCROLLING);
				obstacle(SLOWMODE);
				mainmenu();
				break;

			case KEY3:
					break;

			case KEY4:      
					return (0); 
					break;      
			
			default:        break;

		}
	}
}

int initob()                                                               
/* ********************************************************************* */
/*                                                                       */
/* This routine initializes the Image Processing system, the camera,     */
/* and the screen constants needed by the Obstacle Detection routines.   */
/*                                                                       */
/* ********************************************************************* */
{
/* Initialize screen constants */
	mymaxrow = 54;
	mymaxcol = 82;
	half = (mymaxcol-2)/2;
	third = mymaxcol/3;

/* Initialize the Image Processing System */

	_IPLConfig.colorBlack = 0;
	_IPLConfig.colorWhite = 15;
	_IPLConfig.imageWidth = mymaxcol;
	_IPLConfig.imageHeight = mymaxrow;

	IPL_init(&_IPLConfig);  

	/* Clear Display */
	LCDClear();
	
	/* Display warning in case camera doesn't initialize */
	LCDPrintf("Camera?....");

	/* Initialize camera */
	version = CAMInit(WIDE);
	while(version==254)     /* In case CamInit failed the first time */
		{ 
		version=CAMInit(WIDE);
		LCDPrintf("I");
		}

	if (version==255)       /* CamInit failed due to no camera */
		{
		LCDSetPos(2,0);
		LCDPrintf("No Camera !!\a");
		KEYWait(0);
		} 
	return(0);
}


int obstacle (int mode)
/* ********************************************************************* */
/*                                                                       */
/* This routine is the heart of the application.  It is called by the    */
/* main routine with an integer value for mode (0 = realtime, 1 = slow). */
/* It executes the algorithm that gets the image data, processes it with */
/* edge-detection and thresholding functions, draws the sector borders,  */
/* computes lowest and highest edges, and computes the new steering      */
/* angle and speed.                                                      */
/*                                                                       */
/* ********************************************************************* */
{
int steer(int, int, int, int, int, int, int);
int marker(int, int);
int display();

	int i;
	BYTE th = 6;
	BYTE blk = 0;
	BOOL blank, stop;
	int row, col;
	int xL, yL, xC, yC, xR, yR;
	char zp;

	LCDClear();
	if (mode == REALTIME)
		LCDMenu("","","","END");
	else    LCDMenu("","DMP","GO","END");
		
	ip1=&img1;ip2=&img2;ip3=&img3;ip4=&img4;
	angle = 0;
	speed = 0;

	while (1==1) {
		
		/* Get image */
		CAMGetFrame(ip1);

		/* Do edge detection */
		IPSobel(ip1, ip2);
		
		/* Thresholding */ 
		IPL_threshold((BYTE *)ip2, (BYTE *)ip3, th);
		
		/* Display the Edges */ 
		LCDPutGraphic(ip3);                                        
		
		/* Draw the border lines for left, center, and right */
		for (i=1; i<=2; i++) {
			col = i*third;
			for (row=0; row < mymaxrow; row++) {
				LCDSetPixel(row, col, TRUE);
			}
		}

		/* Find Left lowest point */
		yL = 1;
		xL = 1;
		row = mymaxrow - 1;
		stop = FALSE;
		
		for (col=2; col<third; col++) {
			while (row > 1 && stop == FALSE) {
				if ( img3[row][col] == blk) {/* pixel black? */
					if (row >= yL) {
						stop = TRUE;
						yL = row;
						xL = col;
					}
				}
				row -= 1;
			}
			stop = FALSE;           /* Reset stop flag */
			row = mymaxrow - 1; 
		}                                                      

		/* Display the left marker */
		marker(yL, xL);
		
		/* Find Center Highest point */
		yC = mymaxrow - 1;              /* Init y to bottom */
		xC = half;                      /* Init x to center */
		row = mymaxrow - 1;
		blank = TRUE;                   /* Init blank region flag */
		stop = FALSE;                   /* Init stop flag */
		for (col=third+1; col<third*2; col++) {
			while (row > 1 && stop == FALSE) {
				if ( img3[row][col] == blk) {/*pixel black?*/
					blank = FALSE;    /* Not blank */ 
					stop = TRUE;      /* Highest in row*/
					if (row < yC) {   /*Highest overall?*/
						yC = row;
						xC = col;
					}
				}
				row -= 1;
			}
			if (row == 1) {         /* All the way w/o edge? */
				yC = 1;         /* This is highest */
				xC = col;
			}
			row = mymaxrow - 1;             /* Reset to bottom */
			stop = FALSE;                   /* Reset stop flag */
		}                                                      
		
		/* No edges in this Center region? */
		if (blank == TRUE) {                /* Set to Mid-region */
			yC = 1;
			xC = half;
		}
		   
		/* Display the center marker */
		marker(yC, xC);

		/* Find Right lowest point */
		yR = 1;
		xR = mymaxcol-4;
		row = mymaxrow - 1;
		stop = FALSE;
		for (col= mymaxcol-4; col>third*2; col--) {
			while (row > 1 && stop == FALSE) {
				if (img3[row][col] == blk) {/* pixel black? */
					if (row >= yR) {
						stop = TRUE;
						yR = row;
						xR = col;
					}
				}
				row -= 1;
			}
			stop = FALSE;
			row = mymaxrow - 1;
		}                                                      

		/* Display the right marker */
		marker(yR, xR);
			 
		/* Compute new Angle and Speed */
		steer(mode, xL, yL, xC, yC, xR, yR);

		/* Display new Angle and Speed */
		display();

		/* Slow mode */
		if (mode == SLOWMODE) { 
			
			/* Check for Termination */
			KEYGetBuf(&zp);
			if (zp == KEY4)
				break;
			
			if (zp == KEY3) {
				LCDClear();
				LCDMenu("","DMP","GO","END");
			}
			else {
				/* Dump data */
				LCDMenu("","","","GO");
				LCDSetPos(6,10); 
				LCDPrintf("%2d %2d", xL, yL);
				LCDSetPos(7,0);
				LCDPrintf("%2d %2d %2d %2d", xC, yC, xR, yR);

				KEYWait(KEY4);
				LCDClear();
				LCDMenu("","DMP","GO","END");
			}
		}
		else { 
			/* Check for Termination */
			z = KEYRead();
			if (z == KEY4)
				break;
		}
	}
	return(0);
}

		

int marker(int crow, int ccol)    
/* ********************************************************************* */
/*                                                                       */
/* This routine places the arrow markers on the display at the requested */
/* values of row and column.  It ascertains whether the marker should    */
/* point up or down in order to stay within the bounds of the display,   */
/* and inverts its pixels based on what would be most visible in its     */ 
/* current image location.                                               */
/*                                                                       */
/* ********************************************************************* */
{
int i, j;
BOOL set;
	
	if (crow+4 < mymaxrow) {                  /* Marker off bottom? */
		for (i=crow; i<=crow+4; i++) {    /* Yes, point up at pixel */
			if (LCDGetPixel(i, ccol)) /* Pixel already on? */
				set = FALSE;      /* Invert for visibility*/
			else    set = TRUE;
			LCDSetPixel(i, ccol, set);
			if (i == crow+1)  {
				for (j=ccol-1; j<=ccol+1; j++) {
					if (LCDGetPixel(i, j) && j!=ccol)
						set = FALSE;   /* Invert */
					else    set = TRUE;
					LCDSetPixel(i, j, set);
				}
			}
		}
	}
	else {                                  /* Point down at pixel */                   
		for (i=crow; i>=crow-4; i--) {
			if (LCDGetPixel(i, ccol))  /* Pixel already on? */
				set = FALSE;       /* Invert for visibility*/
			else    set = TRUE;
			LCDSetPixel(i, ccol, set);
			if (i == crow-1)  {
				for (j=ccol-1; j<=ccol+1; j++) {
					if (LCDGetPixel(i, j) || j==ccol)
						set = FALSE;   /* Invert */
					else    set = TRUE;
					LCDSetPixel(i, j, set);
				}
			}
		}
	}
	return(0);
}

int steer(int md, int xlt, int ylt, int xcn, int ycn, int xrt, int yrt)
/* ********************************************************************* */
/*                                                                       */
/* This routine computes the new steering angle and speed, based on the  */
/* execution mode (Realtime or Slow) and the (x,y) values of the sector  */
/* markers.                                                              */
/*                                                                       */
/* ********************************************************************* */

{
int new_angle;
int border_rt, border_lt;
int band = 5;
float const1 = 0.025;
float const2 = 0.75;
float const3 = 0.25;

	/* Adjust Y values to reflect origin at base of LCD */
	ylt = mymaxrow - ylt;             
	ycn = mymaxrow - ycn;             
	yrt = mymaxrow - yrt;

	/* Adjust X values to reflect origin at center of LCD */
	xlt = xlt - half;
	xcn = xcn - half;
	xrt = xrt - half;

	/* Compute adjusted x-value for sector boundaries */
	border_lt = third - half;
	border_rt = 2 * third - half;
	
	/* Compute the new speed */
	if (md == REALTIME)             /* include previous speed */
		speed = (const2 * speed) + (const3 * (ycn - 5));

	else                            /* new speed from image data only */
		speed = ycn - 5;

	if (speed < 5)                  /* obstacle immediately ahead? */
		speed = 0;              

	/* If path blocked, execute right-angle turn */
	if (speed==0) 
		angle = 90;
	
	/* Otherwise, compute current image angle and new steering angle */
	else { 
		/* Is there a blank left or right sector condition? */
		if ((xlt <= -half && ylt >= mymaxrow)
					|| (xrt >= half && yrt >= mymaxrow))
			new_angle = xcn;
		else {
			/* Is there either diagonal condition? */
			if (ylt >= mymaxrow-band && yrt <= band)
		
				new_angle = xcn;
			 
			else {
				if (ylt <= band && yrt >= mymaxrow-band)
					new_angle = xcn;
				
				else
					/* Normal condition */
					new_angle = xcn 
					   + const1 * (yrt-ylt) * (xrt-xlt);

			}
		}
	
		/* Compute the new steering angle */
		if (md == REALTIME)     /* include previous angle */
			angle = (const2 * angle) + (const3 * new_angle);

		else                    /* new angle from image data only */
			angle = new_angle;

		/* Computed angle out of legal range? */
		if (angle > 90) 
			angle = 90;
		
		if (angle < -90)      
			angle = -90;

	}
	return(0);
}

int display() 
/* ********************************************************************* */
/*                                                                       */
/* This routine displays the new steering angle and speed on the LCD,    */
/* using the global variables for angle an speed.                        */
/*                                                                       */
/* ********************************************************************* */
{
	
	LCDSetPos(0,10);                
	if (speed == 0) {               /* Check if stopped */
		LCDPrintf("STOP ");
		LCDSetPos(1,10);
		LCDPrintf("       ");
	}
	else {   
		LCDPrintf("SPEED");        /* Display speed */
		LCDSetPos(1,10);
		LCDPrintf("%3d", speed);
	}

	/* Display angle */
	if (angle != 0) {               /* Check not straight ahead */
		LCDSetPos(3,10);
		LCDPrintf("TURN ");
		
		LCDSetPos(4,10);        /* Display turn angle */
		LCDPrintf("%3d ", angle);

		LCDSetPos(5,10);        /* Display turn direction */
		if (angle < 0)        
			LCDPrintf("LEFT ");
		else    LCDPrintf("RIGHT");
	}
	else {
		LCDSetPos(3,10);        /* Display straight ahead */
		LCDPrintf("AHEAD");
		LCDSetPos(4,10);
		LCDPrintf("       ");
		LCDSetPos(5,10);
		LCDPrintf("       ");

	}
	return(0);
}


int mainmenu()
/* ********************************************************************* */
/*                                                                       */
/* This routine displays the application's main menu on the LCD          */
/*                                                                       */
/* ********************************************************************* */

{
	LCDClear();
	LCDMode(SCROLLING|CURSOR);
	LCDMenu("RT","SL","","END");

	LCDPrintf("Obstacle\n");
	LCDPrintf("Avoidance: READY\n");

	LCDPrintf("Choose Mode:\n");
	LCDPrintf("Realtime\n");
	LCDPrintf("or Slow");

	return(0);
}

