
// Figure.java
// Thana Konglikhit, October 2003, s4310170@maliwan.psu.ac.th
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Třída Figure provádí tři hlavní činnosti:     
     1. konstruuje postavu propojování objektů typu Limb

     2. zpracovává příkazy končetin jejich předáváním příslušným objektům typu Limb
       
     3. zpracovává příkazy postavy pohyb a rotací jejího uzlu typu TransformGroup 
        (figureTG).

  Hierarchie třídy Limb:
   
               Limb
                |---------------------
                |                    |             
           MoveableLimb         EllipticLimb
                |
                |
       MoveableEllipticLimb
 
  Třída Limb definuje vzhled končetiny (pomocí tvaru typu Lathe),
  a způsob jejího napojení skrze klouby k ostatním končetím.
  Nastavuje její počáteční orientaci. Objekty typu Limb a EllipticLimb
  se nemohou pohyboat a nepoužívají názvy končetin.

  Naproti tomu objekty typu MoveableLimb a MoveableEllipticLimb se pohybovat mohou.

  Mají názvy končetin a rozsahy os x, y a z, ve kterých se mohou natáčet.
  Není-li rozsah specifikován, předpokládá se, že jeho hodnota je 0 
  (tj. rotace kolem dané osy není možná).

  Objekty končetin se uchovávají v seznamu typu ArrayList s názvem limbs.
  Jeho pozici odpovídá jeho číslo.

  Hašovací tabulka limbNames uchovává dvojice (název končetiny, její číslo).
  Používá se pro stanovéní čísla končetiny, zadá-li uživatel do příkazu její název.
*/

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


public class Figure
{
  // pohybové konstanty postavy
  private final static double MOVERATE = 0.3;
  private final static double ROTATE_AMT = Math.PI / 16.0;  // 11.25 stupně

  private final static int FWD = 0;
  private final static int BACK = 1;
  private final static int LEFT = 2;
  private final static int RIGHT = 3;
  private final static int UP = 4;
  private final static int DOWN = 5;

  private final static int CLOCK = 0;   // ve směru 
  private final static int CCLOCK = 1;  // a proti směru hodinových ručiček

  // konstanty pro jednotlivé osy
  private final static int X_AXIS = 0;
  private final static int Y_AXIS = 1;
  private final static int Z_AXIS = 2;

  // velkost kroku pro pohyb k jednotlivých směrech
  private static final Vector3d fwdVec = new Vector3d(0, 0, MOVERATE);
  private static final Vector3d backVec = new Vector3d(0, 0, -MOVERATE);

  private static final Vector3d leftVec = new Vector3d(-MOVERATE, 0, 0);
  private static final Vector3d rightVec = new Vector3d(MOVERATE, 0, 0);

  private static final Vector3d upVec = new Vector3d(0, MOVERATE, 0);
  private static final Vector3d downVec = new Vector3d(0, -MOVERATE, 0);


  private ArrayList limbs;     
          // seznam typu Arraylist objektů typu Limb indexovaný číslem končetiny

  private HashMap limbNames;   
        // uchovává dvojice (name, limbNo) viditelných končetin


  // pro pohyb postavy jako celku
  private TransformGroup figureTG;
  private Transform3D t3d, toMove, toRot; // pro operace nad uzly typu TG
  private int yCount;    // pro umístění postavy podél osy y

 
  public Figure()
  {
    yCount = 0;     // na začátku stojí postava na podlaze
    t3d = new Transform3D();
    toMove = new Transform3D();
    toRot = new Transform3D();

    limbs = new ArrayList();
    limbNames = new HashMap();

    // sestav postavu po částech (propojené objekty typu Limb)
    buildTorso();
    buildHead();

    buildRightArm();
    buildLeftArm();

    buildRightLeg();
    buildLeftLeg();
       
    printLimbsInfo();

    buildFigureGraph();   // převeď postavu na graf knihovny Java 3D
  }  // konec konstruktoru: Figure()



