//
// Highfield.java, Frank Buß 03/1999
//
// Eine Klasse zur Darstellung eines sogenannten Highfields.
//

// imports für verschiendene Knoten-Klassen
import javax.media.j3d.*;

// imports für verschiedene Vektor-Klassen
import javax.vecmath.*;

/**
 * Die Klasse <code>Highfield</code> erzeugt ein sogenanntes Highfield
 * mit Hilfe eines <code>IndexedTriangleArray</code>-Objekts. Dabei
 * werden die Werte eines zweidimensionalen Arrays als
 * H&ouml;heninformationen f&uuml; eine dreidimensionale Landschaft
 * gewertet.
 *
 * @version     1.1 03/1999
 * @author      Frank Bu&szlig;
 */
public class Highfield extends Shape3D {
	/**
	 * Hilfsvektor 1 zur Normalenberechnug.
	 */
	private Vector3f v1 = new Vector3f();

	/**
	 * Hilfsvektor 2 zur Normalenberechnug.
	 */
	private Vector3f v2 = new Vector3f();

	/**
	 * Berechnet den Normalenvektor eines Dreiecks
	 * und liefert ihn als neuen Vektor zurück.
	 *
	 * @param p0 Erster Punkt des Dreiecks.
	 * @param p1 Zweiter Punkt des Dreiecks.
	 * @param p2 Dritter Punkt des Dreiecks.
	 * @return Neu erzeugter Normalenvektor des Dreiecks.
	 */
	private Vector3f getNormal(Point3f p0, Point3f p1, Point3f p2) {
		v1.sub(p1, p0);
		v2.sub(p2, p0);
		Vector3f normal = new Vector3f();
		normal.cross(v1, v2);
		normal.normalize();
		return normal;
	}

