
// Landscape.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Tato třída používá třídu FractalMesh pro generování bodů sítě
   fraktálové krajiny.

   Každá síť (čtyři body) objektu typu FractalMesh je rozdělena
   na zvláštní seznamy v závislosti na jejich průměrné výšce.

   Sítě (čtyřúhelníky) v rámci téhož výškového rozsahu se použijí 
   pro vytvoření objektů typu TexturedPlane, jež mají asociovanou
   texturu.

   Kolem povrchu jsou pomocí třídy ColouredPlane vytvořeny čtyři stěny.

   Metodu getLandHeight() používá třída KeyBehavior pro získání výšky 
   povrchu v dané lokaci (x,z) - využívá techniku vybírání.
*/

import java.util.*;

import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.picking.*;


public class Landscape
{
  private static final int WORLD_LEN = 64;   // délka strany povrchu

  private static final int NUM_TEXTURES = 5;
  private static final String textureFns[] =
					{"water-shallow.jpg", "sand.jpg", "grass.gif", 
					 "dryEarth.jpg", "stone.gif"};

  private final static double MIN_HEIGHT = -2.0;
  private final static double MAX_HEIGHT = 8.0;

  
  /* Počet textur, které se využívají pro výpočet výšky hranic mezi
     MIN_HEIGHT a MAX_HEIGHT. První pojmenovaná textura v poli textureFns[]
     se použije pro čtyřúhelníky v prvním výškovém pásmu (nejnižším),
     druhá textura pro druhé pásmo, atd. */

  private final static Vector3d DOWN_VEC = new Vector3d(0.0,-1.0, 0.0);
		// směr pro techniku vybírání -- dolů pod výhled


  private BranchGroup landBG, floorBG;
  private Point3d vertices[];				// souřadnice krajiny
  private double textureBoundaries[];   
     // výšky, jež označují hranice, dle kerých se vybírají příslušné textury

  private Vector3d originVec = new Vector3d();   // počáteční bod výhledu
  private boolean foundOrigin = false;
  private PickTool picker;


  public Landscape(double flatness)
  {
    landBG = new BranchGroup();
    floorBG = new BranchGroup();
    landBG.addChild(floorBG);  // takže landBG-->floorBG

    setTexBoundaries();

    picker = new PickTool(floorBG);   // ověřuj pouze povrch
    picker.setMode(PickTool.GEOMETRY_INTERSECT_INFO);

    FractalMesh fm = new FractalMesh(flatness);
    // fm.printMesh(1);   // pro ladění: x=0; y=1; z=2
    vertices = fm.getVertices();    // vezmi vrcholy vygenerované objektem typu FractalMesh

    platifyFloor();
    addWalls();
  } // konec metody: Landscape()


  private void setTexBoundaries()
  // Uloží výšku hranic pro textury
  {
    textureBoundaries = new double[NUM_TEXTURES];
    double boundStep = (MAX_HEIGHT - MIN_HEIGHT) / NUM_TEXTURES;
    double boundary = MIN_HEIGHT + boundStep;
    for(int j=0; j < NUM_TEXTURES; j++) {
      textureBoundaries[j] = boundary;     // ulož ve vzrůstajícím pořadí
      boundary += boundStep;
    }
  } // konec metody: setTexBoundaries()



  private void platifyFloor()
  /* Prozkoumá čtyřúhelníky uložené v poli vertices[]. 
     Ověří průměrnou výšku a přiřadí ji seznamu typu ArrayList
     souřadnicím příslušícím tomuto výškovému rozsahu.

     Všechny seznamy typu ArrayList včetně jmen jejich souborů
     předá objektům typu TexturedPlane, které z těchto souřadnic
     vybudují 3D-síť.
  */
  {
    ArrayList[] coordsList = new ArrayList[NUM_TEXTURES];
    for (int i=0; i < NUM_TEXTURES; i++)
      coordsList[i] = new ArrayList();

    int heightIdx;
    for (int j=0; j < vertices.length; j=j+4) {   // otestuj každý čtyřúh.
      heightIdx = findHeightIdx(j);   // který výškový index  se týká daného čtyřúh.
      addCoords( coordsList[heightIdx], j);   // přidej čtyřúh. do příslušného seznamu
      checkForOrigin(j);       // ověř, je-li (0,0) bod v tomto čtyřúh.
    }

    // pro vytvoření objektů typu TexturedPlane použij postupně všechny textury a záznamy
    // v seznamu coordsList
    for (int i=0; i < NUM_TEXTURES; i++)
      if (coordsList[i].size() > 0)    // pokud se používá
        floorBG.addChild( new TexturedPlanes(coordsList[i],  // tak jej přidej k povrchu
						"images/"+textureFns[i]) );
  } // konec metody: platifyFloor()



  private int findHeightIdx(int vertIndex)
  /* Najdi výškový index pro čtyřúh. začínající v době vertices[vertIndex].
     Vezmi průměrnou výšku pro 4 body ve čtyřúh.
     
     Je-li hodnota nižší než hraniční, tak čtyřúhelník patří do tohoto
     výškového rozsahu. */
  {
    double ah = avgHeight(vertIndex);
    for(int i=0; i < textureBoundaries.length; i++)
      if (ah < textureBoundaries[i])
        return i;
    return NUM_TEXTURES-1;   // poslední seznam typu ArrayList je výchozí
  } // konec metody: findHeightIdx()