  private void buildTorso()
  /* Končetina vyžaduje číslo, počáteční a koncový kloub,
     osu orientace, úhel této osy, souřadnice lathe-tvaru a
     texturu.

     Pokud bude končetina pohyblivá (tj. objekt typu 
     MoveableLimb nebo MoveableEllipticLimb), pak bude
     také vyžadovat jméno a rozsahy rotací kolem os x, y a z.
  */
  {
    // zadnice postavy
    double xsIn1[] = {0, -0.1, 0.22, -0.2, 0.001};
    double ysIn1[] = {0, 0.03, 0.08, 0.25, 0.25};
    EllipticLimb limb1 = new EllipticLimb(
          1, "j0", "j1", Z_AXIS, 0, xsIn1, ysIn1, "denim.jpg");
    // žádný pohyb, so no name or ranges

    // hruď postavy: pohyblivá, má tudíž jméno ("chest") a rotační rozsahy
    double xsIn2[] = {-0.001, -0.2, 0.36, 0.001};
    double ysIn2[] = {0, 0, 0.50, 0.68};
    MoveableEllipticLimb limb2 = new MoveableEllipticLimb("chest",
         2, "j1", "j2", Z_AXIS, 0, xsIn2, ysIn2, "camoflage.jpg"); 
    limb2.setRanges(0, 120, -60, 60, -40, 40);
         // rozsah x: 0 až 120; rozsah y: -60 až 60; rozsah z: -40 až 40

    limbs.add(limb1);
    limbs.add(limb2);

    limbNames.put("chest", new Integer(2));
       // ulož (název, číslo) pairs in limbNames
  }  // konec metody: buildTorso()


  private void buildHead()
  {
    // krk postavy
    double xsIn3[] = {-0.001, -0.09, -0.09, 0.001};
    double ysIn3[] = {0, 0, 0.1, 0.1};
    Limb limb3 = new Limb(
         3, "j2", "j3", Z_AXIS, 0, xsIn3, ysIn3, "skin.jpg");
    // žádný pohyb

    // hlava postavy
    double xsIn4[] = {-0.001, 0.09, 0.17, 0};
    double ysIn4[] = {0, 0, 0.2, 0.4};
    MoveableLimb limb4 = new MoveableLimb("head",
        4, "j3", "j4", Z_AXIS, 0, xsIn4, ysIn4, "head.jpg");
    limb4.setRanges(-40, 40, -40, 40, -30, 30);

    limbs.add(limb3);
    limbs.add(limb4); 

    limbNames.put("head", new Integer(4));
  }  // konec metody: buildHead()



  private void buildRightArm()
  {
    /* Neviditelná končetina spojující krk s pravou horní paží.
       Je neviditelná, neboť nejsou zadané souřadnice lathe-tvaru
       ani textura. Nicméně má délku (0,35). */
    Limb limb5 = new Limb(5, "j2", "j5", Z_AXIS, 95, 0.35);

    // pravá horní paže
    double xsIn6[] = {0, 0.1, 0.08, 0};
    double ysIn6[] = {0, 0.08, 0.45, 0.55};
    MoveableLimb limb6 = new MoveableLimb("urArm",
        6, "j5", "j6", Z_AXIS, 80, xsIn6, ysIn6, "rightarm.jpg");
    limb6.setRanges(-60, 180, -90, 90, -90, 30);

    // pravá dolní paže
    double xsIn7[] = {0, 0.08, 0.055, 0};
    double ysIn7[] = {0, 0.08, 0.38, 0.43};
    MoveableLimb limb7 = new MoveableLimb("lrArm",
         7, "j6", "j7", Z_AXIS, 5, xsIn7, ysIn7, "skin.jpg");
    limb7.setRanges(0, 150, -90, 90, -90, 90);
    
    // pravá ruka
    double xsIn8[] = {0, 0.06, 0.04, 0};
    double ysIn8[] = {0, 0.07, 0.16, 0.2};
    MoveableEllipticLimb limb8 = new MoveableEllipticLimb("rHand",
         8, "j7", "j8", Z_AXIS, 0, xsIn8, ysIn8, "skin.jpg");
    limb8.setRanges(-50, 50, -40, 90, -40, 40);

    limbs.add(limb5);
    limbs.add(limb6);
    limbs.add(limb7);
    limbs.add(limb8);   
 
    limbNames.put("urArm", new Integer(6));
    limbNames.put("lrArm", new Integer(7));
    limbNames.put("rHand", new Integer(8));
  }  // konec metody: buildRightArm()


