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

/* Objekt typu LatheCurve rotujeme kolem osy y, čímž vytvoříme tvar.
   Pro jeho vzhled určíme nějakou barvu a texturu. Texturu kolem
   něj obtočíme a roztáhneme po celé jeho výšce.

   Pro rotace křivky využíváme kód odvozený ze třídy SurfaceOfRevolution
   od Chrise Buckalewa.

   Barva povrchu je buď růžová, jiná specifikovaná barva nebo definovaná
   texturou.
*/


import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.*;
import javax.vecmath.*;
import java.text.DecimalFormat;


public class LatheShape3D extends Shape3D
{
  private final static double RADS_DEGREE = Math.PI/180.0;

  private static final double ANGLE_INCR = 15.0;   // 20, 15, 10, 5
      // úhel jednoho pootočení při tvorbě stěn tělesa
  private static final int NUM_SLICES = (int)(360.0/ANGLE_INCR);


  // výchozí tmavé a světlé barvy tvaru
  private static final Color3f pink = new Color3f(1.0f, 0.75f, 0.8f);
  private static final Color3f darkPink = new Color3f(0.25f, 0.18f, 0.2f);

  private static final Color3f black = new Color3f(0.0f, 0.0f, 0.0f);

  private double height;    // výška tvaru



  public LatheShape3D(double xsIn[], double ysIn[], Texture tex)
  {
    LatheCurve lc = new LatheCurve(xsIn, ysIn);
    buildShape(lc.getXs(), lc.getYs(), lc.getHeight(), tex);
  }


  public LatheShape3D(double xsIn[], double ysIn[], 
							Color3f darkCol, Color3f lightCol)
  // vyžaduje dvě barvy: tmavou a normální verzi barvy
  {
    LatheCurve lc = new LatheCurve(xsIn, ysIn);
    buildShape(lc.getXs(), lc.getYs(), lc.getHeight(), darkCol, lightCol);
  }


  // -------------------- vytvoření tvaru -------------------------------

  private void buildShape(double[] xs, double[] ys, double h)
  {
    height = h;
    createGeometry(xs, ys, false);
    createAppearance(darkPink, pink);
  } // konec metody: buildShape()


  private void buildShape(double[] xs, double[] ys, double h, 
                                Color3f darkCol, Color3f lightCol)
  { height = h;
    createGeometry(xs, ys, false);
  
    if ((darkCol == null) || (lightCol == null)) {
      System.out.println("One of the colours is null; using defaults");
      createAppearance(darkPink, pink);
    }
    else
      createAppearance(darkCol, lightCol);
  } // konec metody: buildShape()


  private void buildShape(double[] xs, double[] ys, double h, Texture tex)
  {
    height = h;
    if (tex == null) {
      System.out.println("The texture is null; using default colours");
      createGeometry(xs, ys, false);
      createAppearance(darkPink, pink);
    }
    else {
      createGeometry(xs, ys, true);
      createAppearance(tex);
    }
  } // konec metody: buildShape()

  

  private void createGeometry(double[] xs, double[] ys, boolean usingTexture)
  /* Vytvoří povrch pomocí křivky definované souřadnicemi (x,y) v polích xs[] a ys[].

     Povrch je realizován objektem typu QuadArray, který obsahuje také normálové vektory,
     takže bude odrážet světlo.

     Souřadnice textury mohou být definovaný tak, aby se její obrázek obtáčel kolem
     vnější strany tvaru, počínaje zadní částí a jdoucí proti směru hodinových růčiček
     (tj. zleva doprava) kolem čela zpět dozadu.
  */
  {
    double verts[] = surfaceRevolve(xs, ys);
    // printVerts(verts);

    // pomocí třídy GeometryInfo spočítej normálové vektory
    GeometryInfo geom = new GeometryInfo(GeometryInfo.QUAD_ARRAY);
    geom.setCoordinates(verts);

    if (usingTexture) {
      geom.setTextureCoordinateParams(1, 2);    // připrav souřadnice textury
      TexCoord2f[] texCoords = initTexCoords(verts);
      // printTexCoords(texCoords);
      correctTexCoords(texCoords);
      geom.setTextureCoordinates(0, texCoords); 
    }

    NormalGenerator norms = new NormalGenerator();
    norms.generateNormals(geom);
   
    setGeometry( geom.getGeometryArray() );   // převeď zpět na pole geom
  }  // konec metody: createGeometry()


