/** Alternative Relativity Demonstrator MAIN APPLET CLASS by Robert John Morton (all rights reserved) Belo Horizonte-MG AWT version: 31 July 2006, Swing version: 5 May 2012 Jarred version: 6 February 2017 */ 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 import java.awt.image.BufferedImage; public class altrel extends JPanel 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 // Integer values which are private to this class XE = 552, // Horizontal extent of window and JFrame. 550 + 2 YE = 175, // Vertical extent of window and JFrame. 150 + 25 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 construct 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,10,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 BufferedImage IB = null, // reference for an off-screen image buffer TB = null; // reference for an off-screen test raster private Graphics2D 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 Thread T; // declare a thread reference variable altrel(int XE, int YE, int ls) { this.XE = XE; this.YE = YE; this.ls = ls; ds = 0; setLayout(null); // allow control panels to be laid out manually setBounds(0,0,XE,YE); // the JPanel fills the whole of the JFrame area IB = new BufferedImage(XEI,YEI,BufferedImage.TYPE_INT_RGB); gi = IB.createGraphics(); // create the off-screen image buffer TB = new BufferedImage(XET,YET,BufferedImage.TYPE_INT_RGB); gt = TB.createGraphics(); // create the off-screen text raster gt.setFont(font); // set up the annotation font bp = new butpan(this,ds,ls); // create button panel add(bp); // add it to the applet's content pane bp.setBounds(0,0,550,30); // set its position and size within JPanel T = new Thread(this); // creating the thread object T.start(); // and starting it running // Note the System Time when the first time frame will end t = System.currentTimeMillis() + p; } /* NOTE: because I have overridden paint() in the main JPanel, JLabels won't show up so I have to repaint annotations each time. I should really repaint the Images in a child JPanel. Naughty boy! */ 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 when text changes } public void run() { while(T != null) { // while the run thread remains alive... // time remaining in the current time frame δT = t - System.currentTimeMillis(); if(δT < st) δT = st; // enforce minimum sleep time // sleep while allowing external events to interrupt try{Thread.sleep(δT);} // triggers a 'reset' if app is restarted catch(InterruptedException e){} // set the System Time at which the nect time frame will end t = System.currentTimeMillis() + p; // to allow Swing to untwist its knickers before starting the loop if(--delay <= 0) { if(FTT) { // if this is the first time through selectVelocity(ds); // set the default module velocity FTT = false; // kill the 'first time through' flag } else advance(); // update the module and phantom positions } } } void advance() { // for all possible module/phantom journeys for(int i = 0; i < mp; i++) { 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 JPanel if(r.getmh()) // if module has reached home on return journey if(!r.getnl()) // if new journey has not yet been launched newJourney(i); // start a new journey else if(r.getah()) // if both phantoms have arrived, MP[i] = null; // kill this journey } } } private void newJourney(int k) { for(int j = 0; j < mp; j++) { // for all possible journey objects if(++k >= mp) // cycle round to the start k = 0; // of the references array /* If no live journey exists in this element, spawn a new journey object, signal that new launch has been done, stop seaching for an element with a null reference and bail out of the for() loop. */ 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) { mv = MV[ds]; // set up the newly selected velocity p = P[ds]; // set cycle period to best illustrate the selected velocity mvr = -mv; // module's return velocity = reverse of outbound velocity // phantom's velocity (as a fraction of the module velocity) outbound pv = 1 / (1 + mv); TDo = pv; // compute time dilation on outward journey // phantom's velocity (as a fraction of the module velocity) inbound pvr = 1 / (1 + mvr); TDr = pvr; // compute time dilation on return journey /* Kill all journey objects by setting all journey instance references = null. */ for(int i = 0; i < mp; i++) MP[i] = null; MP[0] = new journey(mv,pv,pvr); // spawn a new journey object /* 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. */ gt.setColor(Color.black); // background colour for off-screen raster gt.fillRect(0,0,XET,YET); // paint background of off-screen raster gt.setColor(Color.white); // set text colour showFigures(19,mv,pv*mv,TDo); // display the outbound journey figures showFigures(41,mvr,pvr*mvr,TDr); // display the return journey figures gi.setColor(Color.lightGray); // set background colour for this panel gi.fillRect(0,0,XEI,YEI); // fill the whole panel with this colour repaint(); // display off-screen images on JPanel } 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",128,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 flashed = false, // true = flash of return phantom displayed (1.0c only) 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 // outbound and return velocities for the module and its phantom mv = a; mvr = -a; pv = b; pvr = c; } // these methods allow applet to access this class's flags boolean getmh() {return (mh);} boolean getah() {return (pa && ph);} boolean getnl() {return (nl);} // allows applet to set "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 not yet wiped if(mv == 1 && !wiped) if(ftt) // if first time through ftt = false; //don't wipe flash but kill ftt flag else { //else gi.fillRect(0,0,530,25); //wipe the graphics field's display area wiped = true; //and signal that it has been wiped gi.setColor(Color.black); //set module colour } else { //wipe last time's: if(bx1){gi.drawLine(x1,0,x1,Y); bx1 = false;} //outbound module if(bx2){gi.drawLine(x2,0,x2,Y); bx2 = false;} //inbound module if(bd1){gi.drawLine(d1,0,d1,Y); bd1 = false;} //outbound phantom if(bd2){gi.drawLine(d2,0,d2,Y); bd2 = false;} //inbound phantom } if(D1 < X) { // if outbound phantom not yet reached the distant star... if(++X1 < X) { // if outbound module not yet reached distant star gi.setColor(Color.black); // set module colour x1 = (int)X1; // compute its window x coordinate gi.drawLine(x1,0,x1,Y); // display outbound module line bx1 = true; // =outbound module has been displayed } D1 = X1 * pv; // update distance of outbound phantom from observer /* if the phantom is currently within display area, set phantom colour, compute its window x-coordinate, display the outbound phantom in new position and set flag to in- dicate that the outbound phantom has now been displayed. */ if(D1 < X && D1 > 0) { gi.setColor(Color.white); d1 = (int)D1; gi.drawLine(d1,0,d1,Y); bd1 = true; } } else // otherwise... pa = true; // the phantom has already arrived at the distant star if (X1 > X) { // if the outbound module has already reached the star, X2--; // decrement the return distance /* If the module is still en-route on its return journey, set module colour, compute its new window x-coordinate, display the inbound module line and set flag to show that inbound module has been displayed. */ if(X2 < X && X2 > 0) { gi.setColor(Color.black); x2 = (int)X2; gi.drawLine(x2,0,x2,Y); bx2 = true; } else { // otherwise mh = true; // the module has reached home again /* if the module velocity is c and the 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 done. */ if(mv == 1 && !flashed) { gi.setColor(Color.white); // flash colour gi.fillRect(10,30,500,16); // flood display area flashed = true; // set the done-flag } } D2 = X2 * pvr; // update distance of inbound phantom from observer /* if the inbound phantom is within the display area, set phantom colour, compute its window x-coordinate, display the inbound phantom and signal that inbound phantom has been displayed. */ if(D2 < X && D2 > 0) { gi.setColor(Color.white); d2 = (int)D2; gi.drawLine(d2,0,d2,Y); bd2 = true; } else // otherwise... ph = true; // signal that the 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:"}; String s = SA[ls]; //select the language required for annotation String SB[] = {"0.1","0.5","0.8","1.0","1.2","5.0","9.9"}; if(ls == 1) //substitute comma for point if Portuguese for(int i = 0; i < 7; i++) SB[i] = SB[i].replace('.',','); JLabel L = new JLabel(s); add(L); //add the title label to the applet panel ButtonGroup G = new ButtonGroup(); //group for speed selector buttons // 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 buttons to button-group so they act together as a set of radio-buttons. */ G.add(a); G.add(b); G.add(c); G.add(d); G.add(e); G.add(f); G.add(g); // Add all the buttons to this panel (i.e. mount them on the panel). add(a); add(b); add(c); add(d); add(e); add(f); add(g); // Create for each button a listener to detect when each button 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)); } void selectVelocity(int i) { // CALLED BY THE LISTENER CLASS BELOW... ap.selectVelocity(i); // set up the newly selected velocity } } // 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 public velbut(int id, butpan bp) { // constructor for a new checkbox event this.id = id; // set id number of this instance of 'velbut' this.bp = bp; // set the reference to this instance of the above class } // from which it came. (only one instance anyway). // 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); } } }