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

/* Uživatel zadá název souboru s 3D-objektem, který má být načten.
   Jeho ohraničující koule se automaticky upraví tak, aby měla poloměr
   o velikosti 1 dílek a dále, pokud jde o 3ds-model, se celý 
   otočí o -90 stupňů kolem osy x

   Lze načítat celou škálu různých formátů 3D-objektů, protože
   používáme načítací rutiny knihovny Portfolio.

   Jakmile je objekt načten, lze s ním pohybovat, otáčet kolem os x, y
   a z a škálovat. Výsledná pozice, rotace a údaje o měřítku lze uložit
   do konfiguračního souboru (který má stejné jméno jako 3D-soubor s
   připojeným řetězcem "Coords.txt").

   Údaje o rotaci se ukládají jako řada "rotačních čísel", které se musejí
   provést, aby se objekt uvedl do aktuální polohy:
      1 = positivní rotace kolem osy x o ROT_INCR stupňů
      2 = negativní rotace kolem osy x o ROT_INCR stupňů

      3 = positivní rotace kolem osy y o ROT_INCR stupňů
      4 = negativní rotace kolem osy y o ROT_INCR stupňů

      5 = positivní rotace kolem osy z o ROT_INCR stupňů
      6 = negativní rotace kolem osy z o ROT_INCR stupňů

   Pomocí této metody se vyhneme problémům, které mohou vyvstat z toho,
   že smíchání rotací kolem různých os nemusí vést ke stejnému výsledku,
   pokud se provedou v odlišném pořadí.

   Infomrace z konfiguračního souboru lze načíst společně s objektem
   takto:
		java Loader3D -c <3D filename>

   Načtený objekt je pověšen pod několik uzlů typu TG, z nichž ten nejvyšší
   je přístupný voláním metody getTG().

   Změny:
   - odebráno použití načítací rutiny pro j3d-fly VRML a starfire 3DS
*/

import java.util.*;
import java.io.*;
import java.text.DecimalFormat;

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.loaders.*;

// balíčky knihovny Portfolio
import ncsa.j3d.*;
import ncsa.j3d.loaders.*;


public class PropManager
{
  // pro stanovení pohybů a rotací
  private static final int X_AXIS = 0;
  private static final int Y_AXIS = 1;
  private static final int Z_AXIS = 2;
  private static final int INCR = 0;
  private static final int DECR = 1;

  private static final double MOVE_INCR = 0.1;   // pohybový přírustek objektu
  private static final double ROT_INCR = 10;     // přírustek rotace (ve stupních)
  private static final double ROT_AMT = Math.toRadians(ROT_INCR);    // v radiánech


  // uzly typu TG, ze kterých visí načtený objekt:
  //       moveTG-->rotTG-->scaleTG-->objBoundsTG-->objekt
  private TransformGroup moveTG, rotTG, scaleTG;    
  private Transform3D t3d;           // pro přístup k transformacím
  private Transform3D chgT3d;        // uchovává aktuální změnu pozice, rotace či škálování

  private String filename;           // jméno souboru načteného objektu
  private double xRot, yRot, zRot;   // celkově provedené úhly rotace
  private ArrayList rotInfo;         // uchovává sekvenci rotačních čísel
  private double scale;              // aktuální měřítko objektu

  private DecimalFormat df;    // pro ladění


  public PropManager(String loadFnm, boolean hasCoordsInfo)
  {
    filename = loadFnm;
    xRot = 0.0; yRot = 0.0; zRot = 0.0;    // počáteční nastavení načteného objektu
    rotInfo = new ArrayList();
    scale = 1.0;

    t3d = new Transform3D();     // připrav znovupoužitelné objekty typu Transform3D
    chgT3d = new Transform3D();

    df = new DecimalFormat("0.###");  // 3 dm

    loadFile(loadFnm);
    if (hasCoordsInfo)     // načti také konfigurační soubor
      getFileCoords(loadFnm);
  }  // konec metody: konstruktoru: PropManager()



