package com.janusresearch.concept.Xith3DTerrainLightingTest;

import javax.vecmath.*;

import com.xith3d.loaders.texture.TextureLoader;
import com.xith3d.scenegraph.*;
import com.xith3d.terrain.Terrain;

/**
 * TerrainShape is a Xith scenegraph BranchGroup containing terrain geometry.
 * 
 * Xith's <code>terrain.render()</code> gave us an IndexedTriangleArray as our
 * terrain's geometry object, which is terribly inefficient. The reason Xith did
 * this was to allow for future division of adaptive quads for level of detail,
 * but we don't plan on implementing that any time soon. For our purposes, the
 * IndexedTriangleArray from Xith's implementation sucks as we're storing 12
 * vertices for each 32x32 square but only dividing the squares into 4
 * triangles. For every vertex, we're storing coordinates, a color, a normal,
 * and a texture coordinate. This all adds up and most definitely takes its toll
 * on system resources. I switched away from Xith's TerrainRenderInterface and
 * went with a TriangleStripArray setup. The triangle strips allow us to break
 * the same 32x32 square up into 8 triangles (twice the number of the old
 * implementation) using the same number of vertices (12).
 * </p>
 * 
 * <p>
 * As always, there is a trade-off between performance and beauty. By default,
 * the scaling is set to 16, meaning the two "legs" of our right triangles are
 * 16 (hyp = sqrt(2) * 16). This gives us twice the number of triangles as we
 * previously had using roughly the same number of vertices to do it. If you
 * want to improve performance (sacrificing beauty), increase this scale value
 * to 32 or something larger.
 * </p>
 * 
 * @author mwright
 */

public class TerrainShape extends BranchGroup {
  Terrain terrain;

  int MAX_HEIGHT = 400;

  int defaultScale = 16;

  int totalVertexCount = 0;

  Shape3D shape;

  PolygonAttributes pa;

  TextureAttributes texAttribs;

  TransparencyAttributes transAttrib;

  TriangleStripArray geo;

  public TerrainShape(Terrain terrain) {
    this.terrain = terrain;
    setName("Terrain Shape");
    buildShape();
  }

  private void buildShape() {
    /*
     * Since we are controlling everything about our geometry, we can now
     * calculate exactly how many triangle strips our terrain will have as well
     * as the number of vertices per strip.
     */
    int numStrips = terrain.getWidth() / defaultScale;
    int numVertsPerStrip = (numStrips + 1) * 2;

    /*
     * The TriangleStripArray takes an array which holds the number of vertices
     * per strip, the length of which is the number of strips in the shape.
     */
    int stripVertexCounts[] = new int[numStrips];
    for (int i = 0; i < stripVertexCounts.length; i++) {
      stripVertexCounts[i] = numVertsPerStrip;
    }

    /*
     * This geometry is a TriangleStripArray which is much more efficient than
     * basically every other geometry array available in Xith. For our purposes,
     * we need to allow coordinate, normal (for future lighting), color, and
     * texture coordinate creation.
     */
    geo = new TriangleStripArray(numStrips * numVertsPerStrip, TriangleStripArray.COORDINATES
        | TriangleStripArray.NORMALS
        | GeometryArray.COLOR_3
        | GeometryArray.TEXTURE_COORDINATE_2, stripVertexCounts);

    /*
     * This is so beautiful I want to cry.
     * 
     * Starting with (0,0), loop through our array skipping every scale points.
     * Since we're building strips, we want to stop when our x value gets to the
     * NEXT TO THE LAST x value in our terrain because we're just going to add
     * scale to the x and that will become our last strip. Make sense? Good.
     * Let's go!
     */
    for (int x = 0; (x + defaultScale) <= terrain.getWidth(); x += defaultScale) {
      for (int z = 0; z <= terrain.getDepth(); z += defaultScale) {
        //Be sure you look at initVert() to see the meat of this class...

        // First point at (x, z):
        initVert(totalVertexCount, x, terrain.getY(x, z), z);
        totalVertexCount++;

        // Second point at (x+scale, z)
        initVert(totalVertexCount, x + defaultScale, terrain.getY(x + defaultScale, z), z);
        totalVertexCount++;
      }
    }
    Appearance a = new Appearance();

    pa = new PolygonAttributes();
    pa.setPolygonMode(PolygonAttributes.POLYGON_FILL);
    a.setPolygonAttributes(pa);

    // Lighting stuff ...
    Material mat = new Material();
    mat.setLightingEnable(true);
    // If you want to, you can uncomment this line, comment the
    // following two lines, and set the vertex color in the initVert method.
    // mat.setColorTarget(Material.AMBIENT_AND_DIFFUSE);
    mat.setAmbientColor(1.f, 1.f, 1.f);
    mat.setDiffuseColor(1.f, 1.f, 1.f);
    mat.setSpecularColor(0.f, 0.f, 0.f);
    a.setMaterial(mat);

    // Texture loading
    /*
    TextureLoader.tf.registerPath("/");
    TextureLoader.tf.registerPath("./");
    TextureLoader.tf.registerPath("../");
    TextureLoader.tf.registerPath("./resources/textures/");
    TextureLoader.tf.registerJarPath("/");
    TextureLoader.tf.registerJarPath("/textures/");
    */
    Texture2D texture = (Texture2D) TextureLoader.tf.getMinMapTexture("stone.jpg");
    a.setTexture(texture);

    shape = new Shape3D(geo, a);

    shape.setBoundsAutoCompute(false);
    shape.setBounds(new BoundingSphere(new Point3f(0, 0, 0), 100000));

    this.addChild(shape);
  }

