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

/* Částicový systém pro demonstraci fontány pomocí otexturovaného
   a obarveného objektu typu QuadArray složeného z řady čtyřúhelníků.

   Používá pouze jednu texturu a barva pochází z uzlové komponenty
   Material.

   Body čyřúhelníku jsou uloženy v objektu QuadArray 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 QuadParticles. 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 QuadArray.
   
   Třída QuadParticles je odvozená od třídy OrientedShape3D a ne Shape3D.
   Je nastavena tak, aby se otáčela kolem kladné osy y. 
*/


import javax.swing.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.image.*;
import java.util.Enumeration;


public class QuadParticles extends OrientedShape3D
{
  private final static String TEX_FNM = "smoke.gif";
  private static final float QUAD_LEN = 0.5f;

  // barvy částic
  private final static Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
  private final static Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
  private final static Color3f red = new Color3f(0.9f, 0.1f, 0.2f);
  private final static Color3f darkRed = new Color3f(1.0f, 0.0f, 0.0f);

  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; 

  private static final float DELTA = 0.05f;  // pro náhodné změny barev


  private QuadArray quadmarts;       // Geometrie uchovávající souřadnice apod.
  private PartclesControl partBeh;   // objekt chování spouštějící aktualziace

  private float[] cs, vels, accs, norms;        // bohužel musíme používat typ float
  private float[] tcoords;

  // Počáteční pozices pro čtyřúhelník, proti směru hodinových ručiček od levého spodního bodu
  private float[] p1 = {-QUAD_LEN/2, 0.0f, 0.0f};
  private float[] p2 = {QUAD_LEN/2, 0.0f, 0.0f};
  private float[] p3 = {QUAD_LEN/2, QUAD_LEN, 0.0f};
  private float[] p4 = {-QUAD_LEN/2, QUAD_LEN, 0.0f};
  
  private int numPoints;