  private void loadFile(String fnm)
  /* soubor s 3D-objektem načteme pomocí načítací knihovny Portfolio.

     Načtený objekt má nad sebou 4 transformační skupiny -- uzel objBoundsTG
     slouží pro úpravu ohraničující koule objektu tak, aby její střed byl v bodě 
     (0,0,0) a měla jednotkový poloměr.

     Ostatní uzly typu TG jsou pro odlišení pohybu, rotací a změny měřítka objektu.
        moveTG-->rotTG-->scaleTG-->objBoundsTG-->objekt
  */
  { System.out.println("Načítám soubor s objektem: models/" + fnm);

	Scene s = null;
    ModelLoader modelLoader = new ModelLoader();
	try {
	  s = modelLoader.load("models/"+fnm);   // umí celou řadu typů
	}
	catch (Exception e) {
	  System.err.println(e);
	  System.exit(1);
	}

    // získej větev načteného objektu
    BranchGroup sceneGroup = s.getSceneGroup();

    // vytvoř skupinovou transformaci pro ohraničující kouli objektu
    TransformGroup objBoundsTG = new TransformGroup();
    objBoundsTG.addChild( sceneGroup );

    // změň velikost (a případně otoč) ohraničující koule načteného objektu
    String ext = getExtension(fnm);
    BoundingSphere objBounds = (BoundingSphere) sceneGroup.getBounds();
    setBSPosn(objBoundsTG, objBounds.getRadius(), ext);

    // vytvoř skupinovou transformaci pro změnu velikosti objektu
    scaleTG = new TransformGroup();
    scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    scaleTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    scaleTG.addChild( objBoundsTG );

    // vytvoř skupinovou transformaci pro rotaci objektu
    rotTG = new TransformGroup();
    rotTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    rotTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    rotTG.addChild( scaleTG );

    // vytvoř skupinovou transformaci pro posun objektu
    moveTG = new TransformGroup();
    moveTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    moveTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    moveTG.addChild( rotTG );

  } // konec metody: metody: loadFile()


  private String getExtension(String fnm)
  // vrátí příponu souboru jménem fnm, nebo "(none)"
  {
    int dotposn = fnm.lastIndexOf(".");
    if (dotposn == -1)  // no extension
      return "(none)";
    else
      return fnm.substring(dotposn+1).toLowerCase();
  }


  private void setBSPosn(TransformGroup objBoundsTG, 
					double radius, String ext)
  // Změní měřítko objektu na jednotkový poloměr a otočí jej o -90 stupňů kolem
  // osy x, pokud soubor obsahuje 3ds-model
  {
    Transform3D objectTrans = new Transform3D();
    objBoundsTG.getTransform( objectTrans );

    // System.out.println("poloměr: " + df.format(radius));

    // změň měřítko objektů tak, aby se jeho hranice vešly do jednotkové koule
    Transform3D scaleTrans = new Transform3D();
    double scaleFactor = 1.0/radius;
    // System.out.println("scaleFactor: " + df.format(scaleFactor) );
    scaleTrans.setScale( scaleFactor );

    // finální transformace = [original] * [scale] (and možná *[rotate])
    objectTrans.mul(scaleTrans);
    
    if (ext.equals("3ds")) {   // soubor je typu 3ds
      // System.out.println("Otáčím o -90 stupňů kolem osy x");
      Transform3D rotTrans = new Transform3D();
      rotTrans.rotX( -Math.PI/2.0 );    // 3ds-modely často leží na čele, opravit
      objectTrans.mul(rotTrans);
    }

    objBoundsTG.setTransform(objectTrans);
  } // konec metody: metody: setBSPosn()


  public TransformGroup getTG()
  // tuto metodu používá třída WrapLoader3D pro přidání objektu do 3D-světa
  {  return moveTG; }


  // ----------------------------------------------------------------
  // získej údaje konfiguračního souboru a aplikuj je na načtený objekt


