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

/* Částicový systém pro demonstraci efektu fontány pomocí úseček.
   
   Body jsou uloženy v poli LineArray pomocí geometrie BY_REFERENCE.
   Mění se pouze souřadnice úseček.

   Třídy odvozené od rozhraní GeometryUpdater a třídy Behavior jsou
   uvnitř třídy LineParticles. Aktualizační objekt tak může 
   při spuštění metody updateData() přímo přistupovat k souřadnicím.
   Instance třídy odvozené od třídy Behavior pak má k dispozici pole
   typu LineArray.
*/

import javax.media.j3d.*;
import javax.vecmath.*;
import java.util.Enumeration;


public class LineParticles extends Shape3D 
{
  private final static int LINEWIDTH = 3;

  private static final float GRAVITY = 9.8f;
  private static final float TIMESTEP = 0.05f;
  private static final float XZ_VELOCITY = 2.0f; 
  private static final float Y_VELOCITY = 8.0f; 

  // barvy částic
  private final static Color3f red = new Color3f(1.0f, 0.0f, 0.0f);
  private final static Color3f yellow = new Color3f(1.0f, 1.0f, 0.6f);

  private LineArray lineParts;  // Geometrie uchováváající souřadnice a barvy
  private PartclesControl partBeh;   // objekt chování spouštějící aktualziace

  private float[] cs, vels, accs, cols;     // bohužel musíme používat typ float
  private int numPoints;


  public LineParticles(int nps, int delay) 
  {
    if (nps%2 == 1)  // není sudé
      nps++;
    numPoints = nps;

    // objekt typu LineArray s geometrií odkazovanou BY_REFERENCE
    lineParts = new LineArray(numPoints, 
						LineArray.COORDINATES | LineArray.COLOR_3 |
						LineArray.BY_REFERENCE );

    // lineParts.setCapability(LineArray.ALLOW_COORDINATE_WRITE);
    // odkazovaná data lze číst i zapisovat
    lineParts.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
    lineParts.setCapability(GeometryArray.ALLOW_REF_DATA_WRITE);

    // bity oprávnění uzlu typu Shape3D
    // setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);

    LinesUpdater updater = new LinesUpdater();
    partBeh = new PartclesControl(delay, updater);

    createGeometry();
    createAppearance();
  } // konec metody: LineParticles()


  public Behavior getParticleBeh()
  // pro objekt typu WrapParticles3D
  { return partBeh;  }


  private void createGeometry()
  { 
    cs = new float[numPoints*3];   // pro uložení každé souřadnice (x,y,z)
    vels = new float[numPoints*3];
    accs = new float[numPoints*3];
    cols = new float[numPoints*3];

    // krok velikosti 6 == dvě souřadnice (x,y,z) == jedna čára
    for(int i=0; i < numPoints*3; i=i+6)
      initTwoParticles(i);

    // ulož souřadnice a barvy do pole typu LineArray
    lineParts.setCoordRefFloat(cs);    // použij odkaz BY_REFERENCE
    lineParts.setColorRefFloat(cols);

    setGeometry(lineParts);
  }  // konec metody: createGeometry()


  private void initTwoParticles(int i)
  /* Inicializuje souřadnice v pole cs[] od i do i+5 (2 body / 1 úsečka).
     Vše provádíme po 2 bodech, protože tvoří dva konce úsečky.
  */
  { cs[i] = 0.0f; cs[i+1] = 0.0f; cs[i+2] = 0.0f;   // (x,y,z) v počátku

    // náhodná rychlost v rovnině XZ spojená s vektorem XZ_VELOCITY
    double xvel = Math.random()*XZ_VELOCITY;
    double zvel = Math.sqrt((XZ_VELOCITY*XZ_VELOCITY) - (xvel*xvel));
    vels[i] = (float)((Math.random() < 0.5) ? -xvel : xvel);    // rychlost ve směru x
    vels[i+2] = (float)((Math.random() < 0.5) ? -zvel : zvel);  // rychlost ve směru z
    // rychlost ve směru yocity
    vels[i+1] = (float)(Math.random() * Y_VELOCITY);
    
    // neměnné zrychlení směrem dolů podél osy y
    accs[i] = 0.0f; accs[i+1] = -GRAVITY; accs[i+2] = 0.0f;

    // další částice začíná tamtéž, je však o jednu aktualizaci vpředu
    cs[i+3] = cs[i]; cs[i+4] = cs[i+1]; cs[i+5] = cs[i+2];
    vels[i+3] = vels[i]; vels[i+4] = vels[i+1]; vels[i+5] = vels[i+2];
    accs[i+3] = accs[i]; accs[i+4] = accs[i+1]; accs[i+5] = accs[i+2];
    updateParticle(i+3);

    // nastav počáteční barvy pro první častici
    Color3f col = (Math.random() < 0.5) ? yellow : red;
    cols[i] = col.x;   cols[i+1] = col.y; cols[i+2] = col.z;
    // další částice má stejné barvy
    cols[i+3] = col.x; cols[i+4] = col.y; cols[i+5] = col.z;

  }  // konec metody: initTwoParticles()


