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

/* Demonstruje střílečku z vlastního pohledu (bez střílení). 
   Jde o procházení bludištěm se dvěma kamerami (přední, zadní) a 
   pohledem z ptačí perspektivy.

   Pozadí tvoří koule s texturou nalepenou na její vnitřní stranu.
   Také podlaha má texturu a bludiště je umístěno na ni.
   Osvětlení je sníženo na minimun -- ambientní světlo je vypnuté.
  
   K hlavnímu výhledu uživatele je přidán reflektor. (K dispozici je
   také nepoužitý kód pro přidání kužele na pozici výhledu, který
   by měl hrát roli avatara. )

   Výhled je upraven tak, aby měl širší ohniskovou vzdálenost (FOV)
   a dále jsou změněny přední a zadní ořezávací vzdálenosti. Výhled je
   umístěn do stanovené počáteční pozice v bludišti a otočen tak, aby
   směřoval podél kladné osy z.

   Pohyb je ovládán pomocí kláves.
*/

import javax.swing.*;
import java.awt.*;

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


public class WrapMaze3D extends JPanel
{
  private static final int PWIDTH = 512;   // velikost panelu
  private static final int PHEIGHT = 512; 

  private static final int BOUNDSIZE = 100;  // větší než virtuální svět
  private static final String SKY_TEX = "images/lava.jpg";  // textura oblohy

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

  private MazeManager mazeMan;      // správce bludiště
  private TransformGroup camera2TG; // zpětná kamera


  public WrapMaze3D(MazeManager mm, BirdsEye be, TransformGroup c2TG)
  // vytvoří scénu a hlavní kameru
  {
    mazeMan = mm;       // odkaz na správce bludiště
    camera2TG = c2TG;   // odkaz na sekundární, zpětnou kameru

    setLayout( new BorderLayout() );
    setOpaque( false );
    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();
    prepareViewPoint(be);

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


  void createSceneGraph()
  // inicializuje scénu 
  { 
    sceneBG = new BranchGroup();
    bounds = new BoundingSphere(new Point3d(0,0,0), BOUNDSIZE); 

    lightScene();     // přidej světla
    addBackground();  // přidej oblohu
    
    // přidej otexturovanou podlahu
    TexturedFloor floor = new TexturedFloor();
    sceneBG.addChild( floor.getBG() );

    sceneBG.addChild( mazeMan.getMaze() );  // pomocí objektu typu MazeManager přidej bludiště
    sceneBG.addChild( camera2TG );          // přidej druhou kameru

    sceneBG.compile();   // zafixuj scénu
  } // konec metody: createSceneGraph()



  private void lightScene()
  // Žádné ambientní osvětlení, 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);   // ambientní světlo deaktivováno

    // 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()
  // pomocí uzlu typu Background přidej geometrické pozadí
  {
    System.out.println("Načítám texturu oblohy: " + SKY_TEX);
    TextureLoader tex = new TextureLoader(SKY_TEX, null);

    Sphere sphere = new Sphere(1.0f, Sphere.GENERATE_NORMALS |
			      Sphere.GENERATE_NORMALS_INWARD |
				  Sphere.GENERATE_TEXTURE_COORDS, 4);   // standardně = 15
    Appearance backApp = sphere.getAppearance();
	backApp.setTexture( tex.getTexture() );

	BranchGroup backBG = new BranchGroup();
    backBG.addChild(sphere);

    Background bg = new Background();
    bg.setApplicationBounds(bounds);
    bg.setGeometry(backBG);

    sceneBG.addChild(bg);
  }  // konec metody: addBackground()
*/

  private void addBackground()
  // Přidá horizont nakreslený na vnitřní stěnu koule.
  // Objekt typu Background tedy není třeba.
  // Na některých starších počítačích vypadá věrohodněji (?)
  {
    System.out.println("Načítám texturu oblohy: " + SKY_TEX);
    TextureLoader tex = new TextureLoader(SKY_TEX, null);

    // vytvoř vzhled a přiřaď mu texturu
    Appearance app =  new Appearance();
	app.setTexture( tex.getTexture() );

    Sphere sphere = new Sphere(100.0f,    // poloměr pro rozšíření k okraji scény
			   Sphere.GENERATE_NORMALS_INWARD |
			   Sphere.GENERATE_TEXTURE_COORDS, 4, app);   // standardně = 15

    sceneBG.addChild( sphere );
 } // konec metody: addBackground()



// --------------------- ovládací prvky uživatele ----------------------------------


  private void prepareViewPoint(BirdsEye be)
  {
    // uprav parametry výhledu
    View userView = su.getViewer().getView();
    userView.setFieldOfView( Math.toRadians(90.0));  // širší zorné pole
    // 10 a 0,1; udržuj poměr 100-1000
    userView.setBackClipDistance(20);      // můžeme vidět hodně daleko
    userView.setFrontClipDistance(0.05);   // můžeme vidět blízské objekty

    ViewingPlatform vp = su.getViewingPlatform();

    // do výhledu přidej reflektor a avatara
    PlatformGeometry pg = new PlatformGeometry();
    pg.addChild( makeSpot() );
    // pg.addChild( makeAvatar() );    // avatara zde nepoužijeme
    vp.setPlatformGeometry( pg );

    // oprav počáteční pozici a orientaci výhledu
    TransformGroup steerTG = vp.getViewPlatformTransform();
    initViewPosition(steerTG);

    // připrav ovládací prvky klávesnice
    KeyBehavior keybeh = new KeyBehavior(mazeMan, be, camera2TG);
    keybeh.setSchedulingBounds(bounds);
    vp.setViewPlatformBehavior(keybeh);
  } // konec metody: prepareViewPoint()


  private void initViewPosition(TransformGroup steerTG)
  // otočí a přesune výhled
  {
    Transform3D t3d = new Transform3D();
    steerTG.getTransform(t3d);
    Transform3D toRot = new Transform3D();
    toRot.rotY(-Math.PI);   
    // otoč o 180 stupňů kolem osy y, směřuje tedy podél kladné osy z

    t3d.mul(toRot);
	t3d.setTranslation( mazeMan.getMazeStartPosn() );  // umísti na začátek bludiště
    steerTG.setTransform(t3d); 
  }  // konec metody: initViewPosition()


  private SpotLight makeSpot()
  // reflektor pomáhající uživateli vidět v (relativní) tmě
  {
    SpotLight spot = new SpotLight();
    spot.setPosition(0.0f, 0.5f, 0.0f);      // mírně nad uživatelem
    spot.setAttenuation(0.0f, 1.2f, 0.0f);   // lineární pokles
    spot.setSpreadAngle( (float)Math.toRadians(30.0));  // menší úhel
    spot.setConcentration(5.0f);            // rychlejší redukce síly
    spot.setInfluencingBounds(bounds);
    return spot;
  }  // konec metody: makeSpot()


  private TransformGroup makeAvatar()
  /* V tomto kódu se nepoužívá, protože neexistuje pohled na uživatele,
     ve kterém by bylo třeba avatara zobrazit. */
  {
    Transform3D t3d = new Transform3D();
    t3d.rotX(Math.PI/2);    // natoč tak, aby špička kuželu směřovala vpřed
    TransformGroup userTG = new TransformGroup(t3d);

    userTG.addChild( new Cone(0.35f, 1.0f) );  // avatara tvoří tenký kužel
    return userTG;
  }  // konec metody: makeAvatar()

} // konec třídy: WrapMaze3D