  private void buildLeftArm()
  // velmi podobná metodě buildRightArm()
  {
    // neviditelná končetina spojující krk s levou horní paží
    Limb limb9 = new Limb(9, "j2", "j9", Z_AXIS, -95, 0.35);

    // levá horní paže
    double xsIn10[] = {0, 0.1, 0.08, 0};
    double ysIn10[] = {0, 0.08, 0.45, 0.55};
    MoveableLimb limb10 = new MoveableLimb("ulArm",
         10, "j9", "j10", Z_AXIS, -80, xsIn10, ysIn10, "leftarm.jpg");
    limb10.setRanges(-60, 180, -90, 90, -30, 90);

    // levá dolní paže
    double xsIn11[] = {0, 0.08, 0.055, 0};
    double ysIn11[] = {0, 0.08, 0.38, 0.43};
    MoveableLimb limb11 = new MoveableLimb("llArm",
          11, "j10", "j11", Z_AXIS, -5, xsIn11, ysIn11, "skin.jpg");
    limb11.setRanges(0, 150, -90, 90, -90, 90);
    
    // levá ruka
    double xsIn12[] = {0, 0.06, 0.04, 0};
    double ysIn12[] = {0, 0.07, 0.16, 0.2};
    MoveableEllipticLimb limb12 = new MoveableEllipticLimb("lHand",
         12, "j11", "j12", Z_AXIS, 0, xsIn12, ysIn12, "skin.jpg");
    limb12.setRanges(-50, 50, -90, 40, -40, 40);

    limbs.add(limb9); 
    limbs.add(limb10);
    limbs.add(limb11);
    limbs.add(limb12);

    limbNames.put("ulArm", new Integer(10));
    limbNames.put("llArm", new Integer(11));
    limbNames.put("lHand", new Integer(12));
  }  // konec metody: buildLeftArm()



  private void buildRightLeg()
  {
    // neviditelná končetina spojující pozadí s pravou horní nohou
    Limb limb13 = new Limb(13, "j0", "j13", Z_AXIS, 50, 0.20);

    // pravá horní noha
    double xsIn14[] = {0, 0.12, 0.1, 0};
    double ysIn14[] = {0, 0.1, 0.6, 0.7};
    MoveableLimb limb14 = new MoveableLimb("urLeg",
         14, "j13", "j14", Z_AXIS, 130, xsIn14, ysIn14, "denim.jpg");
    limb14.setRanges(-45, 80, -20, 20, -45, 30);

    // pravá dolní noha
    double xsIn15[] = {0, 0.10, 0.06, 0};
    double ysIn15[] = {0, 0.15, 0.62, 0.7};
    MoveableLimb limb15 = new MoveableLimb("lrLeg",
         15, "j14", "j15", Z_AXIS, 0, xsIn15, ysIn15, "lowDenim.jpg");
    limb15.setRange(X_AXIS, -120, 0);

    // neviditelná končetina spojující pravou dolní nohu s pravým chodidlem
    Limb limb16 = new Limb(16, "j15", "j16", Z_AXIS, 0, 0.07);
    
    // pravé chodidlo
    double xsIn17[] = {0, 0.08, 0.06, 0};
    double ysIn17[] = {0, 0.07, 0.21, 0.25};
    MoveableEllipticLimb limb17 = new MoveableEllipticLimb("rFoot",
         17, "j16", "j17", X_AXIS, 90, xsIn17, ysIn17, "shoes.jpg");
    limb17.setRanges(-90, 0, 0, 0, -30, 30);

    limbs.add(limb13);
    limbs.add(limb14);
    limbs.add(limb15);
    limbs.add(limb16);   
    limbs.add(limb17);

    limbNames.put("urLeg", new Integer(14));
    limbNames.put("lrLeg", new Integer(15));
    limbNames.put("rFoot", new Integer(17));
  }  // konec metody: buildRightLeg()



