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

/*   Graf scény pro větev stromu obsahuje:

    startBG --->orientTG --->scaleTG ---> baseTG ---> cylinder
                         |
                         ---> endLimbTG [ --> leafBG --> ImagesCsSeries 
                                          --> leafBG --> ImagesCsSeries  ]


   Uzel startBG je typu BranchGroup a je spojen s rodičovskou větví za
   běhu programu. Tento spoj končí v uzlu endLimbTG rodiče. Poněvadž
   ke spojení dochází až za běhu programu, musíme použít uzel typu BranchGroup.

   uzel orientTG: obsahuje orientaci větve (osu orientace a úhel).
             Během provádění programu se nemění.

   uzel scaleTG: pro změnu měřítka válce. X-ové i z-ové hodnoty jsou stejné
            (představují poloměr válce), zatímco y-ová hodnota jeho délku.

   uzel baseTG: bod (0,0,0) pro obejkt typu Cylinder knihovny Java 3D je uprostřed, 
            takže uzel baseTG jej zvedá podél osy y o length/2, a bod (0,0,0) je
            středem jeho základny.

   Uzel endLimbTG je posunutý o téměř celou délku válce. Nachází se těsně před koncem, 
   aby se větve potomků překrývaly s větví rodiče. Tím se mezi nimi částečně zakryje
   jakákoliv díra při extrémním vychílení potomka.
 
   Uzel endLimbTG není připojen k objektu typu Cylinder, protože tím by se stal
   nepřístupný škálování, což by také mělo vliv na jakéhokoliv jeho potomka.

   Jsou-li k větvi přidány listy, pak se k uzlu endLimbTG přidají dvě větve grafu.
   Obě začínají uzlem typu BranchGroup, neboť se přidávají až za běhu programu.

   Uzly s "obrazovkami" typu ImageCsSeries zobrazují obrázky listů. Tyto obrázky
   lze za běhu programu měnit, což vytváří nejrůznější animační efekty, v tomto
   případě iluzi růstu listů.
*/


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


public class TreeLimb
{
  private static final double OVERLAP = 0.1;     
       // procentuální překryv větví potomků a této větve

  // počet kroků pro změnu z počáteční barvy (zelená) na hnědou
  private final static int MAX_COLOUR_STEP = 15;
  
  // 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;

  // barvy pro materiál větví
  private final static Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
  // private final static Color3f specular = new Color3f(1.0f, 1.0f, 1.0f);

  /* zelená je počáteční barvou větve, hnědá její konečnou. */
  private final static Color3f green = new Color3f(0.0f, 1.0f, 0.1f); 
  private final static Color3f brown = new Color3f(0.35f, 0.29f, 0.0f);

  // inkrementální změna v rámci RGB jdoucí od zelené do hnědé
  private float redShift = (brown.x - green.x)/((float) MAX_COLOUR_STEP);
  private float greenShift = (brown.y - green.y)/((float) MAX_COLOUR_STEP);
  private float blueShift = (brown.z - green.z)/((float) MAX_COLOUR_STEP);

  private int orientAxis;              // počáteční orientace os končetiny
  private double orientAngle = 0;      // úhel natočení osy

  private float radius;                // původní poloměr válce
  private float limbLen;               // původní délka válce

  private TreeLimb parent;

  private TransformGroup scaleTG;  
  private TransformGroup endLimbTG;
  private Material limbMaterial;     // uchovává barvu

  private Transform3D currTrans, toMove;    // používáme pro opakované výpočty
  private Vector3d endmos;

  private Vector3d scaleLimb;   // pro změnu měřítka větve
  private Color3f currColour;   // začíná jako zelená
  private int colourStep = 0;
  private int age = 0;

  private int numChildren = 0;
  private ArrayList limbChildren;    // uchovává objekty typu TreeLimb
  private int level;           // úroveň této větve ve stromu

  private boolean hasLeaves;
  private ImageCsSeries frontLeafShape, backLeafShape;   // obrazovky pro listy



  public TreeLimb(int axis, double angle, float rad, float len, 
                           TransformGroup startLimbTG, TreeLimb par)
  { 
    orientAxis = axis;
    orientAngle = angle;
    radius = rad;
    limbLen = len;
    parent = par;

    scaleLimb = new Vector3d(1,1,1); 
    currColour = new Color3f( green );

    limbChildren = new ArrayList();

    if (parent == null)
      level = 1;
    else
      level = parent.getLevel() + 1;

    hasLeaves = false;
    frontLeafShape = null; backLeafShape = null;

    currTrans = new Transform3D();
    toMove = new Transform3D();
    endmos = new Vector3d();

    buildSubgraph(startLimbTG);

    if (parent != null)
      parent.addChildLimb(this);     // oznam rodiči, že má nového potomka
  }  // konec metody: TreeLimb()