  public QuadParticles(int nps, int delay)
  {
    if (nps%4 != 0)  // není násobkem 4
      nps = ((int)((nps+4)/4))*4;   // zaokrouhli nahoru k nejbližšímu násobku   
    numPoints = nps;

    // pro sledování pozorovatele rotuj kolem osy y
	// setAlignmentAxis( 0.0f, 1.0f, 0.0f);
	setAlignmentAxis( 0.0f, 0.0f, 1.0f);

    // Objekt typu QuadArray s odkazy BY_REFERENCE
    quadmarts = new QuadArray(numPoints, 
                     GeometryArray.COORDINATES | 
					 GeometryArray.TEXTURE_COORDINATE_2 |
					 GeometryArray.NORMALS  |
                     GeometryArray.BY_REFERENCE );

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

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

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


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


  private void createGeometry()
  /* Vytvoří pole reálných čísel pro souřadnice, rychlosti, akcelerace,
     normálové vektory a souřadnice textury. Přiřadí souřadnice,
     normálové vektory a souřadnice textury objektu typu QuadArray
     pomocí odkazu BY_REFERENCE.

     Později se budou měnit jen souřadnice.
  */ 
  { 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];
    norms = new float[numPoints*3];

    tcoords = new float[numPoints*2];  // dvě souřadnice textury pro jeden bod
    // textureCoordsArray = new TexCoord2f[numPoints];

    // inicializuj pozici, rychlost a akceleraci
    // kroky o velikosti 12 == 4 body (x,y,z) == 1 čtyřúhelník
    for(int i=0; i < numPoints*3; i=i+12)
      initQuadmarticle(i);
    // odkazuj se na souřadnice v objektu QuadArray
    quadmarts.setCoordRefFloat(cs);    // použij odkaz BY_REFERENCE

    /* Inicializuje souřadnice textury. 
       Texturu aplikujeme na každý čtyřúhelník v poli.
       Pro každý čtyřúhelník (4 body):
       We are applying the texture to each quad in the array
         { 0.0f, 0.0f,  1.0f, 0.0f,
		   1.0f, 1.0f,  0.0f, 1.0f  };
       Jedna souřadnice textury (s,t) pro každý bod (x,y,z).
    */
    for(int i=0; i < numPoints*2; i=i+8) {
      tcoords[i]   = 0.0f; tcoords[i+1] = 0.0f;  // pro 1 bod
      tcoords[i+2] = 1.0f; tcoords[i+3] = 0.0f;
      tcoords[i+4] = 1.0f; tcoords[i+5] = 1.0f;
      tcoords[i+6] = 0.0f; tcoords[i+7] = 1.0f;
    }
	quadmarts.setTexCoordRefFloat(0, tcoords);   // použij odkaz BY_REFERENCE


    // inicializuj normálové vektory tak, aby směřovaly podél kladné osy z
    Vector3f norm = new Vector3f();
    for(int i=0; i < numPoints*3; i=i+3) {
      randomNormal(norm);
      norms[i] = norm.x;  norms[i+1] = norm.y; norms[i+2] = norm.z;
    }
    quadmarts.setNormalRefFloat(norms);   // použij odkaz BY_REFERENCE

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


  private void initQuadmarticle(int i)
  // inicializuje čtyřúhelník složený ze 4 částic od i do i+11
  { 
    setCoord(cs, i, p1);  // nastav počáteční pozice
    setCoord(cs, i+3, p2);
    setCoord(cs, i+6, p3);
    setCoord(cs, i+9, p4);

    // 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;
 
    // všechny částice ve čtyřúhelníku mají stejnou rychlost i akceleraci
    copyCoord(vels, i+3, i);  
    copyCoord(vels, i+6, i);
    copyCoord(vels, i+9, i);

    copyCoord(accs, i+3, i);
    copyCoord(accs, i+6, i);
    copyCoord(accs, i+9, i);
  }  // konec metody: initQuadmarticle()


  private void setCoord(float[] fs, int i, float[] p)
  // zkopíruje 3 reálná čísla z pole p do pole fs počínaje indexem i
  {  fs[i] = p[0];
     fs[i+1] = p[1];
     fs[i+2] = p[2];
  }  // konec metody: setCoord()


  private void copyCoord(float[] fs, int to, int from)
  // zkopíruje 3 reálná od fs[from] do fs[to]
  { fs[to] = fs[from];
    fs[to+1] = fs[from+1];
    fs[to+2] = fs[from+2];
  } // konec metody: copyCoord()


  private void randomNormal(Vector3f v)
  // Vytvoří jednotkový vektor. x-ové a y-ové hodnoty mohou být +ve či -ve. 
  // z-ová hodnota je kladná, takže vektor směřuje k pozorovateli.
  { float z = (float) Math.random();     // mezi 0-1
    float x = (float)(Math.random()*2.0 - 1.0);   // -1 až 1
    float y = (float)(Math.random()*2.0 - 1.0);   // -1 až 1
    v.set(x,y,z);
    v.normalize();
  } // konec metody: randomNormal()


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

    // vypni ořezávání zadních stěn
    // PolygonAttributes pa = new PolygonAttributes();
    // pa.setCullFace( PolygonAttributes.CULL_NONE );
    // app.setPolygonAttributes( pa );

    // míchání barev s průhledností; textura může mít nepravidelná tvar
    TransparencyAttributes tra = new TransparencyAttributes();
    tra.setTransparencyMode( TransparencyAttributes.BLENDED );
    app.setTransparencyAttributes( tra );

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

    // načti a nastav texturu
    System.out.println("Loading textures from " + TEX_FNM);
    TextureLoader loader = new TextureLoader(TEX_FNM, null);
    Texture2D texture = (Texture2D) loader.getTexture();
    app.setTexture(texture);      // nastav texturu

    // nastav materiál: krvavá
    Material mat = new Material(darkRed, black, red, white, 20.f);
    mat.setLightingEnable(true);
    app.setMaterial(mat);

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



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

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

      // krok velikosti 12 == 4 souřadnice (x,y,z) == jeden čtyřúhelník
      for(int i=0; i < numPoints*3; i=i+12)
         updateQuadmarticle(i);
    }  // konec metody: updateData()


    private void updateQuadmarticle(int i)
    {
      if ((cs[i+1] < 0.0f) && (cs[i+4] < 0.0f) && 
          (cs[i+7] < 0.0f) && (cs[i+10] < 0.0f))   
        // celý čtyřúhelník klesl pod osu y
        initQuadmarticle(i);
      else {
        updateParticle(i);   // všechny body čtyřúh. se aktualizují stejně way
        updateParticle(i+3);
        updateParticle(i+6);
        updateParticle(i+9);
      }
    }  // konec metody: updateQuadmarticle()



    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

      // perturbuj (trošku pozměň) hodntoy (x,y,z)
      /* Bez následujícího kódu by velikost každého čtyřúhelníku zůstala
         beze nezměněná, protože každý bod (x,y,z) se pohybuje úplně stejně. */
      cs[i] = perturbate(cs[i], DELTA);
      cs[i+1] = perturbate(cs[i+1], DELTA);
      cs[i+1] = perturbate(cs[i+1], DELTA);

      // calculate new velocities
      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 float perturbate(float f, float range)
    // vrátí reálné číslo, které vzniklo přičtením čísla v rozsahu
    // -range až range k číslu f
    {
      float randomRange = ((float)(Math.random()*range*2.0f))-range;
      return (f + randomRange);
    }  // konec metody: perturbate()

    
  } // konec metody: QuadsUpdater class


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

    public PartclesControl(int delay, QuadsUpdater 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
      quadmarts.updateData(updater);   // požadavek aktualizace geometrie
      wakeupOn( timedelay );
    }

  } // konec třídy: PartclesControl



} // konec třídy: QuadParticles