  private void buildLeftLeg()
  // velmi podobná metodě buildRightLeg()
  {
    // neviditelná končetina spojující pozadí s levou horní nohou
    Limb limb18 = new Limb(18, "j0", "j18", Z_AXIS, -50, 0.20);

    // levá horní noha
    double xsIn19[] = {0, 0.12, 0.1, 0};
    double ysIn19[] = {0, 0.1, 0.6, 0.7};
    MoveableLimb limb19 = new MoveableLimb("ulLeg",
         19, "j18", "j19", Z_AXIS, -130, xsIn19, ysIn19, "denim.jpg");
    limb19.setRanges(-45, 80, -20, 20, -30, 45);

    // levá dolní noha
    double xsIn20[] = {0, 0.10, 0.06, 0};
    double ysIn20[] = {0, 0.15, 0.62, 0.7};
    MoveableLimb limb20 = new MoveableLimb("llLeg",
         20, "j19", "j20", Z_AXIS, 0, xsIn20, ysIn20, "lowDenim.jpg");
    limb20.setRange(X_AXIS, -120, 0);

    // neviditelná končetina spojující levou dolní nohu s levým chodidlem
    Limb limb21 = new Limb(21, "j20", "j21", Z_AXIS, 0, 0.07);

    // levé chodidlo
    double xsIn22[] = {0, 0.08, 0.06, 0};
    double ysIn22[] = {0, 0.07, 0.21, 0.25};
    MoveableEllipticLimb limb22 = new MoveableEllipticLimb("lFoot",
       22, "j21", "j22", X_AXIS, 90, xsIn22, ysIn22, "shoes.jpg"); 
    limb22.setRanges(-90, 0, 0, 0, -30, 30);

    limbs.add(limb18);
    limbs.add(limb19);
    limbs.add(limb20);
    limbs.add(limb21);
    limbs.add(limb22);

    limbNames.put("ulLeg", new Integer(19));
    limbNames.put("llLeg", new Integer(20));
    limbNames.put("lFoot", new Integer(22));
  }  // konec metody: buildLeftLeg()



  private void printLimbsInfo()
  /* Používá se pro ladění.
     Pro každý objekt typu Limb vypíše údaje o končetine a
     mapování jméno -> číslo. */
  {
   /*
    for(int i = 0; i < limbs.size(); i++)   
      ((Limb)limbs.get(i)).printLimb();
    */

    int i = 0;
    Iterator it = limbNames.keySet().iterator();
    while( it.hasNext() ){
      String lNm = (String) it.next();
      System.out.print( "(" + lNm + " = " + limbNames.get(lNm) + ") " );
      i++;
      if (i == 5) {    // maximální počet elementů na řádku
        System.out.println();
        i = 0;
      }
    }
    System.out.println();
  }  // konec metody: printLimbsInfo()