  private double avgHeight(int vi)
  // Vypočítej průměrnou výšku pro 4 body čtyřúhelníka
  { 
    return (vertices[vi].y + vertices[vi+1].y +
			vertices[vi+2].y + vertices[vi+3].y)/4.0;
  }


  private void addCoords(ArrayList coords, int vi)
  // přidá 4 rohy (čtyřúhelník) začínající v bodě vertices[vi] do seznamu typu ArrayList
  {
    coords.add( vertices[vi] ); coords.add( vertices[vi+1] ); 
    coords.add( vertices[vi+2] ); coords.add( vertices[vi+3] );
  }  // konec metody: addCoords()


  private void checkForOrigin(int vi)
  // Pokud jsou vrcholy vertices[vi] v počátku, ulož jejich pozici do proměnné originVec
  {
    if (!foundOrigin) {
       if ((vertices[vi].x == 0.0) && (vertices[vi].z == 0.0)) {
         // System.out.println("Nalezen počátek: (" + vertices[vi].x + ", " + 
		 //			vertices[vi].y + ", " + vertices[vi].z + ")");
         originVec.y = vertices[vi].y;
         foundOrigin = true;
       }
    }
  }  // konec metody: checkForOrigin()



  private void addWalls()
  // Přidá 4 stěny kolem krajiny
  {
    Color3f eveningBlue = new Color3f(0.17f, 0.07f, 0.45f);  // wall colour

    // osm rohových bodů
    // zadní, levý
    Point3d p1 = new Point3d(-WORLD_LEN/2.0f, MIN_HEIGHT, -WORLD_LEN/2.0f);
    Point3d p2 = new Point3d(-WORLD_LEN/2.0f, MAX_HEIGHT, -WORLD_LEN/2.0f);

    // přední, levý
    Point3d p3 = new Point3d(-WORLD_LEN/2.0f, MIN_HEIGHT, WORLD_LEN/2.0f);
    Point3d p4 = new Point3d(-WORLD_LEN/2.0f, MAX_HEIGHT, WORLD_LEN/2.0f);

    // přední, pravý
    Point3d p5 = new Point3d(WORLD_LEN/2.0f, MIN_HEIGHT, WORLD_LEN/2.0f);
    Point3d p6 = new Point3d(WORLD_LEN/2.0f, MAX_HEIGHT, WORLD_LEN/2.0f);

    // zadní pravý
    Point3d p7 = new Point3d(WORLD_LEN/2.0f, MIN_HEIGHT, -WORLD_LEN/2.0f);
    Point3d p8 = new Point3d(WORLD_LEN/2.0f, MAX_HEIGHT, -WORLD_LEN/2.0f);

    // levá stěna; proti směru hodinových ručiček
    landBG.addChild( new ColouredPlane(p3, p1, p2, p4, 
							new Vector3f(-1,0,0), eveningBlue) );
    // přední stěna; proti směru hodinových ručiček from back
    landBG.addChild( new ColouredPlane(p5, p3, p4, p6,
							new Vector3f(0,0,-1), eveningBlue) );
    // pravá stěna
    landBG.addChild( new ColouredPlane(p7, p5, p6, p8, 
							new Vector3f(-1,0,0), eveningBlue) );
    // zadní stěna
    landBG.addChild( new ColouredPlane(p7, p8, p2, p1, 
							new Vector3f(0,0,1), eveningBlue) );
  } // konec metody: addWalls()



  // ------------- veřejné metody ------------------


  public BranchGroup getLandBG()
  {  return landBG;  }


  public boolean inLandscape(double xPosn, double zPosn)
  // je (xPosn, zPosn) na povrchu?
  {
    int x = (int) Math.round(xPosn);   // pro řešení chyb z desetinným místem
    int z = (int) Math.round(zPosn);

    if ((x <= -WORLD_LEN/2) || (x >= WORLD_LEN/2) ||
        (z <= -WORLD_LEN/2) || (z >= WORLD_LEN/2))
      return false;
    return true;
  }  // konec metody: inLandscape()


  public Vector3d getOriginVec()
  {  return originVec;  }


  public double getLandHeight(double x, double z, double currHeight)
  /* Vrhne výběrový paprsek dolů pod bod (x,z), pro výpočet průsečíku s
     povrchem. Extrahuje y-ovou hodnotu (výšku povrchu) a vrátí ji.

     Technika vybírání je však poněkud nespolehlivá, zejména poblíž okrajů
     čtyřúhelníků, takže pokud neobdržíme žádný výsledek, pak vratíme
     předchozí výšku výhledu.
  */
  {
    Point3d pickStart = new Point3d(x, MAX_HEIGHT*2, z);    // začni shora
    picker.setShapeRay(pickStart, DOWN_VEC);   // vystřel paprsek dolů

    PickResult picked = picker.pickClosest();
	if (picked != null) {    // technika výběru někdy mine hranu/roh
      if (picked.numIntersections() != 0) {    // někdy průsečík není are found
        PickIntersection pi = picked.getIntersection(0);
        Point3d nextPt;
        try {   // ošetří chybu: 'Interp point outside quad'
          nextPt = pi.getPointCoordinates();
        }
        catch (Exception e) {
           // System.out.println(e);
           return currHeight;
        }
        return nextPt.y;
      }
    }
    return currHeight;    // pokud jsme se dostali až sem, tak je to špatně; 
                          // vrať existující výšku
  }  // konec metody: getLandHeight()


} // konec třídy: Landscape class