  private void buildSubgraph(TransformGroup startLimbTG)
  /* Vytvpřé graf scény podrobně zachycený v komentáři výše.
     Uzel startLimbTG je uzlem endLimbTG rodiče. */
  { 
    BranchGroup startBG = new BranchGroup();

    // nastav orientaci větve
    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);
    }

    // uzel pro změnu měřítka
    scaleTG = new TransformGroup();
    scaleTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ);
    scaleTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE);  // lze upravovat

    // sekvence uzlů typu TG podgrafu větve
    startBG.addChild(orientTG);
    orientTG.addChild(scaleTG);
    scaleTG.addChild( makeLimb() );

    TransformGroup endLimbTG = locateEndLimb();
    orientTG.addChild(endLimbTG);

    startBG.compile();

    startLimbTG.addChild(startBG);   // připoj větev k uzlu endLimbTG rodiče
  } // konec metody: buildSubgraph()


  private TransformGroup makeLimb()
  // zelený válec jehož základna má střed v bodě (0,0,0)
  {
    // zafixuj počáteční polohu větve
    TransformGroup baseTG = new TransformGroup();
    Transform3D trans1 = new Transform3D();
    trans1.setTranslation( new Vector3d(0, limbLen/2, 0) );   // posuň vzhůru length/2
    baseTG.setTransform(trans1);

    Appearance app = new Appearance();
    limbMaterial = new Material(black, black, green, brown, 50.f);  // zrcadlový, 100
    limbMaterial.setCapability( Material.ALLOW_COMPONENT_READ);
    limbMaterial.setCapability( Material.ALLOW_COMPONENT_WRITE);
              // lze měnit barvy, pouze difusní barva bude upravována

    limbMaterial.setLightingEnable(true);

    app.setMaterial( limbMaterial );
    Cylinder cyl = new Cylinder( radius, limbLen, app);

    baseTG.addChild( cyl );
    return baseTG;
  }  // konec metody: makeLimb()



  private TransformGroup locateEndLimb()
  {  
    // zafixuj koncovou pozici končetiny a ulož ji do uzlu endLimbTG
    endLimbTG = new TransformGroup();
    endLimbTG.setCapability( TransformGroup.ALLOW_CHILDREN_EXTEND );
    endLimbTG.setCapability( TransformGroup.ALLOW_TRANSFORM_READ );
    endLimbTG.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE );
 
    Transform3D trans2 = new Transform3D();   
    trans2.setTranslation(new Vector3d(0, limbLen*(1.0-OVERLAP), 0));
    /* Koncová pozice je kousek před konec větve, aby mezi větvemi potomků
       a větví rodiče nevznikaly nepěkné díry. */
    endLimbTG.setTransform(trans2);

    return endLimbTG;
  }  // konec metody: locateEndLimb()


  // --------------------- veřejné metody -----------------

  public TransformGroup getEndLimbTG()
  {  return endLimbTG;  }

  public void incrAge()
  { age++;  }

  public int getAge()
  {  return age;  }

  public int getLevel()
  {  return level;  }

  
  // ----------- get/set-metody pro škálování používající délku a poloměr ------------

  public double getScaleLength()
  {  return scaleLimb.y;  }

  public double getScaleRadius()
  {  return scaleLimb.x; }     // škálovací hodnoty pro x a y zůstávají stejné

  public void scaleLength(double yChange)
  { scaleLimb.y *= yChange;  
    applyScale();
  }

  public void scaleRadius(double radChange)
  { scaleLimb.x *= radChange;
    scaleLimb.z *= radChange;
    applyScale();
  }


  private void applyScale()
  { 
    moveEndLimbTG( scaleLimb.y);

    scaleTG.getTransform(currTrans);
    currTrans.setScale(scaleLimb);
    scaleTG.setTransform(currTrans);
  }  // konec metody: applyScale()


  private void moveEndLimbTG( double yScale)
  /* Poněvadž uzel endLimbTG není připojen k objektu typu Cylinder, nebude automaticky
     upraven při škálovnání válce (např. prodloužen či zkrácen). 
     To znamená, že musíme vypočítat novou pozici uzlu endLimbTG sami.

     Parametr yScale udává změnu měřítka objektu typu Cylinder. Toto měřítko
     se aplikuje na y-ovou hodnotu uzlu endLimbTG.
  */
  {
    endLimbTG.getTransform( currTrans );
    currTrans.get( endmos );         // aktuální pozice uzlu endLimbTG
    double currLimbLen = endmos.y;    
      // aktuální y-ová pozice, délka válce včetně změny měřítka

    double changedLen = ((double) limbLen*(1.0-OVERLAP) * yScale) - currLimbLen;
      // délka po aplikaci změny měřítka

    endmos.set(0, changedLen, 0);       // použij endPos pro uložení změny délky
    toMove.setTranslation( endmos );    // otoč sprite o daný úhel v radiánech kolem jeho osy y
    currTrans.mul(toMove);              
    endLimbTG.setTransform(currTrans);  // přesuň uzel endLimbTG
  }  // konec metody: moveEndLimbTG() 



  // --------------- škálování pomocí hodnot poloměru a délky ----------

  /* Jakékoliv metody používající aktuální poloměr válce nebo délku musejí
     nejdříve aplikovat aktuální hodnoty změny měříkta (uložené v proměnné scaleLimb)
     na původní hodnoty poloměru a délky válce.
  */


  public float getRadius()
  {  return (radius * (float) scaleLimb.x);  }    // mohli bychom také použít scaleLimb.z

  public void setRadius(float newRadius)
  // změní poloměr válce na newRadius (změnou měřítka)
  {
    double škálovanýRadius = ((double) radius) * scaleLimb.x;
    double radChange = ((double) newRadius) / škálovanýRadius;
    scaleRadius(radChange);
  }  // konec metody: setRadius()


  public float getLength()
  {  return (limbLen * (float) scaleLimb.y);  }


  public void setLength(float newLimbLen)
  // změní délku válce na newLimbLen (změnou měřítka)
  { double scaledLimbLen = ((double) limbLen) * scaleLimb.y;
    double lenChange = ((double) newLimbLen) / scaledLimbLen;
    scaleLength( lenChange );
  }  // konec metody: setLimbLen()


  // -------------- metody získávání/nastavování barvy ---------------------

  public Color3f getCurrColour()
  {  return currColour;  }


  public void setCurrColour(Color3f c)
  // Změní barvu větve na c
  { currColour.x = c.x;
    currColour.y = c.y;
    currColour.z = c.z;
    limbMaterial.setDiffuseColor( currColour );
  }

  public void stepToBrown()
  // Postupně změní barvu větve ze zelené na hnědou
  {
    if (colourStep <= MAX_COLOUR_STEP) {
      currColour.x += redShift;
      currColour.y += greenShift;
      currColour.z += blueShift;
      limbMaterial.setDiffuseColor( currColour );
      colourStep++;
    }
  }  // konec metody: stepToBrown


  
  // -------------------- metody pro přístup k rodiči a potomkům ---------------

  public TreeLimb getParent()
  { return parent; }

  public int getNumChildren()
  {  return numChildren;  }

  public void addChildLimb(TreeLimb child)
  { limbChildren.add(child);
    numChildren++;
  }

  public ArrayList getLimbChildren()
  { return limbChildren; }


  // -------------------- metody týkající se listů -------------------


  public boolean hasLeaves()
  {  return hasLeaves;  }


  public void addLeaves(ImageCsSeries fls, ImageCsSeries bls)
  // Listoví reprezentujeme dvěma objekty typu ImageCsSeries
  {
    if (!hasLeaves) {
      frontLeafShape = fls;
      backLeafShape = bls;

      // add the screens to endLimbTG, via BranchGroups
      BranchGroup leafBG1 = new BranchGroup();
      leafBG1.addChild(frontLeafShape);
      endLimbTG.addChild(leafBG1);

      BranchGroup leafBG2 = new BranchGroup();
      leafBG2.addChild(backLeafShape);
      endLimbTG.addChild(leafBG2);

      hasLeaves = true;
    }
  } // konec metody: addLeaves()


  public void showLeaf(int i)
  // zobrazí i-tý obrázek listu
  { if (hasLeaves) {
      frontLeafShape.showImage(i);  
      backLeafShape.showImage(i);  
    }
  }

  public void showNextLeaf()
  // zobrazí následující obrázek listoví
  { if (hasLeaves) {
      frontLeafShape.showNext(); 
      backLeafShape.showNext(); 
    } 
  }

  public void showPrevLeaf()
  // zobrazí předhcozí obrázek listoví
  { if (hasLeaves) {
      frontLeafShape.showPrev();
      backLeafShape.showPrev();
    }  
  }

}  // konec třídy: TreeLimb
