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

/* Třída HeightFinder byla navržena tak, aby skrývala pomalost
   techniky vybírání pro takto rozsáhlý objekt (síť načtenou ze 
   souboru typu OBJ).

   Vybírán provádíme v metodě getLandHeight() a aplikujeme
   na uzel landBG. Tato metoda může výpočty strávit 1-2 sekundy.

   Třída KeyBehavior komunikuje se třídou HeightFinder voláním
   metody requestMoveHeight(), která uloží souřadnice (x,y,z) do
   vektoru theMove.
   Y-ová hodnota je falešná, avšak dvojice (x,z) se v metodě 
   getLandHeight() použije  pro získání y-ové výšky v tomto
   bodě.

   Metoda requestMoveHeight() vrací výsledek okamžitě. Třída KeyBehavior
   nečeká na odpověď, neboť by mohla čekat 1-2 sekundy, možná i déle. 
   Místo toho pro svůj pohyb jednoduše použije aktuální y-ovou výšku.

   Metoda run() třídy HeightFinder se pohybuje v neustálém cyklu: načte
   aktuální požadavek pohybu z objektu theMove a zavolá getLandHeight().
   Poté předá výsledek zpět třídě KeyBehavior voláním metody adjustHeight().

   Tímto způsobem oddělíme běh objektu typu KeyBehavior od pomalé operace
   vybírání. Nevýhodou ovšem je, že objekt typu KeyBehavior používá 
   zastaralé výšky až do okamžiku, než dostane nové údaje od vlákna HeightFinder.

   Třída KeyBehavior může vláknu HeightFinder poslat celou řadu požadavků,
   zatímco metoda getLandHeight() se pohybuje vlemi pomalu, což nám nevadí.
   Když se totiž její běh dokončí, vezme metoda run() nejaktuálnější požadavek
   z objektu theMove spustí ji znovu.

   Tento přístup znamená, že třída KeyBehavior umí reagovatna velmi rychlé
   stisky kláves uživatele, ačkoliv technika vybírání je pomalá.
*/

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



public class HeightFinder extends Thread
{
  private final static Vector3d DOWN_VEC = new Vector3d(0.0,-1.0,0.0);

  private Landscape land;
  private KeyBehavior keyBeh;

  private PickTool picker;    // pro techniku vybírání
  private double scaleLen;
  private Vector3d theMove;   // uchovává aktuální požadavek pohybu od třídy KeyBehavior
  private boolean calcRequested;


  public HeightFinder(Landscape ld, KeyBehavior kb)
  {
    land = ld;
    keyBeh = kb;

    picker = new PickTool(land.getLandBG());   // kontroluj pouze krajinu
    picker.setMode(PickTool.GEOMETRY_INTERSECT_INFO);

    scaleLen = land.getScaleLen();
    theMove = new Vector3d();
    calcRequested = false;
  }  // konec metody: heightFinder()



  synchronized public void requestMoveHeight(Vector3d mv)
  // objekt typu KeyBehavior odesílá požadavek na nový pohyb
  { 
    theMove.set(mv.x, mv.y, mv.z);    // přepíše čekající požadavek
    calcRequested = true;
  }

  synchronized private Vector3d getMove()
  /* Metoda getLandHeight() vezme požadavek. Použíje se synchronizovaná metoda,
     takže získávání dat nemůže kolidovat s jeho nastavováním, pokud
     by objekt typu KeyBehavior současně zavolal metodu requestMoveHeight().
  */
  { calcRequested = false;
    return new Vector3d(theMove.x, theMove.y, theMove.z);
  }


  public void run()
  /* Opakovaně ber požadavek na pohyb a zpracuj jej technikou vybírání.
     Pokud žádný čekající požadavek není (calcRequested == false), tak
     po nějaký čas spi.
  */
  { Vector3d vec;
    while(true) {
      if (calcRequested) {
        vec = getMove();      // získej vektor požadovaného pohybu
        getLandHeight(vec.x, vec.z);   // použij jej pro techniku vybírání
      }
      else {   // žádný čekající požadavek
        try {
          Thread.sleep(200);    // trošku se prospi
        }
        catch(InterruptedException e) {}
      }
    }
  }  // konec metody: run()



  private void getLandHeight(double x, double z)
  /* Vybírání je trochu nespolehlivé, zejména poblíž okrajů a rohů,
     takže pokud nedostaneme žádný výsledek, nepočítáme žádný průsečík.
     Pokud extrakce souřadnic průsečíku selže, nevoláme metodu
     keyBeh.adjustHeight().

     Pro vybírání se používají souřadnice virtuální světa, ale
     údaje o průsečíku vracíme v souřadnicích krajiny (podlaha XZ, výška Z).
     To znamená, že výška musí být převedena n souřadnice světa (vynásobena) 
     před odesláním objektu typu KeyBehavior.
  */
  {
    Point3d pickStart = new Point3d(x, 2000, z);    // výška v souřadnicích světa
    picker.setShapeRay(pickStart, DOWN_VEC);   // vystřel dolů paprsek

    PickResult picked = picker.pickClosest();
    // System.out.println("vybraná hodnota: " + picked);

    if (picked != null) {    // výber někdy mine okraj/roh
      if (picked.numIntersections() != 0) {    // někdy průsečík není nalezen
        PickIntersection pi = picked.getIntersection(0);
        Point3d nextPt;
        try {   // ošetří chybu: 'Interp point outside quad'
          nextPt = pi.getPointCoordinates();
          // System.out.println("Vybrán: " + nextPt);
        }
        catch (Exception e) {
           System.out.println(e);
           return;    // nekomunikuj s objektem typu KeyBehavior, protože výšku nemáme
        }

        double nextYHeight = nextPt.z * scaleLen;    // z-ová osa krajiny -> y-ová osa světa
        // System.out.println("nextYHeight: " + nextYHeight);
        keyBeh.adjustHeight(nextYHeight);    // oznam objektu typu KeyBehavior novou výšku
      }
    }
  }  // konec metody: getLandHeight()


}  // konec třídy: HeightFinder
