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

/* Částicový systém pro demonstraci efektu fontány pomocí bodů.

   Body jsou uloženy v poli LineArray pomocí geometrie BY_REFERENCE.
   Mění se pouze souřadnice úseček.

   Mění se souřadnice a barva bodů. Barva každé částice postupně
   přechází ze žluté na červenou.
   
   Třídy odvozené od rozhraní GeometryUpdater a třídy Behavior jsou
   uvnitř třídy PointParticles. 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 PointArray.
*/


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


public class PointParticles extends Shape3D 
{
  private final static int POINTSIZE = 3;
  private final static float FADE_INCR = 0.05f;

  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 = 6.0f; 

  // počáteční barva částice
  private final static Color3f yellow = new Color3f(1.0f, 1.0f, 0.6f);

  private PointArray pointParts;     // 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 PointParticles(int nps, int delay) 
  {
    numPoints = nps;

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

    // pointParts.setCapability(PointArray.ALLOW_COORDINATE_WRITE);
	// pointParts.setCapability(PointArray.ALLOW_COLOR_WRITE);

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

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

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

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


  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 3 == jedna souřadnice (x,y,z)
    for(int i=0; i < numPoints*3; i=i+3)
      initParticle(i);

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

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


  private void initParticle(int i)
  // Inicializuje souřadnice v pole cs[] od i do i+2 (1 bod)
  { 
    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;

    // na začátku je barva částice žlutá
    cols[i] = yellow.x;  cols[i+1] = yellow.y; cols[i+2] = yellow.z;
  }  // konec metody: initParticle()



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

    PointAttributes pa = new PointAttributes();
    pa.setPointSize( POINTSIZE );    // způsobí chybu při z-ovém uspořádání
    app.setPointAttributes(pa);

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



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

  public class PointsUpdater 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 PointParticles.
    */
    { // GeometryArray ga = (GeometryArray) geo;
      // float cds[] = ga.getCoordRefFloat();

      // krok velikosti 3 == jedna souřadnice (x,y,z)
      for(int i=0; i < numPoints*3; i=i+3) {
        if (cs[i+1] < 0.0f)    // částice klesla pod osu y
          initParticle(i);     // znovu ji inicializuj
        else       // aktualizuj částici
          updateParticle(i);
      }
    }  // konec metody: updateData()


    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

      updateColour(i);
    } // konec metody: updateParticle()


    private void updateColour(int i)
    /* Prolne barvu do červené snížením zelené a modré části
       počáteční barvy po krocích velikosti FADE_INCR
    */
    { cols[i+1] = cols[i+1] - FADE_INCR;   // zelená část
      if (cols[i+1] < 0.0f)
        cols[i+1] = 0.0f;

      cols[i+2] = cols[i+2] - FADE_INCR;   // modrá část
      if (cols[i+2] < 0.0f)
        cols[i+2] = 0.0f;
    }  // konec metody: updateColour()
    
  } // konec třídy: PointsUpdater



  // ---------------- 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 PointsUpdater updater;

    public PartclesControl(int delay, PointsUpdater 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
      pointParts.updateData(updater);   // požadavek aktualizace geometrie
      wakeupOn( timedelay );
    }
  } // konec třídy: PartclesControl


} // konec třídy: PointParticles
