/****************************************************************************e CHAREC-2: This is an update of Charec using a more sophisticated algorithm and it can also decode a number of characters at the same time. However it can't do the whole ASCII alphabet and its still rubbish compared to commercial systems. Originally written by Neil, December 2005 to January 2006 Version: 20060107 *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #define WIN_WIDTH 700 #define WIN_HEIGHT 150 #define NUM_CHARS 5 #define NUM_EVENTS 8 #define MIN_DIST_DIV 40 #define ALLOC 5 #define MAX_REC_GROUPS 10 #define MAX_REC_LINES 6 #define MIN_CH_WIDTH_DIV 2 #define MIN_CH_HEIGHT_DIV 4 #define MIN_SHORT_LINE_DIV 5 #define NUM_STORED_ANGLES 3 #define NEW_LINE_ANG1 60 // Turn angle when drawn line #define NEW_LINE_ANG2 80 // Turn angle when drawn semi, circle or S #define DECODE_DELAY 300000 #define DEGS_PER_RADIAN 57.29578 void parseCmdLine(int argc, char **argv); void XInit(); void init(); void run(); void buttonPress(); void buttonRelease(); void keyPress(); void keyRelease(); bool motionNotify(); void setCharacter(); void decodeChars(); void totalReset(); void lineReset(); void partialLineReset(); void drawDividers(); void setMinMax(); void clearStoredAngles(); void storeAngle(double ang); double angle(int fx, int fy, int tx, int ty); double angleDiff(double ang, double pang); double getTotalStoredAngle(); // Vars Window win; Display *display; XEvent event; GC gcblack; GC gcwhite; GC gcblue; GC gcgreen; GC gcred; Atom delete_atom; bool button; bool do_uppercase; bool show_debug; int white; int black; int win_width; int win_height; int mouse_x,mouse_y; int prev_x,prev_y; int start_x,start_y; int minx, miny; int maxx, maxy; int min_dist; int num_chars; int chnum; int char_width; int min_ch_width; int min_ch_height; int linetype; int segments; int click_cnt; int next_angle_pos; double ang; double prev_ang; double total_ang; double dist; double stored_ang[NUM_STORED_ANGLES]; char *output; enum { TYPE_NOTSET, TYPE_LINE, TYPE_SEMI, TYPE_CIRCLE, TYPE_S }; /******************************** Squares layout is as follows: 1 2 3 4 5 6 7 8 9 ********************************/ enum { SQ_1 = 1, SQ_2 = 1 << 1, SQ_3 = 1 << 2, SQ_4 = 1 << 3, SQ_5 = 1 << 4, SQ_6 = 1 << 5, SQ_7 = 1 << 6, SQ_8 = 1 << 7, SQ_9 = 1 << 8, SQ_ALL = 0xFFF }; ///////////////////////// CHARACTER LINE DATA ETC ////////////////////////// struct char_template { int line_groups; int lines_in_group[MAX_REC_GROUPS]; struct { int type; uint16_t start_mask; uint16_t end_mask; } data[MAX_REC_GROUPS][MAX_REC_LINES]; } *char_list[256]; //// LETTERS //// struct char_template char_A = { 5, { 3,3,3,3,3 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_1 | SQ_2, SQ_9 }, { TYPE_LINE, SQ_4 | SQ_7, SQ_6 } }, { { TYPE_LINE, SQ_2 | SQ_3, SQ_7 }, { TYPE_LINE, SQ_2 | SQ_3, SQ_9 }, { TYPE_LINE, SQ_4 | SQ_7, SQ_6 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_4 | SQ_7 }, { TYPE_LINE, SQ_1 | SQ_2, SQ_8 | SQ_9 }, { TYPE_LINE, SQ_4 | SQ_7, SQ_9 } }, { { TYPE_LINE, SQ_2 | SQ_3, SQ_7 }, { TYPE_LINE, SQ_2 | SQ_3, SQ_9 }, { TYPE_LINE, SQ_4 | SQ_7, SQ_9 } }, { { TYPE_LINE, SQ_2 | SQ_3, SQ_4 | SQ_7 }, { TYPE_LINE, SQ_2 | SQ_3, SQ_9 }, { TYPE_LINE, SQ_4 | SQ_7, SQ_9 } } } }; struct char_template char_B = { 3, { 2,3,4 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_SEMI, SQ_4, SQ_7 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_SEMI, SQ_1, SQ_4 }, { TYPE_SEMI, SQ_4, SQ_7 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_6 }, { TYPE_LINE, SQ_6, SQ_4 } } } }; struct char_template char_C = { 2, { 1, 3 }, { { { TYPE_SEMI, SQ_3 | SQ_2, SQ_9 | SQ_8 } }, { { TYPE_LINE, SQ_3 | SQ_2, SQ_1 }, { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_8 | SQ_9 } } } }; struct char_template char_D = { 3, { 2,2,4 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_SEMI, SQ_1, SQ_7 } }, { { TYPE_LINE, SQ_3 | SQ_2, SQ_9 }, { TYPE_SEMI, SQ_6, SQ_9 } }, { { TYPE_LINE, SQ_3 | SQ_2, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 }, { TYPE_LINE, SQ_7, SQ_4 }, { TYPE_LINE, SQ_4, SQ_6 } } } }; struct char_template char_E = { 4, { 2,4,2,3 }, { { { TYPE_SEMI, SQ_3, SQ_6 }, { TYPE_SEMI, SQ_6, SQ_9 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_1 | SQ_2, SQ_3 }, { TYPE_LINE, SQ_4, SQ_5 | SQ_6 }, { TYPE_LINE, SQ_7, SQ_8 | SQ_9 } }, { { TYPE_CIRCLE, SQ_6, SQ_ALL }, { TYPE_LINE, SQ_4 | SQ_5, SQ_6 } }, { { TYPE_LINE, SQ_1, SQ_2 | SQ_3 }, { TYPE_SEMI, SQ_1, SQ_8 | SQ_9 }, { TYPE_LINE, SQ_4, SQ_5 | SQ_6 } } } }; struct char_template char_F = { 1, { 3 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_1 | SQ_2, SQ_3 }, { TYPE_LINE, SQ_4, SQ_5 | SQ_6 } } } }; struct char_template char_G = { 4, { 2,3,4,5 }, { { { TYPE_SEMI, SQ_3, SQ_6 | SQ_9 }, { TYPE_LINE, SQ_6 | SQ_9, SQ_5 | SQ_4 } }, { { TYPE_SEMI, SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_6 }, { TYPE_LINE, SQ_6 | SQ_9, SQ_5 | SQ_4 } }, { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_6 } }, { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_6 }, { TYPE_LINE, SQ_6, SQ_5 } } } }; struct char_template char_H = { 3, { 3,2,3 }, { { { TYPE_LINE, SQ_1, SQ_7 | SQ_8 }, { TYPE_LINE, SQ_3, SQ_8 | SQ_9 }, { TYPE_LINE, SQ_4, SQ_6 } }, { { TYPE_LINE, SQ_1, SQ_7 | SQ_8 }, { TYPE_SEMI, SQ_7 | SQ_8, SQ_9 } }, { { TYPE_LINE, SQ_1, SQ_7 | SQ_8 }, { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_6, SQ_9 } } } }; struct char_template char_I = { 2, { 1,3 }, { { { TYPE_LINE, SQ_2, SQ_8 } }, { { TYPE_LINE, SQ_2, SQ_8 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_J = { 2, { 2,3 }, { { { TYPE_SEMI, SQ_2 | SQ_3, SQ_7 }, { TYPE_LINE, SQ_1, SQ_3 } }, { { TYPE_LINE, SQ_2 | SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_4 | SQ_7 }, { TYPE_LINE, SQ_1, SQ_3 } } } }; struct char_template char_K = { 1, { 3 }, { { { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_4, SQ_9 }, { TYPE_LINE, SQ_4, SQ_3 | SQ_6 } } } }; struct char_template char_L = { 1, { 2 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_M = { 2, { 4,4 }, { { { TYPE_LINE, SQ_7, SQ_1 | SQ_2 }, { TYPE_LINE, SQ_1 | SQ_2, SQ_5 | SQ_8 }, { TYPE_LINE, SQ_5 | SQ_8 , SQ_2 | SQ_3 }, { TYPE_LINE, SQ_2 | SQ_3, SQ_9 } }, { { TYPE_LINE, SQ_7, SQ_1 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_2, SQ_5 | SQ_8 } } } }; struct char_template char_N = { 3, { 1,3,3 }, { { { TYPE_SEMI, SQ_7, SQ_9 } }, { { TYPE_LINE, SQ_7, SQ_1 }, { TYPE_LINE, SQ_1, SQ_9 }, { TYPE_LINE, SQ_9, SQ_3 } }, { { TYPE_LINE, SQ_7, SQ_1 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 } } } }; struct char_template char_O = { 2, { 1,4 }, { { { TYPE_CIRCLE, SQ_ALL, SQ_ALL } }, { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 }, { TYPE_LINE, SQ_7, SQ_1 } } } }; struct char_template char_P = { 2, { 2,4 }, { { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_SEMI, SQ_1, SQ_4 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_6 }, { TYPE_LINE, SQ_6, SQ_4 } } } }; struct char_template char_Q = { 1, { 2 }, { { { TYPE_CIRCLE, SQ_ALL, SQ_ALL }, { TYPE_LINE, SQ_1 | SQ_2 | SQ_4 | SQ_5, SQ_9 } } } }; struct char_template char_R = { 3, { 2,3,5 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_1, SQ_7 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_SEMI, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_8 | SQ_9 } }, { { TYPE_LINE, SQ_1 | SQ_2, SQ_7 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_6 }, { TYPE_LINE, SQ_6, SQ_4 }, { TYPE_LINE, SQ_4, SQ_8 | SQ_9 } } } }; struct char_template char_S = { 2, { 1,5 }, { { { TYPE_S, SQ_ALL, SQ_ALL } }, { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_6, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 } } } }; struct char_template char_T = { 3, { 2,2,2 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_2, SQ_7 | SQ_8 | SQ_9 } }, { { TYPE_LINE, SQ_4, SQ_3 }, { TYPE_LINE, SQ_2, SQ_7 | SQ_8 | SQ_9 } }, { { TYPE_LINE, SQ_1, SQ_6 }, { TYPE_LINE, SQ_2, SQ_7 | SQ_8 | SQ_9 } } } }; struct char_template char_U = { 2, { 1,3 }, { { { TYPE_SEMI, SQ_1, SQ_3 } }, { { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_3 } } } }; struct char_template char_V = { 2, { 2,2 }, { { { TYPE_LINE, SQ_1, SQ_7 | SQ_8 }, { TYPE_LINE, SQ_3, SQ_7 | SQ_8 } }, { { TYPE_LINE, SQ_1, SQ_8 | SQ_9 }, { TYPE_LINE, SQ_3, SQ_8 | SQ_9 } } } }; struct char_template char_W = { 2, { 4,4 }, { { { TYPE_LINE, SQ_1, SQ_7 | SQ_8 }, { TYPE_LINE, SQ_7| SQ_8 , SQ_2 | SQ_5 }, { TYPE_LINE, SQ_2 | SQ_5, SQ_8 | SQ_9 }, { TYPE_LINE, SQ_8 | SQ_9, SQ_3 } }, { { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_3 }, { TYPE_LINE, SQ_8, SQ_5 | SQ_2 } } } }; struct char_template char_X = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_9 }, { TYPE_LINE, SQ_3, SQ_7 } } } }; struct char_template char_Y = { 2, { 2, 3 }, { { { TYPE_SEMI, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 } }, { { TYPE_LINE, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_3, SQ_9 } } } }; struct char_template char_Z = { 1, { 3 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; //// NUMBERS //// struct char_template char_0 = { 2, { 2,5 }, { { { TYPE_CIRCLE, SQ_ALL, SQ_ALL }, { TYPE_LINE, SQ_3, SQ_7 } }, { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 }, { TYPE_LINE, SQ_7, SQ_1 }, { TYPE_LINE, SQ_3, SQ_7 } } } }; struct char_template char_1 = { 2, { 2,3 }, { { { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_3, SQ_4 } }, { { TYPE_LINE, SQ_2, SQ_8 }, { TYPE_LINE, SQ_2, SQ_4 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_2 = { 2, { 2,5 }, { { { TYPE_SEMI, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 } }, { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_6 }, { TYPE_LINE, SQ_6, SQ_4 }, { TYPE_LINE, SQ_4, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_3 = { 2, { 2, 4 }, { { { TYPE_SEMI, SQ_1, SQ_4 }, { TYPE_SEMI, SQ_4, SQ_7 } }, { { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_4 | SQ_5, SQ_6 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_4 = { 1, { 3 }, { { { TYPE_LINE, SQ_2, SQ_8 }, { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_4, SQ_1 | SQ_2 } } } }; struct char_template char_5 = { 1, { 3 }, { { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_4 }, { TYPE_SEMI, SQ_4, SQ_7 } } } }; struct char_template char_6 = { 2, { 5,3 }, { { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_7, SQ_9 }, { TYPE_LINE, SQ_9, SQ_6 }, { TYPE_LINE, SQ_6, SQ_4 } }, { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_SEMI, SQ_7, SQ_4 } } } }; struct char_template char_7 = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_7 | SQ_8 | SQ_9 } } } }; struct char_template char_8 = { 5, { 5 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 }, { TYPE_LINE, SQ_7, SQ_1 }, { TYPE_LINE, SQ_4, SQ_6 } } } }; struct char_template char_9 = { 2, { 2, 4 }, { { { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_SEMI, SQ_3, SQ_6 } }, { { TYPE_LINE, SQ_3, SQ_9 }, { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_6 } } } }; /// MISC ASCII CHARS /// struct char_template char_minus = { 2, { 1,1 }, { { { TYPE_LINE, SQ_1 | SQ_4 | SQ_7, SQ_6 } }, { { TYPE_LINE, SQ_4, SQ_3 | SQ_6 | SQ_9 } } } }; struct char_template char_plus = { 1, { 2 }, { { { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_2, SQ_8 } } } }; struct char_template char_equals = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_3 }, { TYPE_LINE, SQ_7, SQ_9 } } } }; struct char_template char_star = { 1, { 4 }, { { { TYPE_LINE, SQ_1, SQ_9 }, { TYPE_LINE, SQ_3, SQ_7 }, { TYPE_LINE, SQ_2, SQ_8 }, { TYPE_LINE, SQ_4, SQ_6 } } } }; struct char_template char_backward_slash = { 1, { 1 }, { { { TYPE_LINE, SQ_1, SQ_9 } } } }; struct char_template char_forward_slash = { 1, { 1 }, { { { TYPE_LINE, SQ_3, SQ_7 } } } }; struct char_template char_dollar = { 2, { 2,6 }, { { { TYPE_S, SQ_ALL, SQ_ALL }, { TYPE_LINE, SQ_2, SQ_8 } }, { { TYPE_LINE, SQ_3, SQ_1 }, { TYPE_LINE, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_6 }, { TYPE_LINE, SQ_6, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 }, { TYPE_LINE, SQ_2, SQ_8 } } }, }; struct char_template char_hat = { 1, { 2 }, { { { TYPE_LINE, SQ_7, SQ_2 }, { TYPE_LINE, SQ_2, SQ_9 } } } }; struct char_template char_comma = { 1, { 2 }, { { { TYPE_LINE, SQ_3, SQ_3 | SQ_6 }, { TYPE_LINE, SQ_6, SQ_7 } } } }; struct char_template char_question = { 2, { 2, 2 }, { { { TYPE_SEMI, SQ_1, SQ_5 }, { TYPE_LINE, SQ_5, SQ_8 } }, { { TYPE_SEMI, SQ_1, SQ_4 }, { TYPE_LINE, SQ_4, SQ_7 } } } }; struct char_template char_double_quotes = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_3, SQ_9 } } } }; struct char_template char_greater = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_6 }, { TYPE_LINE, SQ_6, SQ_7 } } } }; struct char_template char_less = { 1, { 2 }, { { { TYPE_LINE, SQ_3, SQ_4 }, { TYPE_LINE, SQ_4, SQ_9 } } } }; //// CONTROL CHARS //// struct char_template char_newline = { 2, { 1, 2 }, { { { TYPE_SEMI, SQ_2 | SQ_3, SQ_7 } }, { { TYPE_LINE, SQ_2 | SQ_3, SQ_9 }, { TYPE_LINE, SQ_9, SQ_7 } } } }; struct char_template char_backspace = { 1, { 2 }, { { { TYPE_LINE, SQ_1, SQ_7 }, { TYPE_LINE, SQ_4, SQ_6 } } } }; // List of short lines that can be joined uint16_t joinlist[512]; // 512 = 2^9 // List of duplicate line masks uint16_t duplist[512]; // Count of bits for a given 3 bit nibble 000 (0) -> 111 (7) u_char bitcnt[8] = { 0,1,1,2,1,2,2,3 }; //////////////////////////////// LINE CLASS //////////////////////////////// class cl_line { public: cl_line( int sx, int sy, int ex, int ey, int ty); int type; int startx; int starty; int endx; int endy; uint16_t sqmask; bool inactive; bool matched; }; /*** Constructor ***/ cl_line::cl_line( int sx, int sy, int ex, int ey, int ty) { startx = sx; starty = sy; endx = ex; endy = ey; type = ty; sqmask = 0; inactive = false; matched = false; } ////////////////////////////// CHARACTER CLASS ////////////////////////////// class cl_charac { public: cl_charac(int cn); void reset(); void add(cl_line *line); void drawGrid(); void setMinBoundingBox(); bool decode(); int recognise(); void colourSquare(GC gc); int getSquare(int x, int y); int cnum; int xleft; int ch_minx,ch_miny; int ch_maxx,ch_maxy; bool uppercase; cl_line **lines; int num_lines; int num_valid_lines; int num_allocated; } **charac; /*** Constructor ***/ cl_charac::cl_charac(int cn) { cnum = cn; xleft = (win_width / num_chars) * cnum; reset(); } /*** Reset everything ***/ void cl_charac::reset() { int i; if (num_lines) { for(i=0;i < num_lines;++i) delete lines[i]; free(lines); } lines = NULL; num_lines = 0; num_allocated = 0; ch_maxx = ch_maxy = -1; ch_minx = win_width; ch_miny = win_height; uppercase = false; } /*** Add a line to this character ***/ void cl_charac::add(cl_line *line) { if (num_lines >= num_allocated) { num_allocated += ALLOC; lines = (cl_line **)realloc(lines,sizeof(lines) * num_allocated); } lines[num_lines] = line; num_lines++; if (do_uppercase) uppercase = true; // Change bounding box if required if (minx < ch_minx) ch_minx = minx; if (maxx > ch_maxx) ch_maxx = maxx; if (miny < ch_miny) ch_miny = miny; if (maxy > ch_maxy) ch_maxy = maxy; } /*** If box is too narrow then the start and end squares for single vertical or horizontal lines (I and -) will be all over the place ***/ void cl_charac::setMinBoundingBox() { int w,h,add; if (!num_lines) return; if ((w = ch_maxx - ch_minx) < min_ch_width) { add = (min_ch_width - w) / 2; ch_minx -= add; ch_maxx += add; } if ((h = ch_maxy - ch_miny) < min_ch_height) { add = (min_ch_height - h) / 2; ch_miny -= add; ch_maxy += add; } } /*** Draw the character grid ***/ void cl_charac::drawGrid() { int x3,y3; if (!num_lines) return; // Main box XDrawLine(display,win,gcgreen,ch_minx,ch_miny,ch_maxx,ch_miny); XDrawLine(display,win,gcgreen,ch_maxx,ch_miny,ch_maxx,ch_maxy); XDrawLine(display,win,gcgreen,ch_maxx,ch_maxy,ch_minx,ch_maxy); XDrawLine(display,win,gcgreen,ch_minx,ch_maxy,ch_minx,ch_miny); // Internal grid x3 = (ch_maxx - ch_minx) / 3; y3 = (ch_maxy - ch_miny) / 3; XDrawLine(display,win,gcgreen,ch_minx+x3,ch_miny,ch_minx+x3,ch_maxy); XDrawLine(display,win,gcgreen,ch_maxx-x3,ch_miny,ch_maxx-x3,ch_maxy); XDrawLine(display,win,gcgreen,ch_minx,ch_miny+y3,ch_maxx,ch_miny+y3); XDrawLine(display,win,gcgreen,ch_minx,ch_maxy-y3,ch_maxx,ch_maxy-y3); } /*** Decode the character then recognise ***/ bool cl_charac::decode() { uint16_t mask,mask2; int sq1,sq2,recnum,cnt; int i,j; double line_len; double min_line_len; // '2' to get average of X and Y widths min_line_len = (double)((ch_maxx - ch_minx) + (ch_maxy - ch_miny)) / (2 * MIN_SHORT_LINE_DIV); if (show_debug) printf("\nDECODING CHARACTER %d (%d lines, min length %f)...\n", cnum,num_lines,min_line_len); num_valid_lines = num_lines; // Remove short lines & get and set squares of the remaining ones for(i=0;i < num_lines;++i) { // Can only check straight lines for too short as semis & circles // start & end points don't correspond to their size if (lines[i]->type == TYPE_LINE) { line_len = hypot(lines[i]->endx - lines[i]->startx, lines[i]->endy - lines[i]->starty); if (line_len < min_line_len) { if (show_debug) printf(" Line %d is too short. Length = %f\n", i,line_len); lines[i]->inactive = true; num_valid_lines--; continue; } if (show_debug) printf(" Line %d length = %f\n",i,line_len); } sq1 = getSquare(lines[i]->startx,lines[i]->starty); sq2 = getSquare(lines[i]->endx,lines[i]->endy); if (show_debug) printf(" Line %d start square = %d, end square = %d\n", i,sq1 + 1,sq2 + 1); lines[i]->sqmask = ((1 << sq1) | (1 << sq2)); } // Should never happen but just in case... if (!num_valid_lines) { if (show_debug) puts("NO LINES LEFT!"); return false; } // Join 2 short lines that can be made into 1 long line. Only makes sense to // join lines, not semis or other types. for(i=0;i < num_lines-1;++i) { if (lines[i]->inactive || lines[i]->type != TYPE_LINE || !joinlist[lines[i]->sqmask]) continue; for(j=i;j < num_lines;++j) { if (j == i || lines[j]->inactive || lines[j]->type != TYPE_LINE) continue; mask = lines[i]->sqmask; mask2 = lines[j]->sqmask; if (joinlist[mask] == mask2) { if (show_debug) printf(" Lines %d and %d will be joined\n",i,j); lines[i]->sqmask = mask ^ mask2; // XOR lines[j]->inactive = true; num_valid_lines--; break; } } } // Remove duplicate lines - each full line has 3 possible dups; Itself, // top (left) half and bottom (right) half for(i=0;i < num_lines;++i) { if (lines[i]->inactive || !duplist[lines[i]->sqmask]) continue; // Go through all the lines again since a short line may have come // before a longer line and we want to find the shorter line as the // duplicate, not the longer one. for(j=0;j < num_lines;++j) { if (j == i || lines[j]->inactive || lines[i]->type != lines[j]->type) continue; if (lines[i]->sqmask == lines[j]->sqmask) goto DUP; // We use max 9 bits in mask. Divide into 3 of 3 bits and // count bits set in each. If more than 2 bits set in total // then we have duplicate mask = duplist[lines[i]->sqmask] & lines[j]->sqmask; cnt = bitcnt[mask & 0x7]; cnt += bitcnt[(mask >> 3) & 0x7]; cnt += bitcnt[(mask >> 6) & 0x7]; if (cnt > 1) { DUP: if (show_debug) printf(" Line %d is a duplicate\n",j); lines[j]->inactive = true; num_valid_lines--; } } } // Got mask set , now attempt to recognise if (show_debug) { printf(" Working with the following lines:"); for(i=0;i < num_lines;++i) { if (!lines[i]->inactive) printf(" %d (%03X),",i,lines[i]->sqmask); } puts("\b \n Initial recognise..."); } if ((recnum = recognise()) == -1) { // Didn't work , so now try adding extra bits to a line at a time if (show_debug) puts(" Bitwise modification recognise..."); // Go through all lines for this character object for(i=0;i < num_lines;++i) { mask = lines[i]->sqmask; // Or in each bit in turn for(j=0;j < 9;++j) { lines[i]->sqmask |= (1 << j); if ((recnum = recognise()) != -1) goto FOUND; lines[i]->sqmask = mask; } } // No luck. Flag as red and give up. if (show_debug) puts("UNKNOWN"); colourSquare(gcred); reset(); return false; } // Well bugger me, it worked! FOUND: output[cnum] = uppercase ? toupper(recnum) : recnum; if (show_debug) printf("RECOGNISED: %c\n",output[cnum]); colourSquare(gcgreen); return true; } /*** Recognise this character object ***/ int cl_charac::recognise() { int i,j,k,l; int cnt; // Go through all data characters for(i=0;i < 256;++i) { // If no layout defined for this ascii code just continue if (!char_list[i]) continue; // Go through all groups in data character for(j=0;j < char_list[i]->line_groups;++j) { // Character object must have same line count as data // character group if (num_valid_lines != char_list[i]->lines_in_group[j]) continue; // Go through all lines in data character group for(k=0,cnt=0;k < char_list[i]->lines_in_group[j];++k) { // Go through all lines in character object for(l=0;l < num_lines;++l) { if (!lines[l]->inactive && !lines[l]->matched && lines[l]->type == char_list[i]->data[j][k].type && (lines[l]->sqmask & char_list[i]->data[j][k].start_mask) != 0 && (lines[l]->sqmask & char_list[i]->data[j][k].end_mask) != 0) { lines[l]->matched = true; cnt++; break; } } } if (cnt == num_valid_lines) return i; for(l=0;l < num_lines;++l) lines[l]->matched = false; } } return -1; } /*** Colour the square after a decode ***/ void cl_charac::colourSquare(GC gc) { XFillRectangle(display,win,gc,xleft+1,0,char_width-2,win_height); XFlush(display); usleep(30000); XFillRectangle(display,win,gcblack,xleft+1,0,char_width-2,win_height); XFlush(display); } /*** Get the square position for the given x,y. Squares go from 1 -> 9 but we return 0 -> 8 here since they correspond to bit positions shifted by the square number - 1 ***/ int cl_charac::getSquare(int x, int y) { int w,h,gx,gy; int w3,h3; x -= ch_minx; y -= ch_miny; w = ch_maxx - ch_minx; h = ch_maxy - ch_miny; if (!(w3 = w / 3)) gx = 1; else { gx = x / w3; if (gx > 2) gx = 2; } if (!(h3 = h / 3)) gy = 1; else { gy = y / h3; if (gy > 2) gy = 2; } return gx + gy * 3; } /////////////////////////////////// START ///////////////////////////////// int main(int argc, char **argv) { parseCmdLine(argc,argv); XInit(); init(); run(); return 0; } /*** Parse the command line values ***/ void parseCmdLine(int argc, char **argv) { const char *opt[] = { "w", "h", "chars", "debug" }; enum { OPT_WIDTH, OPT_HEIGHT, OPT_CHARS, OPT_DEBUG, OPT_END }; int i,o,val; win_width = WIN_WIDTH; win_height = WIN_HEIGHT; num_chars = NUM_CHARS; show_debug = false; for(i=1;i < argc;i+=2) { if (argv[i][0] != '-') goto USAGE; for(o=0;o != OPT_END;++o) if (!strcasecmp(opt[o],argv[i]+1)) break; // Options with no arguments switch(o) { case OPT_DEBUG: --i; show_debug = true; continue; } val = atoi(argv[i+1]); // Options with arguments following switch(o) { case OPT_WIDTH: if (val < 0) goto USAGE; win_width = val; break; case OPT_HEIGHT: if (val < 0) goto USAGE; win_height = val; break; case OPT_CHARS: if (val < 1) goto USAGE; num_chars = val; break; default: goto USAGE; } } return; USAGE: printf("Usage: %s\n" " -w : Default = %d\n" " -h : Default = %d\n" " -chars <# of character slots> : Default = %d\n" " -debug\n", argv[0], WIN_WIDTH, WIN_HEIGHT, NUM_CHARS); exit(0); } /*** Init X windows stuff ***/ void XInit() { Window rootwin; XSizeHints size_hints; XGCValues gcvals; XColor col,unused; XTextProperty win_name; Colormap cmap; int screen; char *win_name_str = "CHAREC-2"; if (!(display=XOpenDisplay(NULL))) { printf("ERROR: Can't connect to display %s\n",XDisplayName(NULL)); exit(1); } screen = DefaultScreen(display); cmap = DefaultColormap(display,screen); rootwin = RootWindow(display,screen); white = WhitePixel(display,screen); black = BlackPixel(display,screen); win = XCreateSimpleWindow( display,rootwin,0,0,win_width,win_height,0,white,black); size_hints.flags = PMinSize | PMaxSize; size_hints.min_width = win_width; size_hints.min_height = win_height; size_hints.max_width = win_width; size_hints.max_height = win_height; XStringListToTextProperty(&win_name_str,1,&win_name); XSetWMProperties(display,win,&win_name,NULL,NULL,0,&size_hints,NULL,NULL); gcvals.foreground = white; gcvals.background = black; gcwhite = XCreateGC(display,win,GCForeground | GCBackground,&gcvals); gcvals.foreground = black; gcblack = XCreateGC(display,win,GCForeground | GCBackground,&gcvals); XAllocNamedColor(display,cmap,"blue",&col,&unused); gcvals.foreground = col.pixel; gcblue = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); XAllocNamedColor(display,cmap,"green",&col,&unused); gcvals.foreground = col.pixel; gcgreen = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); XAllocNamedColor(display,cmap,"red",&col,&unused); gcvals.foreground = col.pixel; gcred = XCreateGC(display, win, GCForeground | GCBackground, &gcvals); XSelectInput( display,win, ExposureMask | ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | PointerMotionMask); // Do this so we know when the delete button has been pressed on the // window top. Otherwise server would just kill us. delete_atom = XInternAtom(display,"WM_DELETE_WINDOW",True); XSetWMProtocols(display,win,&delete_atom,1); XMapWindow(display,win); XNextEvent(display,&event); } /*** Initialise the line duplicate & character list ***/ void init() { int i; charac = new cl_charac*[num_chars]; for(i=0;i < num_chars;++i) charac[i] = new cl_charac(i); output = new char[num_chars+1]; // The index and value of the join can be XOR'd to give the long line. // eg: (SQ_1 or SQ_2) xor (SQ_2 or SQ_3) -> SQ_1 or SQ_3 bzero(joinlist,sizeof(joinlist)); // Horizontal joinlist[SQ_1 | SQ_2] = SQ_2 | SQ_3; joinlist[SQ_2 | SQ_3] = SQ_1 | SQ_2; joinlist[SQ_4 | SQ_5] = SQ_5 | SQ_6; joinlist[SQ_5 | SQ_6] = SQ_4 | SQ_5; joinlist[SQ_7 | SQ_8] = SQ_8 | SQ_9; joinlist[SQ_8 | SQ_9] = SQ_7 | SQ_8; // Vertical joinlist[SQ_1 | SQ_4] = SQ_4 | SQ_7; joinlist[SQ_4 | SQ_7] = SQ_1 | SQ_4; joinlist[SQ_2 | SQ_5] = SQ_5 | SQ_8; joinlist[SQ_5 | SQ_8] = SQ_2 | SQ_5; joinlist[SQ_3 | SQ_6] = SQ_6 | SQ_9; joinlist[SQ_6 | SQ_9] = SQ_3 | SQ_6; // Diagonal joinlist[SQ_1 | SQ_5] = SQ_5 | SQ_9; joinlist[SQ_5 | SQ_9] = SQ_1 | SQ_5; joinlist[SQ_3 | SQ_5] = SQ_5 | SQ_7; joinlist[SQ_5 | SQ_7] = SQ_3 | SQ_5; // Duplicate lines bzero(duplist,sizeof(duplist)); duplist[SQ_1 | SQ_3] = SQ_1 | SQ_2 | SQ_3; duplist[SQ_1 | SQ_7] = SQ_1 | SQ_4 | SQ_7; duplist[SQ_1 | SQ_8] = SQ_1 | SQ_5; duplist[SQ_1 | SQ_9] = SQ_1 | SQ_5 | SQ_9; duplist[SQ_2 | SQ_7] = SQ_7 | SQ_5; duplist[SQ_2 | SQ_9] = SQ_5 | SQ_9; duplist[SQ_2 | SQ_8] = SQ_2 | SQ_5 | SQ_8; duplist[SQ_3 | SQ_8] = SQ_3 | SQ_5; duplist[SQ_3 | SQ_9] = SQ_3 | SQ_6 | SQ_9; duplist[SQ_3 | SQ_7] = SQ_3 | SQ_5 | SQ_7; duplist[SQ_4 | SQ_6] = SQ_4 | SQ_5 | SQ_6; duplist[SQ_7 | SQ_9] = SQ_7 | SQ_8 | SQ_9; // Characters for(int i=0;i < 256;++i) char_list[i] = NULL; char_list['a'] = &char_A; char_list['b'] = &char_B; char_list['c'] = &char_C; char_list['d'] = &char_D; char_list['e'] = &char_E; char_list['f'] = &char_F; char_list['g'] = &char_G; char_list['h'] = &char_H; char_list['i'] = &char_I; char_list['j'] = &char_J; char_list['k'] = &char_K; char_list['l'] = &char_L; char_list['m'] = &char_M; char_list['n'] = &char_N; char_list['o'] = &char_O; char_list['p'] = &char_P; char_list['q'] = &char_Q; char_list['r'] = &char_R; char_list['s'] = &char_S; char_list['t'] = &char_T; char_list['u'] = &char_U; char_list['v'] = &char_V; char_list['w'] = &char_W; char_list['x'] = &char_X; char_list['y'] = &char_Y; char_list['z'] = &char_Z; char_list['0'] = &char_0; char_list['1'] = &char_1; char_list['2'] = &char_2; char_list['3'] = &char_3; char_list['4'] = &char_4; char_list['5'] = &char_5; char_list['6'] = &char_6; char_list['7'] = &char_7; char_list['8'] = &char_8; char_list['9'] = &char_9; char_list['-'] = &char_minus; char_list['+'] = &char_plus; char_list['='] = &char_equals; char_list['*'] = &char_star; char_list['\\'] = &char_backward_slash; char_list['/'] = &char_forward_slash; char_list['$'] = &char_dollar; char_list['^'] = &char_hat; char_list[','] = &char_comma; char_list['?'] = &char_question; char_list['"'] = &char_double_quotes; char_list['>'] = &char_greater; char_list['<'] = &char_less; char_list['\n'] = &char_newline; char_list['\b'] = &char_backspace; // Other stuff char_width = win_width / num_chars; min_ch_width = char_width / MIN_CH_WIDTH_DIV; min_ch_height = win_height / MIN_CH_HEIGHT_DIV; min_dist = win_height / MIN_DIST_DIV; } /*** Main input loop ***/ void run() { XClientMessageEvent *ce; fd_set mask; struct timeval tv; bool drawing; int fd; button = false; do_uppercase = false; drawing = false; totalReset(); fd = ConnectionNumber(display); // Main loop while(1) { FD_ZERO(&mask); FD_SET(fd,&mask); tv.tv_sec = 0; tv.tv_usec = DECODE_DELAY; switch(select(FD_SETSIZE,&mask,0,0,&tv)) { case -1: perror("select()"); exit(1); case 0: if (drawing && !click_cnt && !button) { decodeChars(); drawing = false; } continue; } if (!FD_ISSET(fd,&mask)) exit(0); // XEventsQueued() is used to see if XNextEvent() has anything to // read. If we didn't do this XNextEvent() could block. while(XEventsQueued(display,QueuedAfterReading)) { /* Can't use XCheckWindowEvent() or XCheckMaskEvent() because they don't handle ClientMessage types and other types automatically selected (ie not explicitly stated in XSelectInput() */ XNextEvent(display,&event); switch(event.type) { case Expose: case MapNotify: drawDividers(); break; case ButtonPress: buttonPress(); break; case ButtonRelease: buttonRelease(); break; case KeyPress: keyPress(); break; case KeyRelease: keyRelease(); break; case MotionNotify: if (motionNotify()) drawing = true; break; case ClientMessage: ce = (XClientMessageEvent *)&event; if (ce->data.l[0] == delete_atom && ce->window == win) { if (show_debug) puts("Got WM destroy window message."); exit(0); } } } } } /*** A button has been pressed ***/ void buttonPress() { Window rw,cw; int rx,ry,wx,wy; u_int unused; int i; if (event.xbutton.button > 1) { if (++click_cnt == 1) { // Draw grids for(i=0;i < num_chars;++i) { // Have to set box before we draw the grid obviously charac[i]->setMinBoundingBox(); charac[i]->drawGrid(); } XFlush(display); return; } // Decode decodeChars(); return; } button = true; lineReset(); XQueryPointer(display,win,&rw,&cw,&rx,&ry,&wx,&wy,&unused); start_x = mouse_x = prev_x = wx; start_y = mouse_y = prev_y = wy; setMinMax(); chnum = -1; } /*** Button has been released ***/ void buttonRelease() { if (event.xbutton.button == 1) { button = false; if (segments) { // Add line to character. The character we used is fixed by // the first line drawn. if (chnum == -1) setCharacter(); if (show_debug) printf("Adding line type %d to character %d after button release\n", linetype,chnum); charac[chnum]->add( new cl_line(start_x,start_y,mouse_x,mouse_y,linetype)); } } } /*** Key has been pressed ***/ void keyPress() { KeySym ksym; char key; XLookupString(&event.xkey,&key,1,&ksym,NULL); switch(ksym) { case XK_Shift_L: do_uppercase = true; } } /*** Key has been released ***/ void keyRelease() { KeySym ksym; char key; XLookupString(&event.xkey,&key,1,&ksym,NULL); switch(ksym) { case XK_Shift_L: do_uppercase = false; break; case XK_r: case XK_R: totalReset(); } } /*** Mouse is moving ***/ bool motionNotify() { double f_total_ang; double ang_diff; double new_line_ang; // If button not pressed or we haven't moved the length of one section yet // then ignore if (!button || hypot((double)(event.xmotion.x - prev_x), (double)(event.xmotion.y - prev_y)) < min_dist) return false; prev_x = mouse_x; prev_y = mouse_y; mouse_x = event.xmotion.x; mouse_y = event.xmotion.y; setMinMax(); // Draw the line section XDrawLine(display,win,gcwhite,prev_x,prev_y,mouse_x,mouse_y); XFlush(display); // Get angles prev_ang = ang; ang = angle(prev_x,prev_y,mouse_x,mouse_y); // Don't do anything until we're on the 2nd segment if (!segments++) return true; // Check angle of this section to last section ang_diff = angleDiff(ang,prev_ang); storeAngle(ang_diff); if (linetype != TYPE_S && linetype != TYPE_CIRCLE) { // See if we've turned a sharp corner for a new line new_line_ang = (linetype == TYPE_LINE ? NEW_LINE_ANG1 : NEW_LINE_ANG2); if (fabs(getTotalStoredAngle()) > new_line_ang) { if (chnum == -1) setCharacter(); if (show_debug) printf("Adding line type %d to character %d after direction change\n", linetype,chnum); charac[chnum]->add(new cl_line(start_x,start_y,prev_x,prev_y,linetype)); partialLineReset(); start_x = prev_x; start_y = prev_y; return true; } } // Just keep tally of total angle and set line type total_ang += ang_diff; f_total_ang = fabs(total_ang); switch(linetype) { case TYPE_S: case TYPE_CIRCLE: // Don't change once we're in this state break; case TYPE_NOTSET: linetype = TYPE_LINE; break; case TYPE_LINE: if (f_total_ang > 90) linetype = TYPE_SEMI; break; case TYPE_SEMI: if (f_total_ang < 40) linetype = TYPE_S; else if (f_total_ang > 300) linetype = TYPE_CIRCLE; } return true; } //////////////////////////// SUPPORT FUNCTIONS /////////////////////////// /*** Set the character number the middle X position of the line is in ***/ void setCharacter() { chnum = (mouse_x > start_x) ? (start_x + (mouse_x - start_x) / 2) / char_width : (mouse_x + (start_x - mouse_x) / 2) / char_width; } /*** Decode all the characters then reset if we got them all ok ***/ void decodeChars() { int i,j; for(i=0;i < num_chars;++i) { charac[i]->setMinBoundingBox(); // If no lines in this character treat it as a space ONLY if // there are lines in a character further on if (!charac[i]->num_lines) { for(j=i+1;j < num_chars;++j) if (charac[j]->num_lines) break; if (j < num_chars) { output[i] = ' '; continue; } break; } if (!charac[i]->decode()) break; } if (i) { output[i] = '\0'; if (show_debug) printf("\nWORD: \"%s\"\n",output); else write(1,output,strlen(output)); } totalReset(); } /*** Reset everything ***/ void totalReset() { int i; for(i=0;i < num_chars;++i) charac[i]->reset(); lineReset(); XClearWindow(display,win); drawDividers(); click_cnt = 0; } /*** Reset stuff for a new line ***/ void lineReset() { start_x = -1; start_y = -1; prev_x = -1; prev_y = -1; mouse_x = -1; mouse_y = -1; ang = 0; partialLineReset(); } /*** Things that need to be reset when new line created by turning a corner ***/ void partialLineReset() { total_ang = 0; prev_ang = 0; segments = 0; maxx = maxy = -1; minx = win_width; miny = win_height; linetype = TYPE_NOTSET; clearStoredAngles(); } /*** Draw the divider lines ***/ void drawDividers() { int i,x,w; w = win_width / num_chars; for(i=0,x=w;i < num_chars-1;++i) { XDrawLine(display,win,gcblue,x,0,x,win_height); x += w; } XFlush(display); } /*** Set min and max x,y positions based on current mouse position ***/ void setMinMax() { if (mouse_x < minx) minx = mouse_x; if (mouse_x > maxx) maxx = mouse_x; if (mouse_y < miny) miny = mouse_y; if (mouse_y > maxy) maxy = mouse_y; } /*** Returns the angle to the vertical axis given by the line defined by the co-ordinates passed. It assumes that the origin is top left as per the X windows system ***/ double angle(int fx, int fy, int tx, int ty) { int xd,yd; double ang; xd = tx - fx; yd = ty - fy; if (!xd) return (yd > 0) ? 180 : 0; if (!yd) return (xd > 0) ? 90 : 270; ang = -atan((double)xd / (double)yd) * DEGS_PER_RADIAN; if (yd > 0) ang += 180; else if (xd < 0) ang += 360; return ang; } /*** Return the difference in degrees. Imagining a circle, if the previous angle is to the right of the new one we return a positive number 0 to 180 else a negative one -1 to -179 ***/ double angleDiff(double ang, double pang) { /* Imagine whole circle is rotated round so "ang" is at zero */ pang -= ang; if (pang < 0) pang += 360; return pang <= 180 ? pang : -(360 - pang); } /*** Clear all the stored angles ***/ void clearStoredAngles() { bzero(stored_ang,sizeof(stored_ang)); next_angle_pos = 0; } /*** Add an angle to the round robin list ***/ void storeAngle(double ang) { stored_ang[next_angle_pos] = ang; next_angle_pos = (next_angle_pos + 1) % NUM_STORED_ANGLES; } /*** Get the current total ***/ double getTotalStoredAngle() { double tot; int i; for(i=0,tot=0;i < NUM_STORED_ANGLES;++i) tot += stored_ang[i]; return tot; }