/* * Program: Lissajous Figures animation * Programmer: Robert John Morton UK-YE572246C * Started: Mon 09 Dec 2019 10:16:25 -03 * Finished: Wed 11 Dec 2019 10:50:06 -03 * From the directory in which lissajous.c resides, compile command: gcc lissajous.c -L/usr/X11R6/lib -lX11 -o lissajous execute command: ./lissajous */ #include // contains the X11 grapical drawing functions #include // input output to/from the console and keyboard #include // general functions for the 'C' language #include // for initialising strings as character arrays #include // contains the function usleep() /* These variables pertain to the plot() function. Each row is respectively the variables in the plot() function. Note that F and f are integers used as Booleans. The value 2164 is used to trigger the first cycle. */ int T[4][5] = { {0,1,2164,1,0}, {0,1,0,1,0}, {0,1,0,1,0}, {0,1,0,1,0} }, S[] = { // Sine Table giving sin(a) every half degree 0, 87, 175, 262, 349, 436, 523, 610, 698, 785, 872, 958, 1045, 1132, 1219, 1305, 1392, 1478, 1564, 1650, 1736, 1822, 1908, 1994, 2079, 2164, 2250, 2334, 2419, 2504, 2588, 2672, 2756, 2840, 2924, 3007, 3090, 3173, 3256, 3338, 3420, 3502, 3584, 3665, 3746, 3827, 3907, 3987, 4067, 4147, 4226, 4305, 4384, 4462, 4540, 4617, 4695, 4772, 4848, 4924, 5000, 5075, 5150, 5225, 5299, 5373, 5446, 5519, 5592, 5664, 5736, 5807, 5878, 5948, 6018, 6088, 6157, 6225, 6293, 6361, 6428, 6494, 6561, 6626, 6691, 6756, 6820, 6884, 6947, 7009, 7071, 7133, 7193, 7254, 7314, 7373, 7431, 7490, 7547, 7604, 7660, 7716, 7771, 7826, 7880, 7934, 7986, 8039, 8090, 8141, 8192, 8241, 8290, 8339, 8387, 8434, 8480, 8526, 8572, 8616, 8660, 8704, 8746, 8788, 8829, 8870, 8910, 8949, 8988, 9026, 9063, 9100, 9135, 9171, 9205, 9239, 9272, 9304, 9336, 9367, 9397, 9426, 9455, 9483, 9511, 9537, 9563, 9588, 9613, 9636, 9659, 9681, 9703, 9724, 9744, 9763, 9781, 9799, 9816, 9833, 9848, 9863, 9877, 9890, 9903, 9914, 9925, 9936, 9945, 9954, 9962, 9969, 9976, 9981, 9986, 9990, 9994, 9997, 9998, 10000, 10000 }; /* Returns the x or y value for a point on either the tracer or the wiper according to the supplied value of 'i' [0,1,2,3]. It uses the above table to calculate an interpolated value of the sine of the phase angle of the test signal. The arguement values for each value of 'i' are pre- served in the array T[][]. p = 1 = x-plot, 0 = y-plot m = 1 or 2 depending on whether test signal is 1x or 2x ref signal O = window coordinate of the origin of the graph. i = plot number 0: x-plot, 1: y-plot, 2: x-wipe, 3: y-wipe */ int plot(int p, int m, int O, int i) { int a = T[i][0], // main phase angle d = T[i][1], // main phase angle incrementer Q = T[i][2], // quadrant counter F = T[i][3], // output sign 'F' 1 = '+' 0 = '-' f = T[i][4]; // trigger start of wiper int x = S[a] >> 7; // get x or y co-ord corresponding to given angle /* If the output should be positive, add it to the co-ordinate origin otherwise, take it from the co-ordinate origin. */ if(F == 1) x = O + x; else x = O - x; // Advance the main angle by the appropriate amount (can be > 1). a += d << m; /* If, as a result, we are now at or 'below' the bottom of the table, wrap back up the table and add this quadrant's phase increment then set the main angle to move up the table and reverse F for the next 2 quadrants. [Q is the quadrant counter] */ if(a < 0) { a = p - a; d = 1; Q++; if(F == 0) F = 1; else F = 0; } /* Otherwise, if, as a result, we are now at or 'above' the top of the table, wrap back down the table and subtract this quadrant's phase increment, increment the quadrant counter [Q] and set 'd' to move the angle DOWN the table. */ else if(a > 180) { a = 360 - a - p; Q++; d = -1; } /* if the wiper start flag is not set and more than 3 quadrants have been completed then set the wiper start flag. */ if(f == 0 && Q > 3) f = 1; T[i][0] = a; T[i][1] = d; T[i][2] = Q; T[i][3] = F; T[i][4] = f; return(x); // return the new x or y co-ordinate. } int main(void) { Display *d; // pointer to an X11 display device Window w; // reference for the window being created XEvent e; // reference to an event from the created window GC gc; int R = 100, // signal radius: half-length of each axis B = 30, // margin between end of graph axis and edge of window O = R + B, // graphical origin Q = O + R, // far right of graphical X-axis D = R + R, // full width/height of plotting area W = O + O, // width of the window H = O + O, // height of the window B1 = B + 20, // centrallising bias for message M[0] B2 = B + 20, // centrallising bias for message M[1] s, // number of the current default screen SB, // centrallising bias for current annotation string m = 1, // x-axis frequency multiplier q = 4; /* quadrant counter [incremented 4 quadrants at a time] in order to test when each cycle has completed. */ char *M[] = { " Y-FREQ ALMOST = X-FREQ ", "Y-FREQ ALMOST TWICE X-FREQ" }; /* Check that you can actually open the X-display by getting a non-NULL reference to a display. Else send an error message to the terminal. */ d = XOpenDisplay(NULL); if(d == NULL) { fprintf(stderr,"Cannot open display\n"); exit(1); } s = DefaultScreen(d); // get the number of the default screen gc = DefaultGC(d, s); // set the graphics context to use w = XCreateSimpleWindow( // create an application window d, // within the default display RootWindow(d,s), // within the root window [desktop] 10, 10, // position of top right corner W, H + 20, // width and height of the window 1, // border width in pixels BlackPixel(d,s), // colour of window's border BlackPixel(d,s) // colour of window's background ); XStoreName(d,w,"Lissajous Figures"); // set window title /* Make input possible from the keyboard and window controls then display the new window on the screen. */ XSelectInput(d,w,ExposureMask | KeyPressMask); XMapWindow(d,w); /* Define a unique indentifier WM_DELETE_WINDOW (referred to as an atom) that will invoke the X11 protocol that closes windows upon the display 'd' we have just created. [At least I think this is what it means] */ Atom WM_DELETE_WINDOW = XInternAtom(d,"WM_DELETE_WINDOW",False); /* Make 'delete window' events from our particular window visible to us as a ClientMessage event.*/ XSetWMProtocols(d,w,&WM_DELETE_WINDOW,1); /* The event-handling [or 'run'] loop [a permanent loop] broken only by the 'break' function in response to an external event. */ while (1) { /* Provided there exist events on the event queue, go fetch the first one on the queue. */ if(XPending(d) > 0) { XNextEvent(d,&e); /* If it is the 'window ready' event, set the foreground [drawing] colour to white [hex value FFFFFF] and draw the X and Y annotations for the axes. */ if(e.type == Expose) { XSetForeground(d,gc,0xFFFFFF); XDrawString(d,w,gc,O - 2,B - 4,"Y",strlen("Y")); XDrawString(d,w,gc,Q + 5,O + 5,"X",strlen("X")); } /* Else if any key has been pressed, or a box control has been clicked, break out of the permanent while() loop, close the window and exit. Should also check here for other client message types; however, as the only protocol registered above is WM_DELETE_WINDOW, it is safe for this program. */ else if(e.type == KeyPress || e.type == ClientMessage) break; } else { /* At the start of the program - or after 2160 quadrants have been completed, wipe plotting area, change the lissajous mode and display the mode annotation message underneath the graph. */ if(T[0][2] > 2160) { XSetForeground(d,gc,0x000000); XDrawString(d,w,gc,SB,H,M[m],strlen(M[m])); if(m == 0) { m = 1; SB = B2; } else { m = 0; SB = B1; } XSetForeground(d,gc,0xFFFFFF); XDrawString(d,w,gc,SB,H,M[m],strlen(M[m])); /* Reset the x and y coordinate data sets for both the painter trace and the wiper trace, then wipe the plotting area and re-draw the axes. */ for(int i = 0; i < 4; i++) { T[i][0] = 0; // main phase angle 'a' T[i][1] = 1; // main phase angle incrementer 'd' T[i][2] = 0; // clear the quadrant counter 'Q' T[i][3] = 1; // output sign 'F' 1 = '+' 0 = '-' T[i][4] = 0; // clear the wiper start trigger 'f' } XSetForeground(d,gc,0x000000); XFillRectangle(d,w,gc,B,B,D,D); XSetForeground(d,gc,0xFFFFFF); XDrawLine(d,w,gc,O - R, O,O + R,O); XDrawLine(d,w,gc,O,O - R,O,O + R); q = 4; // number of quadrants to update } /* While the current main cycle is not yet finished, get the new x and y plots and paint the next bit of line in green. */ if(T[0][2] < q) { int x = plot(1,0,O,0), y = plot(0,m,O,1); XSetForeground(d,gc,0x00FF00); XDrawLine(d,w,gc,x,y,x,y); /* If painter has completed more than 3 quadrants of the first cycle [ie the wiper flag 'f' has been set], then get the wiper's next x and y coordinates, wipe the trace to black and re-draw the axes. */ if(T[0][4] == 1) { x = plot(1,0,O,2); y = plot(0,m,O,3); XSetForeground(d,gc,0x000000); XDrawLine(d,w,gc,x,y,x,y); XSetForeground(d,gc,0xFFFFFF); XDrawLine(d,w,gc,O - R,O, O + R,O); XDrawLine(d,w,gc,O,O - R,O,O + R); } } q = T[0][2] + 4; // set 'q' to 4 quadrants ahead } usleep(100); // pause the thread for 100 microseconds. } // end of outer permanent while() loop XCloseDisplay(d); // clear this window from the X11 display 'd' return 0; // return that everything went OK } // end of main()