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

/* Lathe-křivku je řada (x,y)-bodů repreznetujících 
   křivku tvořenou jejími segmenty a rovnými čarami.

   Lathe-křivka začíná v bodu y==0 a pokračuje směrem vzhůru. Všechny 
   souřadnice musejí být kladné, neboť naším záměrem je obtočit lathe-křivku
   kolem osy y, pro vytvoření zamýšleného tvaru.

   Segmenty křivky v lathe-křivce interpolujeme pomocí Hermitovy interpolace.
   K tomu potřebujeme ke všem bodům přidat tečné vektory, které spočítáme pomocí
   varianty algoritmu Catmull-Rom pro spliny.

   Pro danou dvojici souřadnic (např. (x1,y1), (x2,y2)) vytvoříme segment křivky tak, 
   že mezi ně přidáme několik dodatečných bodů (jejich počet definuje konstanta STEP).

   Čili, počáteční sekvenci bodů (x,y) rozšíříme tak, aby tvořili sekvenci představující
   danou lathe-křivku. Tato rozšířená sekvence je přístupná voláním metod getXs() a getYs().

   Daný bod (x,y) můž být počátkem segmentu křivky nebo může jít k následujícímu bodu.
   Tyto dva stavy rozlišujeme pomocí znaménka u x-ové hodnoty.
   Po vytvoření celé sekvence záporná znaménka odstraníme.
   * Jedná se tak trochu o trik. Na druhou stranu se tak lathe-křivka specifikuje
     mnohem snadněji a bez složitých dat vstupní sekvence.
*/

import javax.vecmath.*;


public class LatheCurve
{
  // počet interpolačních bodů přidaných mezi dva vstupní body
  private final static int STEP = 5;

  double xs[], ys[];     // sekvence bodů (x,y) reprezentujících danou křivku
  double height;         // maximální výška křivky (největší možná y-ová hodnota)


  public LatheCurve(double xsIn[], double ysIn[])
  {
    checkLength(xsIn, ysIn);
    checkYs(ysIn);
    int numVerts = xsIn.length;

    // nastav vektor startTangent tak, aby směřoval doprava
    Point2d startTangent =      // tečný vektor pro první bod vstupní sekvence
        new Point2d((Math.abs(xsIn[1]) - Math.abs(xsIn[0]))*2, 0);

    // nastav vektor endTangent tak, aby směřoval doleva
    Point2d endTangent =        // tečný vektor pro koncový bod vstupní sekvence
         new Point2d((Math.abs(xsIn[numVerts-1]) - 
                      Math.abs(xsIn[numVerts-2]))*2, 0); 

    /* Vynásob tečné vektory číslem 2, takže budou silnější, což povede k tomu, 
       že se křivka prohne více ve specifikovaném směru. */

    makeCurve(xsIn, ysIn, startTangent, endTangent);
  }  // konec metody: LatheCurve()


  public LatheCurve(double xsIn[], double ysIn[], 
					Point2d startTangent, Point2d endTangent)
  // nechá uživatele zadat počáteční a koncové tečné vektory
  {
    checkLength(xsIn, ysIn);
    checkYs(ysIn);
    checkTangent(startTangent);
    checkTangent(endTangent);
    makeCurve(xsIn, ysIn, startTangent, endTangent);
  } // konec konstruktoru LatheCurve()



  private void checkLength(double xsIn[], double ysIn[])
  // testy týkající se délky sekvence
  {
    int numVerts = xsIn.length;

    if (numVerts < 2) {
      System.out.println("Nedostatek bodů pro tvorbu křivky");
      System.exit(0);
    }
    else if (numVerts != ysIn.length) {
      System.out.println("xsIn[] a ysIn[] nemají stejný počet bodů");
      System.exit(0);
    }
  } // konec metody: checkLength()


  private void checkYs(double[] ysIn)
  /* Testy y-ových složek sekvence.
     Také hledá největší y-ovou hodnotu, která se stane výškou křivky.
  */
  {
    if (ysIn[0] != 0) {
      System.out.println("První y-ová souřadnice musí být 0; opravuji");
      ysIn[0] = 0;
    }

   // najdi maximální výšku a uprav všechny y-ové souřadnice tak, aby byly >= 0
    height = ysIn[0]; 
    for(int i=1; i < ysIn.length; i++) {
      if (ysIn[i] >= height)
        height = ysIn[i];
      if (ysIn[i] < 0) {
        System.out.println("Nalezena záporná y-ová souřadnice; upravji na 0");
        ysIn[i] = 0;
      }
    }
    if (height != ysIn[ ysIn.length-1 ])
      System.out.println("Upozornění: maximální výška není poslední y-ovou souřadnicí");
    
  }  // konec metody: checkYs()


  private void checkTangent(Point2d tangent)
  {
    if ((tangent.x == 0) && (tangent.y == 0)) {
      System.out.println("A tangent cannot be (0,0)");
      System.exit(0);
    }
  }  // konec metody: checkTangent()


