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

/*  Vytváří podgraf scény:

      beamTG --> beamSW --> beamDir TG --> válec paprsku
                       |
                        --> explShape (animace exploze)
     
    Uzel beamTG slouží k pohybu paprsku a pro explozi. Uzel typu Switch
    je pro výběr viditelnosti tvarů (nic, paprsek nebo exploze), a uzel
    beamDir typu TG slouží k doladění pozice válce a k jeho rotaci.

    Objekt typu KeyBehavior volá metodu fireBeam() třídy AmmoManager,
    která na každém objektu typu LaserShot volá metodu requestFiring().

    Pokud se daný objekt typu LaserShot zrovna nepoužívá (inUse == false), 
    pak spustí vlákno AnimBeam, které zavolá jeho metodu moveBeam() a inUse
    se nastaví na hodnotu true.

    Metoda moveBeam() postupně pohybuje paprskem kupředu, počínaje aktuální 
    polohou pozorovatele (steerTG). Pokud se paprsek dostate dostatečně blízko
    k cíli, zobrazí se exploze. V opačném případě, pokud urazí od zbraně vzdálenost
    MAX_RANGE, tak zmizí. Nakonec se inUse opět vrátí na hodnotu false.
*/

import java.text.DecimalFormat;

import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.*;
import javax.vecmath.*;


public class LaserShot
{
  private static final double MAX_RANGE = 6.0;  // maximální možná vzdálenost paprsku od zbraně
  private static final double STEP = 1.0;       // vzdálenost urazená při každém posunu paprsku
  private static final Vector3d INCR_VEC = new Vector3d(0, 0, -STEP);
  private static final long SLEEP_TIME = 100;   // prodleva mezi posuny paprsku
  private static final double HIT_RANGE = 1.0;  // tolerance zásahu cíle


  private TransformGroup steerTG;   // uzel typu TG pro výhled uživatele

  private TransformGroup beamTG;    // pohyb paprsku/exploze
  private Switch beamSW;            // pro volbu viditelnosti paprsku/exploze
  private Cylinder beam;            // paprsek je válec
  private ImageCsSeries explShape;  // uchovává explozi

  private boolean inUse;        // zda-li parsek letí, či nikoliv
  private Vector3d targetVec;   // lokace cíle

  // pro opakované výpočty
  private Transform3D tempT3d = new Transform3D();
  private Transform3D toMove = new Transform3D();
  private Transform3D localT3d = new Transform3D();
  private Vector3d currVec = new Vector3d();



  public LaserShot(TransformGroup steerTG, ImageComponent2D[] exploIms, Vector3d tarVec)
  {
    this.steerTG = steerTG;
    makeBeam(exploIms);
    targetVec = tarVec;
    inUse = false;
   }  // konec metody: LaserShot()


  private void makeBeam(ImageComponent2D[] exploIms)
  // Vytváří podgraf paprsku a exploze podrobně popsaný výše.
  {
    // pozice paprsku
    Transform3D beanT3d = new Transform3D();
    beanT3d.rotX(Math.PI/2);   // 90 stupňů, aby ukazoval do scény
    beanT3d.setTranslation(new Vector3d(0, -0.3, -0.25));  // dolů a dovnitř
    TransformGroup beanDir = new TransformGroup();
    beanDir.setTransform(beanT3d);

    beam = new Cylinder(0.05f, 0.5f, makeRedApp() );  // tenký červený válec
    beam.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);  // pro pozdější testování pozice

    beanDir.addChild( beam );

    // vytvoř přepínač viditelnosti tvarů
    beamSW = new Switch();
    beamSW.setCapability(Switch.ALLOW_SWITCH_WRITE);

    beamSW.addChild( beanDir );             // přidej paprsek do přepínače
    beamSW.setWhichChild( Switch.CHILD_NONE );   // zpočátku není vidět

    // přidej do přepínače explozi, se středem v bodě (0,0,0) a velikosti 2.0f
    explShape = new ImageCsSeries( new Point3f(), 2.0f, exploIms);
    beamSW.addChild( explShape );
    beamSW.setWhichChild( Switch.CHILD_NONE );   // zpočátku neviditelná

