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

/* Třída GroundCover vyšperkuje krajinu objekty specifikovanými
   v GC-souboru.

   Tento soubor, jehož jméno by mělo být ve tvaru /models/<fnm>GC.txt,
   třída načítá.

   Formát souboru je:
       range [ <min> <max> ]
       <gc file1> <scale1> <number1>
       <gc file2> <scale2> <number2>
           :

   Rozsah stanoví výškový rozsah, v rámci něhož by se měla objevit
   pokrývka povrchu. Poznámka: Slovo "range" musí být přítomno,
   a to i v případě, že hodnoty <min> a <max> nejsou zadány.

   Slovo "range" spouští načítáí souboru typu .obj jménem /models/<fnm>.obj
   jako textového souboru, takže souřadnice (x,y,z)
   této sítě lze uložit jako objekty typu Vector3d v seznamu typu ArrayList.

   Každý GC-soubor se načítá pomocí objektu typu GroundShape. 
   Tento objekt je zabalen do uzlu SharedGroup, takže
   stačí jen jeden z nich.

   Každá sdílená verze objektu typu GroundShape se umístí na náhodnou lokaci
   někde na povrchu krajiny tak, že se ze seznamu souřadnic náhodně jedna vybere.

   Parametr <scale> působí na měřítko obrázku pokrývky povrchu. Je třeba 
   experimentovat, dokud obrázek nevypadá tak, jak má! Vhodnou počáteční hodnotou
   je 90.
*/

import java.io.*;
import java.util.*;

import javax.media.j3d.*;
import javax.vecmath.*;


public class GroundCover
{
  private ArrayList coords;     // souřadnice sítě z OBJ-souboru (jako objekty typu Vector3d)
  private BranchGroup coverBG;  // uzel typu BG nejvyšší úrovně pro veškerou pokrývku povrchu

  public GroundCover(String fnm)
  {
    coords = new ArrayList();
    coverBG = new BranchGroup();

    loadCoverInfo(fnm);
  }  // konec metody: GroundCover()


 //--------------- metody pro načítání dat ----------------------

  private void loadCoverInfo(String fnm)
  // načte GC-soubor /models/<fnm>GC.txt
  {
    String gcFile = new String("models/" + fnm + "GC.txt");
    System.out.println("Loading ground cover file: " + gcFile);
    try {
      BufferedReader br = new BufferedReader( new FileReader(gcFile) );
      String line;

      if ((line = br.readLine()) != null)     // řádek: range [ <min> <max> ]
        loadObj(fnm, line);
      else {
        System.out.println(gcFile + " je prázndý!");
        return;
      }

      // načti několik řádků: <gc file> <scale> <number>
      StringTokenizer tokens;
      String gcFnm;
      int numItems = 0;
      while((line = br.readLine()) != null) {
        tokens = new StringTokenizer(line);
        gcFnm = tokens.nextToken();
        double scale = Double.parseDouble( tokens.nextToken() );
        int gcNo = Integer.parseInt( tokens.nextToken() );
        loadGC(gcFnm, scale, gcNo);    // pro tuto pokrývku načti objekt typu GroundShape
        numItems++;
      }
      System.out.println(numItems + " typů pokrývky povrchu");
      br.close();
    } 
    catch (Exception e) 
    {  System.out.println(e); }
  }  // konec metody: loadCoverInfo()



  private void loadObj(String fnm, String line)
  /* řádek:    range [ <min> <max> ]
     OBJ-soubor /models/<fnm>.obj se načte jako textový soubor 
     a souřadnice jeho sítě se uloží do seznamu typu ArrayList.

     Nicméně jakékoliv souřadnice s výškou mimo rozsah min-max
     se ignorují.
  */


  { StringTokenizer tokens = new StringTokenizer(line);
    tokens.nextToken();   // přeskoč slovo "range"

    if (tokens.hasMoreTokens()) {
      try {
        double min = Double.parseDouble( tokens.nextToken() );
        double max = Double.parseDouble( tokens.nextToken() );
        readObj(fnm, min, max, true);
        System.out.println("Rozsahy pokrývky povrchu -- min: " + min + "; max: " + max);
        return;
      }
      catch (NumberFormatException ex)
      { System.out.println("min/max values must be double"); }
    }

    readObj(fnm, 0, 0, false);    // výchozí hodnoty; min == max == 0
  }  // konec metody: loadObj()