  private void printVerts(double[] verts)
  // pro ladící účely
  {
    DecimalFormat df = new DecimalFormat("0.###");  // 3 dm

    int numPerLine = 6;  // multiple of 3
    int count = 0;
    System.out.println("No. vertices: " + verts.length + "\n");

    for(int i=0; i < verts.length; i=i+3) {
      if (count == numPerLine) {
        System.out.println();
        count = 0;
      }
      System.out.print("(" + df.format(verts[i]) + ", " +
                             df.format(verts[i+1]) + ", " +
                             df.format(verts[i+2]) + ")  " );
      count +=3;
    }
    System.out.println("\n");
  }  // konec metody: printVerts()



  private TexCoord2f[] initTexCoords(double[] verts)
  /* 
     Obtočí texturu kolem tvaru, levý okraj začíná vzadu a jde
     proti směru hodinových ručiček kolem čela.
     Textura je natažena podél celé délky na ose y, takže hodnota t
     1 odpovídá maximální výšce tvaru.

     s získáme z úhlu svíraného souřadnicí (x,z)
     t získáme jako škálovanou výšku souřadnice
  */
  { int numVerts = verts.length;
    // System.out.println("Počet vrcholů: " + numVerts)

    TexCoord2f[] tcoords = new TexCoord2f[numVerts/3];

    double x, y, z;
    float sVal, tVal;
    double angle, frac;

    int idx = 0;
    for(int i=0; i < numVerts/3; i++) {
      x = verts[idx];  y = verts[idx+1];  z = verts[idx+2];

      angle = Math.atan2(x,z);       // -PI až PI
      frac = angle/Math.PI;          // -1.0 až 1.0
      sVal = (float) (0.5 + frac/2);   // 0.0f až 1.0f

      tVal = (float) (y/height);    // 0.0f až 1.0f; používá globální výškové hodnoty

      tcoords[i] = new TexCoord2f(sVal, tVal);
      idx += 3;
    }

    return tcoords;
  }  // konec metody: initTexCoords()


  private void printTexCoords(TexCoord2f[] tcoords)
  // pro ladící účely
  {
    System.out.println("Počet souřadnic textury: " + tcoords.length + "\n");

    for(int i=0; i < tcoords.length; i=i+2) {
      System.out.println(tcoords[i] + "  " + tcoords[i+1] );
    }
    System.out.println("\n");
  }  // konec metody: printTexCoords()


  private void correctTexCoords(TexCoord2f[] tcoords)
  /* Najde čtverce textury, v nichž jsou souřadnice textury převrácené,
     a převrátí je zpět,

     K převrácení dochází na spojnici mezi -PI a PI funkce tan(x/z)
     (na zadní straně tvaru). 

     Souřadnice s na straně -PI budou blízko 0 a ty na straně PI budou blízko 1,
     což povede k převrácené textuře.
 
     Oprava spočívá ve změně hodnot 0 na 1, takže čtvereček bude mít texturu blízko
     souřadnice s s hodnotou 1.
  */
  {
    // System.out.println("Checking");
    for(int i=0; i < tcoords.length; i=i+4) {
      if( (tcoords[i].x < tcoords[i+3].x) &&
          (tcoords[i+1].x < tcoords[i+2].x)) {  // neměla by vzrůstat
        // System.out.println(tcoords[i] + "  " + tcoords[i+1] );
        // System.out.println(tcoords[i+2] + "  " + tcoords[i+3] + "\n");
        // tcoords[i].x = 1.0f;
        tcoords[i].x = (1.0f + tcoords[i+3].x)/2 ;  // mezi x a 1.0
        // tcoords[i+1].x = 1.0f;
        tcoords[i+1].x = (1.0f + tcoords[i+2].x)/2 ;  // mezi x a 1.0
      }
    }
    // System.out.println("\n");
  }  // konec metody: correctTexCoords()


  private void createAppearance(Color3f darkCol, Color3f lightCol)
  /* Vzhled tvoří barva odrážející světlo. Tmavá barva je použita pro 
     ambientní efekty a světla pro difusní. */
  {
    Appearance app = new Appearance();

    PolygonAttributes pa = new PolygonAttributes();
    pa.setCullFace(PolygonAttributes.CULL_NONE);  // aby byly vidět obě strany tvaru
    app.setPolygonAttributes(pa);

    Material mat = new Material(darkCol, black, lightCol, black, 1.0f);
         // nastaví ambientní, vyzařující, difusní, zrcadlovou barvu a lesklost
    mat.setLightingEnable(true);    // osvětlení je zapnuté
    app.setMaterial(mat);

    setAppearance(app);
  }  // konec metody: createAppearance()