  private void getFileCoords(String fnm)
  /* Získej údaje z konfiguračního souboru jménem fnm.
     Jeho formát je:
         <jméno souboru s 3D-objektem>
         [-p px py pz]
         [-r řada čísel]
         [-s měřítko]
  */
  { 
    String coordFile = "models/" + getName(fnm) + "Coords.txt";
    try {
      BufferedReader br = new BufferedReader( new FileReader(coordFile));
      br.readLine();    // přeskoč řádek se jménem souboru (již ho známe)
      String line;
      char ch;
      while((line = br.readLine()) != null) {
        ch = line.charAt(1);
        if (ch == 'p')
          setCurrentPosn(line);
        else if (ch == 'r')
          setCurrentRotation(line);
        else if (ch == 's')
          setCurrentScale(line);
        else
          System.out.println(coordFile + ": neznámý řádek: " + line);
      }
      br.close();
      System.out.println("Načten konfigurační soubor: " + coordFile);
    } 
    catch (IOException e) 
    { System.out.println("Chyb při čtení konfiguračního souboru: " + coordFile);
      System.exit(1);
    }
  }  // konec metody: metody: getFileCoords()



  private String getName(String fnm)
  // Extrahuje jméno před koncovým znakem '.'
  {
    int dotposn = fnm.lastIndexOf(".");
    if (dotposn == -1)  // žádná přípona
      return fnm;
    else
      return fnm.substring(0, dotposn);
  }


  private void setCurrentPosn(String line)
  // extrahuje údaje o pozici (x,y,z) z konfiguračního souboru
  // a aplikuje je na načtený objekt
  {
    double vals[] = new double[3];            
    vals[0] = 0; vals[1] = 0; vals[2] = 0;    // reprezentuje (x,y,z)

    StringTokenizer tokens = new StringTokenizer(line);
    String token = tokens.nextToken();    // přeskoč označení příkazu
    int count = 0;
    while (tokens.hasMoreTokens()) {
      token = tokens.nextToken();
      try {
        vals[count] = Double.parseDouble(token);
        count++;
      }
      catch (NumberFormatException ex){ 
        System.out.println("Nesprávný formát pozičních dat konfiguračního souboru"); 
        break;
      }
    }
    if (count != 3)
      System.out.println("Nedostatek pozičních dat v konfiguračím souboru");

    // na načtený objekt aplikuj přesun
    doMove( new Vector3d( vals[0], vals[1], vals[2]) );

  }  // konec metody: metody: setCurrentPosn()



  private void setCurrentRotation(String line)
  // extrahuje údaje o rotaci z konfiguračního souboru
  // a aplikuje ji na načtený objekt
  {
    int rotNum;
    StringTokenizer tokens = new StringTokenizer(line);
    String token = tokens.nextToken();    // přeskoč označení příkazu
    if (!tokens.hasMoreTokens())   // nemusí existovat žádné rotační číslo
      return;
    token = tokens.nextToken();
    for (int i=0; i < token.length(); i++) {
      try {
        rotNum = Character.digit(token.charAt(i),10);
        // rotInfo.add( new Integer(rotNum));
      }
      catch (NumberFormatException ex){ 
        System.out.println("Nesprávný formát rotačních dat konfiguračního souboru"); 
        break;
      }
      if (rotNum == 1)         // pozitiní rotace kolem osy x
        rotate(X_AXIS, INCR);   
      else if (rotNum == 2)    // negativní
        rotate(X_AXIS, DECR);
      else if (rotNum == 3)    // pozitiní rotace kolem osy y
        rotate(Y_AXIS, INCR);   
      else if (rotNum == 4)    // negativní
        rotate(Y_AXIS, DECR);
      else if (rotNum == 5)    // pozitiní rotace kolem osy z
        rotate(Z_AXIS, INCR);   
      else if (rotNum == 6)    // negativní
        rotate(Z_AXIS, DECR);
      else
        System.out.println("Rotační údaje v konfiguračním souboru nelze rozpoznat");
    }
  }  // konec metody: metody: setCurrentRotation()