  private void makeCurve(double xsIn[], double ysIn[], 
					Point2d startTangent, Point2d endTangent)
  {
    int numInVerts = xsIn.length;
    int numOutVerts = countVerts(xsIn, numInVerts);
    xs = new double[numOutVerts];    // výsledná sekvence po přidání dodatečných bodů
    ys = new double[numOutVerts];

    xs[0] = Math.abs(xsIn[0]);    // začátek křivnky je již inicializován
    ys[0] = ysIn[0];
    int startPosn = 1;

    // uchovává tečné vektory pro aktuální segment křivky mezi dvěma body
    Point2d t0 = new Point2d();
    Point2d t1 = new Point2d();

    for (int i=0; i < numInVerts-1; i++) {
      if (i == 0)
        t0.set( startTangent.x, startTangent.y);
      else   // použij předchozí tečný vektor t1
        t0.set(t1.x, t1.y);

      if (i == numInVerts-2)    // další bod je bodem posledním
        t1.set( endTangent.x, endTangent.y);
      else
        setTangent(t1, xsIn, ysIn, i+1);   // tečný vektor v bodě Pi+1

      // pokud xsIn[i] < 0, tak použij odkaz (x,y) na další (x,y)
      if (xsIn[i] < 0) {
        xs[startPosn] = Math.abs(xsIn[i+1]);    // v případě, že další bod je také záporný
        ys[startPosn] = ysIn[i+1];
        startPosn++;
      }
      else {   // vytvoř Hermitovu křivku mezi dvěma body přidání STEP nových bodů
        makeHermite(xs, ys, startPosn, xsIn[i], ysIn[i], xsIn[i+1], ysIn[i+1], t0, t1);
        startPosn += (STEP+1);
      }
    }
  }  // konec metody: makeCurve()


  private int countVerts(double xsIn[], int num)
  /* Počet bodů v nové sekvenci závisí na tom, kolik se má přidat segmentů.
     Každž z segment mezi dvěma body přidá do výsledné sekvence STEP bodů.

     Bod (x,y), kde x je záporné, je počátkem pro úsečku, takže žádné nové body
     přidávat nebudeme. Je-li kladný, přidáme STEP nových bodů.
  */
  {
    int numOutVerts = 1;  
    for(int i=0; i < num-1; i++) {
      if (xsIn[i] < 0)   //  začíná tady
        numOutVerts++;
      else    // v tomto bodě začíná křivka 
        numOutVerts += (STEP+1);
    }
    return numOutVerts;
  }  // konec metody: countVerts()



  private void setTangent(Point2d tangent, double xsIn[], double ysIn[], int i)
  /* Vypočítá tečné vektory na pozici i pomocí Catmull-Ron interpolace pro spliny
  */
  {
    double xLen = Math.abs(xsIn[i+1]) - Math.abs(xsIn[i-1]); 
    double yLen = ysIn[i+1] - ysIn[i-1];
    tangent.set(xLen/2, yLen/2);
  }  // konec metody: setTangent()




  private void makeHermite(double[] xs, double[] ys, int startPosn,
			double x0, double y0, double x1, double y1, Point2d t0, Point2d t1)
  /* Vypočítá souřadnice (x,y) Hermitovy křivky mezi body (x0,y0) a (x1,y1). t0 a t1 jsou
     jejich tečné vektory.

     Bod (x0,y0) se do výsledku nezahrne, neboť byl již přidán na konci předchozího 
     volání této metody.

     Souřadnice uloží do polí xs[] a ys[] počínaje indexem startPosn.

  */
  { 
    double xCoord, yCoord;   
    double tStep = 1.0/(STEP+1);
    double t;

    if (x1 < 0)      // další bod je záporný pro vykreslení úsečky,
      x1 = -x1;      // pro tvorbu křivky z něj udělej bod kladný

    for(int i=0; i < STEP; i++) {
      t = tStep * (i+1);
      xCoord = (fh1(t) * x0) + (fh2(t) * x1) +
               (fh3(t) * t0.x) + (fh4(t) * t1.x);
      xs[startPosn+i] = xCoord;

      yCoord = (fh1(t) * y0) + (fh2(t) * y1) +
               (fh3(t) * t0.y) + (fh4(t) * t1.y);
      ys[startPosn+i] = yCoord;
    }

    xs[startPosn+STEP] = x1;
    ys[startPosn+STEP] = y1;
  }  // konec metody: makeHermite()


  /* Hermitovy interpolační funkce.
     První dvě interpolují body, poslední dvě vektory.
     Pro vysvětlení jejich odvození se nejlépe hodí jakýkoliv kvalitní
     článek z oboru počítačové grafiyk, např. Foley and Van Dam. */

  private double fh1(double t)
  {  return (2.0)*Math.pow(t,3) - (3.0*t*t) + 1;  }

  private double fh2(double t)
  {  return (-2.0)*Math.pow(t,3) + (3.0*t*t); }

  private double fh3(double t)
  {  return Math.pow(t,3) - (2.0*t*t) + t; }

  private double fh4(double t)
  {  return Math.pow(t,3) - (t*t);  }


  // ----------------------------------------
  // přístup k souřadnicím a výšce

  public double[] getXs()
  {  return xs;  }

  public double[] getYs()
  {  return ys;  }

  public double getHeight()
  {  return height;  }

}  // konec třídy: LatheCurve