  private void readObj(String fnm, double min, double max, boolean hasRange)
  /* OBJ-soubor se načte jako textový soubor. Měl by obsahovat data
     ve formě řady souřadnic sítě:
           v x-coord y-coord z-coord

     Souřadnice se přidají do seznamu coords jako objekty typu Vector3d,
     vyjma těch, jejichž výška (z-ová hodnota) je mimo rozsah min-max.
     Dále se vynechají bodfy na okraji krajiny (x nebo y == 0).

     Další data generovaná aplikací Terragen jsou údaje o stěnách:
          f index1 index2 index3
     které zde ignorujeme.

     Dobrý popis formátu OBJ-souboru lze nalézt v dokumentaci knihovny Java 3D
     pro třídu ObjectFile.

     OBJ-soubor lze otevří v jakémkoliv textovém editoru, ovšem takovém, který
     je schopen načítat i několika-megabajtové soubory.
  */
  {
    String objFile = new String("models/" + fnm + ".obj");
    System.out.println("Loading terrain mesh file: " + objFile);

    try {
      BufferedReader br = new BufferedReader( new FileReader(objFile) );
      String line, token;
      StringTokenizer tokens;
      double xCoord, yCoord, zCoord;
      int numExcluded = 0;

      while((line = br.readLine()) != null) {
        // System.out.println(line);
        if(line.startsWith("v")) {  
          tokens = new StringTokenizer(line);   // řádek: v  x y z
          tokens.nextToken();   // přeskoč 'v'
          xCoord = Double.parseDouble( tokens.nextToken() );
          yCoord = Double.parseDouble( tokens.nextToken() );
          zCoord = Double.parseDouble( tokens.nextToken() );

          //System.out.println(" (" + xCoord + ", " + yCoord + 
          //                           ", " + zCoord + ")");
          if (hasRange) {
            if ((zCoord >= min) && (zCoord <= max))
              coords.add( new Vector3d(xCoord, yCoord, zCoord) );
            else 
              numExcluded++;
          }
          else {
            if ((xCoord != 0) && (zCoord != 0))
              coords.add( new Vector3d(xCoord, yCoord, zCoord) );
            else
              numExcluded++;
          }
        }
      }
      br.close();
      // System.out.println("Počet souřadnic: " + coords.size());
      // System.out.println("Vynechaných souřadnic: " + numExcluded);
    } 
    catch (Exception e) 
    { System.out.println("Chyba při čtení souboru: " + objFile); }
  }  // konec metody: readObj()



  private void loadGC(String gcFnm, double scale, int gcNo)
  /* Každý GC-soubor se načte jako objekt typu GroundShape, načež se
     zabalí do uzlu SharedGroup, takže stačí jen jeden objekt.

     Vytvoří se několi uzlů typu TransfromGroup, které se umístí
     pod uzel typu SharedGroup na náhodná místa v krajině.
  */
  { String gcFile = new String("models/" + gcFnm);
    System.out.println("Načítám GC-soubor: " + gcFile + "; scale: " +
               scale + "; " + gcNo + " copies");

    SharedGroup gcSG = new SharedGroup();
    gcSG.addChild( new GroundShape((float) scale, gcFile) );
    gcSG.setPickable(false);   // není tedy ve scéně vybratelný

    Vector3d coordVec;  
    for(int i=0; i < gcNo; i++) {   // vytvoř několik uzlů typu TG pomocí jednoho typu SG
      coordVec = selectCoord();
      placeCover(gcSG, coordVec);
    }
  }  // konec metody: loadGC()


  private Vector3d selectCoord()
  // zvol náhodně souřadnici terénu
  { int index = (int) Math.floor( Math.random()*coords.size());
    return (Vector3d) coords.get(index);
  }


  private void placeCover(SharedGroup gcSG, Vector3d coordVec)
  /* Umístí pokrývku povrchu na krajinu do místa coordVec.
     Výsledkem je podgraf:
       coverBG -----> posTG ----> rotTG ----> Link ---> gcSG

     Několik kopií pokrývky povrchu budou mít vlastní
     pozici, rotaci a uzly typu Ling, i když tyto uzly
     spojují stejný uzel typu SharedGroup.
  */
  { 
    Transform3D t3d = new Transform3D();
    t3d.rotX( Math.PI/2.0 );    // pro anulování rotace krajiny
                                // protože se jako podlaha používala rovina XY
    TransformGroup rotTG = new TransformGroup(t3d);
    rotTG.addChild( new Link(gcSG) );

    Transform3D t3d1 = new Transform3D();
    t3d1.set( coordVec ); 
    TransformGroup posTG = new TransformGroup(t3d1);
    posTG.addChild( rotTG ); 

    coverBG.addChild( posTG );
  }  // konec metody: placeCover()



  public BranchGroup getCoverBG()
  // pro objekt typu Landscape
  {  return coverBG;  }

}  // konec třídy: GroundCover