  private void setCurrentScale(String line)
  // extrahuje údaje o měřítku z konfiguračního souboru
  // aplikuje jej na načtený objekt
  {
    StringTokenizer tokens = new StringTokenizer(line);
    String token = tokens.nextToken();    // přeskoč označení příkazu
    double startScale;

    token = tokens.nextToken();    // mělo by se jednat o hodnotu měřítka
    try {
      startScale = Double.parseDouble(token);
    }
    catch (NumberFormatException ex){ 
      System.out.println("INesprávný formát měřítka konfiguračního souboru");
      startScale = 1.0;
    }
    // System.out.println("Načtené počáteční měřítko: " + startScale);
    if (startScale != 1.0) {
      scale(startScale);
    }
  }  // konec metody: metody: setCurrentScale()



  //---------------------------------------------------------
  // úprava pozice/rotace/měříkta načteného objektu

  /* Následující metody se volají při aplikaci údaků z konfigurančího
     souboru a při zpracování příkazů uživatele zasílaných z rozhraní GUI.
  */


  public void move(int axis, int change)
  // přesune objekt podél dané osy
  {
    double moveStep = (change == INCR) ? MOVE_INCR : -MOVE_INCR ;
    Vector3d moveVec;
    if (axis == X_AXIS)
      moveVec = new Vector3d(moveStep,0,0);
    else if (axis == Y_AXIS)
      moveVec = new Vector3d(0,moveStep,0);
    else   // Z_AXIS
      moveVec = new Vector3d(0,0,moveStep);
    doMove( moveVec );
  }  // konec metody: metody: move()


  private void doMove(Vector3d theMove)
  // posune objekt o zadaný vektor
  {
    moveTG.getTransform(t3d);        // vezmi aktuální pozici z uzlu typu TG
    chgT3d.setIdentity();            // vynulu změny v transformaci
    chgT3d.setTranslation(theMove);  // připrav posun
    t3d.mul(chgT3d);                 // 'přičti' posun k aktuálnímu posunu
    moveTG.setTransform(t3d);        // aktualizuj uzel typu TG
  }  // konec metody: metody: doMove()



  public void rotate(int axis, int change)
  // otočí objekt kolem dané osy a zapamatuje si změnu
  {
    doRotate(axis, change);
    storeRotate(axis, change);
  }  // konec metody: metody: rotate()


  private void doRotate(int axis, int change)
  // otočí objekt kolem dané osy o daný počet radiánů
  {
    double radians = (change == INCR) ? ROT_AMT : -ROT_AMT;
    rotTG.getTransform(t3d);     // získej aktuální rotaci z uzly typu TG
    chgT3d.setIdentity();        // vynuluj změny v transformaci
    switch (axis) {              // připrav novou rotaci
      case X_AXIS: chgT3d.rotX(radians); break;
      case Y_AXIS: chgT3d.rotY(radians); break;
      case Z_AXIS: chgT3d.rotZ(radians); break;
      default: System.out.println("Neznámá osa rotace"); break;
    }
    t3d.mul(chgT3d);     // 'přičti' novou rotaci k aktuální
    rotTG.setTransform(t3d);     // aktualizuj uzel typu TG
  }  // konec metody: metody: doRotate()


  private void storeRotate(int axis, int change)
  // uloží údaje o rotaci
  {
    double degrees = (change == INCR) ? ROT_INCR : -ROT_INCR;
    switch (axis) {
      case X_AXIS: storeRotateX(degrees); break;
      case Y_AXIS: storeRotateY(degrees); break;
      case Z_AXIS: storeRotateZ(degrees); break;
      default: System.out.println("Neznámé úložiště pro osovou rotaci"); break;
    }
  }  // konec metody: metody: storeRotate() 


  private void storeRotateX(double degrees)
  // zaznamená rotaci kolem osy x
  {
    xRot = (xRot+degrees)%360;   // aktualizuj celkovou rotaci kolem osy  x
    if (degrees == ROT_INCR)
      rotInfo.add(new Integer(1));  // rotační číslo
    else if (degrees == -ROT_INCR)
       rotInfo.add(new Integer(2));
    else 
      System.out.println("No X-axis rotation number for " + degrees);
  } // konec metody: metody: storeRotateX()


