/** Alternative Relativity Demonstrator MAIN APPLET CLASS by Robert John Morton (all rights reserved) UK-YE572246C Belo Horizonte-MG AWT version: 31 July 2006, Swing version: 5 May 2012 Jarred version: 6 February 2017 */ /* HTML call for this applet [un-jarred]: HTML call for this applet [jarred]: */ import javax.swing.*; // the Swing GUI system import java.awt.*; // abstract windowing toolkit import java.lang.System; // accent support for Portuguese import java.awt.event.*; // for the new-fangled 1.1 event handling public class altrel extends JApplet implements Runnable { private static final int SL = 16; // maximum string length for quantities private Font font = new java.awt.Font("System", Font.BOLD, 13); private boolean FTT = true; //run() method's "first time through" flag private int mp = 6, // size of the module/phantom references array ds = 0, // default speed selection ls = 0, // language selection parameter 0=English 1=Português delay = 50, // delay 50 loop cycles to give Swing time to initialse XEI=530, // image buffer X-extent YEI=25, // image buffer Y-extent XOI=10, // image buffer X off-set YOI=35, // image buffer Y off-set XET=455, // text raster X-extent YET=50, // text raster Y-extent XOT=85, // text raster X off-set YOT=90; // text raster Y off-set private long // cycle periods of the applet according to warp speed P[] = {30, 30, 30, 30, 30, 18, 10}, //20, 20, 20, 20, 20, 12, 7 p = 30, // applet's prescribed cycle period (milliseconds) T = 0, // System Time at end of last cycle δT = 0, // change in System Time between last cycle and this cycle st = 10; // required sleep time (P - δT) private double // FOR newJourney() AND selectVelocity() MV[] = { // velocity selection options 0.1, 0.5, 0.8, 1.0, 1.2, 5.0, 9.9 }, mv, // (default) module velocity - outbound mvr, // module velocity - return pv, // phantom velocity - outbound pvr, // phantom velocity - return TDo = 0, // time dilation of phantom's clock - outbound TDr = 0; // time dilation of phantom's clock - return private Image IB = null, // reference for an off-screen image buffer TB = null; // reference for an off-screen test raster private Graphics gi = null, // graphics context for the off-screen image gt = null; // graphics context for the off-screen image private journey MP[] = new journey[mp], // for references to instances of journey class r; // current instance of 'journey' private butpan bp; private volatile Thread TH; // declare a thread reference variable public void init() { //INITIALIZE THE APPLET ds = Integer.valueOf(getParameter("selvel")).intValue(); ls = Integer.valueOf(getParameter("lang")).intValue(); try { // Attempt Phase 1 of GUI build javax.swing.SwingUtilities.invokeAndWait( new Runnable() { public void run() { init1(); } } ); } catch(Exception e) { showStatus("couldn't create Swing GUI"); } } /* THE FIRST PHASE OF INITIALISATION: Since this involves the creation and set-up of Swing widgets, it must be done on the Event Dispatching thread. It is invoked from the applet's init() method above. */ private void init1() { /* Get the content pane reference of the JApplet and set it to allow the control panels to be laid out manually. */ Container cp = getContentPane(); cp.setLayout(null); setBackground(new Color(240, 240, 240)); String SA[][] = { //annotations in both languages {"Module Velocity", "Phantom Velocity", "Phantom Clock Speed", "Outward:", "Return:"}, {"Veloc. do Módulo", "Veloc. do Fantasma", "Relógio do Fantasma", "Saída:", "Retorno:"} }; /* Copy the annotations for the selected language from the SA[][] array to the SB[] array. */ String SB[] = new String[5]; for(int i = 0;i < 5;i++) SB[i] = SA[ls][i]; /* Create a set of panel labels in the selected language, add each to the applet pane then set its position and size on the pane. */ JLabel a = new JLabel(SB[0]); JLabel b = new JLabel(SB[1]); JLabel c = new JLabel(SB[2]); JLabel d = new JLabel(SB[3], JLabel.RIGHT); JLabel e = new JLabel(SB[4], JLabel.RIGHT); cp.add(a); a.setBounds( 85, 65, 145, 20); cp.add(b); b.setBounds(230, 65, 170, 20); cp.add(c); c.setBounds(385, 65, 160, 20); cp.add(d); d.setBounds( 10, 93, 70, 20); cp.add(e); e.setBounds( 10,115, 70, 20); /* Create an off-screen image buffer and get its graphics context reference. */ IB = createImage(XEI, YEI); gi = IB.getGraphics(); /* Create the off-screen test raster, get its graphics context reference and set up an annotation font for it. */ TB = createImage(XET, YET); gt = TB.getGraphics(); gt.setFont(font); /* Create an instance of the button panel, add it to the applet's content pane then set its position and size within the applet pane. */ bp = new butpan(this, ds, ls); cp.add(bp); bp.setBounds(0, 0, 550, 30); } public void paint(Graphics g) { //PAINT THE IMAGE SO FAR g.drawImage(IB,XOI,YOI,null); //draw from the off-screen image buffer g.drawImage(TB,XOT,YOT,null); //draw from the off-screen text raster bp.repaint(); //re-paint button panel whenever text changes } /* Create and start the run() thread, then note the System Time at which the run() thread was started. Returns a call to run(). */ public void start() { TH = new Thread(this); TH.start(); T = System.currentTimeMillis(); } public void run() { while(TH != null) { //while the Applet's thread remains alive... /* Get the elapsed time 'δT' since the previous pass. If 'δT' is less than the prescribed period [time frame] 'p', set the sleep time 'st' to the time now remaining in this period. Otherwise, set the sleep time 'st' to a default of 10 milliseconds. */ δT = System.currentTimeMillis() - T; if(δT < p) st = p - δT; else st = 10; /* Put the run() thread to sleet for this remaining time 'st' and while the thread is asleep, catch any external event exceptions but do nothing about them since they are normal and expected. */ try{TH.sleep(st);} catch(InterruptedException e){} // Note current System Time as the start time of the next period T = System.currentTimeMillis(); /* The following is to delay the advance of module and phan- tom positions in order to allow Swing time to terminate its GUI build after it has signalled that it has already done so as follows. Provided initialisation delay has expired, set the default module velocity and unset the 'first time through' flag; else, update the module and phantom positions*/ if(--delay <= 0) { if(FTT) { selectVelocity(ds); FTT = false; } else advance(); } } } public void stop(){ TH = null; } // kill this applet's thread void advance() { // ADVANCE MODULE AND PHANTOMS ALONG THEIR JOURNEYS for(int i = 0; i < mp; i++) { // for all possible module/phantom // journeys ... r = MP[i]; // reference to this journey instance if(r != null) { // if this jouney is still in progress r.show(gi); // update this journey repaint(); // re-display the image buffer on the applet panel if(r.getmh()) /*if module has reached home on return journey then, if a new journey has not yet been launched, start a new journey; else, if both phantoms have arrived, kill this journey. */ if(!r.getnl()) newJourney(i); else if(r.getah()) MP[i] = null; } } } private void newJourney(int k) { // CREATE A NEW JOURNEY for(int j = 0; j < mp; j++) { //for all possible journey objects ... //cycle round to the start of the references array. if(++k >= mp) k = 0; /* if no live journey exists in this element, spawn a new journey object, signal that new launch has been done and stop the seach for an element with a null reference. */ if(MP[k] == null) { MP[k] = new journey(mv,pv,pvr); r.setnl(); break; } } //end of search loop } // CALLED FROM run() AND THE BUTTON SELECTOR CLASS 'butpan' void selectVelocity(int ds) { /* Set up the newly selected velocity, then set the cycle period to best illustrate the selected velocity. Set the module's return velocity as the reverse of its outbound velocity. */ mv = MV[ds]; p = P[ds]; mvr = -mv; /* Compute the phantom's velocity (as fraction of the module's velocity) and its time dilation for the out- ward journey. Do the same for the return journey. */ pv = 1 /(1 + mv); TDo = pv; pvr = 1 /(1 + mvr); TDr = pvr; /* Kill all the journey objects by setting all the journey instance references to null. */ for(int i = 0; i < mp; i++) MP[i] = null; // spawn a new journey object (generate a new one) MP[0] = new journey(mv,pv,pvr); /* Display the velocity figures in the off-screen text raster, then call repaint(); in order to redisplay the figures via update(). The graphics context for this operation (gt) is a universal variable within this applet, so it does not need to be passed to the showText() method. */ // paint the off-screen raster with the background colour gt.setColor(Color.black); gt.fillRect(0,0,XET,YET); gt.setColor(Color.white); // set pain colour to light grey showFigures(19,mv,pv * mv,TDo); // display the graphics panel showFigures(41,mvr,pvr * mvr,TDr); gi.setColor(Color.lightGray); gi.fillRect(0,0,XEI,YEI); repaint(); // display off-screen images on applet panel } private void showFigures(int l, double m, double p, double t) { String sm = "" + m, // make string version of module velocity sp = "" + p, // phantom velocity st = "" + t; // time dilation if(ls == 1) { sm = sm.replace('.', ','); // subst comma for decimal point sp = sp.replace('.', ','); // in Portuguese version st = st.replace('.', ','); } if(m >= 0) sm = " " + sm; // insert a space in lieu of a + sign if(p >= 0) sp = " " + sp; if(t >= 0) st = " " + st; if(sp.length() > SL) sp = sp.substring(0, SL); // limit the length of if(st.length() > SL) st = st.substring(0, SL); // the data strings gt.drawString(sm + "c", 10, l); // display the module velocity gt.drawString(sp + "c", 145, l); // display the phantom velocity gt.drawString(st, 300, l); // display the time dilation } } class journey { // HANDLES ONE ROUND TRIP OF A MODULE AND ITS PHANTOM private boolean mh = false, // true means inbound module has arrived back home pa = false, // true means outbound phantom has reached distant star ph = false, // true means inbound phantom has reached home nl = false, // new journey not yet launched from this instance bx1 = false, // true = outbound module has been drawn on the screen bx2 = false, // true = inbound module has been drawn on the screen bd1 = false, // true = outbound phantom has been drawn on the screen bd2 = false, // true = inbound phantom has been drawn on the screen // TRUE when flash of return phantom has been displayed (for 1.0c only) flashed = false, wiped = false, // true = flash has been wiped (for 1.0c only) ftt = true; // true = first time through private static int X = 530, // maximum extent of journey (in pixels) Y = 25; // bottom point of trace lines private int x1 = 0, // last time's screen x-coordinate of outbound module x2 = 0, // last time's screen x-coordinate of inbound module d1 = 0, // last time's screen x-coordinate of outbound phantom d2 = 0; // last time's screen x-coordinate of inbound phantom private double X1 = 0, // outbound distance of module from observer X2 = X, // return distance of module from observer D1 = 0, // distance of outbound phantom from observer D2 = 0, // distance of inbound phantom from observer mv, // module velocity - outbound mvr, // module velocity - return pv, // phantom velocity - outbound pvr; // phantom velocity - return journey(double a, double b, double c) { // JOURNEY INSTANCE CONSTRUCTOR mv = a; mvr = -a; pv = b; pvr = c; // outbound and return velocities } // for the module and its phantom // These 3 methods allow the applet to access this class's flags boolean getmh() { return (mh); } boolean getah() { return (pa && ph); } boolean getnl() { return (nl); } // Allows applet to set the "new launch expedited" flag herein. void setnl() { nl = true; } void show(Graphics gi) { gi.setColor(Color.lightGray); // set wipe colour // If selected velocity is c and flash has not yet been wiped ... if(mv == 1 && !wiped) if(ftt) // if first time through, ftt = false; // don't wipe flash but kill the ftt flag else { // else [not first time through] gi.fillRect(0,0,530,25); // wipe the graphics field's display area wiped = true; // signal that it has been wiped gi.setColor(Color.black); // then set the module colour } /* Else wipe last time's outbound module, inbound module, outbound phantom and inbound phantom. */ else { if(bx1){gi.drawLine(x1,0,x1,Y); bx1 = false;} if(bx2){gi.drawLine(x2,0,x2,Y); bx2 = false;} if(bd1){gi.drawLine(d1,0,d1,Y); bd1 = false;} if(bd2){gi.drawLine(d2,0,d2,Y); bd2 = false;} } if(D1 < X) { // if outbound phantom not yet reached distant the star /* if the outbound module has not yet reached the distant star, set the module colour, compute its window x coordinate, display the line representing the outbound module and signal that out- bound module has been displayed. */ if(++X1 < X) { gi.setColor(Color.black); x1 = (int)X1; gi.drawLine(x1, 0, x1, Y); bx1 = true; } D1 = X1 * pv; // update distance of outbound phantom from observer /* if phantom currently within display area, set phantom colour, compute the phantom's window x-coordinate, display the outbound phantom in its new position, and signal that outbound phantom has been displayed. */ if (D1 < X && D1 > 0) { gi.setColor(Color.white); d1 = (int)D1; gi.drawLine(d1, 0, d1, Y); bd1 = true; } } else pa = true; // else, the phantom has arrived at the distant star if(X1 > X) { // if the outbound module has already reached the star X2--; // decrement the return distance /* If the INBOUND MODULE has not yet reached home, set its colour, compute its new window x-coordinate, display the line representing it and signal that it has been displayed. */ if (X2 < X && X2 > 0) { gi.setColor(Color.black); x2 = (int)X2; gi.drawLine(x2, 0, x2, Y); bx2 = true; } else { // else the module has reached home again mh = true; // so, set the 'module is home' flag /* if module velocity is c and return phantom flash has not yet been displayed, set the flash colour, flash the whole graphics field's display area and signal that the flash has been displayed. */ if(mv == 1 && !flashed) { gi.setColor(Color.white); gi.fillRect(10, 30, 500, 16); flashed = true; } } D2 = X2 * pvr; // update distance of inbound phantom from observer /* If the inbound phantom is within the display area, set its colour, compute its window x-coordinate, display it and signal that it has been displayed. */ if(D2 < X && D2 > 0) { gi.setColor(Color.white); d2 = (int)D2; gi.drawLine(d2, 0, d2, Y); bd2 = true; } else ph = true; // else signal that phantom has reached home again } } } class butpan extends JPanel { // VELOCITY SELECTOR BUTTONS private altrel ap; butpan(altrel ap, int ds, int ls) { this.ap = ap; boolean DS[] = {false, false, false, false, false, false, false}; DS[ds] = true; //default speed received from HTML call parameter String SA[] = {"Velocity:", "Velocidade:"}, SB[] = {"0.1", "0.5", "0.8", "1.0", "1.2", "5.0", "9.9"}, s = SA[ls]; // select the language required for annotation if(ls == 1) // substitute comma for point if Portuguese for(int i = 0; i < 7; i++) SB[i] = SB[i].replace('.', ','); // Create 'Title' label and add it to the applet pane. JLabel L = new JLabel(s); add(L); ButtonGroup G = new ButtonGroup(); // group for speed selector buttons JRadioButton a = new JRadioButton(SB[0] + " c", DS[0]); JRadioButton b = new JRadioButton(SB[1] + " c", DS[1]); JRadioButton c = new JRadioButton(SB[2] + " c", DS[2]); JRadioButton d = new JRadioButton(SB[3] + " c", DS[3]); JRadioButton e = new JRadioButton(SB[4] + " c", DS[4]); JRadioButton f = new JRadioButton(SB[5] + " c", DS[5]); JRadioButton g = new JRadioButton(SB[6] + " c", DS[6]); /* Add all the buttons to the button-group so that they act together as a set of radio-buttons. Then add all the buttons to this panel (i.e. mount them on the panel). */ G.add(a); G.add(b); G.add(c); G.add(d); G.add(e); G.add(f); G.add(g); add(a); add(b); add(c); add(d); add(e); add(f); add(g); // Create for each button a listener to detect when it is pressed. a.addItemListener(new velbut(0, this)); b.addItemListener(new velbut(1, this)); c.addItemListener(new velbut(2, this)); d.addItemListener(new velbut(3, this)); e.addItemListener(new velbut(4, this)); f.addItemListener(new velbut(5, this)); g.addItemListener(new velbut(6, this)); } // CALLED BY THE LISTENER CLASS BELOW to set up a newly selected velocity void selectVelocity(int i) { ap.selectVelocity(i); } } // LISTENS FOR EVENTS FROM VELOCITY SELECTOR BUTTONS class velbut implements ItemListener { int id; // one of the above events butpan bp; // application that called: always the above class /* Constructor for a new checkbox event: sets 'id' number of this instance of 'velbut' and the reference to this instance of the above class from which it came. (only one instance anyway). */ public velbut(int id, butpan bp) { this.id = id; this.bp = bp; } // an event has occurred from button 'id' public void itemStateChanged(ItemEvent e) { switch(id) { // communicate the selection to the above class case 0: bp.selectVelocity(0); break; case 1: bp.selectVelocity(1); break; case 2: bp.selectVelocity(2); break; case 3: bp.selectVelocity(3); break; case 4: bp.selectVelocity(4); break; case 5: bp.selectVelocity(5); break; case 6: bp.selectVelocity(6); break; } } }