/** Lissajous Figures demonstrator by Robert John Morton UK-YE572246C */ /* This version uses a sine table for reason of speed so that the applet does not take up too much of the host machine's processing time. Finished 01 October 1997 */ import java.awt.*; import java.lang.System; class TabCoord { int a = 0, // main phase angle d = 1, // main phase angle incrementer Q = 0; // quadrant counter boolean s = true, // indicates the output sign f = false; // flag to trigger start of wiper static int[] 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 }; // FINDS VALUE OF CO-ORDINATE CORRESPONDING TO INCREMENTED ANGLE public int Advance(int p, int m, int b) { int x = S[a] >> 7; // get x or y co-ord corresponding to given angle if(s) // If the output should be positive x = b + x; // add it to the co-ordinate origin bias else // otherwise x = b - x; // subtract it from the co-ordinate origin bias a += d << m; // advance the main angle by the appropriate amount // (can be > 1) if(a < 0) { // If as a result we're now at or 'below' bottom of a = p - a; // table, wrap back up table & add this quadrant's d = 1; // phase increment, set main angle to move up table Q++; // increment the quadrant counter s = !s; // reverse the sign for the next 2 quadrants } else if(a > 180) { // if as a result we're now at or 'above' top of a = 360 - a - p; // table, wrap back down table & subtract this Q++; // quadrant's phase increment, increment quadrant d = -1; // counter, set to move the angle down the table } if(!f && Q > 6) // If the wiper start flag not set && 3 quadrants + 80 f = true; // degrees have been completed, set wiper start flag return(x); // return new x or y co-ordinate } public void Reset() { // RESET THE VALUES IN A CO-ORDIATE OBJECT a = 0; // main phase angle d = 1; // main phase angle incrementer Q = 0; // clear the quadrant counter s = true; // indicates the output sign f = false; // wiper start flag } } public class lissajou extends java.applet.Applet implements Runnable { int R = 80, // radius of the trace vector B = R + 10, // bias of centre of graph (both x and y the same) W = B + B, // width of graphics window Bx = B - 4, // bias for the x-axis annotation 'X' dBy, // half the height of the letter 'X' By = B + 5, // bias for the y-axis annotation 'Y' Z = 192, // vertical position of annotations q = 4, // number of quadrants to paint on each call to update() m = 1, // x-axis frequency multiplier B1, // centrallising bias for S1 B2, // centrallising bias for S2 b; // centrallising bias for current annotation string long TF = 100, // total Time Frame for an update cycle T; // time at which the next new cycle is due to begin boolean wipe = true; // trigger flag to wipe scope trace Thread TH; // declare a thread reference variable Font font = new Font("Dialog",Font.PLAIN,12); // font for annotations FontMetrics fm; // dimensions of letters etc of chosen font TabCoord X1, // x co-ordinate of the painter trace Y1, // y co-ordinate of the painter trace X2, // x co-ordinate of the wiper trace Y2; // y co-ordinate of the wiper trace String // Annotation string for the ... S1 = "Y-FREQ ALMOST = X-FREQ", // single ellipse S2 = "Y-FREQ ALMOST 2 * X-FREQ", // double ellipse s; // current annotation string Color tr = new Color( 0,255,128), // create special bright green for trace ax = new Color(64,192, 0), // create special browny-green for axes bg = new Color( 0, 0, 0); // black background public void init() { X1 = new TabCoord(); // create co-ordinate objects for painter trace Y1 = new TabCoord(); X2 = new TabCoord(); // create co-ordinate objects for wiper trace Y2 = new TabCoord(); /* Get the metrics (pixel dimensions of height, leading etc.) for the annotations font. */ fm = getFontMetrics(font); dBy = fm.getAscent() >> 1; // half the height of the letter 'X' // Widths of first tace's and second trace's annotations. B1 = B - (fm.stringWidth(S1) >> 1); B2 = B - (fm.stringWidth(S2) >> 1); // Set the finish time for the first time frame T = System.currentTimeMillis() + TF; } public void paint(Graphics g) { // SET UP THE INITIAL 'SCOPE SCREEN' g.setFont(font); // set up the annotation font g.setColor(Color.black); // set appropriate wiping colour g.fillRect(0,0,180,200); // clear the applet window g.drawString(s, b, Z); // display the graph's annotation // put the 'X' and 'Y' at the ends of their respective axes. g.drawString("X",Bx + R + 3,By + dBy); g.drawString("Y",Bx - 3,By - R - 4); // Set colour to paint new trace then display and label the x & y axes. g.setColor(ax); g.drawLine(Bx - R,By,Bx + R,By); g.drawLine(Bx,By - R,Bx,By + R); } public void update(Graphics g) { // UPDATE THE 'SCOPE TRACE' int x, y; // temporary co-ordinate variables if (wipe) { // if it's time to wipe trace ... if (m == 0) { // If just finished doing a double ellipse, m = 1; // start to do a single ellipse, s = S2; b = B2; } else { // otherwise m = 0; // start to do a new double ellipse s = S1; b = B1; } X1.Reset(); // reset Coord objects for painter trace Y1.Reset(); X2.Reset(); // reset Coord objects for wiper trace Y2.Reset(); paint(g); // re-display the blank window with axes and annotations q = 4; // number of quadrants to update wipe = false; // cancel the 'wipe' request } while(X1.Q < q) { // while current main cycle not yet finished x = X1.Advance(1,0,Bx); // get the new x plot y = Y1.Advance(0,m,By); // get the new y plot g.setColor(tr); // Set the trace colour and g.drawLine(x,y,x,y); // draw the next bit of the line. if (X1.f) { // If painter has completed at least 1 cycle x = X2.Advance(1,0,Bx); // get the new x plot to be wiped y = Y2.Advance(0,m,By); // get the new y plot to be wiped if(x != Bx && y != By) // provided the plot is not on one g.setColor(bg); // of the axes, wipe the trace else // else g.setColor(ax); // repaint it in the axis colour g.drawLine(x,y,x,y); // thereby preserving the axial pixels } } q = X1.Q + 4; // reset 'q' to 4 quadrants ahead } public void run() { // RUN THE TH THREAD while(true) { // permanent loop broken by external event if (X1.Q > 1080) // If 1080 quadrants not yet done wipe = true; // trigger a scope screen wipe repaint(); // sets up a call to update() /* Get time remaining in the current time frame, not letting it be less than 5 milliseconds, and then put the thread to sleep for this remaining time. */ long s = T - System.currentTimeMillis(); if (s < 5) s = 5; try { Thread.currentThread().sleep(s); } /* Allow browser events to break the thread and trigger a 'reset' if applet is restarted. This happens for instance if you return to applet's HTML page after having left it. */ catch (InterruptedException e) { wipe = true; } // Set finishing time of next time frame. T = System.currentTimeMillis() + TF; } } /* Start program thread by creating the thread object and starting it running by returning a call to run(). */ public void start() { TH = new Thread(this); TH.start(); } public void stop() { TH = null; } // Stop program thread }