  private void updateParticle(int i)
  /* Vypočítá novou pozici a rychlost částice (kterou bere jako
     projektil). Akcelerace je konstantní.
  */
  {
    cs[i] += vels[i] * TIMESTEP +
                      0.5 * accs[i] * TIMESTEP * TIMESTEP;     // x-ová souřadnice
    cs[i+1] += vels[i+1] * TIMESTEP +
                      0.5 * accs[i+1] * TIMESTEP * TIMESTEP;   // y-ová souřadnice
    cs[i+2] += vels[i+2] * TIMESTEP +
                      0.5 * accs[i+2] * TIMESTEP * TIMESTEP;   // z-ová souřadnice

    // vypočítej nové rychlosti
    vels[i] += accs[i] * TIMESTEP;      // rychlost ve směru x
    vels[i+1] += accs[i+1] * TIMESTEP;  // rychlost ve směru y
    vels[i+2] += accs[i+2] * TIMESTEP;  // rychlost ve směru z
  } // konec metody: updateParticle()


  private void createAppearance()
  {
    Appearance app = new Appearance();

    LineAttributes la = new LineAttributes();
    // la.setLineWidth( LINEWIDTH );    // způsobí chybu při z-ovém uspořádání
    // la.setLineanti-aliasingEnable(true);
    app.setLineAttributes(la);

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



  // -------------- vnitřní tířda LinesUpdater ----------------

  public class LinesUpdater implements GeometryUpdater
  {
    public void updateData(Geometry geo)
    /* Aktualizace geometrie spouští systém.
      Místo použití parametru geo přistupujeme přímo do pole cs[]
      naší třídy LineParticels.
    */
    { // GeometryArray ga = (GeometryArray) geo;
      // float cds[] = ga.getCoordRefFloat();

      // krok velikosti 6 == dvě souřadnice (x,y,z) == jedna čára
      for(int i=0; i < numPoints*3; i=i+6) {
        if ((cs[i+1] < 0.0f) && (cs[i+4] < 0.0f))   
        // obě částice čáry klesly pod osu y
          initTwoParticles(i);   // znovu ji inicializuj
        else {       // proveď aktualizaci obou částic
          updateParticle(i);
          updateParticle(i+3);
        }
      }
    }  // konec metody: updateData()

  } // konec třídy: LinesUpdater


  // ---------------- vnitřní třída: PartclesControl --------------


  public class PartclesControl extends Behavior
  // Každých timedelay ms si pomocí aktualizačního objektu vyžádá aktualizaci.
  {
    private WakeupCondition timedelay;
    private LinesUpdater updater;

    public PartclesControl(int delay, LinesUpdater updt)
    {  timedelay = new WakeupOnElapsedTime(delay); 
       updater = updt;
    }

    public void initialize( )
    { wakeupOn( timedelay );  }

    public void processStimulus(Enumeration criteria)
    { // ignoruj kritéria
      // aktualizuj pole úseček voláním aktualizačního objektu
      lineParts.updateData(updater);   // požadavek aktualizace geometrie
      wakeupOn( timedelay );
    }

  } // konec třídy: PartclesControl


} // konec třídy: LineParticles
