/** 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;
}
}
}