Appendix A - ttt_vision.c
1:  /*ttt_vision.c
2:  
3:    This is the main source file for the tic-tac-toe vision system.
4:  
5:    Programmer: Michael Zalokar
6:    Date: Spring 2000
7:  */
8:  /*The calculation of the corners is done in the following way.  The first
9:    two corners are found first and named corner1 and corner2.  The second
10:    set of corners are found next, named corner3 and corner4.  When all four
11:    are found the two sets are diagnols in the object.  The corners can be in
12:    any orientation to each other, but they will always have the same two
13:    corners adjacent and the same one as the diagnol.
14:  
15:    When the ttt board has been singled out from all of the objects in the
16:    list, the corners are assigned into the board array as follows.  The
17:    assignment to the array occurs inside of the detect_ttt() function.  The
18:    array subscripts are listed in row major form.
19:  
20:    corner1
21:    ___________________________ corner4
22:    |[0][0]  |[0][1]  |[0][2]  |[0][3]
23:    |        |        |        |
24:    |        |        |        |
25:    |        |        |        |
26:    ----------------------------
27:    |[1][0]  |[1][1]  |[1][2]  |[1][3]
28:    |        |        |        |
29:    |        |        |        |
30:    |        |        |        |
31:    ----------------------------
32:    |[2][0]  |[2][1]  |[2][2]  |[2][3]
33:    |        |        |        |
34:    |        |        |        |
35:    |corner3 |        |        |
36:    ---------------------------- corner2
37:     [3][0]   [3][1]   [3][2]   [3][3]
38:  
39:    The pieces array is alligned in the same way.
40:  
41:    Since, the corners are calculated from the chaincodes, there will always
42:    the unique property that the 4th corner will be the farthest point from
43:    the 1st and 2nd corners between these two points in the chaincode.  The
44:    third corner is either before the first corner in the chaincode or
45:    after the second corner in the chaincode.
46:  
47:    The reason that the corners are determined using distance vs. using slope
48:    is becuase, distance doesn't have a situation where the result is
49:    undefined.
50:  
51:    I used the _OpenGL Programming for the X Window System_ "green book", _OpenGL
52:    Programming Guide_ "red book" and the _OpenGL Reference Manual_ "blue book"
53:    to write the OpenGL/glut windowing code.
54:    
55:    For the multithreading and socket programming I used the _Pthreads
56:    Programming_ "catapiller book" and _UNIX systems programming for SVR4_
57:    "lion book" from O'Reilly.
58:  */
59:  
60:  
61:  #include < stdio.h >
62:  #include < string.h >
63:  #include < math.h >
64:  #include < float.h >
65:  #include < GL/glut.h >
66:  #include < pthread.h >
67:  #include < sched.h >
68:  #include < errno.h >
69:  #include < stdlib.h >
70:  #ifdef __linux
71:  #include < getopt.h >
72:  #endif
73:  
74:  #include "ttt_vision.h"
75:  #include "sie_protocol.h"
76:  #include "device_type_id_number.h"
77:  #include "ttt_vision_protocol.h"
78:  #include "shelley_sockets.h"
79:  #include "sie_utils.h"
80:  #include "colors.h"
81:  
82:  #define Round(v) ((int)(v+0.5))
83:  
84:  /*host:port:file:buffer*/
85:  #define GETOPTARGS "hf:p:b"
86:  
87:  /*minimal size to consider an "object" as length of chaincode*/
88:  #define DECENT_SIZED_OBJECT 200
89:  
90:  /*Used by the game detection algorithms.  Represent the distance between two
91:    known points as a fraction between 0 and 1.*/
92:  #define ONE_THIRD  (1.0 / 3.0)
93:  #define ONE_HALF   (1.0 / 2.0)
94:  #define ONE_SIXTH  (1.0 / 6.0)
95:  #define ONE_TWELTH (1.0 / 12.0)
96:  #define ONE_24TH   (1.0 / 24.0)
97:  #define ONE_32ND   (1.0 / 32.0)
98:  
99:  /*the value used to threash the values of the moravec filter to 0 and 255*/
100:  /*is the sum of the differences between the center point and its neighbors.*/
101:  /*lighted*/
102:  /*#define THREASH_CONSTANT 128*/
103:  /*unlighted*/
104:  #define THREASH_CONSTANT 30
105:  /*the pratical ratio of the area searched for edge pixels of the marbles.*/
106:  #define EDGE_COUNT 0.25
107:  /*Since the Moravec is threashed to 0 and 255 as long as this value is between
108:    the two everything will work.*/
109:  #define EDGES_FOR_PIECE 128
110:  
111:  /*the constants that control how many pixels are inserted agains one during
112:    the perspective correction*/
113:  #define X_DIR_PERS_CORR 1.6
114:  #define Y_DIR_PERS_CORR 1.9
115:  
116:  /*closeness factor to consider to marbles on the same team*/
117:  #define DIFFERENCE .5
118:  
119:  #define DIVIDERS 4
120:  
121:  /*error on the board to be off of perfectly square*/
122:  #define ALLOWABLE_PERCENT_ERROR 7.5
123:  
124:  /*structure defined as the object type.  All identified objects are stored in
125:    a struct of this type.  This is a very general data type and is not
126:    specific to a tic-tac-toe game board.*/
127:  typedef struct
128:  {
129:    coord corner1;
130:    coord corner2;
131:    coord corner3;
132:    coord corner4;
133:  }object;
134:  
135:  /*This struct contains all the relavent information about the location,
136:    orientation, etc. of the tic-tac-toe game board itself.*/
137:  /*typedef struct
138:    {*/
139:     coord points[DIVIDERS][DIVIDERS];
140:    
141:     /*The corners of the board.  corner1 and corner2 are always opposite
142:       (diagnol), just like corner3 and corner4 too.*/
143:    /* object corners;*/
144:  
145:     /*The sides.  The numbers represent the to corners that the point is
146:      between.  The first number is the corner that it is closest to.*/
147:    /*   coord side14, side13, side23, side24;
148:     coord side31, side32, side41, side42;*/
149:     
150:     /*The points surrounding the middle square.  The number indicates the
151:       closest corner.*/
152:    /*   coord middle1, middle2, middle3, middle4;*/
153:  /*}ttt;
154:   */
155:  /*Evil globals, but not do-able otherwise.*/
156:  static PGMImage *img_cur;       /*current*/
157:  static PGMImage *img_original;  /*original*/
158:  static PGMImage *img_pers_corr; /*perspective correction*/
159:  static PGMImage *img_grayscale;
160:  static PGMImage *img_moravec;   /*moravec*/
161:  /*static*/ int HSIZE;  /*width*/
162:  /*static*/ int VSIZE;  /*height*/
163:  /*static*/ int MVAL;   /*max val*/
164:  
165:  /*pointer to a ttt structer containing the board info.*/
166:  /*static ttt *ttt_board;*/
167:  
168:  /*pointer to an array of list_info structs  which point to the first, last and
169:    current nodes in each of the chain codes.*/ 
170:  list_info* chain_codes;
171:  
172:  /*pointer to the array of objects found in the image.  Counting starts at
173:    zero and the array must be less than MAX_CHAINS in size.   Unused values
174:    should be set to zero.*/
175:  static object *all_objects;
176:  
177:  
178:  
179:  /*used to determine if the lines connecting the corners should be
180:    drawn to the screen.  Set to < 0 if no abstract lines are to be drawn.
181:    Otherwise is set to the number of objects found (AKA number of chaincodes).*/
182:  int draw_abstract_lines = -1;
183:  
184:  /*used to draw the single object most likely to be considered the
185:    tic-tac-toe board.  Should be < 0 if this should not be drawn.
186:    Should be equal to the chain-code number (from 0 to MAX_CHAINS - 1)
187:    if it is to be drawn.*/
188:  int draw_abstract_board = -1;
189:  
190:  
191:  /*thread stuff*/
192:  pthread_t vision_thread;
193:  pthread_t conn_thread;
194:  pthread_mutex_t calculate_mutex;/* = PTHREAD_MUTEX_INITIALIZER;*/
195:  pthread_mutex_t images_mutex;/* = PTHREAD_MUTEX_INITIALIZER;*/
196:  
197:  /*command line variables*/
198:  char *PGMfileName; /*string containing the filename*/
199:  char *hostname;
200:  int port_number;
201:  int is_buffered; /*  Is set to TRUE or FALSE.*/
202:  
203:  int socket_num;
204:  
205:  /*array of where the pieces are*/
206:  RGB_INT *data;
207:  unsigned char pieces[3][3];
208:  
209:  bool redraw_needed = 1;
210:  
211:  /*do be able to determine which team is which color when only one move has been
212:    made, this variable stores that color until the second move is made to
213:    compare colors*/
214:  int team_first;
215:  
216:  /*boolean toggle for displaying time data*/
217:  bool display_time = TRUE;
218:  /*boolean toggle for displaying verbose text*/
219:  bool display_verbose = FALSE;
220:  
221:  /**************Time Display funcition************************************/
222:  /********************************************************************/
223:  /*1st: initial time set by gettimeofday()
224:    2nd: final time set by gettimeofday();
225:    3rd: text message of the type of calculation timing*/
226:  void do_time(struct timeval t0, struct timeval t1, char *message)
227:  {
228:     double elapsed = t1.tv_sec - t0.tv_sec;
229:  
230:     if(!display_time)
231:        return;
232:        
233:     if(t1.tv_usec  > t0.tv_usec)
234:        elapsed += (t1.tv_usec - t0.tv_usec) / 1000000.0;
235:     else
236:        elapsed += (1000000.0 - abs(t1.tv_usec - t0.tv_usec)) / 1000000.0;
237:  
238:     if(!message)
239:        printf("Processing took: %f seconds\n", elapsed);
240:     else
241:        printf("Processing %s took: %f seconds\n", message, elapsed);
242:  }
243:  
244:  /**************Drawing funcitions************************************/
245:  /********************************************************************/
246:  
247:  /*This function draws a single pixel to the screen.  (0, 0) is the lower left
248:   corner.*/
249:  /*1st: Integer of the x pixel to be drawn.
250:    2nd: Integer of the y pixel to be drawn.
251:    3rd: RGB values as stored as integral types with values between 0 - 255.*/
252:  void setCPixel(int ix, int iy, RGB_INT color)
253:  {
254:    float x = (ix*2.0)/HSIZE - 1.0;
255:    float y = (iy*2.0)/VSIZE - 1.0;
256:  
257:    float red = (float)color.red/(float)MVAL;
258:    float green = (float)color.green/(float)MVAL;
259:    float blue = (float)color.blue/(float)MVAL;
260:  
261:    glColor3f(red, green, blue);
262:    
263:    glBegin(GL_POINTS); 
264:       glVertex2f (x, y);
265:    glEnd();
266:  }
267:  
268:  /*This function draws a line segment to the screen.  (0, 0) is the lower
269:    left corner.*/
270:  /*1st: x coord. of the 1st endpoint.
271:    2nd: y coord, of the 2nd endpoint.
272:    3rd: x coord. of the 2st endpoint.
273:    4th: y coord. of the 2st endpoint.
274:    5th: RGB values as stored as integral types with values between 0 - 255.*/
275:  void setCLines(int ix1, int iy1, int ix2, int iy2, RGB_INT color)
276:  {
277:    float x1 = (ix1*2.0)/HSIZE - 1.0;
278:    float y1 = (iy1*2.0)/VSIZE - 1.0;
279:    float x2 = (ix2*2.0)/HSIZE - 1.0;
280:    float y2 = (iy2*2.0)/VSIZE - 1.0;
281:   
282:    float red = (float)color.red/(float)MVAL;
283:    float green = (float)color.green/(float)MVAL;
284:    float blue = (float)color.blue/(float)MVAL;
285:  
286:    glColor3f(red, green, blue);
287:  
288:    glBegin(GL_LINES);
289:      glVertex2f (x1, y1);
290:      glVertex2f (x2, y2);
291:    glEnd();
292:  }
293:  
294:  /*This function draws a square to the screen.  (0, 0) is the lower left
295:    corner.*/
296:  /*1st: x coord. of the 1st endpoint.
297:    2nd: y coord, of the 2nd endpoint.
298:    3rd: x coord. of the 2st endpoint.
299:    4th: y coord. of the 2st endpoint.
300:    5th: RGB values as stored as integral types with values between 0 - 255.*/
301:  void setCRect(int ix1, int iy1, int ix2, int iy2, RGB_INT color)
302:  {
303:    float x1 = (ix1*2.0)/HSIZE - 1.0;
304:    float y1 = (iy1*2.0)/VSIZE - 1.0;
305:    float x2 = (ix2*2.0)/HSIZE - 1.0;
306:    float y2 = (iy2*2.0)/VSIZE - 1.0;
307:   
308:    float red = (float)color.red/(float)MVAL;
309:    float green = (float)color.green/(float)MVAL;
310:    float blue = (float)color.blue/(float)MVAL;
311:  
312:    glColor3f(red, green, blue);
313:  
314:    glBegin(GL_POLYGON);
315:      glVertex2f (x1, y1);
316:      glVertex2f (x1, y2);
317:      glVertex2f (x2, y2);
318:      glVertex2f (x2, y1);
319:    glEnd();
320:  }
321:  
322:  /*draws a string of text to the screen.*/
323:  /*1st: The x coord of the lower left corner where it is to be displayed.
324:    2nd: The y coord of the lower left corner where it is to be displayed.
325:    3rd: String that is to be displayed.
326:    4th: The color to display the text in.*/
327:  void drawString(int ix, int iy, char theString[256], RGB_INT color)
328:  {
329:     float x = (ix*2.0)/HSIZE - 1.0;
330:     float y = (iy*2.0)/VSIZE - 1.0;
331:     int i;
332:  
333:     float red = (float)color.red/(float)MVAL;
334:     float green = (float)color.green/(float)MVAL;
335:     float blue = (float)color.blue/(float)MVAL;
336:  
337:     glColor3f(red, green, blue);
338:  
339:     glRasterPos2f(x, y);
340:     for (i = 0; theString[i] != '\0'; i++) /* draw the chars one at a time */
341:       glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, theString[i]);
342:  }
343:  
344:  /*Draws a PGMImage to the window.*/
345:  /*1st: The pgm image to display.*/
346:  void showColor (PGMImage *img)
347:  {
348:     int i, j; /*loop counting: i = y, j = x*/
349:     /*This is the simplist way I found to use the fast OpenGL direct drawing
350:       functions without a major rewrite of the code.  First pack the data
351:       into this 3D array.  If that little bit of extra speed is needed, then
352:       the PGMImage functionality would need to be written as a 3D array and not
353:       a 2D array of structs.*/
354:     GLubyte checkImage[(*img).height][(*img).width][3];
355:     int test = 0;
356:     for(i = 0; i <  (*img).height; i++)
357:     {
358:        for(j = 0; j <  (*img).width; j++)
359:        {
360:           checkImage[i][j][0] = (GLubyte) (*img).data[i][j].red;
361:           checkImage[i][j][1] = (GLubyte) (*img).data[i][j].green;
362:           checkImage[i][j][2] = (GLubyte) (*img).data[i][j].blue;
363:  	 if(checkImage[i][j][0] == 0)
364:  	   test++;
365:        }
366:     }
367:  
368:     /*tell OpenGL that the values are packed into byte sized values*/
369:     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
370:     /*since OpenGL is setup for -1 to 1 size, specify (-1, -1) for the lower
371:       left corner which is (0, 0) to most of the program*/
372:     glRasterPos2f(-1, -1);
373:     /*draw the current image*/
374:     glDrawPixels((*img).width, (*img).height, GL_RGB,
375:  		GL_UNSIGNED_BYTE, checkImage);
376:  
377:     glFlush();
378:  }
379:  
380:  /*called by showAbstract()*/
381:  /*draws the (x, y) values of the coordinates specified at that coordinate*/
382:  /*1st: The x coord.
383:    2nd: The y coord.
384:    3rd: Additional text to display, aside from the (x, y) determined from the
385:    1st and 2nd parameters.*/
386:  void display_labels(coord point, int row, int col, char *more_text)
387:  {
388:     char text[40]; /*for displaying the locations in text form*/
389:     int offset = 8; /*offset text # pixels from location*/
390:     char null_char[1];
391:     
392:     if(more_text == NULL)
393:     {
394:        /*give more_text something to point to, to avoid seg. faults*/
395:        null_char[0] = '\0';
396:        more_text = null_char;
397:     }
398:     
399:     if(row <  0 || col <  0)
400:        sprintf(text, "%s (%d, %d)", more_text, point.x, point.y);
401:     else
402:        sprintf(text, "%s [%d][%d](%d, %d)", more_text, row, col,
403:  	      point.x, point.y);
404:     drawString(point.x + offset, point.y + offset, text, blue);
405:  }
406:  
407:  /*display the global abstract data*/
408:  void showAbstract(object* object_list, coord ttt_data[DIVIDERS][DIVIDERS])
409:  {
410:     int i, j;
411:  
412:     /*when displayeing the chaincodes, this is the temp to do that.*/
413:     list_info temp;
414:  
415:     glPointSize(2); /*make points more visible, if desired*/
416:     /*glLineWidth(4);*/
417:     
418:     /*draw the chaincodes*/
419:     /*chain_codes is a global pointer to an array of list_info types*/
420:     for(i = 0; (i <  MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++)
421:     {
422:        memcpy(&temp, &chain_codes[i], sizeof(list_info));
423:        while(RetrieveNextNode(&temp).cur)
424:        {
425:  	 setCPixel(RetrieveInfo(&temp).location.x,
426:  		   RetrieveInfo(&temp).location.y, yellow);
427:  	 Advance(&temp);
428:        }
429:     }
430:     
431:     /*first check for non-null pointer, next check for dereferenced pointers
432:       in the array of head pointers and lastly make sure things stay in
433:       bound of the max incase all MAX_CHAINS number of chains are used.*/
434:     /*draw_abstract_lines is the global that holds the number of "objects"
435:       to draw abstract information for*/
436:     for(i = 0; i <  draw_abstract_lines && i <  MAX_CHAINS; i++)
437:     {
438:        setCLines(object_list[i].corner1.x, object_list[i].corner1.y,
439:  		object_list[i].corner3.x, object_list[i].corner3.y,yellow);
440:        setCLines(object_list[i].corner4.x, object_list[i].corner4.y,
441:  		object_list[i].corner2.x, object_list[i].corner2.y,yellow);
442:        setCLines(object_list[i].corner3.x, object_list[i].corner3.y,
443:  		object_list[i].corner2.x, object_list[i].corner2.y,yellow);
444:        setCLines(object_list[i].corner4.x, object_list[i].corner4.y,
445:  		object_list[i].corner1.x, object_list[i].corner1.y,yellow);
446:  
447:        setCPixel(object_list[i].corner1.x, object_list[i].corner1.y,dk_red);
448:        setCPixel(object_list[i].corner2.x, object_list[i].corner2.y,dk_blue);
449:        setCPixel(object_list[i].corner3.x, object_list[i].corner3.y,white);
450:        setCPixel(object_list[i].corner4.x, object_list[i].corner4.y,dk_green);
451:  
452:        /*labels for corner points*/
453:        display_labels(object_list[i].corner1, -1, -1, "c1");
454:        display_labels(object_list[i].corner2, -1, -1, "c2");
455:        display_labels(object_list[i].corner3, -1, -1, "c3");
456:        display_labels(object_list[i].corner4, -1, -1, "c4");
457:     }
458:  
459:     /*if there is board to draw, draw it to the screen*/
460:     if((draw_abstract_board  > -1))
461:     {
462:        for(i = 1; i <  DIVIDERS - 1; i++)
463:        {
464:  	 setCLines(points[i][0].x, points[i][0].y, points[i][DIVIDERS - 1].x,
465:  		   points[i][DIVIDERS - 1].y, magenta);
466:  
467:  	 setCLines(points[0][i].x, points[0][i].y, points[DIVIDERS - 1][i].x,
468:  		   points[DIVIDERS - 1][i].y, magenta);
469:  
470:  	 setCPixel(points[i][0].x, points[i][0].y, red);
471:  	 setCPixel(points[DIVIDERS - 1][i].x, points[DIVIDERS - 1][i].y, red);
472:        }
473:  
474:        /*lables for all ttt board points*/
475:        for(i = 0; i <  DIVIDERS; i++)
476:  	 for(j = 0; j <  DIVIDERS; j++)
477:  	 {
478:  	    display_labels(points[i][j], i, j, NULL);
479:  	 }
480:     }
481:     glFlush();
482:  }
483:  
484:  /**********************Support functions*******************************/
485:  /***********************************************************************/
486:  
487:  /*take the source pixel and copy it to the destination pixel.*/
488:  /*1st: dest is the pointer to the PGMImage that is to have the pixel changed.
489:    2nd: the row(y coord) that is to be the destination
490:    3rd: the column(x coord) that is to be the destination
491:    4th: dsrc is the pointer to the PGMImage that is to have its pixel copied.
492:    5th: the row(y coord) that is to be the source
493:    6th: the column(x coord) that is to be the source
494:  */
495:  void pxlcpy(PGMImage *dest, int dest_row, int dest_col,
496:  	    PGMImage *src, int src_row, int src_col)
497:  {
498:    /*make sure values are within bounds*/
499:    if(dest_col  > 0 && dest_col <  (*dest).width
500:       && dest_row  > 0 && dest_row <  (*dest).height
501:       && src_col  > 0 && src_col <  (*src).width
502:       && src_row  > 0 && src_row <  (*src).height)
503:    {
504:      (*dest).data[dest_row][dest_col].red =
505:        (*src).data[src_row][src_col].red;
506:  
507:      (*dest).data[dest_row][dest_col].green =
508:        (*src).data[src_row][src_col].green;
509:  
510:      (*dest).data[dest_row][dest_col].blue =
511:        (*src).data[src_row][src_col].blue;
512:    }
513:  } 
514:  
515:  /*calculate the average of the red, green and blue values of a sinfle pixel.*/
516:  /*1st: the RGB_INT to be averaged*/
517:  /*the average of the red, green and blue is returned*/
518:  int rgb_avg(RGB_INT cur_pxl)
519:  {
520:     /*convert each RGB to the average of the original*/
521:     return ((cur_pxl.red + cur_pxl.green + cur_pxl.blue) / 3);
522:  }
523:  
524:  /*Returns average (with RGB avg) pixel value for the image passed in.*/
525:  /*1st: a pointer to a pgm image*/
526:  /*the return value is the average of all the pixel in the image.  Each pixel's
527:    value is the average of its RGB parts.*/
528:  int img_pxl_avg(PGMImage* img)
529:  {
530:     int i, j; /*loop counting*/
531:     int sum = 0;
532:     int temp;
533:  
534:     for(i = 0; i <  (*img).height; i++)/*collumn*/
535:        for(j = 0; j <  (*img).width; j++)/*row*/
536:           sum += rgb_avg((*img).data[i][j]);
537:  
538:  
539:     temp = (sum / ((*img).height * (*img).width));
540:  
541:     return temp;
542:  }
543:  
544:  /*determines what in the image is determined to background and foreground.*/
545:  /*1st: value to compare whether more of the pixels are greater than this or
546:    less than this.
547:    2nd: the image to be checked.*/
548:  /*return  >0 if number of pixels is greater than img. pxl. avg., return < 0 if
549:    number of pixesl is less than img. pxl. avg. and return zero of equal*/
550:  int background(int treash_value, PGMImage* img)
551:  {
552:     int i, j; /*loop counting*/
553:     int pxl_less = 0, pxl_more = 0;
554:  
555:     for(i = 0; i <  (*img).height; i++)/*collumn*/
556:        for(j = 0; j <  (*img).width; j++)/*row*/
557:        {
558:  	 if(rgb_avg((*img).data[i][j]) <  treash_value)
559:  	   pxl_less++;
560:  
561:           if(rgb_avg((*img).data[i][j])  > treash_value)
562:  	   pxl_more++;
563:        }
564:  
565:     if(pxl_less  > pxl_more)
566:        return -1;
567:     else if(pxl_less <  pxl_more)
568:        return 1;
569:     else
570:        return 0;
571:  }
572:  
573:  /*Used by showColor() and detect_pieces() to intrapolate the location of a
574:    point along a line between two known points.
575:    r = (1 - t)p1 + t*p2
576:    This is calculated twice, once for x direction and once for y direction.*/
577:  /*1st: coordinate of the closer point that t will intropolate to.
578:    2nd: coordinate of the farther point that t will intrapolate to.
579:    3rd: the fractional value, with point1 considered t = 0 and point2
580:    considered t = 1 that the return value is intropolated.*/
581:  /*returns the coordinate that is intropolated*/
582:  coord find_dividers(coord point1, coord point2, float t)
583:  {
584:     coord temp;
585:     
586:     temp.x = (int)(((1.0 - t) * point1.x) + (t * point2.x));
587:     temp.y = (int)(((1.0 - t) * point1.y) + (t * point2.y));
588:     
589:     return temp;   
590:  }
591:  
592:  /*takes two coordinates as x and y pairs and returns the distance betweem them
593:     as a decimal*/
594:  float findDist(int x1, int y1, int x2, int y2)
595:  {
596:     return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
597:  }
598:  
599:  
600:  /******Perspective correction*******************************************
601:  ***********************************************************************/
602:  
603:  /*The image that we will be looking at will from the camera will likely
604:    have a perspective, so look at removing the perspective effect.  The x
605:    direction correction starts in the middle and moves toward the sides
606:    stretching the image with more  stretching occuring with higher rows.
607:    The y direction correction inserts more lines near the top than towards the
608:    bottom of the image.  There is a y direction recentering calculation
609:    performed.*/
610:  /*1st: The pointer to the pgm image that the image will be copied into with
611:    the perspective removed.
612:    2nd: The pointer to the pgm image that will have the perspective removed.*/
613:  void pers_corr(PGMImage* new_img, PGMImage* org_img)
614:  {
615:     /******************X direction variables*********/
616:     
617:     /*i and j are the left half, k and l are the right half*/
618:     float i, k; /*loop counting x dir*/
619:     int j, l; /*loop counting x dir*/
620:     int old_i, old_k; /*x dir. counting to remove moire iterference*/
621:  
622:     float ins_s = X_DIR_PERS_CORR; /*insert constant starting value x dir.*/
623:     float ins_k = ins_s; /*current insert x dir.*/
624:  
625:     /*The halfway marks in the width.*/
626:     int mid_width_left;
627:     int mid_width_right;
628:     
629:     
630:     /******************Y direction variables*********/
631:     
632:     /*the recenter in y direction value*/
633:     int recenter;
634:     
635:     float m; /*loop counting y dir*/
636:     int n; /*loop counting y dir*/
637:     int old_n = (*new_img).height, old_n_inc; /*y dir remove moire iterference*/
638:     
639:     float ins_t = Y_DIR_PERS_CORR; /*insert constant starting value y dir.*/
640:     float ins_j = ins_t; /*current insert value y dir.*/
641:  
642:     /*just to be thourough clear the memory and reset maxes*/
643:     memset(new_img, 0, sizeof(PGMImage));
644:     (*new_img).height = (*org_img).height;
645:     (*new_img).width = (*org_img).width;
646:     (*new_img).maxVal = (*org_img).maxVal;
647:  
648:     /*setting these before the new image size was stupid.  never do that again.
649:       Doing so resulted in the first frame being garbaged.*/
650:     mid_width_left = ((*new_img).width / 2) - 1;
651:     mid_width_right = ((*new_img).width / 2);
652:  
653:     /*since the x dir is calculated from the middle, the middle is always
654:       mapped to the middle.  Since the y dir is calculated from the top the
655:       image is pushed to the bottom.  To correct this add to the new pixel
656:       location this value to recenter the image.*/
657:     recenter =  ((*new_img).height / ins_t);
658:     
659:     /*Loop through each row from top to bottom...*/
660:     for(m = n = ((*new_img).height - 1);
661:         (n  >= 0);
662:         m -= ins_j, n--)
663:     {
664:        /*...reset moire interference removal counter x dir...*/
665:        old_i = ((*new_img).width / 2) - 1;
666:        old_k = ((*new_img).width / 2);
667:        
668:        /*...so each half is ajusted to remove perspective effect...*/
669:        /*initalize the x and y starting conditions*/
670:        for(i = j = mid_width_left, k = l = mid_width_right;
671:  	  /*x and y ending conditions*/
672:  	  ((j  >= 0) && (l <  (*new_img).width));
673:  	  /*incremental*/
674:  	  i -= ins_k, j--, k += ins_k, l++)
675:        {
676:  	 for(;old_i  >= (int)i; old_i--)  /*...in the left half...*/ 
677:  	    for(old_n_inc = old_n; old_n_inc  >= m; old_n_inc--)
678:  	       pxlcpy(new_img, old_n_inc + recenter, old_i, org_img, n, j);
679:  
680:  	 for(;old_k < = (int)k; old_k++)  /*...in the right half.*/
681:  	    for(old_n_inc = old_n; old_n_inc  >= m; old_n_inc--)
682:  	       pxlcpy(new_img, old_n_inc + recenter, old_k, org_img, n, l);
683:        }
684:        /*Move the new image x_coord pixel counter to next new image pixel*/
685:        ins_k -= ((ins_s - 1.0) / (*new_img).height);
686:        ins_j -= ((ins_t - 1.0) / (*new_img).width);
687:  
688:        old_n = m; /*store for next row (y direction)*/
689:     }
690:  
691:  
692:  }
693:  
694:  /*****convert color to grayscale****************************************
695:  ***********************************************************************/
696:  
697:  /*Turn a color image into a black and white image.*/
698:  /*1st: The pointer to the pgm image that the image will be copied into with
699:    the perspective removed.
700:    2nd: The pointer to the pgm image that will have the color removed.*/
701:  void color_to_gray(PGMImage* new_img, PGMImage* org_img)
702:  {
703:     int row, col; /*loop counting*/
704:     RGB_INT cur_pxl; /*current pixel*/
705:     
706:     /*just to be thourough clear the memory and reset maxes*/
707:     memset(new_img, 0, sizeof(PGMImage));
708:     (*new_img).height = (*org_img).height;
709:     (*new_img).width = (*org_img).width;
710:     (*new_img).maxVal = (*org_img).maxVal;
711:  
712:     /*Starting with the top row...*/
713:     for(row = (*new_img).height - 1; row  >= 0; row--)
714:        for(col = 0; col <  (*new_img).width - 1; col++)
715:        {
716:  	 cur_pxl = (*org_img).data[row][col]; /*more readable*/
717:  	
718:  	 /*convert each RGB to the average of the original*/
719:  	 (*new_img).data[row][col].red =  rgb_avg(cur_pxl);
720:  	 (*new_img).data[row][col].green =  rgb_avg(cur_pxl);
721:  	 (*new_img).data[row][col].blue =  rgb_avg(cur_pxl);
722:        }
723:  }
724:  
725:  /*******Moravec Edge Highlighting**************************************
726:  **********************************************************************/
727:  
728:  /*use the Moravec algorithm to highlight the edges in an image.  This
729:    algorithm takes the normally grayscale result and threasholds it to
730:    the values of 0 and 255 with the divide at THREASH_CONSTANT*/
731:  /*1st: The pointer to the pgm image that the image will be copied into with
732:    the moravec filter applied.
733:    2nd: The pointer to the pgm image that will have the moravec filter*/
734:  void moravec(PGMImage* new_img, PGMImage* org_img)
735:  {
736:     int row, col; /*loop counting*/
737:     int i, j, k, l; /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/
738:     int running_sum;
739:     float K = 0.5; /*.125 according to org. formula, but .5 is brighter*/
740:     
741:     /*just to be thourough clear the memory and reset maxes*/
742:     memset(new_img, 0, sizeof(PGMImage));
743:     (*new_img).height = (*org_img).height;
744:     (*new_img).width = (*org_img).width;
745:     (*new_img).maxVal = (*org_img).maxVal;
746:    
747:     /*starting at the top row*/
748:     /*don't count pixels along image edge to avoid segmentation fault*/
749:     for(row = (*new_img).height - 1 - 1; row  > 0; row--)
750:        for(col = 1; col <  (*new_img).width - 1; col++) /*left col start*/
751:        {
752:  	 i = row;
753:  	 j = col;
754:  	 running_sum = 0;
755:  
756:  	 /*Sanka, Hlavac & Boyle; p. 97 f. 4.73*/
757:  	 for(k = i - 1; k < = i + 1; k++) /*row*/
758:  	   for(l = j - 1; l < = j + 1; l++) /*column*/
759:  	     running_sum += abs(rgb_avg((*org_img).data[k][l]) -
760:  				rgb_avg((*org_img).data[i][j]));
761:  	 
762:  	 /*assign the new pixel value*/
763:  	 /*since all the data is initalized to 0, we only worry when it
764:             shouldn't be*/
765:  	 if((int)(K * running_sum)  >= THREASH_CONSTANT)
766:  	 {
767:  	    (*new_img).data[row][col].red = 255;
768:  	    (*new_img).data[row][col].green = 255;
769:  	    (*new_img).data[row][col].blue = 255;
770:  	  }
771:  	   /*this is the original code...*/
772:  	   /*(*new_img).data[row][col].red = (int)(K * running_sum);
773:  	 (*new_img).data[row][col].green = (int)(K * running_sum);
774:  	 (*new_img).data[row][col].blue = (int)(K * running_sum);*/
775:        } 
776:  }
777:  
778:  /*******Corners*****************************************************
779:  *******************************************************************/
780:  
781:  /*find the first two corners of all of the obejects in the image.  This is
782:    accomplished by looking at the chaincodes for the images and determining
783:    the two points that are the farthest apart.*/
784:  /*1st: a pointer to an array of object structs.
785:    2nd: the array of chaincodes that is searched for the farthest two points.*/
786:  void findFirstTwoCorners(object *objects_array, list_info* chainCodes)
787:  {
788:     int i; /*loop counting*/
789:     list_info temp, search; /*temp copies of the data*/
790:     double max_dist, test_dist; /*temp distance holders*/
791:  
792:  /*printf("\nFinding first 2 corners.\n");*/
793:  
794:     /*as long as there are codes to check, keep checking.*/
795:     for(i = 0; ((i <  MAX_CHAINS) && chainCodes[i].cur); i++)
796:     {      
797:        memcpy(&temp, &chainCodes[i], sizeof(list_info)); 
798:  
799:        max_dist = 0.0;  /*reset this for each iteration*/
800:  
801:        while(RetrieveNextNode(&temp).cur) /*while there are nodes to check*/
802:        {
803:  	 /*set the faster moving search pointer to temp,
804:  	   this increases the effiecency a lot compared to
805:  	   setting it equal to the first node..*/
806:           memcpy(&search, &temp, sizeof(list_info));
807:           
808:  	 while(RetrieveNextNode(&search).cur)
809:           {
810:  /*setCPixel(RetrieveInfo(&temp).location.x,
811:    RetrieveInfo(&temp).location.y, green);*/
812:  
813:              /*determine if found a new maximum distance between two locations*/
814:              if((test_dist = findDist(RetrieveInfo(&search).location.x,
815:  				     RetrieveInfo(&search).location.y,
816:  				     RetrieveInfo(&temp).location.x,
817:  				     RetrieveInfo(&temp).location.y)) >max_dist)
818:              {
819:                 max_dist = test_dist;
820:                 objects_array[i].corner1.x = RetrieveInfo(&temp).location.x;
821:                 objects_array[i].corner1.y = RetrieveInfo(&temp).location.y;
822:                 objects_array[i].corner2.x = RetrieveInfo(&search).location.x;
823:                 objects_array[i].corner2.y = RetrieveInfo(&search).location.y;
824:              }
825:              Advance(&search);
826:           }
827:           Advance(&temp);
828:        }
829:     }
830:  }
831:  
832:  /*determine the second to corners for the objects.*/
833:  /*1st: the array of objects that contains the first to corners already.
834:    2nd: the array of chaincode data*/
835:  void findSecondTwoCorners(object *objects_array, list_info* chain_code_array)
836:  {
837:     int i; /*loop counting*/
838:     list_info temp;
839:     float temp_dist1, temp_dist2; /*distance between point and each corner*/
840:     coord canidate_coord1, temp_coord;
841:     float canidate_dist1 = 0.0, max_dist;
842:     int corner_count;
843:  
844:  /*printf("\nFinding last 2 corners.\n\n");*/
845:  
846:     /*for each chain code find the corners*/
847:     for(i = 0; (i <  MAX_CHAINS) && chain_code_array[i].cur; i++)
848:     {
849:        memcpy(&temp, &chain_code_array[i], sizeof(list_info));
850:        
851:        /*reset these for the next chain code*/
852:        max_dist = 0.0;
853:        corner_count = 1;
854:  
855:        /*while there are nodes in the chain code to check*/
856:        /*if there isn't a next node cur is NULL, which is checked*/
857:        while(RetrieveNextNode(&temp).cur)
858:        {
859:  /*setCPixel(RetrieveInfo(&temp).location.x,
860:    RetrieveInfo(&temp).location.y, color1);*/
861:  
862:  	/*determine the first canidate coord for corner 3/4*/
863:  	if(((RetrieveInfo(&temp).location.x == objects_array[i].corner1.x)
864:  	    && (RetrieveInfo(&temp).location.y == objects_array[i].corner1.y))
865:  	   || ((RetrieveInfo(&temp).location.x == objects_array[i].corner2.x)
866:  	     &&(RetrieveInfo(&temp).location.y == objects_array[i].corner2.y)))
867:  	{
868:  	  /*if this corner found is the first of the two allready known
869:  	    corners, then set the first canidate coord data and reset data
870:  	    to find the next canidate corner point*/
871:  	   if(corner_count == 1)
872:  	   {
873:  	      canidate_coord1.x = temp_coord.x;
874:  	      canidate_coord1.y = temp_coord.y;
875:  	      canidate_dist1 = max_dist;
876:  
877:  	      corner_count = 2; /*set for next corner*/
878:  	      max_dist = 0.0;
879:  	   }
880:  	   else if(corner_count == 2)
881:  	   {
882:  	      /*the second canidate is always a corner*/
883:  	      all_objects[i].corner4.x = temp_coord.x;
884:  	      all_objects[i].corner4.y = temp_coord.y;
885:  	      
886:  	      max_dist = 0.0; /*set for next corner canidate*/
887:  	   }
888:  	}
889:  
890:  	/*calculate the distance between the current point being checked and
891:  	  each corner point*/
892:  	temp_dist1 = findDist(all_objects[i].corner1.x,
893:  			      all_objects[i].corner1.y,
894:  			      RetrieveInfo(&temp).location.x,
895:  			      RetrieveInfo(&temp).location.y);
896:  	temp_dist2 = findDist(all_objects[i].corner2.x,
897:  			      all_objects[i].corner2.y,
898:  			      RetrieveInfo(&temp).location.x,
899:  			      RetrieveInfo(&temp).location.y);
900:  
901:  	/*if the current point is the furthest away sofar, store this point
902:  	  untill it is overridden or becomes a canidate point*/
903:  	if((temp_dist1 + temp_dist2)  > max_dist)
904:  	{
905:  	   temp_coord.x = RetrieveInfo(&temp).location.x;
906:  	   temp_coord.y = RetrieveInfo(&temp).location.y;
907:  	  
908:  	   max_dist = (temp_dist1 + temp_dist2);
909:  	}
910:  
911:  	Advance(&temp);
912:        }
913:  
914:        /*from the three canidate coords find the two real corners.*/
915:        /*the second canidate will always be a corner, must test 1 vs 3, where
916:  	three is in the variables temp_coord and max_dist.*/
917:        if(canidate_dist1  > max_dist) /*first canidate*/
918:        {
919:           all_objects[i].corner3.x = canidate_coord1.x;
920:  	 all_objects[i].corner3.y = canidate_coord1.y;
921:        }
922:        else /*third canidate*/
923:        {
924:           all_objects[i].corner3.x = temp_coord.x;
925:  	 all_objects[i].corner3.y = temp_coord.y;
926:        }
927:     }
928:  }
929:  
930:  /*returns the number of objects found*/
931:  /*places the corner info in the global corners data*/
932:  int detect_corners(list_info* current_chaincodes, object *objects_array)
933:  {
934:     int i; /*temporarily holds number of chaincodes*/
935:     
936:     if(display_verbose)
937:        printf("Looking for corners.\n");
938:     
939:     /*clear the array of objects*/
940:     memset(objects_array, 0, sizeof(object) * MAX_CHAINS);
941:  
942:     /*find the first two corners.  they will be across a diagnal.*/
943:     findFirstTwoCorners(objects_array, current_chaincodes);
944:     /*find the second two corners.  they will be across a diagnal too.*/
945:     findSecondTwoCorners(objects_array, current_chaincodes);
946:     
947:     /*when this stops, i holds the number of chaincodes*/
948:     for(i = 0; (i <  MAX_CHAINS) && chain_codes && chain_codes[i].cur; i++);
949:     
950:     return i;
951:  }
952:  
953:  /*******Find the game board********************************************
954:  **********************************************************************/
955:  
956:  /*compare two distances between points.*/
957:  /*1st: the distance between two points
958:    2nd: the distance between two points*/
959:  /*returns the difference of the lengths divided by the lengths average.
960:    If there is a division by zero, the two lengths are both zero.  The return
961:  value in this case is FLT_MAX defined in float.h*/
962:  float compare_length(float length1, float length2)
963:  {
964:     float denom_check = fabs((length1 + length2) / 2);
965:  
966:     if(denom_check == 0)
967:     {
968:        /*the only way to possibly pull this off is if one point is
969:  	is considered more than one corner.  This is most likely
970:  	to happen where the chain was only two or three nodes long.*/
971:        /*Just set the error to the maximum value obtained from float.h,
972:  	since such a small chain is not what we are looking for.*/
973:       
974:        return FLT_MAX;
975:     }
976:     else
977:        return fabs(length1 - length2) / denom_check;
978:  }
979:  
980:  /*determine which object is the tic-tac-toe board from those found in the
981:    chaincodes and stored as objects.*/
982:  /*1st: pointer the chaincode array.
983:    2nd: pointer to the array of objects.*/
984:  /*return the object that is most likely the chaincode.  This value is
985:    the array subscript value for the object array.  If none is found then
986:    returns -1.*/
987:  int detect_game(list_info *current_chaincodes, object *object_array)
988:  {
989:     float length2to4[MAX_CHAINS], length1to3[MAX_CHAINS]; /*side pairs*/
990:     float length1to4[MAX_CHAINS], length2to3[MAX_CHAINS]; /*side pairs*/
991:     float length1to2[MAX_CHAINS], length3to4[MAX_CHAINS]; /*diagnaols*/
992:  
993:     float error24to13[MAX_CHAINS], error14to23[MAX_CHAINS];/*opp. sides*/
994:     float error14to13[MAX_CHAINS], error23to24[MAX_CHAINS];/*share corner*/
995:     float error31to32[MAX_CHAINS], error41to42[MAX_CHAINS];/*share corner*/
996:     float error12to34[MAX_CHAINS];/*diagnaols*/
997:  
998:     float error_avg;/*average of the errors stored in error##to## variables*/
999:  
1000:     int i; /*loop counting*/
1001:  
1002:     /*the most likely object (0 to num_of_corners) that is to be considered
1003:       as the board.  The float is the error associated with this object.*/
1004:     int most_likely = -1;
1005:     float ml_error = FLT_MAX; /*just to make sure*/
1006:  
1007:     if(display_verbose)
1008:        printf("Finding game board.  ");
1009:     
1010:     /*for each chaincode*/
1011:     for(i = 0; (i <  MAX_CHAINS) && current_chaincodes &&
1012:  	 current_chaincodes[i].cur; i++)
1013:     {
1014:        /*count the number of nodes in the chaincode.  Unless the size is
1015:         considered long enough, skip it and move on.*/
1016:        if(Length(¤t_chaincodes[i]) <  DECENT_SIZED_OBJECT)
1017:  	 continue;
1018:  
1019:        /*since points 1 & 2 are at a diagnal, and 3 & 4 are at a diagnol,
1020:           then the dist between  2 and 4 & 1 and 3 should be close
1021:           in value.*/
1022:        length2to4[i] = findDist(object_array[i].corner2.x,
1023:  			       object_array[i].corner2.y,
1024:  			       object_array[i].corner4.x,
1025:  			       object_array[i].corner4.y);
1026:        length1to3[i] = findDist(object_array[i].corner1.x,
1027:  			       object_array[i].corner1.y,
1028:  			       object_array[i].corner3.x,
1029:  			       object_array[i].corner3.y);
1030:  
1031:        /*the other side pair*/
1032:        length1to4[i] = findDist(object_array[i].corner1.x,
1033:  			       object_array[i].corner1.y,
1034:  			       object_array[i].corner4.x,
1035:  			       object_array[i].corner4.y);
1036:        length2to3[i] = findDist(object_array[i].corner2.x,
1037:  			       object_array[i].corner2.y,
1038:  			       object_array[i].corner3.x,
1039:  			       object_array[i].corner3.y);
1040:        
1041:        /*diagnols... always will be 1 & 2 and 3 & 4*/
1042:        length1to2[i] = findDist(object_array[i].corner1.x,
1043:  			       object_array[i].corner1.y,
1044:  			       object_array[i].corner2.x,
1045:  			       object_array[i].corner2.y);
1046:        length3to4[i] = findDist(object_array[i].corner3.x,
1047:  			       object_array[i].corner3.y,
1048:  			       object_array[i].corner4.x,
1049:  			       object_array[i].corner4.y);
1050:  
1051:        /*calculate percent errors for all edge (and diagnal) combinations*/
1052:        error24to13[i] = compare_length(length2to4[i], length1to3[i]);/*op.side*/
1053:        error14to23[i] = compare_length(length1to4[i], length2to3[i]);
1054:        error14to13[i] = compare_length(length1to4[i], length1to3[i]);/*1 crn.*/
1055:        error23to24[i] = compare_length(length2to3[i], length2to4[i]);
1056:        error31to32[i] = compare_length(length1to3[i], length2to3[i]);
1057:        error41to42[i] = compare_length(length1to4[i], length2to4[i]);
1058:        error12to34[i] = compare_length(length1to2[i], length3to4[i]);/*diag.*/
1059:  
1060:        /*average all of the error values together*/
1061:        error_avg = ((error24to13[i] + error14to23[i] + error14to13[i] +
1062:  		    error23to24[i] + error31to32[i] + error41to42[i] +
1063:  		    error12to34[i]) / 7);
1064:        
1065:        /*determine if the current object is considered the most likely to
1066:           be the ttt board so far.  Average of all the error##to##'s.
1067:           If the current is */
1068:        if(ml_error  > error_avg)
1069:        {
1070:           most_likely = i;
1071:           ml_error = error_avg;
1072:        }
1073:     }
1074:     if(display_verbose)
1075:        printf("Object %d is most likely the board with %f\%% error.\n",
1076:  	     most_likely, ml_error * 100);
1077:  
1078:    if(ml_error* 100 <  ALLOWABLE_PERCENT_ERROR)
1079:      return most_likely; /*return the object number that is the board*/
1080:    else
1081:      return -1;
1082:  }
1083:  
1084:  /*searches the moravec image for a cross line in the tic-tac-toe board
1085:   int the neighborhood of t = 1/3 */
1086:  /*1st: the first 'corner' to search from.  (t = 0)
1087:    2nd: the second 'corner' to search from. (t = 1)
1088:    3rd: a pointer to the moravec image used in finding the lines.*/
1089:  /*returns the coord of the location determined to be the end of the cross
1090:    line being searched for.  If one isn't found by looking at the image,
1091:    then the return value is the theoretical location where t = 1/3 */
1092:  /*note: the corners passed in are moved from the actuall corners.  see
1093:    find_side_points()*/
1094:  coord search_moravec(coord point1, coord point2, PGMImage *moravec,
1095:  		     float t_divider)
1096:  {
1097:     int i, j; /*loop counting*/
1098:     float t;
1099:     coord temp1, temp2;
1100:     float dist = findDist(point1.x, point1.y, point2.x, point2.y);
1101:     int search_size = 2; /*'radius' that defines the neighborhood of a point*/
1102:  
1103:     coord most_likely_corner = {-1, -1};
1104:     int most_likely_sum1 = 0, most_likely_sum2 = 0;
1105:     int sum_moravec_points1, sum_moravec_points2;
1106:  
1107:     /*calculate the coordinates where the divider edges should be.*/
1108:     for(t = 0; t <  ONE_TWELTH; t += (1 / dist))
1109:     {
1110:        /*check in both the + and - directions simultaniously.*/
1111:        temp1 = find_dividers(point1, point2, t_divider + t);
1112:        temp2 = find_dividers(point1, point2, t_divider - t);
1113:        
1114:        /*clear this before next iteration*/
1115:        sum_moravec_points1 = sum_moravec_points2 = 0;
1116:  
1117:        /*search the neighborhood for edge 'hits' in the moravec image*/
1118:        for(i = -(search_size); i < = search_size; i++)
1119:        {
1120:  	 for(j = -(search_size); j < = search_size; j++)
1121:  	 {
1122:  	    if(rgb_avg((*moravec).data[temp1.y + i][temp1.x + j])  > 128)
1123:  	    {
1124:  /*setCPixel(temp1.x + j, temp1.y + i, red);*/
1125:  	       sum_moravec_points1++;
1126:  	    }
1127:  	    if(rgb_avg((*moravec).data[temp2.y + i][temp2.x + j])  > 128)
1128:  	    {
1129:  /*setCPixel(temp2.x + j, temp2.y + i, red);*/
1130:  	       sum_moravec_points2++;
1131:  	    }
1132:  	    
1133:  	 }
1134:        }
1135:  
1136:        /*if the current point in the search is the best, return it*/
1137:        /*reusing search_size seems good, since it is about the right size*/
1138:        if((search_size < = sum_moravec_points1) &&
1139:  	 (most_likely_sum1 < = sum_moravec_points1) &&
1140:  	 !most_likely_sum2)
1141:        {
1142:  	 most_likely_sum1 = sum_moravec_points1;
1143:  	 most_likely_corner = temp1;
1144:        }
1145:        else if(((search_size < = sum_moravec_points2) &&
1146:  	       (most_likely_sum2 < = sum_moravec_points2) &&
1147:  	       !most_likely_sum1))
1148:        {
1149:  	 most_likely_sum2 = sum_moravec_points2;
1150:  	 most_likely_corner = temp2;
1151:        }
1152:     }
1153:  
1154:     /*if a sutible point is not found, return the theoretical point*/
1155:     if((most_likely_corner.x <  0) || (most_likely_corner.y <  0))
1156:     {
1157:        most_likely_corner = find_dividers(point1, point2, ONE_THIRD);
1158:     }
1159:  
1160:     return most_likely_corner;
1161:  }
1162:  
1163:  
1164:  /*return the intersection of two lines*/
1165:  /*1st: coordinate one of first line
1166:    2nd: coordinate two of first line
1167:    3rd: coordinate one of second line
1168:    4th: coordinate two of second line*/
1169:  /*return the coordinate where the lines intersect.  If none-was found
1170:    or an error occured in finding one return (-1, -1).*/
1171:  coord find_intersection(coord line1_point1, coord line1_point2,
1172:  			coord line2_point1, coord line2_point2)
1173:  {
1174:     float line1_slope, line2_slope; /*temp slope holding variables*/
1175:     float denominator, numerator; /*temp fraction holding varaibles*/
1176:     float x_float; /*use to avoid obscure roundoff errors*/
1177:     coord target = {-1, -1}; /*initalize temp target point*/
1178:     
1179:     /*find slope for first line*/
1180:     if((line1_point1.x - line1_point2.x) != 0)
1181:     {
1182:        line1_slope = ((float)(line1_point1.y - line1_point2.y)) /
1183:  	((float)(line1_point1.x - line1_point2.x));
1184:     }
1185:     else /*otherwise handle the undefined slope*/
1186:     { 
1187:       /*find slope for secon line when first is undefined*/
1188:        if((line2_point1.x - line2_point2.x) != 0)
1189:        {
1190:  	 line2_slope = ((float)(line2_point1.y - line2_point2.y)) /
1191:  	   ((float)(line2_point1.x - line2_point2.x));
1192:        }
1193:        else /*this should never happen, but could if someone specifed the same
1194:               line twice*/
1195:  	return target; /*target is initalized to (-1, -1)*/
1196:  
1197:        /*since the slope is undefined the x coord in known*/
1198:        target.x = line1_point1.x;
1199:        target.y = line2_slope * target.x + line2_point1.y; /*y = mx + b*/
1200:  /*printf("line one has undefined slope\n");*/
1201:        return target;
1202:     }
1203:  
1204:     /*find slope for second line*/
1205:     if((line2_point1.x - line2_point2.x) != 0)
1206:     {
1207:        line2_slope = ((float)(line2_point1.y - line2_point2.y)) /
1208:  	((float)(line2_point1.x - line2_point2.x));
1209:     }
1210:     else
1211:     {
1212:        /*since the slope is undefined the x coord in known*/
1213:        target.x = line2_point1.x;
1214:        target.y = line1_slope * target.x + line1_point1.y; /*y = mx + b*/
1215:  /*printf("line two has undefined slope\n");*/
1216:        return target;
1217:     }
1218:  
1219:     /* slope is m and defined by:
1220:           (y1 - y2)
1221:       m = ---------
1222:           (x1 - x2)
1223:  
1224:       target calculated by setting Yt = M1*(Xt - Xa) + Ya equal to
1225:       Yt = M2 * (Xt - Xc) + Yc to get
1226:  
1227:              (Xa*M1 - M2*Xc + Yc - Ya)
1228:         Xt = -------------------------
1229:                   M1 - M2
1230:  
1231:       then to get the y coordinate sub Xt into:
1232:  
1233:       Yt = M1 * (Xt - Xa) + Ya
1234:  
1235:       Where line1_point1 = a
1236:             line1_point2 = b
1237:             line2_point1 = c
1238:  	   line12point2 = d
1239:       and are indicated as subscripts of their X or Y coordinate.
1240:             M1 = line1_slope
1241:             M2 = line2_slope
1242:     */
1243:  
1244:     /*calculate the x coords nominator and demoninator*/
1245:     numerator = (((float)((float)line1_point1.x * line1_slope)
1246:  		- (float)((float)line2_point1.x * line2_slope)
1247:  		 + (float)line2_point1.y - (float)line1_point1.y));
1248:     denominator = (float)(line1_slope - line2_slope);
1249:     
1250:     /*find the x coord, store is as a float to avoid obscure round off errors
1251:       when using it to calculate the y coord.*/
1252:     x_float = numerator / denominator;
1253:  
1254:     target.x = (int)x_float;
1255:  
1256:     /*find the y coord*/ 
1257:     target.y = line1_slope * ((float)x_float - (float)line1_point1.x)
1258:       + (float)line1_point1.y;
1259:  
1260:     return target;
1261:  }
1262:  
1263:  /*move the location to start searching off of the corners a little so the
1264:    search isn't right on top of the any cross lines that are perpendicular to
1265:    the target line.*/
1266:  /*anchor - the corner closest to the side divider point being looked for.
1267:  common - the corner that the line between itself and the anchor corner contains
1268:     the target divider point.
1269:  anchor_opposite - the corner adjacent to the anchor corner & oppsoite common.
1270:  common_opposite - the corner adjacent to the common corner & oppposite anchor.
1271:  */
1272:  /*return the divider point, or (-1, -1) on error*/
1273:  coord find_side_points(coord anchor, coord common, coord anchor_opposite,
1274:  		       coord common_opposite, PGMImage *moravec,
1275:  		       float t_divider)
1276:  {
1277:       coord temp1, temp2;
1278:  
1279:       temp1 = find_dividers(anchor, anchor_opposite, ONE_24TH);
1280:       temp2 = find_dividers(common, common_opposite, ONE_24TH);
1281:  
1282:       return search_moravec(temp1, temp2, moravec, t_divider);
1283:  }
1284:  
1285:  
1286:  /*find the ttt board's important coordinates*/
1287:  /*1st: The object that is determined to be the board.
1288:    2nd: the pointer to the struct that contains all of the information about
1289:    the tic-tac-toe board important coordinates.
1290:    3rd: a pointer to the Moravec pgm image*/
1291:  void detect_ttt_board(object board_object,
1292:  		      coord board_details[DIVIDERS][DIVIDERS],
1293:  		      PGMImage *moravec)
1294:  {
1295:     int i, j;
1296:     
1297:     /*copy the corners into the ttt datatype*/
1298:     if(board_object.corner1.x  > board_object.corner2.x)
1299:     {
1300:        board_details[0][0] = board_object.corner2;
1301:        board_details[DIVIDERS - 1][0] = board_object.corner2;
1302:        board_details[0][DIVIDERS - 1] = board_object.corner1;
1303:        board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner1;
1304:  
1305:        if((board_object.corner3.y  > board_object.corner4.y)
1306:  	 && (board_object.corner3.x  > board_object.corner4.x))
1307:        {
1308:  	 board_details[DIVIDERS - 1][0] = board_object.corner4;
1309:  	 board_details[0][DIVIDERS - 1] = board_object.corner3;
1310:        }
1311:        else if((board_object.corner3.y  > board_object.corner4.y)
1312:  	 && (board_object.corner3.x <  board_object.corner4.x))
1313:        {
1314:  	 board_details[0][0] = board_object.corner3;
1315:  	 board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner4;
1316:        }
1317:        else if((board_object.corner3.y <  board_object.corner4.y)
1318:  	      && (board_object.corner3.x  > board_object.corner4.x))
1319:        {
1320:  	 board_details[0][0] = board_object.corner4;
1321:  	 board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner3;
1322:        }
1323:        else
1324:        {
1325:  	 board_details[DIVIDERS - 1][0] = board_object.corner3;
1326:  	 board_details[0][DIVIDERS - 1] = board_object.corner4;
1327:        }
1328:        
1329:     }
1330:     else
1331:     {
1332:        board_details[0][0] = board_object.corner1;
1333:        board_details[DIVIDERS - 1][0] = board_object.corner1;
1334:        board_details[0][DIVIDERS - 1] = board_object.corner2;
1335:        board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner2;
1336:  
1337:        if((board_object.corner3.y  > board_object.corner4.y)
1338:  	 && (board_object.corner3.x  > board_object.corner4.x))
1339:        {
1340:  	 board_details[DIVIDERS - 1][0] = board_object.corner4;
1341:  	 board_details[0][DIVIDERS - 1] = board_object.corner3;
1342:        }
1343:        else if((board_object.corner3.y  > board_object.corner4.y)
1344:  	 && (board_object.corner3.x <  board_object.corner4.x))
1345:        {
1346:  	 board_details[0][0] = board_object.corner3;
1347:  	 board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner4;
1348:        }
1349:        else if((board_object.corner3.y <  board_object.corner4.y)
1350:  	      && (board_object.corner3.x  > board_object.corner4.x))
1351:        {
1352:  	 board_details[0][0] = board_object.corner4;
1353:  	 board_details[DIVIDERS - 1][DIVIDERS - 1] = board_object.corner3;
1354:        }
1355:        else
1356:        {
1357:  	 board_details[DIVIDERS - 1][0] = board_object.corner3;
1358:  	 board_details[0][DIVIDERS - 1] = board_object.corner4;
1359:        }      
1360:     }
1361:  
1362:     /*loop along the four sides, finding the side points for the cross lines.*/
1363:     for(i = 1; i <  DIVIDERS - 1; i++)
1364:     {
1365:        /*"side"*/
1366:        board_details[i][0] =
1367:  	 find_side_points(board_details[0][0],
1368:  			  board_details[DIVIDERS - 1][0],
1369:  			  board_details[0][DIVIDERS - 1],
1370:  			  board_details[DIVIDERS - 1][DIVIDERS - 1],
1371:  			  moravec, i / (DIVIDERS - 1.0));
1372:  
1373:        /*side opposite "side"*/
1374:        board_details[i][DIVIDERS - 1] =
1375:  	 find_side_points(board_details[0][DIVIDERS - 1],
1376:  			  board_details[DIVIDERS - 1][DIVIDERS - 1],
1377:  			  board_details[0][0],
1378:  			  board_details[DIVIDERS - 1][0],
1379:  			  moravec, i / (DIVIDERS - 1.0));
1380:  
1381:        /*side to the left of "side"*/
1382:        board_details[0][i] =
1383:  	 find_side_points(board_details[0][0],
1384:  			  board_details[0][DIVIDERS - 1],
1385:  			  board_details[DIVIDERS - 1][0],
1386:  			  board_details[DIVIDERS - 1][DIVIDERS - 1],
1387:  			  moravec, i / (DIVIDERS - 1.0));
1388:  
1389:        /*side opposite the side to the left of "side"*/
1390:        board_details[DIVIDERS - 1][i] =
1391:  	 find_side_points(board_details[DIVIDERS - 1][0],
1392:  			  board_details[DIVIDERS - 1][DIVIDERS - 1],
1393:  			  board_details[0][0],
1394:  			  board_details[0][DIVIDERS - 1],
1395:  			  moravec, i / (DIVIDERS - 1.0));
1396:     }
1397:  
1398:  
1399:     /*correct the length of the lines that is shortend to calculate them
1400:       in the first place above.  simply find the intersection of the *short*
1401:       line with that of the outside line between the corners.*/
1402:     
1403:     for(i = 1; i <  DIVIDERS - 1; i++)
1404:     {
1405:        board_details[i][0] = find_intersection(board_details[i][0],
1406:  					      board_details[i][DIVIDERS - 1],
1407:  					      board_details[0][0],
1408:  					      board_details[DIVIDERS - 1][0]);
1409:     
1410:        board_details[i][DIVIDERS - 1] =
1411:  	 find_intersection(board_details[i][DIVIDERS - 1],
1412:  			   board_details[i][0],
1413:  			   board_details[0][DIVIDERS - 1],
1414:  			   board_details[DIVIDERS - 1][DIVIDERS - 1]);
1415:     
1416:  
1417:        board_details[0][i] = find_intersection(board_details[0][i],
1418:  					      board_details[DIVIDERS - 1][i],
1419:  					      board_details[0][0],
1420:  					      board_details[0][DIVIDERS - 1]);
1421:     
1422:  
1423:        board_details[DIVIDERS - 1][i] =
1424:  	 find_intersection(board_details[DIVIDERS - 1][i],
1425:  			   board_details[0][i],
1426:  			   board_details[DIVIDERS - 1][0],
1427:  			   board_details[DIVIDERS - 1][DIVIDERS - 1]);
1428:     }
1429:  
1430:     /*now that the sides are found, find the intersections to find the
1431:       middle points*/
1432:     for(i = 1; i <  DIVIDERS - 1; i++)
1433:        for(j = 1; j <  DIVIDERS - 1; j++)
1434:  	 board_details[i][j] =
1435:  	    find_intersection(board_details[0][j],
1436:  			      board_details[DIVIDERS - 1][j],
1437:  			      board_details[i][0],
1438:  			      board_details[i][DIVIDERS - 1]);
1439:  
1440:     /*print all the coords*/
1441:     /*for(i = 0; i <  DIVIDERS; i++)
1442:        for(j = 0; j <  DIVIDERS; j++)
1443:  	 printf("points[%d][%d]: (%d, %d)\n", i, j, points[i][j].x,
1444:  	 points[i][j].y);*/
1445:  }
1446:  
1447:  /*not a callback, called by menu() and buffer()*/
1448:  int showGame()
1449:  {
1450:     int object; /*temp variable*/
1451:     struct timeval t0, t1;
1452:  
1453:     printf("\n\n");
1454:     
1455:     /*correct image perspective*/
1456:     pthread_mutex_lock(&images_mutex);
1457:  
1458:     img_cur = img_original;
1459:     gettimeofday(&t0, NULL);
1460:     pers_corr(img_pers_corr, img_cur);
1461:     gettimeofday(&t1, NULL);
1462:     do_time(t0, t1, "perspective correction");
1463:     
1464:     img_cur = img_pers_corr;
1465:  
1466:     pthread_mutex_unlock(&images_mutex);
1467:  
1468:  
1469:  
1470:     /*find chain codes*/
1471:     pthread_mutex_lock(&calculate_mutex);
1472:     pthread_mutex_lock(&images_mutex);
1473:     
1474:     gettimeofday(&t0, NULL);
1475:     showChain(img_pers_corr, &chain_codes); /*chain codes*/
1476:     gettimeofday(&t1, NULL);
1477:     do_time(t0, t1, "chain codes");
1478:  
1479:     pthread_mutex_unlock(&images_mutex);
1480:     pthread_mutex_unlock(&calculate_mutex);
1481:  
1482:  
1483:  
1484:     /*find corners*/
1485:     pthread_mutex_lock(&calculate_mutex);
1486:     
1487:     gettimeofday(&t0, NULL);
1488:     detect_corners(chain_codes, all_objects);
1489:     gettimeofday(&t1, NULL);
1490:     do_time(t0, t1, "corner detection");
1491:  
1492:     pthread_mutex_unlock(&calculate_mutex);
1493:  
1494:  
1495:     
1496:     
1497:     /*detect the ttt game board*/
1498:     pthread_mutex_lock(&calculate_mutex);
1499:  
1500:     gettimeofday(&t0, NULL);
1501:     object = detect_game(chain_codes, all_objects);
1502:     gettimeofday(&t1, NULL);
1503:     do_time(t0, t1, "game detection");
1504:  
1505:     pthread_mutex_unlock(&calculate_mutex);
1506:     
1507:  
1508:     if(object == -1)
1509:     {
1510:        if(display_verbose)
1511:  	 printf("No board was found\n");
1512:        return object;
1513:     }
1514:     
1515:  
1516:     /*do moravec first, then perspective to prevent the loss of edge sharpness
1517:       from doing the perspective first*/
1518:     pthread_mutex_lock(&images_mutex);
1519:  
1520:     gettimeofday(&t0, NULL);
1521:     moravec(img_grayscale, img_original);
1522:     pers_corr(img_moravec, img_grayscale);
1523:     gettimeofday(&t1, NULL);
1524:     do_time(t0, t1, "Moravec filter");
1525:  
1526:     pthread_mutex_unlock(&images_mutex);
1527:  
1528:    
1529:  
1530:     
1531:     /*if a board was found, find the specific information*/
1532:     pthread_mutex_lock(&images_mutex);
1533:     pthread_mutex_lock(&calculate_mutex);
1534:     
1535:     gettimeofday(&t0, NULL);
1536:     detect_ttt_board(all_objects[object], points, img_moravec);
1537:     gettimeofday(&t1, NULL);
1538:     do_time(t0, t1, "detect tic-tac-toe");
1539:  
1540:     pthread_mutex_unlock(&calculate_mutex);
1541:     pthread_mutex_unlock(&images_mutex);
1542:     
1543:     
1544:     return object;
1545:  }
1546:  
1547:  
1548:  /*****Find the pieces***************************************************
1549:  ***********************************************************************/
1550:  
1551:  /*Takes four points that must be in order going around the region.  Also,
1552:    needs a pointer to an image with the moravec filter applied.*/
1553:  /*1st - 4th: The coordinates in order (starting point arbitrary, as long
1554:    as they are in order) that the area inside of is searched for pieces.
1555:    5th: a pointer to the Moravec image to be used.
1556:    6th: a pointer to the memory location were the averages of the pixels
1557:    vals for the marble are stored. (only valid when return value is TRUE)
1558:  */
1559:  /*returns TRUE if a marble exists at the location, FALSE otherwise.*/
1560:  bool search_area(coord point1, coord point2, coord point3, coord point4,
1561:  		PGMImage *moravec, PGMImage *color, RGB_INT *area_avg)
1562:  {
1563:     /*The following variables are used to calculate the existence of a marble
1564:       at a location on the board or not.  Uses the following basic formula
1565:          new_point = ((t-1) * 1st point) + (t * 2nd point)
1566:       where the start & end coords are opposite sides of the area being
1567:       searched.  If you take the entire area of a single position on a ttt
1568:       board, the area that is searched is the region that is bouned by the
1569:       points halfway between those passed in to this function.*/
1570:  
1571:     int search_size = 2, search_area, i, j;
1572:  
1573:     /*cant use pixel_sum directly becuase it is only */
1574:     RGB_INT pixel_sum = {0, 0, 0};
1575:     int pixel_sum_red = 0, pixel_sum_green = 0, pixel_sum_blue = 0;
1576:     
1577:     float t, s, r; /*hold values incrementing from 0 to 1*/
1578:     coord t_start, t_end, s_start, s_end;
1579:     coord temp_t, temp_s, temp_r;
1580:     float dist_t, dist_s, dist_r;
1581:  
1582:     /*pixel count of those over the threashhold and total pixels*/
1583:     int pixel_count = 0, moravec_count = 0;
1584:  
1585:     /*running storage for the middle of the marble*/
1586:     coord sum_locations = {0, 0}; /*sum of x and y coords for average location*/
1587:  
1588:     coord move_start1, move_start2, move_start3, move_start4;
1589:  
1590:     /*this translation of moving the starting points skews the search area
1591:       off enough to avoid counting "hit" boundry pixels that exist along the
1592:       divders/edges and not the marble itself.*/
1593:     move_start1 = find_dividers(point1, point2, ONE_THIRD);
1594:     move_start2 = find_dividers(point2, point3, ONE_THIRD);
1595:     move_start3 = find_dividers(point3, point4, ONE_THIRD);
1596:     move_start4 = find_dividers(point4, point4, ONE_THIRD);
1597:  
1598:     t_start = find_dividers(move_start1, move_start2, ONE_HALF);
1599:     t_end = find_dividers(move_start1, move_start4, ONE_HALF);
1600:  
1601:     s_start = find_dividers(move_start3, move_start2, ONE_HALF);
1602:     s_end = find_dividers(move_start3, move_start4, ONE_HALF);
1603:     
1604:     dist_t = findDist(t_start.x, t_start.y, t_end.x, t_end.y);
1605:     dist_s = findDist(s_start.x, s_start.y, s_end.x, s_end.y);
1606:  
1607:     /*march two points along that parallel each other in the search area*/
1608:     for(t = 0.0, s = 0.0; t < = 1.0; t += (1.0 / dist_t), s += (1.0 / dist_s))
1609:     {
1610:        temp_t = find_dividers(t_start, t_end, t);
1611:  
1612:        temp_s = find_dividers(s_start, s_end, s);
1613:  
1614:        dist_r = findDist(temp_t.x, temp_t.y, temp_s.x, temp_s.y);
1615:  
1616:        /*march a single point along that starts at temp_s and ends
1617:  	at temp_t.  This fills in the region for the search.*/
1618:        for(r = 0.0; r < = 1.0; r += (1 / dist_r))
1619:        {
1620:  	 temp_r = find_dividers(temp_s, temp_t, r);
1621:  
1622:  /*setCPixel(temp_r.x, temp_r.y, red);*/
1623:  	 /*talley the number of edge points found (rgb. avg.  >= 128)*/
1624:  	 if(rgb_avg((*moravec).data[temp_r.y][temp_r.x])  >= EDGES_FOR_PIECE)
1625:  	 {
1626:  	    sum_locations.x += temp_r.x;
1627:  	    sum_locations.y += temp_r.y;
1628:  /*setCPixel(temp_r.x, temp_r.y, blue);*/
1629:  	    moravec_count++;
1630:  	 }
1631:  	 pixel_count++; /*increment the number of pixels*/
1632:        }
1633:  
1634:     }
1635:  
1636:     if(moravec_count)
1637:     {
1638:        sum_locations.x /= moravec_count;
1639:        sum_locations.y /= moravec_count;
1640:     }
1641:     
1642:     /*if the number of edge pixels is greater than 25% of the total, then
1643:       this is an edge of marble.  This is based on the fact the the percentage
1644:       of pixels highlighted theoritically be 25% of the total count.  By
1645:       dividing the circumfernce of the marble (in cm) by the area of the search.
1646:       Since the radius is 2cm and the length and width are 5cm the math works
1647:       out to 25% of the total (theoretically).  However, since the starting
1648:       points are moved 1/3 off the corners this theo. percentage jumps to
1649:       45% for the max.  For practical implementation EDGE_COUNT is set
1650:       to .25 or 25 percent.*/
1651:     /*printf("ttt fp mc: %d  tc: %d   per: %f\n", moravec_count, pixel_count,
1652:       ((float)moravec_count / (float)pixel_count) * 100.0);*/
1653:  
1654:     if(moravec_count   >= EDGE_COUNT * pixel_count)
1655:     {
1656:        for(i = -search_size; i < = search_size; i++) /*y dir*/
1657:  	 for(j = -search_size; j < = search_size; j++) /*x dir*/
1658:  	 {
1659:  	    pixel_sum_red += (*color).data
1660:  	       [sum_locations.y + i][sum_locations.x + j].red;
1661:  	    pixel_sum_green += (*color).data
1662:  	       [sum_locations.y + i][sum_locations.x + j].green;
1663:  	    pixel_sum_blue += (*color).data
1664:  	       [sum_locations.y + i][sum_locations.x + j].blue;
1665:  
1666:  /*setCPixel(sum_locations.x + j, sum_locations.y + i, yellow);*/
1667:  	    
1668:  	 }
1669:  /*setCPixel(sum_locations.x, sum_locations.y, green);
1670:  glFlush();*/
1671:  
1672:        search_area = (((search_size * 2) + 1) * ((search_size * 2) + 1));
1673:  
1674:        pixel_sum.red =
1675:  	 pixel_sum_red / search_area;
1676:        pixel_sum.green =
1677:  	 pixel_sum_green / search_area;
1678:        pixel_sum.blue =
1679:  	 pixel_sum_blue / search_area;
1680:        
1681:        memcpy(area_avg, &pixel_sum, sizeof(RGB_INT));
1682:        /*      printf("pxl avg: %d\n", pixel_sum);*/
1683:        return TRUE;
1684:     }
1685:     
1686:     /*otherwise a marble isn't here*/
1687:     return FALSE;
1688:  }
1689:  
1690:  
1691:  int weight_colors(RGB_INT color)
1692:  {
1693:     int red_to_green = abs(color.red - color.green);
1694:     int red_to_blue = abs(color.red - color.blue);
1695:     int green_to_blue = abs(color.green - color.blue);
1696:     
1697:     return red_to_green + red_to_blue + green_to_blue;
1698:  }
1699:  
1700:  
1701:  /*looks for the tic-tac-toe pieces on the board*/
1702:  /*1st: the pointer to the board information.
1703:    2nd: the pointer to the Moravec image used to find the edges.*/
1704:  void detect_pieces(coord board_data[DIVIDERS][DIVIDERS],
1705:  		   PGMImage *moravec, PGMImage *color)
1706:  {
1707:     int i, j; /*loop counting*/
1708:     int teams_pixel_avg, team1_val = 255, team2_val = 0;
1709:     RGB_INT store[3][3]; /*area pixel averages*/
1710:     int team1_assign = 1, team2_assign = 2;
1711:  
1712:     if(display_verbose)
1713:        printf("Finding pieces.\n");
1714:  
1715:     memset(pieces, 0, 9 * sizeof(unsigned char));
1716:     memset(store, 0, 9 * sizeof(RGB_INT));
1717:  
1718:     for(i = 0; i <  DIVIDERS - 1; i++) /*y*/
1719:       for(j = 0; j <  DIVIDERS - 1; j++) /*x*/
1720:       {
1721:         pieces[i][j] = search_area(board_data[i][j],
1722:  				  board_data[i + 1][j],
1723:  				  board_data[i + 1][j + 1],
1724:  				  board_data[i][j + 1],
1725:  				  moravec, color, &store[i][j]);
1726:       }
1727:  
1728:     /*first position to check*********************************/
1729:     for(i = 0; i <  DIVIDERS - 1; i++) /*y*/
1730:     {
1731:        for(j = 0; j <  DIVIDERS - 1; j++) /*x*/
1732:        {
1733:  	 if(!pieces[i][j]) /*if there isn't a piece*/
1734:  	    continue;
1735:  
1736:  	 pieces[i][j] = weight_colors(store[i][j]);
1737:  	 
1738:  	 if(pieces[i][j] <  team1_val)
1739:  	 {
1740:  	    team1_val = pieces[i][j];
1741:  	 }
1742:  
1743:  	 if(pieces[i][j]  > team2_val)
1744:  	 {
1745:  	    team2_val = pieces[i][j];
1746:  	 }
1747:        }
1748:     } /*for*******************************************************/
1749:  
1750:     if(team1_val == team2_val) /*only one move made so far*/
1751:        /*at this point we don't know which color each team is.  So, store
1752:  	this into the team_first global.*/
1753:        team_first = team1_val;
1754:  
1755:     teams_pixel_avg = (team1_val + team2_val) / 2;
1756:  
1757:     /*the first team to go is assigned team 1.  If the color determining
1758:       sets it to team 2, then this loop sets what needs to be switched.*/
1759:     if(abs(team_first - team1_val)  > abs((team1_val - team2_val) / 2))
1760:     {
1761:        team1_assign = 2;
1762:        team2_assign = 1;
1763:     }
1764:     
1765:     for(i = 0; i <  3; i++) /*y*/
1766:     {
1767:        for(j = 0; j <  3; j++) /*x*/
1768:        {
1769:  	 if(pieces[i][j])
1770:  	 {
1771:  	    /*check if team 1*/
1772:  	    if(pieces[i][j] < = teams_pixel_avg)
1773:  	    {
1774:  	       pieces[i][j] = team1_assign;
1775:  	    }
1776:  	    /*check if team 2*/
1777:  	    else if(pieces[i][j]  >= teams_pixel_avg)
1778:  	    {
1779:  	       pieces[i][j] = team2_assign;
1780:  	    }
1781:  	    /*to many different colored teams*/
1782:  	    else
1783:  	    {
1784:  	       pieces[i][j] = -1;
1785:  	    }
1786:  	 } /*if(pieces[i][j]*/
1787:        }
1788:     }
1789:  }
1790:  
1791:  /*display the state of the board in ascii graphics*/
1792:  void display_board()
1793:  {
1794:     int i, j;
1795:  
1796:     printf("    ");
1797:     for(j = 0; j <  DIVIDERS - 1; j++)
1798:        printf(" [%d] ", j);
1799:     printf("\n-------------------\n");
1800:     
1801:     for(i = 0; i <  DIVIDERS - 1; i++)
1802:     {
1803:        printf("[%d] ", i);
1804:        for(j = 0; j <  DIVIDERS - 1; j++)
1805:        {
1806:  	 printf("  %d  ", pieces[i][j]);
1807:        }
1808:        printf("\n");
1809:     }
1810:  }
1811:  
1812:  void showPieces()
1813:  {
1814:     struct timeval t0, t1;
1815:     /*if we get here there is a likely canidate found*/
1816:     pthread_mutex_lock(&calculate_mutex);
1817:     pthread_mutex_lock(&images_mutex);
1818:  
1819:     gettimeofday(&t0, NULL);
1820:     detect_pieces(points, img_moravec, img_cur);
1821:     gettimeofday(&t1, NULL);
1822:     do_time(t0, t1, "detect peices");
1823:  
1824:     if(display_verbose)
1825:        display_board();
1826:     
1827:     pthread_mutex_unlock(&images_mutex);
1828:     pthread_mutex_unlock(&calculate_mutex);
1829:  }
1830:  
1831:  
1832:  /* =================================================================
1833:   * Callback functions.
1834:   *
1835:   * color = displayed graphics in window
1836:   * menu = menu event handling
1837:   * keyboard = deyboard event handling
1838:   * ----------------------------------------------------------------- */
1839:  void color(void)
1840:  {
1841:     glutReshapeWindow(HSIZE, VSIZE);
1842:     
1843:     if(pthread_mutex_trylock(&images_mutex) == EBUSY)
1844:     {
1845:        return;
1846:     }
1847:  
1848:     /*show the current image*/
1849:     showColor(img_cur);
1850:     pthread_mutex_unlock(&images_mutex);
1851:  
1852:     if(pthread_mutex_trylock(&calculate_mutex) == EBUSY)
1853:     {
1854:        return;
1855:     }
1856:     /*show the current abstract information*/
1857:     showAbstract(all_objects, points);
1858:     pthread_mutex_unlock(&calculate_mutex);
1859:  }
1860:  
1861:  void sie(void)
1862:  {
1863:     glutReshapeWindow(HSIZE, VSIZE);
1864:    
1865:     if(redraw_needed)
1866:     {
1867:        pthread_mutex_lock(&images_mutex);
1868:        showColor(img_cur);
1869:        redraw_needed = FALSE;
1870:        pthread_mutex_unlock(&images_mutex);
1871:     }
1872:     else
1873:        sched_yield();
1874:  }
1875:  
1876:  void* buffer(void *param)
1877:  {
1878:     while(is_buffered)
1879:     {
1880:       /*the mutexes for these calcs are done inside showGame()*/
1881:        draw_abstract_board = showGame();
1882:  
1883:        if(draw_abstract_board != -1)
1884:        {
1885:  	 showPieces();
1886:        }
1887:     }
1888:  
1889:     return NULL;
1890:  }
1891:  
1892:  
1893:  
1894:  #define RESTART 0
1895:  #define PERS_CORR 3
1896:  #define COLOR_TO_GRAY 4
1897:  #define MORAVEC 5
1898:  #define EDGES 6
1899:  #define CORNERS 7
1900:  #define BUFFERS 8
1901:  #define GAME 9
1902:  #define PIECES 10
1903:  
1904:  /*this is not a callback function, but is used inside menu() for setting
1905:    new states of execution*/
1906:  void reset_state(PGMImage* new_current)
1907:  {
1908:     img_cur = new_current;
1909:     draw_abstract_lines = -1;
1910:     draw_abstract_board = -1;
1911:     free_chaincodes(&chain_codes);
1912:  }
1913:  
1914:  void menu(int selection)
1915:  {
1916:     if(selection == RESTART)   
1917:     {
1918:        reset_state(img_original);
1919:     }
1920:     if(selection == PERS_CORR)
1921:     {
1922:        pers_corr(img_pers_corr, img_cur);
1923:        reset_state(img_pers_corr);
1924:     }
1925:     if(selection == COLOR_TO_GRAY)
1926:     {
1927:        color_to_gray(img_grayscale, img_cur);
1928:        reset_state(img_grayscale);
1929:     }
1930:     if(selection == MORAVEC)
1931:     {
1932:        moravec(img_moravec, img_cur);
1933:        reset_state(img_moravec);
1934:     }
1935:     if(selection == EDGES)
1936:     {
1937:        showChain(img_cur, &chain_codes);
1938:     }
1939:     if(selection == CORNERS)
1940:     {
1941:        showChain(img_cur, &chain_codes);
1942:  
1943:        draw_abstract_lines = detect_corners(chain_codes, all_objects);
1944:     }
1945:     if(selection == BUFFERS)
1946:     {
1947:        if(is_buffered == FALSE)
1948:        {
1949:  	 is_buffered = TRUE;
1950:  	 pthread_create(&vision_thread, NULL, &buffer, NULL);
1951:  	 glutIdleFunc(&color);
1952:        }
1953:        else
1954:        {
1955:  	 is_buffered = FALSE;
1956:  	 pthread_join(vision_thread, NULL);
1957:  	 glutIdleFunc(NULL);
1958:        }
1959:     }
1960:     if(selection == GAME)
1961:     {
1962:        draw_abstract_board = showGame();
1963:     }
1964:     if(selection == PIECES)
1965:     {
1966:        draw_abstract_board = showGame();
1967:  
1968:        if(draw_abstract_board != -1)
1969:  	 showPieces();
1970:     }
1971:  
1972:     glutPostRedisplay(); /*redraw the image*/
1973:  }
1974:  
1975:  void keyboard(unsigned char key, int x, int y)
1976:  { 
1977:     switch (key)
1978:     {
1979:        case 27: /*Esc*/
1980:           exit(0);
1981:           break;
1982:  
1983:  	 /*save current image to file image1.pgm*/
1984:        case 115: /*s*/
1985:  	 pthread_mutex_lock(&images_mutex);
1986:  	 save(img_cur);
1987:  	 pthread_mutex_unlock(&images_mutex);
1988:  	 break;
1989:  
1990:  	 /*save original image to file image1.pgm*/
1991:        case 111: /*o*/
1992:  	 pthread_mutex_lock(&images_mutex);
1993:  	 save(img_original);
1994:  	 pthread_mutex_unlock(&images_mutex);
1995:  	 break;
1996:  
1997:  	 /*enable display of calculation times.*/
1998:        case 116: /*t*/
1999:  	 if(display_time)
2000:  	    display_time = FALSE;
2001:  	 else
2002:  	    display_time = TRUE;
2003:  	 break;
2004:  
2005:  	 /*enable display of verbose text.*/
2006:        case 118: /*v*/
2007:  	 if(display_verbose)
2008:  	    display_verbose = FALSE;
2009:  	 else
2010:  	    display_verbose = TRUE;
2011:  	 break;
2012:     }
2013:  }
2014:  
2015:  void mouse(int button, int state, int x, int y)
2016:  {
2017:    char temp[50];
2018:    
2019:    if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
2020:    {
2021:       sprintf(temp, "(x, y): (%d, %d)  red: %d green: %d blue: %d\n",
2022:  	     x, VSIZE - y, (*img_cur).data[VSIZE - y][x].red,
2023:  	     (*img_cur).data[VSIZE - y][x].green,
2024:  	     (*img_cur).data[VSIZE - y][x].blue);
2025:       setCRect(0, 0, 200, 12, black);
2026:       glColor3f(1.0, 0.0, 0.0);
2027:       drawString(0, 0, temp, red);
2028:       glFlush();
2029:    }
2030:  }
2031:  
2032:  
2033:  /* --------------------------------------------------------------------
2034:     Connection functions for work with sie.
2035:     ------------------------------------------------------------------ */
2036:  
2037:  /* now connect to the administrator */
2038:  int admin_connect(int argc, const char** argv)
2039:  {
2040:     int sock;
2041:  
2042:     sock = device_handshake (argc, argv,
2043:  			    TTT_VISION_DEVICE_TYPE_ID_NUMBER, 1);
2044:  
2045:     if (sock == -1)
2046:     {
2047:        printf ("failed in handshake; aborting...\n");
2048:        exit (1);
2049:     }
2050:  
2051:     return sock;
2052:  }
2053:  
2054:  void* sock_com(void* ptr)
2055:  {
2056:     PGMImage *img = (PGMImage*)ptr;
2057:     int sock = socket_num;
2058:     int8 value8, command;
2059:     int32 value32, return_val;
2060:     int x, y;
2061:  
2062:     struct timeval t0, t1; /*time 0 and finished time*/
2063:  
2064:     /******* main loop  ******/
2065:     command = TTT_VISION_INIT_VALUE;
2066:     while (command != ADMIN_DISCONNECT_DEVICE)
2067:     {
2068:       /* block until we get the next command */
2069:        if ((shelley_sockets_read (sock, (char*) &command, sizeof (int8))) == -1)
2070:  	 exit (0);
2071:       
2072:        else
2073:        {
2074:  	/*printf("ttt command: %d\n", command);*/
2075:  	 switch (command)
2076:  	 {
2077:  	    case ADMIN_QUERY_DEVICE_STATUS:
2078:  	       value8 = DEVICE_A_OK;
2079:  	       shelley_sockets_write (sock, &value8, 1);
2080:  	       break;
2081:  	       
2082:  	    case TTT_VISION_SET_RESOLUTION:
2083:  	       shelley_sockets_read (sock, (char*) &value32, sizeof (int32));
2084:  	       (*img).width = HSIZE = value32;
2085:  	       shelley_sockets_read (sock, (char*) &value32, sizeof (int32));
2086:  	       (*img).height = VSIZE = value32;
2087:  	       (*img).maxVal = MVAL = 255;
2088:  	       
2089:  	       /*we need to use a buffer the same size as the data being read
2090:  		 in since the PGMImage type declares the type as  MAX by MAX
2091:  		 in size.  Without this buffer the data could not be unpacked
2092:  		 from the socket stream and into the PGMImage in the correct
2093:  		 way.*/
2094:  	       data = (RGB_INT*)malloc(HSIZE * VSIZE * sizeof(RGB_INT));
2095:  	       
2096:  	       return_val = 1;
2097:  	       shelley_sockets_write (sock, (char*) &return_val,
2098:  				      sizeof (int32));
2099:  	       value8 = 99; /*the admin expects something always returned*/
2100:  	       shelley_sockets_write (sock, (char*) &value8, sizeof (int8));
2101:  	       break;
2102:  
2103:  	    case TTT_VISION_FIND_TTT:
2104:  	       /*read the data into a region of memory before placing
2105:  		it into the image data in the correct way.*/
2106:  	       value32 = shelley_sockets_read (sock, (unsigned char*) data,
2107:  					       HSIZE * VSIZE * 3);
2108:  	       
2109:  	       pthread_mutex_lock(&images_mutex);
2110:  
2111:  	       /*repack the data into the correct storage size.*/
2112:  	       for (y = 0; y <  VSIZE; y++)
2113:  	       {
2114:  		  for (x = 0; x <  HSIZE; x++)
2115:  		  {
2116:  		     (*img).data[y][x].red = data[(y * HSIZE) + x].red;
2117:  		     (*img).data[y][x].green = data[(y * HSIZE) + x].green;
2118:  		     (*img).data[y][x].blue = data[(y * HSIZE) + x].blue;
2119:  		  }
2120:  	       }
2121:        	       pthread_mutex_unlock(&images_mutex);
2122:  
2123:  	       img_cur = img;
2124:  
2125:  	       /*do the timing stuff*/
2126:  	       gettimeofday(&t0, NULL);
2127:  	       menu(PIECES); /*using the menu function is the lazy way*/
2128:  	       gettimeofday(&t1, NULL);
2129:  	       do_time(t0, t1, "total");
2130:  	       
2131:  	       value32 = 9 * sizeof(unsigned char);
2132:  
2133:  	       shelley_sockets_write (sock, (char*) &value32, sizeof(int32));
2134:  	       shelley_sockets_write (sock, (char*) pieces, value32);
2135:  	       break;
2136:  	       
2137:  	    case ADMIN_DISCONNECT_DEVICE:
2138:  	       /* we're supposed to quit, now */
2139:  	       exit (0);
2140:  	       break;
2141:  	       
2142:  	    default:
2143:  	       printf("TTT_VISION: unknown command: %d\n", command);
2144:  	 }
2145:        }
2146:     }
2147:     pthread_exit(NULL);
2148:     return NULL; /*compilers complain otherwise*/
2149:  }
2150:  
2151:  
2152:  /* =================================================================
2153:   * init() & parse_command_line()
2154:   * initalize none-OpenGL related things.
2155:   * ----------------------------------------------------------------- */
2156:  
2157:  void usage(const char* name)
2158:  {
2159:     printf("Usage: %s < [-h < hostname > -p < port_number >] [-f < filename >] > [-b]\n"
2160:  	  "  -h     - remote host\n"
2161:  	  "  -p     - port number\n"
2162:  	  "  -f     - file name\n"
2163:  	  "  -b     - buffer\n"
2164:  	  "\n", name);
2165:  }
2166:  
2167:  /*set global flag variables from things specified at commandline.*/
2168:  /*return the filename specified, if none specified exit().*/
2169:  void parse_command_line(int argc, const char** argv)
2170:  {
2171:    /*int c;*/
2172:     hostname = (char*)malloc (1024);
2173:     port_number = SIE_DEFAULT_PORT_NUMBER;/*needs to be default sie port*/
2174:     PGMfileName = NULL;
2175:     is_buffered = FALSE;
2176:  
2177:     strcpy(hostname, "localhost");
2178:  
2179:     if(argc == 2)
2180:        PGMfileName = (char*)argv[1];
2181:     
2182:     /*parse the command line*/
2183:     /*while((c = getopt(argc, argv, GETOPTARGS)) != EOF)
2184:     {
2185:        switch(c)
2186:        {
2187:  	 case 'h':
2188:  	    strcpy(hostname, optarg);
2189:  	    is_buffered = TRUE;
2190:  	    break;
2191:  
2192:  	 case 'p':
2193:  	    port_number = atoi(optarg);
2194:  	    break;
2195:  
2196:  	 case 'f':
2197:  	    PGMfileName = optarg;
2198:  	    break;
2199:  
2200:  	 case 'b':
2201:  	    is_buffered = TRUE;
2202:  	    break;
2203:  
2204:  	 default:
2205:  	    usage(argv[0]);
2206:  	    exit(1);
2207:        }
2208:        }*/
2209:     
2210:  }
2211:  
2212:  void init_image(PGMImage *image)
2213:  {
2214:     memset(image, 0, sizeof(PGMImage));
2215:     (*image).width = HSIZE;
2216:     (*image).height = VSIZE;
2217:     (*image).maxVal = MVAL;
2218:  }
2219:  
2220:  
2221:  void init (int argc, const char **argv)
2222:  {
2223:     int threaded;
2224:     
2225:     /*parse the command line into globals*/
2226:     parse_command_line(argc, argv);   
2227:  
2228:     
2229:     
2230:  /*
2231:   * Read in image file. - note: sets our global values, too.
2232:   * ----------------------------------------------------------------- */
2233:  
2234:     /*allocate memory for original image*/
2235:     img_original = (PGMImage*) malloc(sizeof(PGMImage));
2236:     memset(img_original, 0, sizeof(PGMImage));
2237:     img_pers_corr = (PGMImage*) malloc(sizeof(PGMImage));
2238:     memset(img_pers_corr, 0, sizeof(PGMImage));
2239:     img_grayscale = (PGMImage*) malloc(sizeof(PGMImage));
2240:     memset(img_grayscale, 0, sizeof(PGMImage));
2241:     img_moravec = (PGMImage*) malloc(sizeof(PGMImage));
2242:     memset(img_moravec, 0, sizeof(PGMImage));
2243:  
2244:     if(argc == 3)
2245:     {
2246:        socket_num = admin_connect(argc, argv);
2247:        threaded = pthread_create(&conn_thread, NULL, sock_com,
2248:  				(void*)img_original);
2249:        sleep(1); /*give new thread time to make copy of sock_number*/
2250:        /*set the filename so the text in the window says the hostname*/
2251:        PGMfileName = (char*)malloc(1024); /*this better be enough*/
2252:        strcpy(PGMfileName, hostname);
2253:  
2254:        img_cur = img_original; /*VERY IMPORTANT to set these*/
2255:        HSIZE = 100;
2256:        VSIZE = 100;
2257:        MVAL = 255;
2258:        /*
2259:        (*img_original).width = HSIZE;
2260:        (*img_original).height = VSIZE;
2261:        (*img_original).maxVal = MVAL;
2262:  
2263:        (*img_pers_corr).width = HSIZE;
2264:        (*img_pers_corr).height = VSIZE;
2265:        (*img_pers_corr).maxVal = MVAL;
2266:        
2267:        (*img_grayscale).width = HSIZE;
2268:        (*img_grayscale).height = VSIZE;
2269:        (*img_grayscale).maxVal = MVAL;
2270:  
2271:        (*img_moravec).width = HSIZE;
2272:        (*img_moravec).height = VSIZE;
2273:        (*img_moravec).maxVal = MVAL;
2274:        */
2275:     }
2276:     else if(argc == 2)
2277:     {
2278:        getPGMfile(PGMfileName, img_original);
2279:        HSIZE = (*img_original).width;
2280:        VSIZE = (*img_original).height;
2281:        MVAL = (*img_original).maxVal;
2282:  
2283:        img_cur = img_original; /*VERY IMPORTANT to set this*/
2284:  
2285:        /*allocate memory for pers. corr. image*/
2286:     
2287:        init_image(img_pers_corr);
2288:        init_image(img_grayscale);
2289:        init_image(img_moravec);
2290:     }
2291:     else
2292:     {
2293:        usage(argv[0]);
2294:        exit(1);
2295:     }
2296:  
2297:     all_objects = (object*) malloc(sizeof(object) * MAX_CHAINS);
2298:     memset(all_objects, 0, sizeof(object) * MAX_CHAINS);
2299:  
2300:     memset(points, 0, sizeof(points));
2301:     memset(pieces, 0, sizeof(pieces));
2302:  }
2303:  
2304:  
2305:  int main(int argc, const char** argv)
2306:  {
2307:     int WindowID; /*unique window id, there is only one used*/
2308:   
2309:  /*  
2310:   * Call our init function to define non-OpenGL related things.
2311:   * ----------------------------------------------------------------- */
2312:     init (argc, argv);
2313:  
2314:  /*
2315:   * Initialize the glut package.
2316:   * ----------------------------------------------------------------- */
2317:  
2318:     glutInit(&argc, (char**)argv);
2319:     glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
2320:  
2321:  /*           
2322:   * Define a new window (its size, position and title).
2323:   * ----------------------------------------------------------------- */
2324:     /*glutInitWindowSize (HSIZE, VSIZE);*/
2325:     glutInitWindowPosition (10, 10); 
2326:     WindowID = glutCreateWindow (PGMfileName);
2327:     glutSetWindow(WindowID);
2328:     glutDisplayFunc(color);
2329:     
2330:  /*
2331:   * select clearing color - white
2332:   */
2333:  
2334:     glClearColor (1.0, 1.0, 1.0, 0.0);
2335:  
2336:  
2337:  /*
2338:   * initialize viewport values
2339:   */
2340:     glMatrixMode(GL_PROJECTION);
2341:     glLoadIdentity();
2342:     glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
2343:  
2344:     /*add menus*/
2345:     if(argc == 2)
2346:     {
2347:        glutCreateMenu(menu);
2348:        glutAddMenuEntry("restart", RESTART);
2349:        glutAddMenuEntry("perspective correction", PERS_CORR);
2350:        glutAddMenuEntry("color to grayscale", COLOR_TO_GRAY);
2351:        glutAddMenuEntry("Moravec filter", MORAVEC);
2352:        glutAddMenuEntry("detect edges", EDGES);
2353:        glutAddMenuEntry("detect corners", CORNERS);
2354:        glutAddMenuEntry("detect game", GAME);
2355:        glutAddMenuEntry("detect pieces", PIECES);
2356:        glutAddMenuEntry("toggle buffering", BUFFERS);
2357:        glutAttachMenu(GLUT_RIGHT_BUTTON);
2358:     }
2359:     else
2360:        glutIdleFunc(sie);
2361:     
2362:     glutMouseFunc(mouse);
2363:     glutKeyboardFunc(keyboard);
2364:  
2365:     glutMainLoop();
2366:  
2367:  /*  
2368:   * When we reach here, we've left the event loop and are ready to
2369:   * exit.
2370:   * ----------------------------------------------------------------- */
2371:     return 0;
2372:  }
2373: