/**
 * Relativistischer Flug durch ein Gitter
 *
 * Corvin Zahn 1.3.2003
 *
 * Beispielprogramm zum in 
 *    ASTRONOMIE+RAUMFAHRT im Unterricht 2/2003
 * erschienenen Artikel
 *    Tempolimit: Lichtgeschwindigkeit
 *    -- Beobachtungen bei Hochgeschwindigkeitsfluegen --
 *    v. U. Kraus
 *
 * (Onlineversion des Artikels: http://www.tempolimit-lichtgeschwindigkeit.de/)
 *
 */

/**

Dieses Programm berechnet, wie ein relativistischer Flug durch ein Gitter
aussieht.


Komponenten
===========

Objekt-Beschreibung
-------------------

class Punkt, Kante, Objekt
allgemeine Datenstrukturen zur Speicherung eines beliebigen
"Drahtobjektes".


konkrete Definition eines Gitterobjektes
----------------------------------------

class Gitter
definiert ein Gitter der Groesse 10*10 am Koordinatenursprung.
Das Gitter ist parallel zur yz-Ebene ausgerichtet, die Kamera
bewegt sich laengs der x-Achse darauf zu.


Physik
------

class Objekt, Funktion projiziere_punkt
Abbildung eines Objektpunktes auf die Bildebene.


Grafische Darstellung, user interface
-------------------------------------

class RealFlug


verwendete Einheiten
====================

Zeiteinheit:    1 Sekunde
Laengeneinheit: 1 Lichtsekunde = 300000km

*/


import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;


/**
 * Konstanten
 */
class CONST
{
    // Lichtgeschwindigkeit = 1 Lichtsekunde/Sekunde
    public static final double c = 1;
    // l ist die Laenge der Kamera
    public static final double l = 1;
    // Startposition der Kamera
    public static final double kamera_start_pos = -20;
}


/**
 * beschreibt einen Punkt im dreidimensionalen Raum
 */
class Punkt
{
    /// erzeugt Punkt an den Koordinaten x,y,z
    public Punkt(double x, double y, double z)
    {
	s = new double[3]; //{x, y, z};
	s[0] = x; s[1] = y; s[2] = z; 
    }

    /// speichert die Koordinaten eines Punktes
    double s[];
}


/**
 * beschreibt eine Objektkante bestehend aus einer Reihe von Punkten
 */
class Kante
{
    /// erzeugt neue Kante, bestehend aus n Punkten
    public Kante(int n)
    {
	punkte = new Punkt[n];
    }

    /// speichert eine Liste v. Punkten
    Punkt punkte[];
}


/**
 * beschreibt ein Drahtgitterobjekt bestehend aus einer Anzahl von Kanten.
 * Dies ist eine Basisklasse fuer beliebige Objekte. Ein konkretes Objekt
 * wird in einer abgeleiteten Klasse (s.u. class Gitter) definiert.
 */
class Objekt
{
    /// Konstruktor
    public Objekt()
    {
    }
    
    /**
     * Projiziert den Punkt raumpunkt auf die Bildebene der bei
     * (kamera_pos, 0, 0) befindlichen, sich mit der Geschwindigkeit v
     * in x-Richtung bewegenden Kamera.
     * Falls die Projektion moeglich ist, werden in bildpunkt die Koordinaten
     * auf der Bildebene zurueckgegeben und der Rueckgabewert ist true.
     */
    public boolean projiziere_punkt(double kamera_pos, double v,
				    Punkt raumpunkt, double[] bildpunkt)
    {
	// Koordinaten des abzubildenden Punktes relativ zur Kamera
	// = -Richtungsvektor des Lichtstrahls
	double x = raumpunkt.s[0] - kamera_pos;
	double y = raumpunkt.s[1];
	double z = raumpunkt.s[2];
	// Richtungsvektor in einen Photonengeschwindigkeitsvektor umrechnen
	double cx = CONST.c * x/Math.sqrt(x*x + y*y + z*z);
	double cy = CONST.c * y/Math.sqrt(x*x + y*y + z*z);
	double cz = CONST.c * z/Math.sqrt(x*x + y*y + z*z);
	// Auftreffpunkt eines Photons auf der Bildebene berechnen:
	try {
	    // ll ist die Lorentz-kontrahierte Laenge der Kamera
	    double ll = CONST.l * Math.sqrt(1 - (v*v)/(CONST.c*CONST.c));
	    // Zeit, die ein Photon zum Durchqueren der Kamera benoetigt
	    double dt = ll / (v + cx);
	    // Die Zeit muss > 0 sein (sonst trifft das Photon nicht auf
	    // das Bildfeld)
	    if (dt < 0) return false;
	    // Der vom Photon getroffene Punkt auf dem Bildfeld wird durch 
	    // Multiplikation dieser Zeitspanne mit den Geschwindigkeits-
	    // komponenten parallel zum Bildfeld berechnet
	    bildpunkt[0] = cy * dt;
	    bildpunkt[1] = cz * dt;
	    return true;
	}
	catch (Exception ex) {
	    // falls wir zB durch Null geteilt haben, gibt es keine Projektion
	    return false;
	}
    }

    /**
     * Projiziere das Objekt auf die Bildebene der bei (x, 0, 0)
     * befindlichen, sich mit der Geschwindigkeit v in x-Richtung
     * bewegenden Kamera
     */
    public void draw(Graphics g, double x, double v)
    {
	double[] letzter_bildpunkt = null;
	// alle Kanten durchgehen
	for (int i=0; i<kanten.length; i++) {
	    boolean neue_linie = true;
	    Kante k = kanten[i];
	    // in jeder Kante alle Punkte durchgehen
	    for (int p=0; p<k.punkte.length; p++) {
		// Punkt auf die Bildebene projizieren
		double[] bildpunkt = new double[2];
		boolean ok = projiziere_punkt(x, v, k.punkte[p], bildpunkt);
		if (ok) {
		    if (neue_linie) {
			letzter_bildpunkt = bildpunkt;
			neue_linie = false;
		    }
		    else {
			int bild_hoehe = 450;
			int bild_breite = 600;
			// Linie vom letzten zum neuen Bildpunkt ziehen, ...
			int x0 = (int)(bild_breite*letzter_bildpunkt[0] +
				       0.5*bild_breite);
			int y0 = (int)(bild_breite*letzter_bildpunkt[1] +
				       0.5*bild_hoehe);
			int x1 = (int)(bild_breite*bildpunkt[0] +
				       0.5*bild_breite);
			int y1 = (int)(bild_breite*bildpunkt[1] +
				       0.5*bild_hoehe);
			// ... falls die Linie aufs Bild passt.
			if (x0 >= 0 && x0 < bild_breite &&
			    y0 >= 0 && y0 < bild_hoehe &&
			    x1 >= 0 && x1 < bild_breite &&
			    y1 >= 0 && y1 < bild_hoehe) {
			    g.drawLine(x0, y0, x1, y1);
			    // zuletzt gezeichneten Bildpunkt merken
			    letzter_bildpunkt = bildpunkt;
			}
			else {
			    // Wenn Linie nicht mehr aufs Bild passt, das
			    // naechste Mal eine neue Linie beginnen
			    neue_linie = true;
			}
		    }
		}
		else {
		    neue_linie = true;
		}
	    }
	}
    }

    /// speichert eine Liste v. Kanten
    Kante[] kanten;
}


/**
 * erzeugt ein Gitter
 */
class Gitter extends Objekt
{
    /**
     * Konstruktor
     */
    public Gitter()
    {
	// Gitter besteht aus 10 horizontalen u. 10 vertikalen Kanten und
	// hat eine Groesse v. 10 auf 10 Lichtsekunden
	int res = 10;
	double kantenlaenge = 10;
	double x_position = 0;
	kanten = new Kante[2*res];
	// horizontale Kanten
	for (int i=0; i<res; i++) {
	    double x = x_position;
	    double y = -kantenlaenge/2;
	    double z = -kantenlaenge/2+i*kantenlaenge/(res-1);
	    int kante_res = 100;
	    kanten[i] = hor_kante(x, y, z, kantenlaenge, kante_res);
	}
	// vertikale Kanten
	for (int i=0; i<res; i++) {
	    double x = x_position;
	    double y = -kantenlaenge/2+i*kantenlaenge/(res-1);
	    double z = -kantenlaenge/2;
	    int kante_res = 100;
	    kanten[res+i] = ver_kante(x, y, z, kantenlaenge, kante_res);
	}
    }
    
    /**
     * generiert horizontale Kante (y-Richtung) ab Position xpos ypos zpos
     * Laenge len, bestehend aus res Punkten
     */
    public Kante hor_kante(double xpos, double ypos, double zpos,
			   double len, int res)
    {
	Kante k = new Kante(res);
	for (int i=0; i<res; i++) {
	    k.punkte[i] = new Punkt(xpos, ypos + i*len/(res-1), zpos);
	}
	return k;
    }

    /**
     * generiert vertikale Kante (z-Richtung) ab Position xpos ypos zpos
     * Laenge len, bestehend aus res Punkten
     */
    public Kante ver_kante(double xpos, double ypos, double zpos,
			   double len, int res)
    {
	Kante k = new Kante(res);
	for (int i=0; i<res; i++) {
	    k.punkte[i] = new Punkt(xpos, ypos, zpos + i*len/(res-1));
	}
	return k;
    }

}


/**
 * Applet
 */
public class RelaFlug extends Applet implements ActionListener, Runnable
{

    /**
     * wird aufgerufen, wenn das applet startet
     */
    public void init()
    {
	/// ein Gitter-Objekt erzeugen
	obj = new Gitter();

	setBackground(Color.white);
	setLayout(null);
	// Start-Button
	startButton = new Button("Start");
	startButton.setBounds(10,10,60,30); 
	add(startButton);
	// Beschriftung Geschwindigkeit
	geschwLabel = new Label("Geschw.");
	geschwLabel.setBounds(10,40,60,30); 
	add(geschwLabel);
	// Geschwindigkeitseingabefeld
	geschwEingabe = new TextField("");
	geschwEingabe.setBounds(10,70,60,30); 
	geschwEingabe.setText(String.valueOf(geschwindigkeit));
	geschwEingabe.setEditable(true);
	add(geschwEingabe);
	// Beschriftung Position
	posLabel = new Label("Position");
	posLabel.setBounds(10,100,60,30); 
	add(posLabel);
	// Positionsanzeige
	posAnzeige = new TextField("");
	posAnzeige.setBounds(10,130,60,30); 
	posAnzeige.setText(String.valueOf(kamera_pos));
	posAnzeige.setEditable(false);
	add(posAnzeige);
	// Attach actions to the components
	startButton.addActionListener(this); 
    }
 
    /**
     * wird aufgerufen, wenn das applet beendet wird
     */
    public void stop()
    {
	if (flug != null && flug.isAlive()) {
	    // Flug-Thread anhalten
	    run_flug = false;
	    notify();
	    flug = null;
	}
    }
  

    /**
     * wird aufgerufen, wenn der Start-Button gedrueckt wird
     */
    public void actionPerformed(ActionEvent evt) 
    {
	if (evt.getSource() == startButton) {

	    // Geschwindigkeitseingabe
	    String text = geschwEingabe.getText();
	    try {
		geschwindigkeit = Double.valueOf(text).doubleValue();
	    }
	    catch (NumberFormatException ex) {
		geschwEingabe.setText(String.valueOf(geschwindigkeit));
		return;
	    }

	    // ev. laufenden Thread stoppen
	    if (flug != null && flug.isAlive()) {
		run_flug = false;
		notify();
		flug = null;
	    }

	    // neuen Flug starten
	    flug = new Thread(this);
	    kamera_pos = CONST.kamera_start_pos;
	    run_flug = true;
	    flug.start();
	}
    } 


    /**
     * wartet milliseconds Millisekunden
     */
    void delay(double milliseconds)
    {
	try {
	    Thread.sleep((int)(milliseconds));
	}
	catch (InterruptedException e) {
	}
    }


    /**
     * Flug durchfuehren
     */
    public void run()
    {
	while (run_flug) {
	    // Eigenzeitdifferenz zw 2 Bildern in ms
	    double dt = 100;
	    // Koordinatenzeitdifferenz
	    double kdt = dt / Math.sqrt(1 -
					(geschwindigkeit*geschwindigkeit)/
					(CONST.c*CONST.c));
	    repaint();
	    kamera_pos += geschwindigkeit * kdt / 1000;
	    delay(dt);
	}
    }


    /**
     * zeichnet das Objekt
     */
    public void paint(Graphics g)
    {
	java.text.DecimalFormat df2
	    = new java.text.DecimalFormat("##0.00");
	posAnzeige.setText(df2.format(kamera_pos));
	obj.draw(g, kamera_pos, geschwindigkeit);
    }


    /**
     * zeichnet Bild unter Verwendung von double buffering
     * um Flackern zu vermeiden
     */
    public void update( Graphics g )
    {
	// Create an offscreen image and then get its
	// graphics context
	if (offScrImg == null)
	    offScrImg =	createImage(getSize().width,
				    getSize().height);
	
	Graphics og = offScrImg.getGraphics();

	og.clearRect(0,0,getSize().width, getSize().height);

	// Now draw on the offscreen image.
	paint(og);

	// Don't bother to call paint,
	// just draw the offscreen image
	// to the screen.
	g.drawImage(offScrImg, 0, 0, this);

	// Get rid of the offscreen graphics context.
	og.dispose();
    }


    Objekt obj;                 ///< das darzustellende Objekt

    Thread flug;                ///< den Flug ausfuehrender Thread
    Button startButton;         ///< Start-Button
    Label geschwLabel;          ///< Geschwindigkeitsbeschriftung
    TextField geschwEingabe;    ///< Geschwindigkeitseingabefeld
    Label posLabel;             ///< Positionsbeschriftung
    TextField posAnzeige;       ///< Positionsanzeige
    private volatile boolean run_flug = false;  ///< fliegen wir gerade?

    /// Kamerageschwindigkeit
    double geschwindigkeit = 0.93;
    /// Position der Kamera auf der x-Achse
    double kamera_pos = CONST.kamera_start_pos;

    Image offScrImg;            ///< zeichnen in Hintergrund
}