  private void buildFigureGraph()
  /* Převede postavu na podgraf knihovny Java 3D uložený v uzlu figureTG.
     
     Uzel offsetTG propojí s uzlem figureTG. Specifikuje vzdálenost
     od podlahy k prvnímu kloubu a končetině.

     Graf postavy pak bude viset pod uzlem offsetTG. */
  { 
    HashMap joints = new HashMap();  
    /* Klouby budou obsahovat dvojice (jointName, TG). Každý uzel typu TG
       definuje pozici kloubu ve scéně.

       Končetina připojená k nějakému kloubu se umístí do scény
       pomocí uzlu typu TG spojeného s tímto kloubem. */       

    figureTG = new TransformGroup();
    figureTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);
    figureTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);  // lze upravovat

    TransformGroup offsetTG = new TransformGroup();
    Transform3D trans = new Transform3D();
    trans.setTranslation( new Vector3d(0, 1.24, 0));  
          // posun od země k prvnímu kloubu
    offsetTG.setTransform( trans );  

    joints.put( "j0", offsetTG);   // ulož počáteční kloub (předpokládáme, že jde o j0)

    /* Zvětši graf o každý objekt končetiny jeho připojením
       k podgrafu postavy pod uzel offsetTG. */
    Limb li;
    for (int i = 0; i < limbs.size(); i++) {   
      li = (Limb)limbs.get(i);
      li.growLimb(joints);
    }

    figureTG.addChild(offsetTG);
  }  // konec metody: buildFigureGraph()


  public TransformGroup getFigureTG()
  {  return figureTG;  }


  // ----------------- operace týkající se končetin ---------------------


  public int checkLimbNo(int no)
  // Je parametr no číslem končetiny?
  {
    if (limbNames.containsValue( new Integer(no)))
      return no;
    else 
      return -1;    // číslo no nebylo v hašovací tabulce limbNames nalezeno
  }  // konec metody: checkLimbNo()



  public int findLimbNo(String name)
  // Vyhledá číslo končetiny pro dané jméno name
  {
    Integer iNo = (Integer) limbNames.get(name);
    if (iNo == null)   // končetina daného jména nenalezena
      return -1;
    else 
      return iNo.intValue();
  }  // konec metody: findLimbNo()


  public void updateLimb(int limbNo, int axis, double angle)
  /* Aplikuje operaci končetiny na končetinu s číslem limbNo.
     Operace je rotací kolem osy axis o úhel angle.
  */
  { 
    Limb li = (Limb) limbs.get(limbNo-1);  // předpokládáme, že limbNo je platné číslo
    li.updateLimb(axis, angle);  // pošli osu a úhel
  }  // konec metody: updateLimb()


  public void reset()
  // vrať všechny končetiny do jejich původnich pozic v prostoru
  { Limb li;
    for (int i = 0; i < limbs.size(); i++) {
      li = (Limb)limbs.get(i);
      li.reset();
    }
  }  // konec metody: reset()



  // ------------------- operace pohybu postavy ------------

  public void doMove(int dir)
  /* Přesune postavu dopředu, dozadu, doleva, doprava, nahoru a dolů.
     Metodu doMove() lze volat z objektů typu LocationBeh a CommandsPanel. */
  {
    if (dir == FWD)
      doMoveVec(fwdVec);
    else if (dir == BACK)
      doMoveVec(backVec);
    else if (dir == LEFT)
      doMoveVec(leftVec);
    else if (dir == RIGHT)
      doMoveVec(rightVec);
    else if (dir == UP) {
      doMoveVec(upVec);
      yCount++;
    }
    else if (dir == DOWN) {
      if (yCount > 0) {
        doMoveVec(downVec);
        yCount--;
      }
    }
    else
      System.out.println("Neznámé volání metdy doMove()");
  }  // konec metody: doMove()


  private void doMoveVec(Vector3d theMove)
  // Přesuň postavu dle vektoru theMove
  {
    figureTG.getTransform( t3d );
    toMove.setTranslation(theMove);    // otoč sprite o daný úhel v radiánech kolem jeho osy y
    t3d.mul(toMove);
    figureTG.setTransform(t3d);
  }  // konec metody: doMoveVec()



  public void doRotateY(int rotDir)
  /* Otočí sprite ve směru nebo proti směru hodinových ručiček kolem osy y,
     Metodu doRotateY() lze volat z objektů typu LocationBeh a CommandsPanel. */
  */
  { if (rotDir == CLOCK)
     doRotateYRad(-ROTATE_AMT);    // ve směru hodinových ručiček
    else if(rotDir == CCLOCK)
      doRotateYRad(ROTATE_AMT);    // proti směru hodinových ručiček
    else
      System.out.println("Neznámé volání metdy doRotateY()");
  }  // konec metody: doRotateY()


  private void doRotateYRad(double radians)
  // Otočí postavu o daný počet radiánů kolem její osy y
  {
    figureTG.getTransform( t3d );
    toRot.rotY(radians);   // přepiš předchozí rotaci
    t3d.mul(toRot);
    figureTG.setTransform(t3d);
  } // konec metody: doRotateYRad()


} // konec třídy: Figure