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

/* Funkčnost kurzorových tlačítek: 
      dopředu, dozadu, vlevo, vpravo, nahoru, dolů, otočka doleva a doprava

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

   Jakmile uživatel požádá o vykonání pohybu, třída KeyBehavior vypočítá
   novou pozici souřadnic (x,z), načež požádá objekt typu Landscape,
   nacházejí-li se na povrchu. Pokud ano, pak pmocí objektu typu Landscape
   nalezne y-ovou složku (tj. výška povrchu v tomto bodě).
*/


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

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.universe.*;
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;

  private static final double USER_HEIGHT = 1.0;  // výška hlavy uživatele 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 WakeupCondition keyPress;

  private Landscape land;         // used for checking/calculating moves
  private double currLandHeight;  // floor height at current position
  private int zOffset;            // used when moving up/down

  // 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;   // na začátku stojí uživatel na povrchu
    initViewPosition(steerTG);

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


  private void initViewPosition(TransformGroup steerTG)
  // umísti výhled na (0,?,0) čelem ke scéně
  {
    Vector3d startPosn = land.getOriginVec();
    // vektor startPosn má hodnotu (0, <height of floor>, 0)

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

    steerTG.getTransform(t3d);      // uzel targetTG dosud není k dispozici
	t3d.setTranslation(startPosn);  // tak použij 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)
  /* Způsobí pohyb pozorovatele dopředu nebo dozadu a
     otočení vlevo či vpravo */
  { 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);
  } // konec metody: standardMove()


  private void altMove(int keycode)
  // pohyb viewer up or down, left or right
  { 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()


  
  // ----------------------- pohyb ----------------------------


  private void moveBy(Vector3d theMove)
  /* Vypočítá následující polohu (x,?,z) na povrchu. Otestuje, nachází-li
     se v rámci hranic povrchu.

     Pokud ano, tak požádá objekt Landscape o výšku povrchu v bodě (x,z).
     Poté nastaví y-ovou hodnotu tak, aby výhled uživatele spočíval v bodě
     (x,z) ve správné výšce nad povrchem.
  */
  { 
    Vector3d nextLoc = tryMove(theMove);   // další (x,?,z) pozice uživ.
    if (!land.inLandscape(nextLoc.x, nextLoc.z))   // nejsme-li na povrchu
       return;

    // objekt typu Landscape vrací výšku povrchu v bodě (x,z)
    double floorHeight = land.getLandHeight(nextLoc.x, nextLoc.z, currLandHeight);
    // Vypočti změnu od aktuální y-ové pozice
    // Vynuluj jakýkoliv posun vzhůru zpět na 0.
    double heightChg = floorHeight - currLandHeight -
					   (MOVE_STEP*zOffset);

    currLandHeight = floorHeight;    // aktualizuj aktuální výšku
    zOffset = 0;                     // zpátky na povrchu: žádný posun
    Vector3d actualMove = new Vector3d(theMove.x, heightChg, theMove.z);
    doMove(actualMove);
  }  // konec metody: moveBy()



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


  private void doMove(Vector3d theMove)
  // přesune výhled o posun theMove
  {
    targetTG.getTransform(t3d);
    toMove.setTranslation(theMove);
    t3d.mul(toMove);
    targetTG.setTransform(t3d);  // aktualizuj uzel typu TG výhledu
  } // 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()


}  // konec třídy: KeyBehavior
