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

/* Třída je podobná svému protějšku v adresáři FractalLand3D/

   Funkčnost kurzorových kláves:
      * dopředu, dozadu, doleva, doprava, nahoru, dolů, otočení vlevo a vpravo

      * w //  vypíše pozici uživatele
   
   Hlavní změnou je, že pohyb neovlivní výšku uživatele v terénu okamžitě.
   Její výpočet je přenechán objektu typu HeightFinder, což může trvat
   1-2 sekundy. Mezitím objekt typu KeyBehavior pokračuje
   dál a používá starou výšku.

   To znamená, že uživatel může případně vstoupit do hory, ovšem nakonec bude 
   jeho výška opravena.
*/


import java.awt.AWTEvent;
import java.awt.event.*;
import java.util.Enumeration;
import java.text.DecimalFormat;

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.behaviors.vp.*;



public class KeyBehavior extends ViewPlatformBehavior
{
  private static final double ROT_AMT = Math.PI / 36.0;   // 5 stupňů
  private static final double MOVE_STEP = 0.2;    // byla 0,2

  private static final double USER_HEIGHT = 0.5;  // výška hlavy nad povrchem


  // pevně stanovené pohybové vektory
  private static final Vector3d FWD = new Vector3d(0,0,-MOVE_STEP);
  private static final Vector3d BACK = new Vector3d(0,0,MOVE_STEP);
  private static final Vector3d LEFT = new Vector3d(-MOVE_STEP,0,0);
  private static final Vector3d RIGHT = new Vector3d(MOVE_STEP,0,0);
  private static final Vector3d UP = new Vector3d(0,MOVE_STEP,0);
  private static final Vector3d DOWN = new Vector3d(0,-MOVE_STEP,0);

  // názvy kláves
  private int forwardKey = KeyEvent.VK_UP;
  private int backKey = KeyEvent.VK_DOWN;
  private int leftKey = KeyEvent.VK_LEFT;
  private int rightKey = KeyEvent.VK_RIGHT;
  private int whereKey = KeyEvent.VK_W;   // tlačítko 'kde'



  private WakeupCondition keyPress;

  private Landscape land;
  private HeightFinder hf;   // vlákno HeightFinder zpracovává požadavky na výšku

  private double currLandHeight;  // výška povrchu v aktuální pozici (v souřadnicích světa)
  private int zOffset;            // pro pohyb nahoru/dolů

  private DecimalFormat df;     // pro jednodušší výstup



  // pro opakované výpočty
  private Transform3D t3d = new Transform3D();
  private Transform3D toMove = new Transform3D();
  private Transform3D toRot = new Transform3D();
  private Vector3d trans = new Vector3d();


  public KeyBehavior(Landscape ld, TransformGroup steerTG)
  {
    land = ld;
    zOffset = 0;   // uživatel na začátku stojí na povrchu
    initViewPosition(steerTG);
    df = new DecimalFormat("0.###");  // 3 dm

    hf = new HeightFinder(land, this);
    hf.start();

    keyPress = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
  } // konec metody: KeyBehavior()


  private void initViewPosition(TransformGroup steerTG)
  // umístí výhled do počáteční pozice tak, že směřuje do scény
  {
    Vector3d startPosn = land.getOriginVec();
    // System.out.println("startPosn: " + startPosn);

    currLandHeight = startPosn.y;   // ulož aktuální výšku povrchu
    startPosn.y += USER_HEIGHT;     // přičti výšku uživatele

    steerTG.getTransform(t3d); 
	t3d.setTranslation(startPosn);  // uzel targetTG zatím neexistuje, tak použij uzel steerTG
    steerTG.setTransform(t3d); 
  }  // konec metody: initViewPosition()


  public void initialize()
  {  wakeupOn( keyPress );  }


  public void processStimulus(Enumeration criteria)
  // reaguje na stisky kláves
  {
    WakeupCriterion wakeup;
    AWTEvent[] event;

    while( criteria.hasMoreElements() ) {
      wakeup = (WakeupCriterion) criteria.nextElement();
      if( wakeup instanceof WakeupOnAWTEvent ) {
        event = ((WakeupOnAWTEvent)wakeup).getAWTEvent();
        for( int i = 0; i < event.length; i++ ) {
          if( event[i].getID() == KeyEvent.KEY_PRESSED )
            processKeyEvent((KeyEvent)event[i]);
        }
      }
    }
    wakeupOn( keyPress );
  } // konec metody: processStimulus()



  private void processKeyEvent(KeyEvent eventKey)
  {
    int keyCode = eventKey.getKeyCode();
    // System.out.println(keyCode);

    if( eventKey.isAltDown() )    // klávesa + <alt>
      altMove(keyCode);
    else
      standardMove(keyCode);
  } // konec metody: processKeyEvent()


