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

/* Třída Sprite3D načte 3D-obrázek a umístí jej do pozice (0,0,0).
   Předpokládáme, že skutečná pozice objektu je (0,0) v rovině XZ.
   Y-ová pozice se může lišit, ale základna objektu se s největší
   pravděpodobností bude dotýkat roviny XZ.

   Pohyby jsou omezeny na rovinu XZ a rotace kolem osy y.

   Objekt se nemůže pohybovat mimo podlahu nebo cestovat skrze překážky
   (definované pomocí objektu typu Obstacles).

   Třída je velmi podobná svému protějšku v adresáři Tour3D.
   Kód týkající se síťové verze:
     - jméno uživatele nad spritem, které zůstává natočené podél kladné osy z,
     - vrátí odpojitelný uzel typu BG pro scénu,
     - uchovává aktuální rotaci kolem osy y a metody, které ji vrací a nastavují.
*/

import java.text.DecimalFormat;
import java.util.*;
import java.io.*;
import java.awt.Font;

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


public class Sprite3D 
{
  private final static double OBS_FACTOR = 0.5;  
             // pro snížení poloměru ohraničující koule kolem spritu
             // při testování průsečíku s překážkami

  private BranchGroup objectBG;       // pro přidání/odebrání do/ze scény
  private TransformGroup objectTG;    // uzel typu TG, ze kterého načtený objekt visí
  private Transform3D t3d, toMove, toRot;    // pro manipulaci s transformacemi objectTG
  private Switch visSwitch;           // pro zviditelnění/zneviditelnění objektu

  private double radius;
  private boolean isActive;   // je sprite aktivní?
  private double currRotation;

  private Obstacles obs;      // překážky na cestě
  private DecimalFormat df;   // pro jednodušší ladící výstup


  public Sprite3D(String userName, String fnm, Obstacles obs)
  { 
    df = new DecimalFormat("0.###");  // 3 dm
    this.obs = obs;
    currRotation = 0.0;

    // načti objekt a souřadnice
    PropManager propMan = new PropManager(fnm, true);

    // získej pozici a polomeř načteného objektu
    // snad bude pozice blízko (0, ??, 0)
    Vector3d objLoc = propMan.getLoc();
    radius = propMan.getScale();      // předpkoládáme, že radius == scale
    // printTuple(objLoc, "objLoc");
    // System.out.println("radius: " + df.format(radius));

    // vytvoř přepínač pro nastavování viditelnosti
    visSwitch = new Switch();
    visSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);
    visSwitch.addChild( propMan.getTG() );     // přidej objekt k přepínači
    visSwitch.setWhichChild( Switch.CHILD_ALL );   // zviditelni

    // vytvoř transformační skupinu pro objekt a jeho jméno
    objectTG = new TransformGroup();
    objectTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    objectTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objectTG.addChild( visSwitch );
    objectTG.addChild( makeName(userName) );  // jméno objektu a uzel typu TG

    // skupinová větev pro objekt
    objectBG = new BranchGroup();
    objectBG.setCapability( BranchGroup.ALLOW_DETACH );		
    objectBG.addChild(objectTG);