  private void storeRotateY(double degrees)
  // zaznamená rotaci kolem osy y
  {
    yRot = (yRot+degrees)%360;   // aktualizuj celkovou rotaci kolem osy  y
    if (degrees == ROT_INCR)
      rotInfo.add(new Integer(3));  // rotační číslo
    else if (degrees == -ROT_INCR)
       rotInfo.add(new Integer(4));
    else 
      System.out.println("No Y-axis rotation number for " + degrees);
  } // konec metody: metody: storeRotateY()


  private void storeRotateZ(double degrees)
  // zaznamená rotaci kolem osy z
  {
    zRot = (zRot+degrees)%360;   // aktualizuj celkovou rotaci kolem osy  z
    if (degrees == ROT_INCR)
      rotInfo.add(new Integer(5));  // rotační číslo
    else if (degrees == -ROT_INCR)
       rotInfo.add(new Integer(6));
    else 
      System.out.println("No Z-axis rotation number for " + degrees);
  } // konec metody: metody: storeRotateZ()


  public void scale(double d)
  // Změň měřítko objektu o d dílků
  {
    scaleTG.getTransform(t3d);    // vezmi aktuální měřítko z uzlu typu TG
    chgT3d.setIdentity();         // vynuluj změny v transformaci
    chgT3d.setScale(d);           // připrav nové měřítko
    t3d.mul(chgT3d);              // vynásob nové měřítko aktuálním
	scaleTG.setTransform(t3d);    // aktualizuj uzel typu TG

    scale *= d;    // aktualizuj proměnnou s měřítkem
  }  // konec metody: metody: scale()



  // ----------------------------------------------------------
  // vrací údaje o aktuální pozici/rotaci/měřítko
  // metody využívá rozhraní GUI


  public Vector3d getLoc()
  { 
    moveTG.getTransform(t3d);
    Vector3d trans = new Vector3d();
    t3d.get(trans);
    // printTuple(trans, "getLoc");
    return trans;
  } // konec metody: metody: getLoc()


  public Point3d getRotations()
  {  return new Point3d(xRot, yRot, zRot);  }

  public double getScale()
  {  return scale;  }


  // ------------------------------ ukládání dat ---------------------------


  public void saveCoordFile()
  // vytvoří konfigurační soubor pro tento objekt
  {
    String coordFnm = "models/" + getName(filename) + "Coords.txt";
    try {
      PrintWriter out = new PrintWriter( new FileWriter(coordFnm));

      out.println(filename);     // jméno souboru objektu
      Vector3d currLoc = getLoc();
      out.println("-p " + df.format(currLoc.x) + " " + df.format(currLoc.y) +
						" " + df.format(currLoc.z) );
      out.print("-r ");
      for (int i=0; i < rotInfo.size(); i++)
         out.print( ""+((Integer) rotInfo.get(i)).intValue() );
      out.println("");

      out.println("-s " + df.format(scale) );

      out.close();
      System.out.println("Uloženo do konfiguračního souboru: " + coordFnm);
    }
    catch(IOException e)
    { System.out.println("Chyba při zápisu do konfiguračního souboru: " + coordFnm); }
  }  // konec metody: metody: saveCoordFile()



  // --------------------- metody používané pro ladění --------------------------


  private void printTG(TransformGroup tg, String id)
  // vypíše posun uložený v uzlu tg
  {
    Transform3D currt3d = new Transform3D( );
    tg.getTransform( currt3d );
    Vector3d currTrans = new Vector3d( );
    currt3d.get( currTrans );
    printTuple( currTrans, id);
  }  // konec metody: metody: printTG()


  private void printTuple(Tuple3d t, String id)
  // pro vypisování objektů typu Vector3d a Point3d
  {
    System.out.println(id + " x: " + df.format(t.x) + 
				", " + id + " y: " + df.format(t.y) +
				", " + id + " z: " + df.format(t.z));
  }  // konec metody: metody: printTuple()


}  // konec metody: třídy: PropManager
