/** Alternative Relativity: Doppler Effect Demonstrator by Robert John Morton (Belo Horizonte-MG 27 September 2006) Produced (concept to full release) in 5 afternoons. */ /* HTML call for this applet: */ import java.awt.*; import java.lang.System; import java.awt.event.*; // for the new-fangled 1.1 event handling public class doppler extends java.applet.Applet implements Runnable { private int X = 5, // initial position of source mp = 16, // size of the wave objects references array ds = 0, // default mode selection I = 2, // number of buttons (check boxes) for mode selector wi = 0, // wave instance index SH = 0, // image shift delay subcounter sh = -600, // image shift variable ls = 0; // language selection parameter 0=English 1=Português private long P = 15, // applet's prescribed cycle period (milliseconds) T = 0, // System Time at end of last cycle dT = 0, // change in System Time between last cycle and this cycle sl = 10; // required sleep time (P - dT) private boolean cf = true; // true means new mode selected private String S[] = new String[3]; // array for localized annotations private Font font; // a font reference for annotations private FontMetrics fm; // dimensions of letters etc of chosen font private Label L; // reference for button group heading private CheckboxGroup G; // reference for this checkbox group private Checkbox cb0, cb1; // references to individual checkboxes within it private Checkbox B[]; // array to hold the check box references private modebut IL[]; // array for "item listener" references private Image H; // off-screen image private Graphics h; // graphics reference variable // declare a thread reference variable for the 'run' thread private volatile Thread dopplerThread; // arrat for references to instances of the wave class private wave MP[] = new wave[mp]; public void init() { // INITIALIZE THE APPLET // localized annotations for applet's mode selector buttons String SA[][] = { {"Mode Selector:", "Selecione Modo:"}, {" Classical Relativity", " Relatividade Classica"}, {" Alternative Relativity", " Relatividade Alternativa"} }; //get language selection from applet's HTML parameter ls = Integer.valueOf(getParameter("lang")).intValue(); for(int i = 0; i < 3; i++) // localize the annotations S[i] = SA[i][ls]; // get the applet's default mode from the HTML parameter ds = Integer.valueOf(getParameter("selmode")).intValue(); boolean DS[] = { true, false }; //boolean array to represent button states DS[ds] = true; //set default mode received from HTML call parameter B = new Checkbox[I]; // array for radio button references IL = new modebut[I]; // array for Item Listener references L = new Label(S[0], Label.LEFT); // mode selector label // checkbox group to contain the two mode buttons G = new CheckboxGroup(); cb0 = new Checkbox(S[1], G, DS[0]); // create the first mode button cb1 = new Checkbox(S[2], G, DS[1]); // create the second mode button add(L); // add the title label to the applet panel IL = new modebut[I]; // create array for "modebut.class" references B[0] = (Checkbox)add(cb0); IL[0] = new modebut(0, this); B[0].addItemListener(IL[0]); //listen for when one of B[1] = (Checkbox)add(cb1); IL[1] = new modebut(1, this); B[1].addItemListener(IL[1]); //the buttons is pressed font = new java.awt.Font("System", Font.PLAIN, 12); // get the metrics (pixel dimensions of height, leading etc.) for this font fm = getFontMetrics(font); // create an image in which to animate the map off screen H = createImage(1200, 191); h = H.getGraphics(); // capture its graphics context wave.sineValues(); // set up quick calc sine values for the wave class wave.setMode(ds); // set up applet's default mode } // (classical or alternative) // set up the graphics & repaint after browser events public void paint(Graphics g) { g.drawImage(H, sh, 40, null); // draw graphics image onto } // the visible window canvas public void update(Graphics g) { // UPDATE THE DISPLAY if (cf) { // if a new mode has been selected cf = false; // cancel the mode change flag h.setColor(Color.black); // set graphics fields's background colour h.fillRect(0, 0, 1200, 191); // wipe the graphics field's display area for(int i = 0; i < mp; i++) // kill all wave objects MP[i] = null; if(ds == 0) { // if applet is in "classical relativity" mode... X = 200; // set start position sh = -250; // shift the off-screen image } else { // otherwise X = 500; // set the corresponding values sh = -600; // for the "alternative relativity" mode } MP[0] = new wave(X); // spawn a new wave object (generate a new one) wi = 0; // reinitialize the wave instance index } // mini loop to do only 4 waves per pass (CPU loading) for(int q = 0; q < 4; q++) { wave r = MP[wi]; // create a reference to this wave instance if(r != null) { // if this wave is still alive (not yet faded out) r.update(h); // update this wave's position if(r.getmh()) // if it is time to spawn the following wave... if(!r.getnl()) { // if new wave has not yet been launched int k = wi; // create a temporary cyclic index variable // FOR ALL POSSIBLE WAVE OBJECTS for(int j = 0; j < mp; j++) { if (++k >= mp) // cycle round to the start k = 0; // of the references array // if no live wave exists in this element if (MP[k] == null) { if(ds == 0) // if in "classical relativity" mode if(X < 1195) // if light-source not yet reached destination X += 10; // shift it another 10 pixels to the right else { // else [if in "alternative relativity" mode] X = 250; // set it to extreme left position (off-screen) cf = true; // trigger a new traverse of the light source } MP[k] = new wave(X); // spawn a new wave object r.setnl(); // signal that new launch has been done break; // stop seach for an element with a null reference } } // end of FOR ALL POSSIBLE WAVE OBJECTS } else if (r.getah()) { // else (if the wave has now faded out) MP[wi] = null; // kill its wave object } } if(++wi >= mp) { // if wave index has been incremented to its wi = 0; // maximum, reset it back to zero if(ds == 1 // if in "alternative relativity" mode AND && SH++ > 1) { // delay sub-counter has exceeded its upper limit SH = 0; // reset it back to zero if(sh++ > 95) { //if image has shifted its maximum distance right sh = -600; //set it back to its extreme left position again cf = true; //trigger start of new traverse of light source } } paint(g); // draw graphics image onto the visible window canvas. } } // end of outer for() loop } public void run() { // set applet's thread as system's current thread Thread thisThread = Thread.currentThread(); // while the doppler Applet's thread remains alive... while (dopplerThread == thisThread) { dT = System.currentTimeMillis() - T; //time since last cycle if(dT < P) sl = P - dT; else sl = 0; //compute sleep time /* Sleep for the remainder of current cycle perion P, catching any interrupt from GUI or browser [triggers re-start of light-source traverse]. */ try {thisThread.sleep(sl);} catch (InterruptedException e) { cf = true; } //Note current System Time ready for next cycle T = System.currentTimeMillis(); repaint(); // sets up a call to update() } // bringing the Graphics environment with it } public void start() { // Start program thread by dopplerThread = new Thread(this); // creating the thread object dopplerThread.start(); // and starting it running T = System.currentTimeMillis(); // Note System Time at start of } // session [returns a call to run()] public void stop() { // kill this applet's thread dopplerThread = null; } void selectMode(int i) { // CALLED BY THE LISTENER CLASS BELOW... cf = true; //set the change flag to re-start the waves ds = i; //set new vale for default mode variable wave.setMode(ds); //re-initialize static mode variable in wave.class repaint(); //re-initialize display - sets up call to update() } //(CALLED BY EVENT LISTENER CLASS BELOW) } // LISTENS FOR EVENTS FROM THE MODE SELECTOR BUTTONS class modebut implements ItemListener { int id; // one of the above events doppler ap; // application that called: always the above applet! //constructor for a new checkbox event public modebut(int id, doppler ap) { this.id = id; // set id number of this instance of 'modebut' this.ap = ap; // set the reference to this instance of the applet } // from which it came. (only one instance anyway). // a checkbox event has occurred from checkbox 'id' public void itemStateChanged(ItemEvent e) { switch(id) { // communicate the selection to the above applet. case 0: ap.selectMode(0); break; //"classical" mode selected case 1: ap.selectMode(1); break; //"alternative" mode selected } } } class wave { // THIS CLASS CREATES AND MANAGES THE LIFE CYCLE OF ONE WAVE private static boolean dm = false; // false = classical mode, true = alternative mode private static int // x & y plots for circular quadrant CX[][] = new int[400][64], // 64 x-plots for each of 400 radii CY[][] = new int[400][64], // 64 y-plots for each of 400 radii Ymax = 95; // extremity height limit of the display area private static double //one 1/256th of a circle as fraction of a radian Angle = 0.024933275028490422527481296692694; private static Color //colour value for each 64th of a quadrant at each of 400 radial CB[][] = new Color[400][64], //intensities for the blue/green and the red/green hemispheres CR[][] = new Color[400][64], //radial intensity levels for the pure green circles CG[] = new Color[400]; private int r = 3, // current radius of the wave circle X = 0, // x-coord of start of display area Y = 95, // y-coord of centre of display area ox = 0, // old value of the x-coordinate oy = 0; // old value of the y-coordinate private boolean mh = false, // true = signal to spawn the following wave pa = false, // true means wave has reached its extremity nl = false; // false means new wave not yet launched from this instance wave (int x) {X = x;} //set horizontal starting position for the wave //these methods allow applet to access this class's flags boolean getmh() { return (mh); } //returns true if wave has reached its final extremity boolean getah() { return (pa); } //returns true if new succeeding wave has already been launched boolean getnl() { return (nl); } //returns true if new succeeding wave has already been launched void setnl() { nl = true; } void update(Graphics g) { if(r > 30) mh = true; // signal to spawn the following wave object if(r > 400) // maximum radius of an active wave pa = true; // signal to kill this wave else { // else if(r > 10) // erase the fifth previous multi-coloured circle wipeWave(g, r - 8); if(dm) drawGreen(g, r++); // plot the next green circle else drawWave(g, r++); // plot the next multi-coloured circle } } /* The 3 following drawing methods are written separately to maximize processing speed in their central loops. */ void drawWave(Graphics g, int r) { ox = r; oy = 0; // old values of the x,y coordinates // for each of 64 angular increments in a quadrant for(int i = 0; i < 64; i++) { int ny = CY[r][i]; // compute y-coordinate of next primary plot if(ny > Ymax && oy <= Ymax) ny = Ymax; // force top and bottom vertical limits if(oy > Ymax && ny <= Ymax) oy = Ymax; // on the y-coordinate int nx = CX[r][i], // compute x-coordinate of next primary plot nX = X + nx, // new x-coordinates for forward semicircles Xn = X - nx, // new x-coordinates for rear semicircles oX = X + ox, // old x-coordinates for forward semicircles Xo = X - ox, // old x-coordinates for rear semicircles nY = Y + ny, // new y-coordinates for forward semicircles Yn = Y - ny, // new y-coordinates for rear semicircles oY = Y + oy, // old y-coordinates for forward semicircles Yo = Y - oy; // old y-coordinates for rear semicircles g.setColor(CB[r][i]); // phased & faded colour for blue/green quadrants g.drawLine(oX, oY, nX, nY); // draw line in 1st blue/green quadrant g.drawLine(oX, Yo, nX, Yn); // draw line in 2d blue/green quadrant g.setColor(CR[r][i]); // phased & faded colour for red/green quadrants g.drawLine(Xo, oY, Xn, nY); // draw line in 1st red/green quadrant g.drawLine(Xo, Yo, Xn, Yn); // draw line in 2d red/green quadrant ox = nx; // set current x,y co-ordinates oy = ny; // as next time's old x,y coordinates } } void drawGreen(Graphics g, int r) { ox = r; oy = 0; // old values of the x,y coordinates // for each of 64 angular increments in a quadrant for(int i = 0; i < 64; i++) { int ny = CY[r][i]; // compute y-coordinate of next primary plot if(ny > Ymax && oy <= Ymax) // force top and bottom vertical limits ny = Ymax; // on the y-coordinates if(oy > Ymax && ny <= Ymax) oy = Ymax; int nx = CX[r][i], // compute x-coordinate of next primary plot nX = X + nx, // new x-coordinates for the forwardsemicircles Xn = X - nx, // new x-coordinates for the rear semicircles oX = X + ox, // old x-coordinates for the forward semicircles Xo = X - ox, // old x-coordinates for the rear semicircles nY = Y + ny, // new y-coordinates for the forward semicircles Yn = Y - ny, // new y-coordinates for the rear semicircles oY = Y + oy, // old y-coordinates for the forward semicircles Yo = Y - oy; // old y-coordinates for the rear semicircles g.setColor( CG[r] ); // set radially-faded green colour g.drawLine(oX, oY, nX, nY); // draw line in 1st blue/green quadrant g.drawLine(oX, Yo, nX, Yn); // draw line in 2nd blue/green quadrant g.drawLine(Xo, oY, Xn, nY); // draw line in 1st red/green quadrant g.drawLine(Xo, Yo, Xn, Yn); // draw line in 2nd red/green quadrant ox = nx; // set the current x,y co-ordinates oy = ny; // as next time's old x,y coordinates } } void wipeWave(Graphics g, int r) { ox = r; oy = 0; // old values of the x,y coordinates g.setColor(Color.black); // colour black for a wipe pass only // for each of 64 angular increments in a quadrant for(int i = 0; i < 64; i++) { int ny = CY[r][i]; // compute y-coordinate of next primary plot if(ny > Ymax && oy <= Ymax) ny = Ymax; // force top and bottom vertical limits if(oy > Ymax && ny <= Ymax) oy = Ymax; // on the y-coordinate int nx = CX[r][i], // compute x-coord of next primary plot nX = X + nx, // new x-coords for forward semicircles Xn = X - nx, // new x-coords for rear semicircles oX = X + ox, // old x-coords for forward semicircles Xo = X - ox, // old x-coords for rear semicircles nY = Y + ny, // new y-coords for forward semicircles Yn = Y - ny, // new y-coords for rear semicircles oY = Y + oy, // old y-coords for forward semicircles Yo = Y - oy; // old y-coords for rear semicircles // Draw line in: g.drawLine(oX, oY, nX, nY); // first blue/green quadrant g.drawLine(oX, Yo, nX, Yn); // second blue/green quadrant g.drawLine(Xo, oY, Xn, nY); // first red/green quadrant g.drawLine(Xo, Yo, Xn, Yn); // second red/green quadrant ox = nx; // set current x,y co-ordinates oy = ny; // as next time's old x,y coordinates } } static void sineValues() { double angle = 0; //angle variable for forming the angles array // for each of 64 angular divisions of the primary quadrant for(int i = 0; i < 64; i++) { double s = Math.sin(angle), // compute the next sine value c = Math.cos(angle), // compute the next cosine value cc = 255 * c, // cosine -modified colour value ss = 255 * s; // sine-modified colour value // for each radius out as far as 400 pixels... for(int j = 0; j < 400; j++) { double r = (double)j; // radius of quarter-circle CX[j][i] = (int)(r * c); // x-coordinate at this angle at this radius CY[j][i] = (int)(r * s); // y-coordinate at this angle at this radius double k = 1 - r / 400; // form colour fading factor from the radius int // Colour values: g = (int)(ss * k), //green is phased by sine, faded by radius rb = (int)(cc * k), //red/blue phased by cosine, faded by radius q = (int)(255 * k); //green is solely faded by radius // colour array for CR[j][i] = new Color(rb,g,0); //the red/green quadrant CB[j][i] = new Color(0,g,rb); //the blue/green quadrant // green intensity array for each of the 400 possible radii CG[j] = new Color(0,q,0); } angle += Angle; // increment the angle by one 64th of a quadrant } } static void setMode(int ds) { // set the "relativity mode" if(ds == 0) dm = false; // false = "classical relativity" else dm = true; // true = "alternative relativity" } }