  private void standardMove(int keycode)
  /* Pohybuje pozorovatelem vpřed a vzad a otačí jej vlevo a vpravo.
     Zpracuje také požadavek 'w' na vypsání aktuální pozice. */
  { if(keycode == forwardKey)
      moveBy(FWD);
    else if(keycode == backKey)
      moveBy(BACK);
    else if(keycode == leftKey)
      rotateY(ROT_AMT);
    else if(keycode == rightKey)
      rotateY(-ROT_AMT);
    else if (keycode == whereKey)   // nová klávesa
      printLandLocation();
  } // konec metody: standardMove()


  private void altMove(int keycode)
  // pohybuje pozorovatelem nahoru, dolů, doleva a doprava
  { if(keycode == forwardKey) {
      doMove(UP);   // test pomocí metody moveBy() neprovádíme
      zOffset++;
    }
    else if(keycode == backKey) {
      if (zOffset > 0) {
        doMove(DOWN);  // test pomocí metody moveBy() neprovádíme
        zOffset--;
      }
    }
    else if(keycode == leftKey)
      moveBy(LEFT);
    else if(keycode == rightKey)
      moveBy(RIGHT);
  }  // konec metody: altMove()


  
  // ----------------------- pohyby ----------------------------


  private void moveBy(Vector3d theMove)
  /* Nakonec vypočítá následující pozici na povrchu (x,?,z).

     Nejprve provede rychlý test, je-li pohyb v rámci 
     hranic krajiny.

     Poté vyšle požadavek na zjištění výšky na dané pozici (x,z).
     Na odpověď nečeká a pokračuje se starou výškou, což znamená,
     že k pohybu dojde beze změny na y-ové ose.

     Požadave na výške odešle vláknu HeightFinder, které
     jej zpracuje pomocí techniky vybírání. Jakmile bude mít odpověď,
     zavolá metodu adjustHeight().

     To znamená, že uživatel může vejít do hory, ale jeho
     y-ová pozice se nakonec vždy opraví.

     Vlákno HeightFinder je napsáno tak, že když objekt typu KeyBehavior
     pošle požadavek, nahradí jím jakýkoliv předchozí.
  */
  {
    // System.out.println("theMove: " + theMove);
    Vector3d nextLoc = tryMove(theMove);   // další (x,?,z) pozice uživ.
    if (!land.inLandscape(nextLoc.x, nextLoc.z))   // nejsme-li na povrchu
       return;

    hf.requestMoveHeight(nextLoc);   // pošli požadavek na výšku vláknu HeightFinder

    Vector3d actualMove = new Vector3d(theMove.x, 0, theMove.z);  // změna na y-ose nebude
    doMove(actualMove);
  }  // konec metody: moveBy()



  public void adjustHeight(double newHeight)
  /* Volá se z vlákna HeightFinder() pro úravu y-ové pozice pozorovatele.
  */
  { // System.out.println("adjustHeight: " + newHeight);
    double heightChg = newHeight - currLandHeight - (MOVE_STEP*zOffset);
    Vector3d upVec = new Vector3d(0, heightChg, 0);

    currLandHeight = newHeight;      // aktualizuj aktuální výšku
    zOffset = 0;                     // zpátky na povrchu: žádný posun
    doMove(upVec);
  }  // konec metody: adjustHeight()



  private Vector3d tryMove(Vector3d theMove)
  /* Vypočítá efekt daného posunu na získání nové souřadnice
      (x,?,z). Uzel typu TG výhledu dosud neaktualzuje.
  */
  { targetTG.getTransform( t3d );
    toMove.setTranslation(theMove);
    t3d.mul(toMove);
    t3d.get( trans );
    return trans;
  }  // konec metody: tryMove()


  synchronized private void doMove(Vector3d theMove)
  /* Přesune výhled dle vektoru theMove.
     Tuto metodu může zavolat třída KeyBehavior z metody moveBy()
     nebo vlákno HeightFinder z metody adjustHeight(), takže je 
     synchronizovaná.
  */
  {
    targetTG.getTransform(t3d);
    toMove.setTranslation(theMove);
    t3d.mul(toMove);
    targetTG.setTransform(t3d);  // aktualizuj uzel typu TG výhleu
  } // konec metody: doMove()


  // -------------- rotace --------------------

  private void rotateY(double radians)
  // otoč výhled kolem osy y o radians radiánů
  {
    targetTG.getTransform(t3d);
    toRot.rotY(radians);
    t3d.mul(toRot);
    targetTG.setTransform(t3d);
  } // konec metody: rotateY()


// --------------- aktuální poloha ---------------------

  private void printLandLocation()
  // vypíše pozici uživatele v souřadnicích krajiny
  {        
    targetTG.getTransform(t3d);
    t3d.get(trans);
    trans.y -= MOVE_STEP*zOffset;   // ignoruj vznášení nad povrchem
    Vector3d whereVec = land.worldToLand(trans);

    System.out.println("Lokace v krajině: (" + df.format(whereVec.x) + ", " +
              df.format(whereVec.y) + ", " + df.format(whereVec.z) + ")");
  } // konec metody: printLandLocation()


}  // konec třídy: KeyBehavior
