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

/* Připraví virtuální svět Tour3D, ovšem i návštěvníky s jiných
   počítačů.

   Nový kód pro síťovou verzi.
     * Metoda makeContact() pro ustavení spojení se serverem;

     * Metoda closeLink() pro ukončení spojení se serverem a ukončení programu;

     * Metoda addVisitor() pro vytvoření distribuovaného turisty,
       volá se z vlákna TourWatcher po přijetí požadavku od serveru;

     * Metoda sendDetails() pro posílání údajů o spritu serveru

  Méně podstatné rozdíli oproti aplikaci Tour3D:
     * žádný vetřelec
     * celoobrazovkový režim není k dispozici
     * program nelze ukončit z klávesnice; místo toho je třeba použít tlačítko pro uzavření okna
     * okno má velikost 500x300 místo 512x512
     * světle modré pozadí místo textury
     * turistu lze umístit pomocí argumentů na příkazovém řádku
*/

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.text.DecimalFormat;

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



public class WrapNetTour3D extends JPanel
// Uchovává 3D-canvas, na nemž se zobrazuje nactený obrázek
{
  private static final int PWIDTH = 500;     // velikost panelu
  private static final int PHEIGHT = 300; 
  private static final int BOUNDSIZE = 100;  // větší než virtuální svět

  private static final int PORT = 5555;      // údaje o serveru
  private static final String HOST = "localhost";


  private SimpleUniverse su;
  private BranchGroup sceneBG;
  private BoundingSphere bounds;   // pro uzly prostredí

  private Obstacles obs;
  private TourSprite bob;   // turista

  private Socket sock;
  private BufferedReader in;     // IO pro klienta
  private PrintWriter out;  
  private DecimalFormat df;   // pro jednodušší výstup



  public WrapNetTour3D(String userName, String tourFnm, 
                                      double xPosn, double zPosn)
  // zkonstruuje 3D-canvas
  {
    setLayout( new BorderLayout() );
    setOpaque( false );
    df = new DecimalFormat("0.####");  // 4 dm

    setPreferredSize( new Dimension(PWIDTH, PHEIGHT));
    GraphicsConfiguration config =
					SimpleUniverse.getPreferredConfiguration();
    Canvas3D canvas3D = new Canvas3D(config);
    add("Center", canvas3D);

    canvas3D.setFocusable(true);
    canvas3D.requestFocus();

    su = new SimpleUniverse(canvas3D);
    createSceneGraph(userName, tourFnm, xPosn, zPosn);

    su.addBranchGraph( sceneBG );
  } // konec metody: WrapNetTour3D()


  void createSceneGraph(String userName, String tourFnm, double xPosn, double zPosn) 
  // inicializuje scénu
  { 
    sceneBG = new BranchGroup();
    bounds = new BoundingSphere(new Point3d(0,0,0), BOUNDSIZE); 

    // to umožní přídávat/odebírat klienty za běhu programu
    sceneBG.setCapability(Group.ALLOW_CHILDREN_READ);
    sceneBG.setCapability(Group.ALLOW_CHILDREN_WRITE);
    sceneBG.setCapability(Group.ALLOW_CHILDREN_EXTEND);


    lightScene();         // přidej světla
    addBackground();      // přidej oblohu
    sceneBG.addChild( new CheckerFloor().getBG() );  // přidej podlahu

    makeScenery(tourFnm);      // přidej scenérii a překážky

    makeContact();     // kontaktuj server (po vytvoření objektu typu Obstacles)

    addTourist(userName, xPosn, zPosn);      
					   // přidej 3D-sprity ovládaný uživatelem
    sceneBG.compile();   // zafixuj scénu
  } // konec metody: createSceneGraph()



  private void lightScene()
  // Jedno ambientní, 2 směrová světla
  {
    Color3f white = new Color3f(1.0f, 1.0f, 1.0f);

    // připrav ambientní světlo
    AmbientLight ambientLightNode = new AmbientLight(white);
    ambientLightNode.setInfluencingBounds(bounds);
    sceneBG.addChild(ambientLightNode);

    // připrav směrová světla
    Vector3f light1Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);
       // vlevo, dolů, dozadu 
    Vector3f light2Direction  = new Vector3f(1.0f, -1.0f, 1.0f);
       // vpravo, dolu, dopredu

    DirectionalLight light1 = 
            new DirectionalLight(white, light1Direction);
    light1.setInfluencingBounds(bounds);
    sceneBG.addChild(light1);

    DirectionalLight light2 = 
        new DirectionalLight(white, light2Direction);
    light2.setInfluencingBounds(bounds);
    sceneBG.addChild(light2);
  }  // konec metody: lightScene()



  private void addBackground()
  // modrá obhloha
  { Background back = new Background();
    back.setApplicationBounds( bounds );
    back.setColor(0.17f, 0.65f, 0.92f);    // barva oblohy
    sceneBG.addChild( back );
  }  // konec metody: addBackground()



  // --------------------- překážky -----------------------

  private void makeScenery(String tourFnm)
  /* Analyzuje konfigurační soubor, který obsahuje jména 
     objektů scenérie pro načtení a pozice překážek.
     Jeho formát vypadá takto:
        <object file>
        [-o (x1,z1) (x2,z2) ... ]     // souřadnice překážek
        <object file>
               :
     Scenérie se načítá pomocí objektu typu PropManager a dále předpokládáme,
     že k modelům existují příslušné konfigurační souboru.

     Překážky jsou uložené v objektu typu Obstacles a poté přidány do scény.
  */
  { obs = new Obstacles();
    PropManager propMan;
    String tourFile = "models/"+tourFnm;
    System.out.println("Loading tour file:" + tourFile);
    try {
      BufferedReader br = new BufferedReader( new FileReader(tourFile));
      String line;
      while((line = br.readLine()) != null) {
        System.out.println(line);
        if (line.startsWith("-o")) 
          obs.store( line.substring(2).trim() );    // ulož informace o překážce
        else {
          propMan = new PropManager(line.trim(),true);  // načti scenérii
          sceneBG.addChild( propMan.getTG() );    // přidej svět
        }
      }
      br.close();
      // System.out.println("Čtení konfiguroačního souboru dokončeno: " + tourFile);
      // obs.print();  // pro ladění
      sceneBG.addChild( obs.getObsGroup() );  // přidej překážky do scény
    } 
    catch (IOException e) 
    { System.out.println("Chyba při čtení konfiguračního souboru: " + tourFile);
      System.exit(1);
    }
  } // konec metody: makeScenery()



  // --------------------- turisti ---------------------------


  private void addTourist(String userName, double xPosn, double zPosn)
  // vytvoří sprite pro tohoto klienta
  {
    bob = new TourSprite(userName, "Coolrobo.3ds", obs, xPosn, zPosn, out);   // sprite
    sceneBG.addChild( bob.getBG() );

    ViewingPlatform vp = su.getViewingPlatform();
    TransformGroup viewerTG = vp.getViewPlatformTransform();   // uzel typu TG pro výhled

    TouristControls tcs = new TouristControls(bob, viewerTG);   // ovládání pro sprite
    tcs.setSchedulingBounds( bounds );

    sceneBG.addChild( tcs );
  } // konec metody: addTourist()



  public DistTourSprite addVisitor(String userName, double xPosn, double zPosn, double rotRadians)
  /* Vytvoří sprite pro návštěvníka virtuální světa.
     Tato metoda se volá z vlákna TourWatcher v reakci na 
     požadavek od serveru TourServer.

     Při přidávání nových uzlů typu BranchGroup za běhu programu
     se občas objevovaly chyby. Ty (snad) byly vyřešeny přidáním
     krátké mezi zkompilováním a připojením uzlu.
  */
  {  
     DistTourSprite dtSprite = new DistTourSprite(userName, "Coolrobo.3ds", obs, xPosn, zPosn);
     if (rotRadians != 0)
       dtSprite.setCurrRotation( rotRadians);

     BranchGroup sBG = dtSprite.getBG();

     sBG.compile();    // obyčejně dobrý nápad     
     try {
       Thread.sleep(200);    // chvíli posečkej, aby se kompilace stihla dokončit
     }
     catch(InterruptedException e) {}
     sceneBG.addChild( sBG ); 

     if (!sBG.isLive())    // pouze pro jistotu, ale vypadá to, že problém je již vyřešen
       System.out.println("Sprite návštěvníka nebyl oživen");
     else
       System.out.println("Sprite návštěvníka byl oživen");

     return dtSprite;
   } // konec metody: addVisitor()




  // ------------------------- network related --------------------------

    
  private void makeContact()
  /* Kontaktuje server a připraví vlákno TourWatcher pro jeho monitorování. */
  {
    try {
      sock = new Socket(HOST, PORT);
      in  = new BufferedReader(new InputStreamReader( sock.getInputStream() ) );
      out = new PrintWriter( sock.getOutputStream(), true );  // autoflush

      new TourWatcher(this, in, obs).start();    // zahaj sledování zpráv serveru
    }
    catch(Exception e)
    { System.out.println("Server se nepodařilo kontaktovat");
      System.exit(0);
    }
  }  // konec metody: makeContact()



  public void closeLink()
  /* Volá se z okna typu JFrame na nejvyšší úrovni po klepnutí na tlačítko pro 
     uzavření okna. Rozloučí se serverem a ukončí program. Tím také zabije vlákno
     TourWatcher. */
  {
    try {
      out.println("bye");    // informuj server, že klient se odpojuje
      sock.close();
    }
    catch(Exception e)
    {  System.out.println("Spojení ukončeno"); }

    System.exit( 0 ); 
  } // konec metody: closeLink()



  public void sendDetails(String cliAddr, String strPort)
  /* Pošle údaje o lokálním spritu klientovi na adrese
    (cliAddr, strPort).
     Formát zprávy:  
         detailsFor cliAddr strPort xPosn zPosn rotRadians
  */
  {  Point3d currLoc = bob.getCurrLoc();
     double currRotation = bob.getCurrRotation();
     String msg = new String("detailsFor " + cliAddr + " " +
						strPort + " " + 
						df.format(currLoc.x) + " " +
						df.format(currLoc.z) + " " + 
						df.format(currRotation) );
     out.println(msg);
  }  // konec metody: sendDetails()


} // konec třídy: WrapNetTour3D