  private void createAppearance(Texture tex)
  /* Vzhled tvoří textura odrážející světlo.
     Textura je natažena kolem tvaru pomocí souřadnic textury vytvořených
     v metodě createGeometry().
  */
  {
    Appearance app = new Appearance();

    PolygonAttributes pa = new PolygonAttributes();
    pa.setCullFace(PolygonAttributes.CULL_NONE);  // aby byly vidět obě strany tvaru   
    app.setPolygonAttributes(pa);

    // smíchej texturu a barvu materiálu
    TextureAttributes ta = new TextureAttributes();
    ta.setTextureMode(TextureAttributes.MODULATE);
    app.setTextureAttributes(ta);

    Material mat = new Material();   // nastav výchozí bílý materiál
    mat.setSpecularColor(black);     // bez zrcadlové barvy
    mat.setLightingEnable(true);
    app.setMaterial(mat);

	app.setTexture( tex );

    setAppearance(app);
  }  // konec metody: createAppearance()



  // --------------- metody rotace povrchu -----------------
  /*  Odvozené od třídy SurfaceOfRevolution od Chrise Buckalewa , (c) 1994-2002.
      Část jeho souboru FreeFormDef.java.
         http://www.csc.calpoly.edu/~buckalew/474Lab6-W03.html
  */


  private double[] surfaceRevolve(double xs[], double ys[]) 
  /* Každá sousedící dvojice souřadnic křivky tvoří jednu stěnu výsledného povrchu. 

     Stěnu konstruujeme proti směru hodinových ručiček, takže její normálový vektor
     bude směřovat ven.
     
     Souřadnice v polích xs[] a ys[] by měly být uspořádány vzestupně.
  */
  { 
    checkCoords(xs);

    double[] coords = new double[(NUM_SLICES) * (xs.length-1) * 4 * 3];

    int index=0;
    for (int i=0; i < xs.length-1; i++) {
      for (int slice=0; slice < NUM_SLICES; slice++) {
        addCorner( coords, xs[i], ys[i], slice, index);        // dolní pravý
        index += 3;

        addCorner( coords, xs[i+1], ys[i+1], slice, index);    // horní pravý
        index += 3;

        addCorner( coords, xs[i+1], ys[i+1], slice+1, index);  // horní levý
        index += 3;

        addCorner( coords, xs[i], ys[i], slice+1, index);      // dolní levý
        index += 3;
      }
    }
    return coords;
  } // konec metody: SurfaceRevolve()


  private void checkCoords(double xs[])
  {
    // všechny x-ové body by měly být >= 0, protože otáčíme kolem osy y
    for (int i=0; i < xs.length; i++) {
      if (xs[i] < 0) {
        System.out.println("Upozornění: nastavuji xs[" + i + "] od do 0");
        xs[i] = 0;
      }
    }
  }  // konec metody: checkCoords()


  private void addCorner(double[] coords, double xOrig, double yOrig, int slice, int index)
  /* Vytvoří novou souřadnici (x,y,z), vyjma situace, kdy se rotace vrátí na začátek.
     Poté použije původní souřadnice.
  */
  {
    double angle = RADS_DEGREE * (slice*ANGLE_INCR);

    if (slice == NUM_SLICES)  // zpátky na začátku
      coords[index] = xOrig;
    else
      coords[index] = xCoord(xOrig, angle);  // x

    coords[index+1] = yOrig;   // y

    if (slice == NUM_SLICES)
      coords[index+2] = 0;
    else
      coords[index+2] = zCoord(xOrig, angle);   // z
  }  // konec metody: addCorner()


  /* ------------------------------------------
     Následující metody rotují o úhel radius,
     čímž vytvářejí kruh bodů.
     Tyto metody mohou být přetíženy pro úpravu poloměrů výsledných bodů,
     tedy např. k vytvoření elipsy.
  */

  protected double xCoord(double radius, double angle)
  {  return radius * Math.cos(angle);  }

  protected double zCoord(double radius, double angle)
  {  return radius * Math.sin(angle);  }

}  // konec třídy: LatheShape3D