	/**
	 * Erzeugt ein neues Highfield. In dem zweidimensionalen float-Array
	 * sind die H&ouml;heninformationen f&uuml;r das Highfield gespeichert.
	 *
	 * @param values Die H&ouml;heninformationen.
	 */
	public Highfield(float values[][]) {
		// Konstruktor der Basisklasse aufrufen
		super();

		// Abmessung in horizontaler und vertikaler Richtung
		int sizeX = values[0].length;
		int sizeY = values.length;

		// Anzahl Rechtecke
		int rects = (sizeX - 1) * (sizeY - 1);

		// Anzahl Punkte
		int points = sizeX * sizeY;

		// neues indiziertes Dreiecks Array anlegen
		IndexedTriangleArray tris =
			new IndexedTriangleArray(
				points,
				IndexedTriangleArray.COORDINATES | IndexedTriangleArray.NORMALS,
				6 * rects);

		//
		// Punkte erzeugen
		//

		// Punktzähler initialisieren
		int pointCounter = 0;

		// Skalierung berechnen
		float scaleX = 2f / (float) (sizeX - 1);
		float scaleY = 2f / (float) (sizeY - 1);

		for (int y = 0; y < sizeY; y++) {
			for (int x = 0; x < sizeX; x++) {
				// Punkt setzen
				tris.setCoordinate(
					pointCounter,
					new Point3f(
						(float) x * scaleX - 1f,
						(float) y * scaleY - 1f,
						values[y][x]));

				// Punktindex erhöhen
				pointCounter++;
			}
		}

		//
		// Dreiecke und Normalenvektoren für jedes Dreieck einzeln erzeugen
		//

		// Punkte für Normalenberechnung
		Point3f p0 = new Point3f();
		Point3f p1 = new Point3f();
		Point3f p2 = new Point3f();
		Point3f p3 = new Point3f();

		// Array zur Speicherung der Normalenvektoren der Dreiecke
		Vector3f triNormals[] = new Vector3f[2 * rects];

		// Punktzähler zurücksetzen
		pointCounter = 0;

		// Dreieckszähler initialisieren
		int triCounter = 0;

		// Zähler für den Koordinatenindex initialisieren
		int i = 0;

		// alle Rechtecke durchgehen und jeweils 2 Dreiecke erzeugen
		for (int y = 0; y < sizeY - 1; y++) {
			for (int x = 0; x < sizeX - 1; x++) {
				// Koordinaten- und Normalenindizes für 1. Dreieck festlegen
				tris.setCoordinateIndex(i, pointCounter);
				tris.setNormalIndex(i++, pointCounter);
				tris.setCoordinateIndex(i, pointCounter + 1);
				tris.setNormalIndex(i++, pointCounter + 1);
				tris.setCoordinateIndex(i, pointCounter + sizeX + 1);
				tris.setNormalIndex(i++, pointCounter + sizeX + 1);

				// Koordinaten- und Normalenindizes für 2. Dreieck festlegen
				tris.setCoordinateIndex(i, pointCounter);
				tris.setNormalIndex(i++, pointCounter);
				tris.setCoordinateIndex(i, pointCounter + sizeX + 1);
				tris.setNormalIndex(i++, pointCounter + sizeX + 1);
				tris.setCoordinateIndex(i, pointCounter + sizeX);
				tris.setNormalIndex(i++, pointCounter + sizeX);

				// Koordinaten des Rechtecks holen
				tris.getCoordinate(pointCounter, p0);
				tris.getCoordinate(pointCounter + 1, p1);
				tris.getCoordinate(pointCounter + sizeX + 1, p2);
				tris.getCoordinate(pointCounter + sizeX, p3);

				// Normalenvektoren der beiden Dreiecke setzen
				triNormals[triCounter++] = getNormal(p0, p1, p2);
				triNormals[triCounter++] = getNormal(p0, p2, p3);

				// Punktzähler erhöhen
				pointCounter++;
			}

			// Punktzähler wieder erhöhen, da die Anzahl Punkte pro Reihe
			//  eins mehr ist als die Anzahl Rechtecke pro Reihe
			pointCounter++;
		}

		//
		// Normalenvektoren für jeden Punkt erzeugen, indem der Durchschnitt
		// der Normalenvektoren aller angrenzenden Dreiecke berechnet wird
		//

		// Dreieckszähler zurücksetzen
		triCounter = pointCounter = 0;

		// Offsets für angrenzende Dreiecke
		int triOfsX[] = { 0, 1, -2, -1, -2, 1 };
		int triOfsY[] =
			{ 0, 0, 0, -2 * sizeX + 2, -2 * sizeX + 2, -2 * sizeX + 2 };

		for (int y = 0; y < sizeY; y++) {
			for (int x = 0; x < sizeX; x++) {
				// neuen Vektor anlegen
				Vector3f normal = new Vector3f(0f, 0f, 0f);

				// Zähler für Anzahl angrenzender Dreiecke initialisieren
				int div = 0;

				// mögliche angrenzende Dreiecke testen
				for (int t = 0; t < 6; t++) {
					// Sonderfälle für Ränder und Ecken berücksichtigen
					if (triOfsX[t] < 0 && x == 0)
						continue;
					if (triOfsX[t] >= 0 && x == sizeX - 1)
						continue;
					if (triOfsY[t] < 0 && y == 0)
						continue;
					if (triOfsY[t] >= 0 && y == sizeY - 1)
						continue;

					// Normalenvektor addieren und Zähler erhöhen
					normal.add(
						triNormals[triCounter + triOfsX[t] + triOfsY[t]]);
					div++;
				}

				// Durchschnitt berechnen, normalisieren und setzen
				normal.scale(1f / ((float) div));
				normal.normalize();
				tris.setNormal(pointCounter, normal);

				// nächstes Rechteck
				triCounter += 2;

				// Punktindex für nächsten Normalenvektor setzen
				pointCounter++;
			}

			// Überlauf korrigieren
			triCounter -= 2;
		}

		//
		// Geometrie-Daten setzen
		//

		setGeometry(tris);
	}
}
