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

/* Končetina se skládá z:
      * jedinečného čísla končetiny;

      * názvů počátečních a koncových kloubů;

      * osy a úhlu této osy;

      * volitelných souřadnic lathe-tvaru a textury
        (nejsou-li k dispozici, pak musí být zadána délka končetiny a
         výsledná končetina bude neviditelná)

   Graf scény pro končetinu obsahuje:

    startLimbTG
       |
       |
       V
    orientTG --> xAxisTG --> yAxisTG --> zAxisTG ----> LatheShape3D
                                             |
                                              -------> endLimbTG


   Uzel startLimbTG tvoří spojnici k počátečnímu kloubu této končetiny.
   Byl vytvořen při sestavování rodičovské končetiny.

   Uzel endLimbTG je vypočítám touto končetinou a uložen se jménem
   koncového kloubu, který je připraven pro případné potomky končetiny.

   Uzel endLimbTG se nachází blízko konce končetiny, takže při napojení
   potomka se budou obě končetiny částečně překrývat. Tím se ještě 
   podtrhne efekt propojenosti končetin, zejména pak při jejich pohybu.

   Význam uzlů typu TG:   
     * orientTG:     otáčí končetinu do bodu v jistém směru při tvorbě končetiny;

     * xAxisTG:      uchovává jakoukoliv rotaci kolem osy x, která se provádí 
                     při pohybu končetiny;
     * yAxisTG, zAxisTG:   stejné jako předchozí, ovšem nyní pro osy y a z

   Důvodem rozdělení pohybových rotací na tři části je zjednodušení
   resetování končetiny do původní polohy.

   Rotace nelze aplikovat na objekt typu Limb.
   Podtřída třídy MoveableLimb přidává schopnost pohybu končetiny a 
   zahrnuje také jméno končetiny.

*/


import com.sun.j3d.utils.image.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.util.*;


public class Limb
{
  private static final double OVERLAP = 0.1;     
       // procentuální překrytí končetinou potomka

  // 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;


  private int limbNo;
  private String startJoint, endJoint;
  private int orientAxis;              // počáteční orientace os končetiny
  private double orientAngle = 0;      // úhel natočení osy
  private double limbLen;
  private boolean visibleLimb = false;


  // následující proměnné jsou pro podtřídy

  protected TransformGroup xAxisTG, yAxisTG, zAxisTG;  
      // pro opakované počítání roatcí končetiny
  protected double xsIn[], ysIn[];     // souřadnice lathe-tvaru
  protected String texPath;            // jméno souboru s texturou tvaru



  public Limb(int lNo, String jn0, String jn1, int axis, double angle, 
              double[] xs, double[] ys, String tex)
  {
    this(lNo, jn0, jn1, axis, angle, 0); 

    visibleLimb = true;    // poněvadž jsou zadány údaje o lathe-tvaru
    xsIn = xs;
    ysIn = ys;
    texPath = tex;

    limbLen = ysIn[ysIn.length-1];   // nastavení délky
      /* Předpokládáme, že finální hodnota y-ové hodnoty objektu typu LatheShape3D
         je maximum y-ovvých hodnot (tj. její výška) */
  } // konec metody: Limb()


  public Limb(int lNo, String jn0, String jn1, int axis, double angle, double len)
  { limbNo = lNo;
    startJoint = jn0; 
    endJoint = jn1;
    orientAxis = axis;
    orientAngle = angle;
    limbLen = len;
    visibleLimb = false;    // údaje o lathe-tvaru nebyly zadány
  }  // konec metody: Limb()



  public void printLimb()
  { System.out.println(
      limbNo + " = < (" + startJoint + ", " + endJoint + ") " +
                       orientAngle + " " + limbLen + " " + 
                       texPath + " " + visibleLimb + ">"  );
  } // konec metody: printLimb()



  public void growLimb(HashMap joints)
  /* Natočí končetinu dle počáteční pozice a pak ji připojí.*/
  {
    TransformGroup startLimbTG = (TransformGroup) joints.get(startJoint);
         // klouby obsahují dvojice (jointName, TG)

    if (startLimbTG == null)
      System.out.println("Neexistuje transformační uzel pro " + startJoint);
    else {
      setOrientation(startLimbTG);
      makeLimb(joints);
    }
  }  // konec metody: growLimb()


  private void setOrientation(TransformGroup tg)
  /* Nastaví orientaci končetiny.

     Končetina ve svém lokálním souřadném systému standardně směřuje nahoru podél osy y.
     Její skutečná orientace ve virtuální světě závisí na natočení končetin v hierarchii
     nad ní.

     Orientace se nastavuje v uzlu orientTG, nad rotačními uzly typu TG xAxisTG, 
     yAxisTG a zAxisTG.
  */
  { TransformGroup orientTG = new TransformGroup();

    if (orientAngle != 0) {
      Transform3D trans = new Transform3D();
      if (orientAxis == X_AXIS)    
        trans.rotX( Math.toRadians(orientAngle));
      else if (orientAxis == Y_AXIS)
         trans.rotY( Math.toRadians(orientAngle));
      else    // musí jít o osu z
        trans.rotZ( Math.toRadians(orientAngle));
      orientTG.setTransform(trans);
    }

    xAxisTG = new TransformGroup();
    xAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);
    xAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);  // může upravovat

    yAxisTG = new TransformGroup();
    yAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);
    yAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);  // může upravovat

    zAxisTG = new TransformGroup();
    zAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);
    zAxisTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);  // může upravovat

    // sekvence uzlů typu TG v grafu scény
    tg.addChild(orientTG);
    orientTG.addChild(xAxisTG);
    xAxisTG.addChild(yAxisTG);
    yAxisTG.addChild(zAxisTG);
  } // konec metody: setOrientation()


  private void makeLimb(HashMap joints)
  // sestaví tvar končetiny a vytvoří a uloží její uzel endLimbTG
  {  
    if (visibleLimb)
      makeShape();

    TransformGroup endLimbTG = new TransformGroup();
    Transform3D trans = new Transform3D();   
    trans.setTranslation(new Vector3d(0.0, limbLen*(1.0-OVERLAP), 0.0));   
    /* Koncová pozice je o kousek před skutečnou délkou končetiny,
       takže končetina jakéhokoliv potomka bude umístěna tak, že se bude
       s touto končetinou překrývat. */
    endLimbTG.setTransform(trans);
    zAxisTG.addChild(endLimbTG);
    
    joints.put(endJoint, endLimbTG);    // ulož dvojici (název kloubu, uzel TG)
  }  // konec metody: makeLimb()


  protected void makeShape()
  /* Vytvoří lathe-tvar pomocí zadaných lathe-souřadnic a textury.
     Tato metoda může být přetížena pro změnu třídy LatheShape3D 
     na nějakou podtřídu, jako třeba EllipseShape3D.
  */
  { LatheShape3D ls;
    if (texPath != null) {
      // System.out.println("Loading textures/" + texPath);
      TextureLoader texLd = new TextureLoader("textures/"+texPath, null);
      Texture tex = texLd.getTexture();      
      ls = new LatheShape3D(xsIn, ysIn, tex);
    }
    else  
      ls = new LatheShape3D(xsIn, ysIn, null);
    
    zAxisTG.addChild(ls);  // přidej tvar do grafu končetiny
  }  // konec metody: makeShape()


  // -----------------------------------
  /* Metody updateLimb() a reset() ovlivňují pozici končetiny,
     takže je třída Limb nepoužívá. V potřídách budou přetíženy. */

  public void updateLimb(int axis, double angleStep) {}

  public void reset() {}

}  // konec třídy: Limb
