1  /*
  2   * Steven Herbst
  3   * AP Physics 2004 - 2005
  4   * Thomas Jefferson HS for Science and Technology
  5   * Instructor: Dr. John Dell
  6   * 
  7   * Software licensed under GNU General Public License (GPL)
  8   * See http://www.gnu.org/copyleft/gpl.html for details
  9   */
 10  import java.util.*;
 11  import java.text.DecimalFormat;
 12  
 13  // Grid Constants
 14  int TICK_SCALE = 10; // Relative spacing of tick marks
 15  int TICK_LENGTH = 2;
 16  int gridX = 75;
 17  int gridY = 75;
 18  int translateX, translateY;
 19  // Point Constants
 20  int DOT_RADIUS = 1;
 21  float FLOAT_ERROR = 1e-10;
 22  DecimalFormat displayFormat;
 23  // Line Constants
 24  int START_LINE = 1;
 25  // Text Constants
 26  int TEXT_HEIGHT = 8;
 27  int TEXT_WIDTH = 8;
 28  int TEXT_DISPLACEMENT = 40;
 29  int TRANSFORM_DISPLACEMENT = 60;
 30  // Menu Constants
 31  int minimumTime = 500;  // Menu selection considered invalid if menu is open for less than minimumTime
 32  // Hover Constants
 33  float LINE_DIST_MARGIN = 4.0;
 34  float POINT_DIST_MARGIN = 5.0;
 35  
 36  // Global variables
 37  // Relativity
 38  float beta;
 39  float gamma;
 40  float[][] transformMatrix, inverseMatrix;
 41  // Display
 42  float zoom = 1;
 43  Point points, transform, lineStart, transformLineStart;
 44  // Frames
 45  State states, sentinel;
 46  // Mouse
 47  boolean dragged; // Used to determine context of mouse release
 48  // Menu
 49  Menu menu;
 50  int menuTime;  // Time when menu was opened
 51  boolean pointAdded; // Used to determine if menu should open
 52  // I/O
 53  boolean saveFrame;
 54  //GUI 
 55  LinkedList items;
 56  // Labels
 57  Label info;
 58  Label graphLabel, transformLabel;
 59  Label lineLabel, transformLineLabel;
 60  // Buttons
 61  Button layerButtons[]; // Contains the 4 buttons that allow user to navigate frames
 62  // Sliders
 63  Slider pslider, sslider, xslider, yslider, zslider;
 64  
 65  /* Class GUI_Item
 66   * Subclasses:
 67   * Label
 68   * Menu
 69   * MenuItem
 70   * Button
 71   * Slider
 72   */
 73  abstract class GUI_Item{
 74    abstract void display();
 75    abstract void update();
 76  }
 77  class Label extends GUI_Item{
 78    int x, y;
 79    String text;
 80    Label(String text, int x, int y){
 81      this.x = x;
 82      this.y = y;
 83      this.text = text;
 84      items.add(this);
 85    }
 86    void display(){
 87      display(0, 0, 0);
 88    }
 89    void display(int r, int g, int b){
 90      fill(r, g, b);
 91      text(text, x, y - TEXT_HEIGHT/ 2);
 92    }
 93    void update(){
 94    }
 95    void setText(String text){
 96      this.text = text;
 97    }
 98    String getText(){
 99      return text;
100    }
101  }
102  class MenuItem extends GUI_Item{
103    String text;
104    int x, y;
105    int width,  height;
106    static final int errorX = 5;
107    static final int bufferX = 5;
108    static final int bufferY = 10;
109    boolean selected = false;
110    Label myLabel;
111    MenuItem(String text, int x, int y){
112      this.text = text;
113      this.x = x;
114      this.y = y;
115      myLabel = new Label(text, x + bufferX, y + bufferY + TEXT_HEIGHT);
116      width = 2 * bufferX + text.length() * TEXT_WIDTH;
117      height = 2 * bufferY + TEXT_HEIGHT;
118    }
119    boolean over(){
120      return x - errorX <= mouseX         && 
121        mouseX <= x + width + errorX &&
122        y <= mouseY                  &&
123        mouseY <= y + height;           
124    }
125    void update(){
126      if (over()){
127        selected = true;
128      }
129      else{
130        selected = false;
131      }
132    }
133    void display(){
134      if (selected){
135        fill(200, 200, 200);
136      }
137      else{
138        fill(225, 225, 225);
139      }
140      stroke(0, 0, 0);
141      rect(x, y, width, height);
142      myLabel.display();
143    }
144  }
145  class Menu extends GUI_Item{
146    LinkedList menuItems;
147    MenuItem selected;
148    int urx, ury,  maxWidth;
149    Menu(int urx, int ury){
150      this.urx = urx;
151      this.ury = ury;
152      menuItems = new LinkedList();
153    }
154    void addMenuItem(String text){
155      MenuItem newItem = new MenuItem(text, urx, ury + menuItems.size() * (2 * MenuItem.bufferY + TEXT_HEIGHT));
156      if (maxWidth < newItem.width){
157        maxWidth = newItem.width;
158      }
159      menuItems.add(newItem);
160    }
161    void update(){
162      selected = null;
163      Iterator it = menuItems.iterator();
164      MenuItem tmp;
165      while(it.hasNext()){
166        tmp = (MenuItem)it.next();
167        tmp.width = maxWidth;
168        tmp.update();
169        if (tmp.selected){
170          selected = tmp;
171        }
172      }
173    }
174    void display(){
175      Iterator it = menuItems.iterator();
176      while(it.hasNext()){
177        ((GUI_Item)it.next()).display();
178      }
179    }
180    void clear(){
181      Iterator it = menuItems.iterator();
182      MenuItem tmp;
183      while(it.hasNext()){
184        tmp = (MenuItem)it.next();
185        items.remove(tmp.myLabel);
186        it.remove();
187      }
188    }
189    String getSelected(){
190      if (selected != null){
191        return selected.text;
192      }
193      else{
194        return null;
195      }
196    }
197  }
198  class Button extends GUI_Item{
199    int x, y, width, height;
200    Label myLabel;
201    boolean pressed, enabled = true;
202    static final int errorMarginX = 0;
203    static final int errorMarginY = 5;
204    static final int depth = 1;
205    static final int padding = 3;
206    Button(String text, int x, int y){
207      this.x = x;
208      this.y = y;
209      this.width = text.length() * TEXT_WIDTH;
210      this.height = TEXT_HEIGHT + 2 * padding;
211      myLabel = new Label(text, x - width/ 2, y + padding);
212      items.add(this);
213    }
214    Button(String text, int x, int y, int width, int height){
215      this.x = x;
216      this.y = y;
217      this.width = width;
218      this.height = height;
219      myLabel = new Label(text, x - width/ 2, y);
220      items.add(this);
221    }
222    boolean over(){
223      return (x - width/ 2 - errorMarginX) <= mouseX  &&
224        mouseX <= (x + width/ 2 + errorMarginX)  &&
225        (y - height/ 2 - errorMarginY) <= mouseY &&
226        mouseY <= (y + height/ 2 + errorMarginY) ;
227    }
228    void update(){
229      if (mousePressed && over()){
230        if (enabled){
231          pressed = true;
232        }
233        pointAdded = true;
234      }
235    }
236    void display(){
237      if (enabled){
238        fill(225, 225, 225);
239      }
240      else{
241        fill(200, 200, 200);
242      }
243      if (pressed && mousePressed && over()){
244        rect(x - width/ 2 + depth, y - width/ 2 - depth, width, height);
245        myLabel.x = x - width/ 2 + depth;
246        myLabel.y = y - depth + TEXT_HEIGHT + padding;
247        myLabel.display();
248      }
249      else{
250        rect(x - width/ 2, y - width/ 2, width, height);
251        myLabel.x = x - width/ 2;
252        myLabel.y = y + TEXT_HEIGHT + padding;
253        myLabel.display();
254      }
255    }
256    void reset(){
257      pressed = false;
258    }
259    void setEnabled(boolean choice){
260      if (choice){
261        enabled = true;
262      }
263      else{
264        enabled = false;
265      }
266    }
267  }
268  class Slider extends GUI_Item{
269    int x, y, width, height, pos, center, displacement;
270    Label myLabel;
271    static final int errorMarginX = 5;
272    static final int errorMarginY = 15;
273    Slider(int x, int y, int width, int height, int displacement){
274      this.x = x;
275      this.center = x;
276      this.y = y;
277      this.width = width;
278      this.height = height;
279      this.displacement = displacement;
280      pos = 0;
281      items.add(this);
282    }
283    Slider(int x, int y, int width, int height, int displacement, String text){
284      this.x = x;
285      this.center = x;
286      this.y = y;
287      this.width = width;
288      this.height = height;
289      this.displacement = displacement;
290      pos = 0;
291      myLabel = new Label(text, x - displacement, y - height);
292      items.add(this);
293    }
294    boolean over(){
295      return (center - displacement - errorMarginX) <= mouseX  &&
296        mouseX <= (center + displacement + errorMarginX)  &&
297        (y - height/ 2 - errorMarginY) <= mouseY         &&
298        mouseY <= (y + height/ 2 + errorMarginY)         ;
299    }
300    void update(){
301      if (mousePressed && over()){
302        pointAdded = true;
303        if(abs(mouseX - center) < displacement){
304          x = mouseX;
305        }
306        else if (mouseX < center){
307          x = center - displacement;
308        }
309        else{
310          x = center + displacement;
311        }
312      }
313      if (abs(x - center) < 5){
314        x = center;
315      }
316    }        
317    void display(){
318      stroke(0, 0, 0);
319      fill(0, 0, 0);
320      line(center - displacement, y, center + displacement, y);
321      line(center - displacement, y - 5, center - displacement, y + 5);
322      line(center, y - 5, center, y + 5);
323      line(center + displacement, y - 5, center + displacement, y + 5);
324      rect(x - width/ 2, y - height/ 2, width, height);
325      if (myLabel != null){
326        myLabel.display();
327      }
328    }
329    float getStatus(){
330      return 1.0 * (x - center)/ displacement;
331    }
332    void setStatus(float s){
333      if (abs(s) <= displacement){
334        x = (int)(displacement * s + center);
335      }
336    }
337  }
338  class Point{
339    float x, y, z;
340    float t;
341    Point next;
342    int condition;
343    Point(){
344    }
345    Point(float x, float t){
346      this.x = x;
347      this.t = t;
348    }
349    Point(float x, float y, float t){
350      this.x = x;
351      this.y = y;
352      this.t = t;
353    }
354    Point(Point p){
355      this.x = p.x;
356      this.y = p.y;
357      this.t = p.t;
358      this.next = p.next;
359      this.condition = p.condition;
360    }
361    String toString(){
362      return "(" + displayFormat.format(x) + ", " + displayFormat.format(t) + ")";
363    }
364  }
365  class State{
366    State prev, next;
367    float beta, zoom, thetaX, thetaY, thetaZ;
368    Point points;
369    State(Point points){
370      this.points = points;
371    }
372    State(Point points, float beta, float zoom){
373      this.points = points;
374      this.beta = beta;
375      this.zoom = zoom;
376    }
377    void append(State s){
378      State tmpNode = next;
379      tmpNode.prev = s;
380      s.next = tmpNode;
381      s.prev = this;
382      this.next = s;
383    }
384  }
385  
386  void setup(){
387    size(600, 600, P3D);
388    textFont(loadFont("CourierNew36.vlw"), 12);
389    dragged = false;
390    pointAdded = false;
391    saveFrame = false;
392    translateX = 0;
393    translateY = 0;
394    points = null;
395    transform = null;
396    lineStart = null;
397    transformLineStart = null;
398    items = new LinkedList();
399    layerButtons = new Button[4];
400    sentinel = new State(null);
401    pslider = new Slider(3 * width/ 4, height/ 8, 10, 10, 50, "Beta:");
402    sslider = new Slider(3 * width/ 4, height/ 4, 10, 10, 50, "Scale:");
403    layerButtons[0] = new Button("<", width/ 4 - gridX, height/ 4 + gridY + 25);
404    layerButtons[1] = new Button(">", width/ 4 - gridX + 10, height/ 4 + gridY + 25);
405    layerButtons[2] = new Button("<", 3 * width/ 4 - gridX, 3 * height/ 4 + gridY + 25);
406    layerButtons[3] = new Button(">", 3 * width/ 4 - gridX + 10, 3 * height/ 4 + gridY + 25);
407    initMatrices();
408    initStates();
409    graphLabel = new Label("",3 * width/ 16, height/ 4 + gridY + 2 * TEXT_HEIGHT);
410    transformLabel = new Label("", 11 * width/ 16, 3 * height/ 4 + gridY + 2 * TEXT_HEIGHT);
411    lineLabel = new Label("", 3 * width/ 16, height/ 4 + gridY + 5 * TEXT_HEIGHT);
412    transformLineLabel = new Label("", 11 * width/ 16, 3 * height/ 4 + gridY + 5 * TEXT_HEIGHT);
413    info = new Label("", 8 * TEXT_WIDTH, height/ 2);
414    displayFormat = new DecimalFormat("#.##");
415    framerate(30);
416  }
417  void draw(){
418    pointAdded = false;
419    translateX = 0;
420    translateY = 0;
421    if (menu != null){
422      menu.update();
423      menu.display();
424    }
425    else{
426      background(255, 255, 255);
427      displayAll();
428      beta = pslider.getStatus();  
429      zoom = pow(10, sslider.getStatus());
430      setTranslate(width/ 4, height/ 4);
431      drawGrid();
432      plotPoints(points);
433      boolean pointSelected = false;
434      Point p = closestPoint(points, getMouseCoords());
435      if (p != null){
436        graphLabel.setText("" + p);
437        transformLabel.setText("" + doTransform(p));
438        pointSelected = true;
439      }
440      setTranslate(3 * width/ 4, 3 * height/ 4);
441      drawGrid();
442      updateTransform();
443      transformPoints();
444      plotPoints(transform);
445      p = closestPoint(transform, getMouseCoords());
446      if (p != null){
447        graphLabel.setText("" + reverseTransform(p));
448        transformLabel.setText("" + p);
449        pointSelected = true;
450      }
451      if (!pointSelected){
452        graphLabel.setText("");
453        transformLabel.setText("");
454      }
455      if (layerButtons[0].pressed || layerButtons[2].pressed){
456        prevState();
457        layerButtons[0].reset();
458        layerButtons[2].reset();
459      }
460      else if(layerButtons[1].pressed || layerButtons[3].pressed){
461        nextState();
462        layerButtons[1].reset();
463        layerButtons[3].reset();
464      }
465      if (saveFrame){
466        saveFrame = false;
467        saveFrame();
468      }
469    }
470  }
471  /* Display methods:
472   * displayLineInfo()
473   * setTranslate()
474   * drawGrid()
475   * plot()
476   * plotPoints()
477   * connect()
478   * addPoint()
479   */
480  void displayLineInfo(float slope, float interval){
481    if (1 < abs(slope)){
482      info.setText("\n\nTime-like\n\nInterval: " + interval);
483    }
484    else if (abs(slope) < 1){
485      info.setText("\n\nSpace-like\n\nInterval: " + interval);
486    }
487    else{
488      info.setText("\n\nLight-like\n\nInterval: " + interval);
489    }
490  }
491  void setTranslate(int x, int y){
492    translate(x - translateX, y - translateY);
493    translateX = x;
494    translateY = y;
495  }
496  void drawGrid(){
497    stroke(0, 0, 255);
498    line(-gridX, 0, 0, gridX, 0, 0);
499    line(0, -gridY, 0, 0, gridY,  0);
500    stroke(0, 255, 0);
501    line(-gridX, gridY, 0, gridX, -gridY, 0);
502    line(-gridX, -gridY, 0, gridX, gridY, 0);
503    stroke(0, 0, 0);
504    for(int i = 0; i <= gridX; i += zoom * TICK_SCALE)  /* Ticks for x-axis */
505    {
506      line(i, -TICK_LENGTH, 0, i, TICK_LENGTH, 0);
507      line(-i, -TICK_LENGTH, 0, -i, TICK_LENGTH, 0);
508    }
509    for(int j = 0; j <= gridY; j += zoom * TICK_SCALE)  /* Ticks for y-axis */
510    {
511      line(-TICK_LENGTH, j, 0, TICK_LENGTH, j, 0);
512      line(-TICK_LENGTH, -j, 0, TICK_LENGTH, -j, 0);
513    }
514  }
515  void plot(Point p){
516    plot(p, 255, 0, 0);
517  }
518  void plot(Point p, int r, int g, int b){
519    stroke(r, g, b);
520    fill(r, g, b);
521    int cx = (int)(p.x);
522    int cy = (int)(p.y);
523    int ct = (int)(-1.0 * p.t);
524    point(cx - 1, ct - 1, cy - 1);
525    point(cx - 1, ct - 1, cy); 
526    point(cx - 1, ct, cy); 
527    point(cx - 1, ct, cy - 1);
528    point(cx, ct - 1, cy);
529    point(cx, ct - 1, cy - 1); 
530    point(cx, ct, cy); 
531    point(cx, ct, cy - 1); 
532  }
533  void connect(Point p){
534    connect(p, 255, 0, 0);
535  }
536  void connect(Point p, int r, int g, int b){
537    if (p.next != null){
538      stroke(r, g, b);
539      line(p.x,  -p.t, p.y, p.next.x,  -p.next.t, p.next.y);
540    }
541  }
542  void plotPoints(Point head){
543    plotPoints(head,  false);
544  }
545  void plotPoints(Point head,  boolean connect){
546    plotPoints(head, null, connect);
547  }
548  void plotPoints(Point head, Point tail, boolean connect){
549    plotPoints(head, tail, connect, 255, 0,  0);
550  }
551  void plotPoints(Point head, Point tail, boolean connect, int r, int g, int b){
552    Point curPoint = head;
553    Point tmpPoint;
554    boolean lineDisplayed = false;
555    while(curPoint != null && !eq(curPoint, tail)){
556      tmpPoint = scale(curPoint);
557      if(inGraph(tmpPoint)){
558        tmpPoint.next = scale(curPoint.next);
559        if (connect || curPoint.condition == START_LINE){
560          float slope = computeM(curPoint, curPoint.next);
561          float y0 = computeB(curPoint, slope);
562          float d1 = computeD(scale(getMouseCoords(width/ 4, height/ 4), 1/zoom), slope, y0);
563          float d2 = computeD(scale(getMouseCoords(3 * width/ 4, 3 * height/ 4), 1/zoom), slope, y0);
564          if (d1 < LINE_DIST_MARGIN){
565            float interval = interval(curPoint, curPoint.next);
566            lineLabel.setText("Start: " + curPoint + "\nEnd: " + curPoint.next);
567            transformLineLabel.setText("Start: " + doTransform(curPoint) + "\nEnd: " + doTransform(curPoint.next));
568            lineDisplayed = true;
569            displayLineInfo(slope, interval);
570          }
571          if (d2 < LINE_DIST_MARGIN){
572            float interval = interval(curPoint, curPoint.next);
573            transformLineLabel.setText("Start: " + curPoint + "\nEnd: " + curPoint.next);
574            lineLabel.setText("Start: " + reverseTransform(curPoint) + "\nEnd: " + reverseTransform(curPoint.next));
575            lineDisplayed = true;
576            displayLineInfo(slope, interval);
577          }
578          connect(tmpPoint, r, g, b);
579        }
580        plot(tmpPoint, r, g, b);
581      }
582      curPoint = curPoint.next;
583    }
584    if (!lineDisplayed){
585      lineLabel.setText("");
586      transformLineLabel.setText("");
587    }
588  }
589  void addPoint(Point p){
590    if (points == null){
591      points = p;
592    }
593    else{
594      p.next = points;
595      points = p;
596    }
597  }
598  /* GUI Methods
599   * displayAll()
600   */
601  void displayAll(){
602    Iterator it = items.iterator();
603    GUI_Item tmp;
604    while(it.hasNext()){
605      tmp = (GUI_Item)it.next();
606      tmp.display();
607    }
608  }
609  /* Point methods
610   * closestPoint()
611   * scale()
612   * eq()
613   * dist()
614   */
615  Point closestPoint(Point head, Point p){
616    if (head == null){
617      return null;
618    }
619    Point closest = head;
620    float closestDist = dist(head, p);
621    float curDist;
622    head = head.next;
623    while(head != null){
624      curDist = dist(head, p);
625      if(curDist < closestDist){
626        closest = head;
627        closestDist = curDist;
628      }
629      head = head.next;
630    }
631    if (closestDist < POINT_DIST_MARGIN){
632      return closest;
633    }
634    else{
635      return null;
636    }
637  }
638  Point scale(Point p){
639    return scale(p, zoom);
640  }
641  Point scale(Point p, float zoom){
642    if (p != null){
643      Point tmp = new Point(p.x * zoom, p.y * zoom, p.t * zoom);
644      tmp.next = p.next;
645      tmp.condition = p.condition;
646      return tmp;
647    }
648    else{
649      return null;
650    }
651  }
652  boolean eq(Point p1, Point p2){
653    return p1          != null        &&
654           p2          != null        &&
655           abs(p1.x - p2.x) <= FLOAT_ERROR &&
656           abs(p1.y - p2.y) <= FLOAT_ERROR &&
657           abs(p1.t - p2.t) <= FLOAT_ERROR;
658  }
659  float dist(Point p1, Point p2){
660    return dist(p1.x, p1.t, p2.x, p2.t);
661  }
662  float dist(Point p1){
663    return dist(p1, new Point(0, 0, 0));
664  }
665  
666  /* Relativity methods
667   * updateGamma()
668   * initMatrices()
669   * updateTransform()
670   * reverseTransform()
671   * doTransform()
672   * transformPoints()
673   * interval()
674   * mult()
675   */
676  void updateGamma(){
677    gamma = 1.0/ sqrt(1.0 - beta*beta);
678  }
679  void initMatrices(){
680    transformMatrix = new float[3][3];
681    inverseMatrix = new float[3][3];
682    transformMatrix[2][2] = 1;
683    inverseMatrix[2][2] = 1;
684  }
685  void updateTransform(){
686    updateGamma();
687    transformMatrix[0][0] = gamma;
688    transformMatrix[0][1] = -gamma * beta;
689    transformMatrix[1][0] = -gamma * beta;
690    transformMatrix[1][1] = gamma;
691    float commonFactor = 1/ (gamma * (1 - beta * beta));
692    inverseMatrix[0][0] = commonFactor;
693    inverseMatrix[0][1] = beta * commonFactor;
694    inverseMatrix[1][0] = beta * commonFactor;
695    inverseMatrix[1][1] = commonFactor;
696  }
697  Point reverseTransform(Point p){
698    return doTransform(p, inverseMatrix);
699  }
700  Point doTransform(Point p, float[][] m){
701    float[][] coords = {{p.t}, {p.x}, {p.y}};
702    float[][] resultMatrix = mult(m, coords);
703    Point transformedPoint = new Point(resultMatrix[1][0], resultMatrix[2][0], resultMatrix[0][0]);
704    transformedPoint.next = p.next;
705    transformedPoint.condition = p.condition;
706    return transformedPoint;
707  }
708  Point doTransform(Point p){
709    return doTransform(p, transformMatrix);
710  }
711  void transformPoints(){
712    if (points == null){
713      transform = null;
714      return;
715    }
716    transform = new Point();
717    Point lastPoint = new Point();
718    Point curPoint = points;
719    Point newPoint = transform;
720    Point tmp;
721    while(curPoint != null){
722      newPoint.next = new Point();
723      tmp = doTransform(curPoint);
724      newPoint.x = tmp.x;
725      newPoint.y = tmp.y;
726      newPoint.t = tmp.t;
727      newPoint.condition = curPoint.condition;
728      lastPoint = newPoint;
729      newPoint = newPoint.next;
730      curPoint = curPoint.next;
731    }
732    lastPoint.next = null;
733  }
734  float interval(float x1, float t1, float x2, float t2){
735    return (x2 - x1)*(x2 - x1) - (t2 - t1) * (t2 - t1);
736  }
737  float interval(Point p){
738    return interval(p, p.next);
739  }
740  float interval(Point p1, Point p2){
741    return interval(p1.x, p1.t, p2.x, p2.t);
742  }
743  
744  float[][] mult(float[][] m1, float[][]m2){
745    float result[][] = new float[3][1];
746    result[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0];
747    result[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0];
748    result[2][0] = m1[2][0] * m2[0][0] + m1[2][1] * m2[2][0] + m1[2][2] * m2[2][0];
749    return result;
750  }
751  /* GUI methods
752   * getMouseCoords()
753   * mouseDragged()
754   * mouseReleased()
755   * mousePressed()
756   * updateAll()
757   */
758  Point getMouseCoords(){
759    return getMouseCoords(translateX, translateY);
760  }
761  Point getMouseCoords(int tx, int tt){
762    return getMouseCoords(new Point(mouseX, mouseY), tx, tt);
763  }
764  Point getMouseCoords(Point m, int tx, int tt){
765    float initX = m.x - tx;
766    float initT = -(m.t - tt);
767    return new Point(initX, initT);
768  }
769  void mouseDragged(){
770    updateAll();
771    dragged = true;
772  }
773  void mouseReleased(){
774    if (menu == null){
775      Point m = getMouseCoords(width/ 4, height/ 4);
776      if (dragged && inGraph(m.x, m.t) && lineStart != null){
777        Point lineEnd = scale(m, 1/zoom);
778        addPoint(lineEnd);
779        addPoint(lineStart);
780      }
781      else{
782        m = getMouseCoords(3 * width/ 4, 3 * height/ 4);
783        if (dragged && inGraph(m.x, m.t) && transformLineStart != null){
784          Point transformLineEnd = scale(m, 1/zoom);
785          Point lineStart = reverseTransform(transformLineStart);
786          Point lineEnd = reverseTransform(transformLineEnd);
787          addPoint(lineEnd);
788          addPoint(lineStart);
789        }
790      }
791    }
792    else{
793      if (minimumTime <= millis() - menuTime){
794        String choice = menu.getSelected();
795        if (choice != null){
796          if (choice.equals("Save screen image")){
797            saveFrame = true;
798          }
799          if (choice.equals("Help")){
800            info.setText(
801              "Click once to add a point; drag \n" +
802              "and release to draw a line.\n" +
803              "Position the mouse over a point\n" +
804              "or line to view information about it\n" +
805              "You can adjust the relative\n" +
806              "velocity between the two\n" +
807              "inertial frames by dragging the\n" + 
808              "\"Beta\" slider.  The scale\n" +
809              "of the spacetime diagrams can be\n" + 
810              "controlled with the \"Scale\"\n" +
811              "slider.  To save your work in one\n" + 
812              "set of frames and move on to\n" +
813              "a new set, just click one of the\n" +
814              "\">\" buttons.  At any point, \n" +
815              "you can return by clicking one of\n" + 
816              "the \"<\" buttons.  If you simply\n" +
817              "wish to clear the current point set,\n" +
818              "click \"Clear\" in the menu\n"
819              );
820          }
821          if (choice.equals("About")){
822            info.setText("Steven Herbst\n" +
823              "AP Physics, 2004 - 2005\n" +
824              "Thomas Jefferson HS for Science and Tech\n" +
825              "Instructor: Dr. John Dell\n"
826              );
827          }
828          if (choice.equals("Clear")){
829            points = null;
830            pslider.setStatus(0.0);
831            sslider.setStatus(0.0);
832            lineLabel.setText("");
833            transformLineLabel.setText("");
834            graphLabel.setText("");
835            transformLabel.setText("");
836            info.setText("");
837          }
838          if (choice.equals("Restart")){
839            setup();
840          }
841        }
842      }
843      menu.clear();
844      menu = null;
845    }
846  }
847  void mousePressed(){
848    updateAll();
849    Point m = getMouseCoords(width/ 4, height/ 4);
850    dragged = false;
851    Point tmp;
852    if (inGraph(m.x, m.t)){
853      lineStart = scale(m, 1/zoom);
854      lineStart.condition = START_LINE;
855      pointAdded = true;
856      addPoint(scale(m, 1/zoom));
857    }
858    else{
859      m = getMouseCoords(3 * width/ 4, 3 * height/ 4);
860      if (inGraph(m.x, m.t)){
861        transformLineStart = scale(m, 1/zoom);
862        transformLineStart.condition = START_LINE;
863        pointAdded = true;
864        addPoint(reverseTransform(scale(m, 1/zoom)));
865      }
866    }
867    if (pointAdded == false){
868      menu = new Menu(mouseX, mouseY);
869      if (!online){
870        menu.addMenuItem("Save screen image");
871      }
872      menu.addMenuItem("Help");
873      menu.addMenuItem("About");
874      menu.addMenuItem("Clear");
875      menu.addMenuItem("Restart");
876      menuTime = millis();
877    }
878  }
879  void updateAll(){
880    Iterator it = items.iterator();
881    GUI_Item tmp;
882    while(it.hasNext()){
883      tmp = (GUI_Item)it.next();
884      tmp.update();
885    }
886  }
887  /* Frame methods:
888   * initStates()
889   * saveState()
890   * nextState()
891   * newState()
892   * prevState()
893   */
894  void initStates(){
895    layerButtons[0].setEnabled(false);
896    layerButtons[2].setEnabled(false);
897    states = new State(null);
898    states.prev = sentinel;
899    states.next = sentinel;
900  }
901  void saveState(){
902    states.points = points;
903    states.beta = pslider.getStatus();
904    states.zoom = sslider.getStatus();
905  }
906  void nextState(){
907    saveState();
908    layerButtons[0].setEnabled(true);
909    layerButtons[2].setEnabled(true);
910    info.setText("");
911    if (states.next != sentinel){
912      states = states.next;
913      points = states.points;
914      pslider.setStatus(states.beta);
915      sslider.setStatus(states.zoom);
916    }
917    else{
918      newState();
919    }
920  }
921  void newState(){
922    states.append(new State(null));
923    states = states.next;
924    points = null;
925    pslider.setStatus(0.0);
926    sslider.setStatus(0.0);
927  }
928  void prevState(){
929    saveState();
930    if (states.prev != sentinel){
931      states = states.prev;
932      points = states.points;
933      pslider.setStatus(states.beta);
934      sslider.setStatus(states.zoom);
935    }
936    if (states.prev == sentinel){
937      layerButtons[0].setEnabled(false);
938      layerButtons[2].setEnabled(false);
939    }
940  }
941  /* Graph methods
942   * computeM()
943   * computeB()
944   * computeD()
945   * inGraph()
946   */
947  float computeM(Point p1, Point p2){
948    return (p2.t - p1.t)/ (p2.x - p1.x);
949  }
950  float computeB(Point p, float m){  
951    return p.t - m * p.x;
952  }
953  float computeD(Point p, float m, float b){
954    return abs((p.t - m * p.x - b)/ sqrt(m * m + 1));
955  }
956  boolean inGraph(Point p){
957    return inGraph(p.x, p.t);
958  }
959  boolean inGraph(float x, float y){
960    return inGraph(x, y, 0, 0);
961  }
962  boolean inGraph(float x, float y, int cx, int cy){
963    return cx - gridX <= x &&
964      x <= cx + gridX &&
965      cy - gridY <= y &&
966      y <= cx + gridY;
967  }