
// Boid.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th
// Sirinart Sakarin, March 2003, s4210315@calvin.coe.psu.ac.th

/* Agent jsme realizovali takto:
          BG --> TG --> Shape3D
     - uzel typu BranchGroup aby jej šlo odpojit
     - uzel typu TransformGroup aby s ním šlo pohybovat
     - uzel typu Shape3D pro jeho tvar

   Agent má pozici a rychlost.
   Jeho výchozí chování vypadá takto:
      * příležitostně usedá
      * vyhává se překážkám
      * zůstává v oblasti scény
      * nepřekračuje maximální rychlost
      * aplikujeme na něj Reynoldovy rychlostní pravidla pro:
          - soudržnost
          - separaci
          - připojení

   Vyhýbání překážkám má přednost před rychlostními pravidly.

   Reynoldova pravidla jsou uložena v objektu realizujícím chování
   agenta. Obecně se dá říci, že pravidla, která vyžadují průzkum
   spolu-agentů (kteří se dynamicky mění) jsou uložena v objektu chování.

   Pravidla kontrolující statické věci (např. překážky, oblast scény)
   se provádějí v samotném agentovi.

   Agent se aktualizuje voláním metody animateBoid(), kterou lze pro 
   přidání dalšího chování přetížit. Ovšem pro přidání dalších rychlostních
   pravidel je určena metoda doVelocityRules().

   Agent může být 'sežrán', což způsobí jeho odpojení od scény a odebrání ze 
   seznamu v objektu typu BoidsList.
*/

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


public class Boid extends BranchGroup
{
  // hranice pohybu agenta ve scéně
  private final static int FLOOR_LEN = 20;  // mělo by být sudé

  private final static Point3f MIN_PT = 
      new Point3f( -(float)FLOOR_LEN/2.0f, 0.05f, -(float)FLOOR_LEN/2.0f);
   private final static Point3f MAX_PT = 
      new Point3f( (float)FLOOR_LEN/2.0f, 8.0f, (float)FLOOR_LEN/2.0f);
 
  private final static float MAX_SPEED = 0.2f;
  private static final float AVOID_WEIGHT = 0.2f;  // škálování pro vyhnutí se rychlosti

  private final static int PERCH_TIME = 5;         // jak dlouho být posazený
  private final static int PERCH_INTERVAL = 100;   // prodleva mezi usednutími

  private static final float BOID_RADIUS = 0.3f;   // větší, než skutečný poloměr			  						   // používá se pro ohraničující kouli agenta

  // k dispozici podtřídám
  protected FlockBehavior beh;
  protected ArrayList velChanges = new ArrayList();   
                        // uchovává všechny změny rychlostí generované
			// v při každé aktualizaci rychlostními pravidly
  protected Vector3f boidmos = new Vector3f();
  protected Vector3f boidVel = new Vector3f();

  private TransformGroup boidTG = new TransformGroup();
  private Obstacles obstacles;
  private BoundingSphere bs = new BoundingSphere();   // ohraničující koule agenta
  private float maxSpeed;

  private int perchTime = 0;
  private int perchInterval = 0;
  private boolean isPerching = false;

  // pro opakované výpočty
  private Vector3f avoidOb = new Vector3f();    // pro vyhýbání překážkám
  private Transform3D t3d = new Transform3D();  // ukládání pohybů agenta
  private Vector3f newVel = new Vector3f();     // nová rychlost agenta



  public Boid(Color3f boidColour, float velFactor, Obstacles obs, FlockBehavior bh)
  {
    maxSpeed = MAX_SPEED*velFactor;    // proměnlivá maximální rychlost
	obstacles = obs;
    beh = bh;

	boidTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    boidTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    addChild(boidTG);

    boidmos.set( randmosn(), (float)(Math.random()*6.0), randmosn() ); 
    boidVel.set( randVel(), randVel(), randVel()); 
	bs.setRadius(BOID_RADIUS);   // nastav poloměr ohraničující koule

	moveBoid();
	boidTG.addChild( new BoidShape(boidColour) );  // přidej tvar agenta k uzlu typu TG
  } // konec konstruktoru: Boid()

  
  private float randmosn()
  // vrátí realáné číslo mezi -FLOOR_LEN/2 a FLOOR_LEN/2
  {  return (float)(Math.random()*FLOOR_LEN - FLOOR_LEN/2);  }


  private float randVel()
  // vrátí reálné číslo mezi -MAX_SPEED/2 a MAX_SPEED/2
  {  return (float)(Math.random()*MAX_SPEED*2 - MAX_SPEED);  }

  

  private void moveBoid()
  /* Pohyb agenta se skládá z rotace a posunu, který používá
     jeho aktuální rychlost a pozici.
  */
  { t3d.setIdentity();   // resetuj uzel t3d
	t3d.rotY( Math.atan2( boidVel.x, boidVel.z) );  // otoč kolem osy y
					// funkce atan2() zvládá i nulové vstupní argumenty
	t3d.setTranslation(boidmos);
	boidTG.setTransform(t3d);   // aplikuj transformaci na uzel typu TG
  } // konec metody: moveBoid()



  // -------------------------- animateBoid ---------------------
  // volá se ze třídy FlockBehavior a jejích podtříd

  public void animateBoid()
  // nejvyšší metoda pro aktualizaci agenta
  {
    if (isPerching) {
      if (perchTime > 0) {
        perchTime--;  // poseď si ještě o chvilku déle
        return;       // přeskoč zbytek aktualizace agenta
      }
      else {   // hra dokončenaed perching
        isPerching = false;
        boidmos.y = 0.1f;  // postrč agenta pryč z podlahy
        perchInterval = 0;
      }
    }
    // aktualizuj rychlost a polohu agenta, 
  // udržuj jej však uvnitř hranic scény
    boidVel.set( calcNewVel() );
	boidmos.add(boidVel);
    keepInBounds();
	moveBoid();
  } // konec metody: animateBoid()