    t3d = new Transform3D();
    toMove = new Transform3D();
    toRot = new Transform3D();
    isActive = true;
  }  // konec metody: Sprite3D()


  private TransformGroup makeName(String userName)
  /* Jméno klienta plující nad sprite a vždy směřující k pozorovateli. */
  {
    // světle žlutý text
    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f brightYellow = new Color3f(1.0f,1.0f, 0.0f);
    Appearance apText = new Appearance();
	Material m = new Material(brightYellow, black, brightYellow, brightYellow, 64f);
    m.setLightingEnable(true);
	apText.setMaterial(m);

    // vyvoř pro jméno 3D-text
    Font3D f3d = new Font3D( new Font("SansSerif", Font.PLAIN, 2),
                             new FontExtrusion() );
    Text3D txt = new Text3D(f3d, userName);
    txt.setAlignment(Text3D.ALIGN_CENTER);

    OrientedShape3D textShape = new OrientedShape3D();
    textShape.setGeometry(txt);
    textShape.setAppearance(apText);
	textShape.setAlignmentAxis( 0.0f, 1.0f, 0.0f);

    // vytvoř transformační skupinu pro jméno objektu
    TransformGroup nameTG = new TransformGroup();
    Transform3D nameT3d = new Transform3D();
    // Předpokládáme jednotnou velikost znaků, nastav měřítko tak,
    // aby se řetězec vešel do pohledu
    nameT3d.setScale( 1.2/(userName.length()) );
    nameT3d.setTranslation( new Vector3d(0, radius*2, 0) );
    nameTG.setTransform(nameT3d);
    nameTG.addChild( textShape ); 

    return nameTG;
  }  // konec metody: makeName()



  public BranchGroup getBG()
  {  return objectBG; }


  public void detach()
  // odebere sprite ze scény
  {  objectBG.detach(); }



  public void setPosition(double xPos, double zPos)
  { Point3d currLoc = getCurrLoc();
    double xMove = xPos - currLoc.x;
    double zMove = zPos - currLoc.z;
    moveBy(xMove, zMove);
  } // konec metody: setPosition()



  public boolean moveBy(double x, double z)
  /* posunue sprite o offset x a z, ale jen tehdy, pokud je uvnitř
     podlahy a v blízkosti nejsou překážky. */
  {
    if (isActive()) {
      Point3d nextLoc = tryMove(new Vector3d(x, 0, z));
      if (obs.nearObstacle(nextLoc, radius*OBS_FACTOR))
        return false;
      else {
        doMove( new Vector3d(x, 0, z) );   // neefektivní přepočítávání
        return true;
      }
    }
    else    // neaktivní
      return false;
  }  // konec metody: moveBy()



  private void doMove(Vector3d theMove)
  // přesuň sprite dle vektoru theMove
  {
    objectTG.getTransform( t3d );
    toMove.setTranslation(theMove);    // otoč sprite o daný úhel v radiánech kolem jeho osy y
    t3d.mul(toMove);
    objectTG.setTransform(t3d);
  }  // konec metody: doMove()



  private Point3d tryMove(Vector3d theMove)
  /* Vypočítá efekt daného posunu, ale pozici spritu aktualizuje až po jeho otestování */
  { objectTG.getTransform( t3d );
    toMove.setTranslation(theMove);
    t3d.mul(toMove);
    Vector3d trans = new Vector3d();
    t3d.get( trans );
    // printTuple(trans, "nextLoc");
    return new Point3d( trans.x, trans.y, trans.z);
  }  // konec metody: tryMove()


  public void doRotateY(double radians)
  // Otočí sprite o radians radiánů kolem jeho osy y
  {
    objectTG.getTransform( t3d );
    toRot.rotY(radians);   // přepiš předchozí rotaci
    t3d.mul(toRot);
    objectTG.setTransform(t3d);
    currRotation += radians;
  } // konec metody: doRotateY()


  public Point3d getCurrLoc()
  {        
    objectTG.getTransform( t3d );
    Vector3d trans = new Vector3d();
    t3d.get( trans );
    // printTuple(trans, "currLoc");
    return new Point3d( trans.x, trans.y, trans.z);
  } // konec metody: getCurrLoc()


  public void setCurrRotation(double rot)
  { double rotChange = rot - currRotation;
    doRotateY(rotChange);
  } // konec metody: setCurrRotation()


  public double getCurrRotation()
  {  return currRotation;  }


  public boolean isActive() 
  {  return isActive;  }

  public void setActive(boolean b)
  // Změny aktivity mají gvliv na viditelnost spritu
  { isActive = b;
    if (!isActive)
       visSwitch.setWhichChild( Switch.CHILD_NONE );   // zneviditelni
    else if (isActive)
      visSwitch.setWhichChild( Switch.CHILD_ALL );   // zviditelni
  } // konec metody: setActive()



  private void printTuple(Tuple3d t, String id)
  // pro ladění
  {
    System.out.println(id + " x: " + df.format(t.x) + 
				", " + id + " y: " + df.format(t.y) +
				", " + id + " z: " + df.format(t.z));
  }  // konec metody: printTuple()


}  // konec třídy: Sprite3D
