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

/* Třída Animator má na starosti rozvrh animací (animSchedule),
   který zpracovává každých timeDelay milisekund. Toto zpracování
   obvykle zahrnuje spuštění operace pohybu či rotace sprite (bob) a 
   aktualizaci jeho postoje.
   
   Objekt typu KeyBehavior volá nejrůznější metody třídy Animator v reakci
   na stisky kláves. Tyto metody přidávají do rozvrhu nové animační sekvence.

   Tato třída dále nastavuje Výhled, a to na základě pozice objektu typu TourSprite.
   Při pohybu spritu se adekvátně mění také tento výhled.
*/

import java.util.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.text.DecimalFormat;


public class Animator extends Behavior
{
  // konstanty pro animace spritu
  private final static double MOVERATE = 0.3;
  private final static double ROTATE_AMT = Math.PI / 16.0;

  private final static double HEIGHT = 2.0;    // výška pozorovatele
  private final static double ZOFFSET = 8.0;   // vzdálenost od spritu
  private final static double ZSTEP = 1.0;     // krok přiblížení (zoom) pozorovatele


  /* Každá operace spritu je reprezentována sekvencí animací,
     které lze přidat do rozvrhu a zpracovat.

     Každá animační sekvence musí končit postojem "stand", jinak by
     totiž seqCount nerozeznal, že skončila.

     Názvy animací v sekvenci musí odpovídat názvům souborů typu 3ds
     s postoji spritů.
  */
  private final static String forwards[] = {"walk1", "walk2", "stand"};
  private final static String backwards[] = {"rev1", "rev2", "stand"};
  private final static String rotClock[] = {"rotClock", "stand"};
  private final static String rotCounterClock[] = {"rotCC", "stand"};
  private final static String moveLeft[] = {"mleft", "stand"};
  private final static String moveRight[] = {"mright", "stand"};

  private final static String active[] = {"toggle", "stand"};
                // toggle není postoj, neboť pouze vypne sprite
  private final static String punch[] = 
		{"punch1", "punch1", "punch2", "punch2", "stand"};


  /* Jedná se o maximální počet animačních sekvencí, jež
     se mohou naráz objevit v rozvrhu. Vyhneme se tak příliš velké
     prodlevě před spuštěním animace.
  */
  private final static int MAX_SEQS = 4;


  private WakeupCondition timeDelay;

  private TransformGroup viewerTG;  // the uzel TG pro výhled
  private Transform3D t3d, toMove;  // používá se pro práci s uzlem viewerTG;

  private AnimSprite3D bob;   // sprite
  private Point3d bobPosn;    // jeho poslední pozice
  private boolean isActive;   // zda-li je sprite aktivní

  // Seznam typu ArrayList pro uložení animační sekvence, která se má zpracovat
  private ArrayList animSchedule;
  private int seqCount;   // počet animačních sekvencí v rozvrhu

  private DecimalFormat df;   // pro jednodušší výstup při ladění



  public Animator(int td, AnimSprite3D b, TransformGroup vTG)
  { 
    df = new DecimalFormat("0.###");  // 3 dm

    timeDelay = new WakeupOnElapsedTime(td);

    animSchedule = new ArrayList();   // inicializuj rozvrh
    seqCount = 0;

    bob = b;
    bobPosn = bob.getCurrLoc();   // počáteční poloha spritu
    viewerTG = vTG;
    isActive = true;
    t3d = new Transform3D();
    toMove = new Transform3D();
    setViewer();
  } // konec konstruktoru: Animator()


  private void setViewer()
  /* Umístí výhled tak, aby byl posunut o ZOFFSET dílků od spritu
     podél osy z a nacházel se ve výšce HEIGHT dílků.
  */
  { bobPosn = bob.getCurrLoc();   // počateční lokace robota
    viewerTG.getTransform( t3d );
    // argumenty jsou: pozice pozorovatele, kam se dívá, směr nahoru
    t3d.lookAt( new Point3d(bobPosn.x, HEIGHT, bobPosn.z + ZOFFSET), 
				new Point3d(bobPosn.x, HEIGHT, bobPosn.z), 
				new Vector3d(0,1,0));
    t3d.invert();
    viewerTG.setTransform(t3d);
  }  // konec metody: setViewer()



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


  public void processStimulus( Enumeration criteria )
  { // ignoruj kritéria
    String anim = getNextAnim();
    if (anim != null)
      doAnimation(anim);
    wakeupOn( timeDelay );
  } // konec metody: processStimulus()