  public void initVert(int counter, float x, float y, float z) {
    // Run some numbers for texture coordinates
    int texSize = 512;
    int texScale = terrain.getWidth() / texSize;
    float texCoordx, texCoordz;
    texCoordx = x / (terrain.getWidth() / texScale);
    texCoordz = z / (terrain.getWidth() / texScale);
    TexCoord2f texCoord = new TexCoord2f(texCoordz, texCoordx);

    // Coordinate
    geo.setCoordinate(x, y, z);

    // Texture coordinate
    geo.setTextureCoordinate(0, counter, texCoord);

    // Color
    geo.setColor(1.f, 1.f, 1.f);

    // Normal ... take a look at calculateNormal() to see what happens here
    geo.setNormal(calculateNormal(x, y, z));
  }

  public Vector3f calculateNormal(float x, float y, float z) {
    // We'll add/subtract this to/from x and z to find our surrounding 8 points
    int vectorCalcRadius = defaultScale;

    // This point (a), plus 8 surrounding points (b-i)
    Point3f a = new Point3f(x, y, z);
    Point3f b = new Point3f(x, terrain.getY(x, (z + vectorCalcRadius)), z + vectorCalcRadius);
    Point3f c = new Point3f(x + vectorCalcRadius, terrain.getY((x + vectorCalcRadius), (z + vectorCalcRadius)), z + vectorCalcRadius);
    Point3f d = new Point3f(x + vectorCalcRadius, terrain.getY((x + vectorCalcRadius), z), z);
    Point3f e = new Point3f(x + vectorCalcRadius, terrain.getY((x + vectorCalcRadius), (z - vectorCalcRadius)), z - vectorCalcRadius);
    Point3f f = new Point3f(x, terrain.getY(x, (z - vectorCalcRadius)), z - vectorCalcRadius);
    Point3f g = new Point3f(x - vectorCalcRadius, terrain.getY((x - vectorCalcRadius), (z - vectorCalcRadius)), z - vectorCalcRadius);
    Point3f h = new Point3f(x - vectorCalcRadius, terrain.getY((x - vectorCalcRadius), z), z);
    Point3f i = new Point3f(x - vectorCalcRadius, terrain.getY((x - vectorCalcRadius), (z + vectorCalcRadius)), z + vectorCalcRadius);

    // Co-planar vectors
    Vector3f ab = new Vector3f();
    Vector3f ac = new Vector3f();
    Vector3f ad = new Vector3f();
    Vector3f ae = new Vector3f();
    Vector3f af = new Vector3f();
    Vector3f ag = new Vector3f();
    Vector3f ah = new Vector3f();
    Vector3f ai = new Vector3f();
    ab.sub(a, b);
    ac.sub(a, c);
    ad.sub(a, d);
    ae.sub(a, e);
    af.sub(a, f);
    ag.sub(a, g);
    ah.sub(a, h);
    ai.sub(a, i);

    // Surface normal vectors
    Vector3f bc = new Vector3f();
    Vector3f cd = new Vector3f();
    Vector3f de = new Vector3f();
    Vector3f ef = new Vector3f();
    Vector3f fg = new Vector3f();
    Vector3f gh = new Vector3f();
    Vector3f hi = new Vector3f();
    Vector3f ib = new Vector3f();
    bc.cross(ab, ac);
    cd.cross(ac, ad);
    de.cross(ad, ae);
    ef.cross(ae, af);
    fg.cross(af, ag);
    gh.cross(ag, ah);
    hi.cross(ah, ai);
    ib.cross(ai, ab);
    bc.normalize();
    cd.normalize();
    de.normalize();
    ef.normalize();
    fg.normalize();
    gh.normalize();
    hi.normalize();
    ib.normalize();

    // Normalized sum of vectors
    Vector3f n = new Vector3f();
    n.add(bc);
    n.add(cd);
    n.add(de);
    n.add(ef);
    n.add(fg);
    n.add(gh);
    n.add(hi);
    n.add(ib);
    n.normalize();

    return n;
  }

  /**
   * @param line
   *              If true, then draw the terrain in wireframe mode. Otherwise draw
   *              the terrain filled.
   */
  public void setLineMode(boolean line) {
    if (line)
      pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
    else
      pa.setPolygonMode(PolygonAttributes.POLYGON_FILL);
  }

  public boolean getLineMode() {
    if (pa.getPolygonMode() == PolygonAttributes.POLYGON_LINE)
      return true;
    else
      return false;
  }

}