  private Vector3f calcNewVel()
  /* Aplikuje rychlostní pravidla, nové rychlosti uloží do
     seznamu typu ArrayList velChanges. Poté rychlosti sečte
     a celkou rychlost seřízne na maxSpeed.

     Rychlostní pravidla se aplikují pouze pokud nejous v blízkostí
     žádné překážky -- vyhnutí se překážce má vyšší prioritu než
¨    rychlostní pravidla
  */
  { velChanges.clear();   // vynuluj ArrayList

    Vector3f v = avoidObstacles();         // ověř, není-li nutné vyhnout se překážkám
    if ((v.x == 0.0f) && (v.z == 0.0f))    // rychlost je stále nulová
      doVelocityRules();    // takže můžeme provést rychlostní pravidla
    else 
      velChanges.add(v);    // jinak se pouze vyhni překážce

    newVel.set( boidVel );     // re-inicializuj proměnnou newVel
    for(int i=0; i < velChanges.size(); i++)
      newVel.add( (Vector3f)velChanges.get(i) );  // sečti rychlosti to newVel
   
    newVel.scale( limitMaxSpeed() );
    return newVel;
  }  // konec metody: calcNewVel()


  protected void doVelocityRules()
  // tuto metodu lze přetížit pro přidání nových rychlostních pravidel
  {
    Vector3f v1 = beh.cohesion(boidmos);
	Vector3f v2 = beh.separation(boidmos);
	Vector3f v3 = beh.alignment(boidmos, boidVel);
    velChanges.add(v1); 
    velChanges.add(v2);
	velChanges.add(v3); 
  } // konec metody: doVelocityRules()



  private Vector3f avoidObstacles()
  /* Agent se vyhýbá překážkám tak, že porovná svou ohraničující kouli
     se všemi překážkami ve scéně. Rychlost pro úhybný manévr uloží do avoidOb.

     Výpočtem rychlosti pro skutečný odraz se nezatěžujeme, prostě jde o přesun
     o náhodnou část ve směru os x a z.

     Nárazy na horní plochu překážek, které by vyžadovaly změnu rychlosti v y-ovém
     směru, neuvažujeme.
  */
  { avoidOb.set(0,0,0);   // vynuluj
    // aktualizuj pozici ohraničující koule (but its radius stays the same)
	bs.setCenter( new Point3d( (double)boidmos.x, (double)boidmos.y,
									(double)boidmos.z) );
    if ( obstacles.isOverlapping(bs)) { 
      avoidOb.set( -(float)Math.random()*boidmos.x, 0.0f, 
				   -(float)Math.random()*boidmos.z); 
      // vynásob pro snížení výsledné vzdálenosti od překážky
      avoidOb.scale(AVOID_WEIGHT);
    }
    return avoidOb;
  }  // konec metody: avoidObstacles()



  private float limitMaxSpeed()
  // vynásob rychlost agenta tak, aby nebyla větší než maxSpeed
  {
	float speed = boidVel.length();
	if(speed > maxSpeed)
	  return maxSpeed/speed;
    else
      return 1.0f;   // žádné násobení
  } // konec metody: limitMaxSpeed



  private void keepInBounds()
  /* Upraví pozici a rychlost agenta tak, aby zůstal v oblasti scény
     definované konstantami MIN_PT a MAX_PT.
     Dále prověří, jestli nemá být zahájeno usedání.
  */
  {  
    // ověř, je-li x-ová část pozice agenta uvnitř oblasti scény
	if (boidmos.x > MAX_PT.x) {     // za maximální hranicí
      boidmos.x = MAX_PT.x;         // vrať ho zpět na okraj
	  boidVel.x = -Math.abs(boidVel.x);   // běž směrem od hranice
	}
	else if (boidmos.x < MIN_PT.x) {
      boidmos.x = MIN_PT.x;
	  boidVel.x = Math.abs(boidVel.x); 
	}
	
    // ověř, je-li z-ová část uvnitř oblasti scény
	if (boidmos.z > MAX_PT.z) {
      boidmos.z = MAX_PT.z;
	  boidVel.z = -Math.abs(boidVel.z);
	}
	else if (boidmos.z < MIN_PT.z) {
      boidmos.z = MIN_PT.z;
	  boidVel.z = Math.abs(boidVel.z); 
	}

    // ověř, je-li y-ová část uvnitř oblasti scény
	if (boidmos.y > MAX_PT.y) {
      boidmos.y = MAX_PT.y;
	  boidVel.y = -Math.abs(boidVel.y);
	}
	else if (boidmos.y < MIN_PT.y) {
      boidmos.y = MIN_PT.y;
	  boidVel.y = Math.abs(boidVel.y); 
    }

    perchInterval++;
    if ((perchInterval > PERCH_INTERVAL) &&
		(boidmos.y <= MIN_PT.y)) {      // nech agenta usednout
      boidmos.y = 0.0f;   // přistaň na podlahu
	  perchTime = PERCH_TIME;  // spusť čas odpočítávající dobu sedění
      isPerching = true;	
	}
  } // konec metody: keepInBounds() 


  // ------------------------- ostatní veřejné metody -------------------

  public Vector3f getBoidmos()
  { return boidmos;  }

  public Vector3f getBoidVel()
  { return boidVel;  }

} // konec třídy: Boid