    // nejvyšší uzel typu TG paprsku
    beamTG = new TransformGroup();
    beamTG.addChild( beamSW );
    beamTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    beamTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
  } // konec metody: makeBeam()


  private Appearance makeRedApp()
  {
    Color3f medRed = new Color3f(0.8f, 0.4f, 0.3f);
    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f specular = new Color3f(0.9f, 0.9f, 0.9f);

    Material redMat = new Material(medRed, black, medRed, specular, 80.0f);
    redMat.setLightingEnable(true);
    
    Appearance redApp = new Appearance();
    redApp.setMaterial(redMat);
    return redApp;
  } // konec metody: makeRedApp()


  public TransformGroup getTG()
  // potřebuje jej třída AmmoManager
  {  return beamTG;  }



  // ------------------------ metody pro střelbu ----------------


  public boolean requestFiring()
  // Pokud se paprsek nepoužívá, pak jej vystřel
  { if (inUse)
      return false;
    else {
      inUse = true;
      new AnimBeam(this).start(); // volá moveBeam() zevnitř vlákna
      return true;
    }
  }  // konec metody: fireBeam()


  public void moveBeam()
  /* Přesune parsek s prodlevami mezi každým krokem.
     Dostane-li paprsek dostatečně blízko k cíli, pak zobrazí explozi,
     jinak při dosažení vzdálenosti MAX_RANGE paprsek zmizí.
  */
  { // umísti paprsek do aktuální pozice ruky se zbraní
    steerTG.getTransform( tempT3d );
    // System.out.println("steerTG Transform3D: " + tempT3d);
    beamTG.setTransform( tempT3d);
    showBeam(true);

    double currDist = 0.0;
    boolean hitTarget = closeToTarget();
    while ((currDist < MAX_RANGE) && (!hitTarget)) {
      doMove(INCR_VEC);
      hitTarget = closeToTarget();
      currDist += STEP;
      try {
        Thread.sleep(SLEEP_TIME);    
      } 
      catch (Exception ex) {}
    }

    showBeam(false);     // zneviditelni paprsek
    if (hitTarget)
      showExplosion();   // došlo-li k zásahu, zobraz explozi
    inUse = false;       // let střely skončil
  }  // konec metody: moveBeam()


  private void doMove(Vector3d mv)
  {
    beamTG.getTransform( tempT3d );
    toMove.setTranslation( mv );
    // System.out.println("Pohybuji: " + mv);
    tempT3d.mul(toMove);
    beamTG.setTransform( tempT3d );
  } // konec metody: doMove()


  private boolean closeToTarget()
  /* Paprsek je dostatečně blízko, pokud je jeho aktuální pozice (currVec)¨
     kousek od pozice cíle (targetVec).
  */
  { 
    beam.getLocalToVworld( localT3d );    // vezmi uzel TG paprsku v souřadnicích světa
    localT3d.get(currVec);                // získej složky (x,y,z)
    // System.out.println("currVec: " + currVec);

    currVec.sub(targetVec);    // vzdálenost mezi dvěma body
    double sqLen = currVec.lengthSquared();
    if (sqLen < HIT_RANGE*HIT_RANGE)
      return true;
    return false;
  }  // konec metody: closeToTarget()


  private void showBeam(boolean toVisible)
  // přepíná viditelnost paprsku
  {
    if (toVisible)
      beamSW.setWhichChild(0);   // zviditelni
    else
      beamSW.setWhichChild( Switch.CHILD_NONE );   // zneviditelni
  }  // konec metody: showBeam()


  private void showExplosion()
  // spustí explozi
  {
    beamSW.setWhichChild(1);   // zviditelni
    explShape.showSeries();    // bum!
    beamSW.setWhichChild( Switch.CHILD_NONE );   // zneviditelni
  }  // konec metody: showExplosion()



  // -------------------- pro ladění ---------------------


  private void printTG(TransformGroup tg, String name)
  // zobrazí údaje o uzlu typu TransformGroup jménem name
  {
     Transform3D t3d = new Transform3D();
     tg.getTransform( t3d );
     // System.out.println(name + " Transformace:\n" + t3d);
     Vector3d vec = new Vector3d();
     t3d.get(vec);

     DecimalFormat df = new DecimalFormat("0.00");  // 2 dm
     // System.out.println(name + " Vector: " + vec);
     System.out.println(name + " Vector: (" +
			df.format(vec.x) + ", " + df.format(vec.y) + ", " +
			df.format(vec.z) + ")");
  }  // konec metody: printTG()


}  // konec třídy: LaserShot