  private void doAnimation(String anim)
  /* Danou animaci (anim) zpracuje dvěma způsoby:
      * použije se pro spuštění transformace spritu
      * použije se pro změnu postoje spritu

     Tato metoda také kontroluje, je-li nutné aktualizovat
     výhled uživatele po změně aplikované na sprite.
  */
  { // System.out.println("zpracovávám :" + anim);
    // System.out.println("  nový animSchedule :" + animSchedule);

    /* Proveď transformaci spritu,
       všimněte si: "stand", "punch1" a "punch2" nemají žádné transformace
    */
    if ( anim.equals("walk1") || anim.equals("walk2"))   // vpřed
      bob.moveBy(0.0, MOVERATE/2);      // půl-krok
    else if ( anim.equals("rev1") || anim.equals("rev2"))  // vzad
      bob.moveBy(0.0, -MOVERATE/2);     // půl-krok
    else if (anim.equals("rotClock"))
      bob.doRotateY(-ROTATE_AMT);       // rotace ve směru hod. ruč.
    else if (anim.equals("rotCC"))
      bob.doRotateY(ROTATE_AMT);        // proti-rotace ve směru hod. ruč.
    else if (anim.equals("mleft"))      // přesun vlevo
      bob.moveBy(-MOVERATE,0.0);    
    else if (anim.equals("mright"))     // přesun vravo
      bob.moveBy(MOVERATE,0.0);
    else if (anim.equals("toggle")) {
      isActive = !isActive;    // přepni aktivaci
      bob.setActive(isActive);
    }

    // nejedná-li se o "toggle", tak aktualizuj postoj spritu
    if (!anim.equals("toggle"))
      bob.setPose(anim); 

    viewerMove();    // aktualizuj výhled uživatel
  }  // konec metody: doAnimation()


  // ---------------------- ovládání výhledu ---------------------

  private void viewerMove()
  // Aktualizuje výhled dle posunu spritu
  {
    Point3d newLoc = bob.getCurrLoc();
    // printTuple(newLoc, "newLoc");
    Vector3d trans = new Vector3d( newLoc.x - bobPosn.x, 0, newLoc.z - bobPosn.z);
    viewerTG.getTransform( t3d );
    toMove.setTranslation(trans);  // přepiš předchozí posun
    t3d.mul(toMove);
    viewerTG.setTransform(t3d);

    bobPosn = newLoc;   // ulož na příště
  }  // konec metody: viewerMove()


  public void shiftInViewer()
  {  shiftViewer(-ZSTEP); }   // přesuň pozorovatele podél záporné osy z

  public void shiftOutViewer()
  {  shiftViewer(ZSTEP); }


  private void shiftViewer(double zDist)
  // posune pozorovatele směrem "ven" či "dovnitř"
  {
    Vector3d trans = new Vector3d(0,0,zDist);
    viewerTG.getTransform( t3d );
    toMove.setTranslation(trans);  // přepiš předchozí posun
    t3d.mul(toMove);
    viewerTG.setTransform(t3d);
  }  // konec metody: viewerMove()


  // -------------- manipulace se seznamem animSchedule -------------------
  /* Následující metody jsou synchronizované, neboť je možné, 
     že processStimulus() zavolá getNextAnim() ve stejném okamžiku,
     kdy se volá addAnims().
  */


  synchronized private void addAnims(String ims[])
  { 
    if (seqCount < MAX_SEQS) {  // animačních sekvencí by nemělo být příliš
      for(int i=0; i < ims.length; i++)
        animSchedule.add(ims[i]);
      // System.out.println("přidáno do animSchedule :" + animSchedule);
      seqCount++;
      // System.out.println("seqCount :" + seqCount);
    }
  }

  synchronized private String getNextAnim()
  { if (animSchedule.isEmpty())
      return null;
    else {
      String anim = (String) animSchedule.remove(0);
      if (anim.equals("stand"))   // konec sekvence
         seqCount--;
      return anim;
    }
  }


  // -----  příkazy pro sprite --> přírustky do rozvrhu animací ----------
  /* Tyto metody tvoří viditelné rozhraní pro ovládání spritu. 
     Každé volání se převádí na přírustek animační sekvence v rozvrhu animSchedule.
  */

  // pohyb
  public void moveForward()
  { addAnims(forwards); }

  public void moveBackward()
  { addAnims(backwards); }

  public void moveLeft()
  { addAnims(moveLeft); }

  public void moveRight()
  { addAnims(moveRight); }

  // rotations kolem osy y
  public void rotClock()
  { addAnims(rotClock); }

  public void rotCounterClock()
  { addAnims(rotCounterClock); }


  public void punch()
  { addAnims(punch); }

  public void toggleActive()
  { addAnims(active); }


  // ----------------- ladící metody ------------------------------

  private void printTuple(Tuple3d t, String id)
  // používá se pro ladění
  {
    System.out.println(id + " x: " + df.format(t.x) + 
				", " + id + " y: " + df.format(t.y) +
				", " + id + " z: " + df.format(t.z));
  }  // konec metody: printTuple()


}  // konec třídy: Animator
