// Filename: heightfieldTesselator.cxx
// Created by:  jyelon (17jul06)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "heightfieldTesselator.h"
#include "geomNode.h"
#include "transformState.h"
#include "sceneGraphReducer.h"
#include "lvector3.h"

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::fix_heightfield
//       Access: Published
//  Description: Makes sure that the heightfield is a grayscale
//               image of valid dimensions.  If necessary, adds a
//               band of zeros onto two sides of the heightfield,
//               so as to make the size of the heightfield a multiple
//               of the given size plus one.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
fix_heightfield(int size) {
  
  // Calculate the padded size of the heightfield.
  int xsize = _heightfield.get_x_size();
  int ysize = _heightfield.get_y_size();
  int xcells = (xsize + size - 2) / size;
  int ycells = (ysize + size - 2) / size;
  int xpadded = xcells * size + 1;
  int ypadded = ycells * size + 1;

  // If the heightfield is already in good shape, done.
  if ((xpadded == _heightfield.get_x_size()) &&
      (ypadded == _heightfield.get_y_size()) &&
      (_heightfield.is_grayscale())) {
    return;
  }

  // Pad the heightfield, and convert to grey.
  PNMImage unfixed(_heightfield);
  _heightfield.clear(xpadded, ypadded, 1,
                     unfixed.get_maxval(), 
                     unfixed.get_type());
  for (int y = 0; y < ysize; y++) {
    for (int x = 0; x < xsize; x++) {
      _heightfield.set_gray_val(x, y, unfixed.get_gray_val(x, y));
    }
  }
  for (int y = ysize; y < ypadded; y++) {
    for (int x = xsize; x < xpadded; x++) {
      _heightfield.set_gray_val(x, y, 0);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::get_elevation
//       Access: Private
//  Description: Fetches the elevation at (x,y), where the input
//               coordinate is specified in pixels.  This ignores the
//               current tesselation level and instead provides an
//               accurate number.  Linear blending is used for 
//               non-integral coordinates.
////////////////////////////////////////////////////////////////////
double HeightfieldTesselator::
get_elevation(double x, double y) {
  int scale = 7;
  int size = 1 << scale;
  fix_heightfield(size);
  int xlo = (int)x;
  int ylo = (int)y;
  if (xlo < 0) xlo = 0;
  if (ylo < 0) ylo = 0;
  if (xlo > _heightfield.get_x_size()-2)
    xlo = _heightfield.get_x_size()-2;
  if (ylo > _heightfield.get_y_size()-2)
    ylo = _heightfield.get_y_size()-2;
  int xhi = xlo+1;
  int yhi = ylo+1;
  double xoffs = x - xlo;
  double yoffs = y - ylo;
  double grayxlyl = _heightfield.get_gray(xlo,ylo);
  double grayxhyl = _heightfield.get_gray(xhi,ylo);
  double grayxlyh = _heightfield.get_gray(xlo,yhi);
  double grayxhyh = _heightfield.get_gray(xhi,yhi);
  double lerpyl = grayxhyl * xoffs + grayxlyl * (1.0 - xoffs);
  double lerpyh = grayxhyh * xoffs + grayxlyh * (1.0 - xoffs);
  return lerpyh * yoffs + lerpyl * (1.0 - yoffs);
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::get_vertex
//       Access: Private
//  Description: Fetches the vertex at (x,y), or if the vertex
//               does not exist, creates it.
////////////////////////////////////////////////////////////////////
int HeightfieldTesselator::
get_vertex(int x, int y) {
  int xsize = _heightfield.get_x_size();
  int vtx = _vertex_index[x+y*xsize];
  if (vtx >= 0) {
    return vtx;
  }
  int nx = x-1;
  int px = x+1;
  int ny = y-1;
  int py = y+1;
  if (nx < 0) nx++;
  if (ny < 0) ny++;
  if (px >= _heightfield.get_x_size()) px--;
  if (py >= _heightfield.get_y_size()) py--;
  double drx = _heightfield.get_gray(px,y) - _heightfield.get_gray(nx,y);
  double dry = _heightfield.get_gray(x,py) - _heightfield.get_gray(x,ny);
  LVector3f normal(drx * _vertical_scale * 0.5, dry * _vertical_scale * 0.5, _horizontal_scale);
  normal.normalize();
  double z = _heightfield.get_gray(x,y);
  _vertex_writer->add_data3f(x*_horizontal_scale,-y*_horizontal_scale,z*_vertical_scale);
  _normal_writer->add_data3f(normal);
  vtx = _next_index++;
  _dirty_vertices[vtx] = x+y*xsize;
  _vertex_index[x+y*xsize] = vtx;
  return vtx;
}


////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::generate
//       Access: Published
//  Description: Generates a tree of nodes that represents the
//               heightfield.  This can be reparented into the scene.
////////////////////////////////////////////////////////////////////
NodePath HeightfieldTesselator::
generate() {
  int scale = 7;
  int size = 1 << scale;
  fix_heightfield(size);
  int xsize = _heightfield.get_x_size();
  int ysize = _heightfield.get_y_size();
  int xcells = (xsize + size - 2) / size;
  int ycells = (ysize + size - 2) / size;

  _vertex_index = new int[xsize * ysize];
  _dirty_vertices = new int[xsize * ysize];
  _triangle_totals = new int[xsize * ysize];
  for (int y=0; y<ysize; y++) {
    for (int x=0; x<xsize; x++) {
      _vertex_index[y*xsize+x] = -1;
    }
  }
  
  if (!_radii_calculated) {
    int saved_focal_x = _focal_x;
    int saved_focal_y = _focal_y;

    _focal_x = _heightfield.get_x_size() >> 1;
    _focal_y = _heightfield.get_y_size() >> 1;
    
    calculate_radii(scale);
    
    _focal_x = saved_focal_x;
    _focal_y = saved_focal_y;
    
    _radii_calculated = true;
  }
  
  PT(PandaNode) result = new PandaNode(get_name());
  NodePath root(result);

  int total = 0;
  for (int y=0; y<ycells; y++) {
    for (int x=0; x<xcells; x++) {
      total += count_triangles(scale,x*size,y*size);
    }
  }
  for (int y=0; y<ycells; y++) {
    for (int x=0; x<xcells; x++) {
      generate_square(root,scale,x*size,y*size,true);
    }
  }
  delete _vertex_index;
  delete _dirty_vertices;
  delete _triangle_totals;
  _vertex_index =0;
  _dirty_vertices =0;
  _triangle_totals =0;

  return root;
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::calculate_radii
//       Access: Private
//  Description: Sets the radii appropriately to achieve the
//               desired polygon count.  This is achieved by binary
//               search.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
calculate_radii(int scale) {
  int size = 1 << scale;
  int xsize = _heightfield.get_x_size();
  int ysize = _heightfield.get_y_size();
  int xcells = (xsize + size - 2) / size;
  int ycells = (ysize + size - 2) / size;
  
  double lo = 5.0;
  double hi = _heightfield.get_x_size() + _heightfield.get_y_size();
  while (1) {
    double mid = (lo + hi) * 0.5;
    for (int i=0; i<16; i++) {
      _radii[i] = (int)(mid * (1<<i));
    }
    int total = 0;
    for (int y=0; y<ycells; y++) {
      for (int x=0; x<xcells; x++) {
        total += count_triangles(scale,x*size,y*size);
      }
    }
    if (total > _poly_count) {
      hi = mid;
    } else {
      lo = mid;
    }
    if (hi - lo < 1.0) {
      break;
    }
  }
  double mid = (lo + hi) * 0.5;
  for (int i=0; i<16; i++) {
    _radii[i] = (int)(mid * (1<<i));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::generate_square
//       Access: Private
//  Description: Adds a square region to the current geom.
//               This relies on the following preconditions:
//
//               1. A square of scale N can be adjacent to
//               a square of scale N or scale N-1, but not
//               scale N-2 or smaller.
//               
//               2. A square of scale N can be adjacent to
//               at most one square of scale N-1.
//
//               Precondition 1 is assured by spacing out the 
//               detail radii sufficiently.  Precondition 2 is
//               assured by using rectangular detail radii.
//
//               I may someday rewrite this code to eliminate
//               precondition 2, to allow circular detail radii.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
generate_square(NodePath root, int scale, int x, int y, bool forceclose) {
  // There are nine possible vertices in the square,
  // which are labeled as follows:
  //
  //    G--H--I
  //    |     |
  //    D  E  F
  //    |     |
  //    A--B--C

  int size = 1<<scale;
  int hsize = size>>1;

#define POINTA get_vertex(x      ,y)
#define POINTB get_vertex(x+hsize,y)
#define POINTC get_vertex(x+size ,y)
#define POINTD get_vertex(x      ,y+hsize)
#define POINTE get_vertex(x+hsize,y+hsize)
#define POINTF get_vertex(x+size ,y+hsize)
#define POINTG get_vertex(x      ,y+size)
#define POINTH get_vertex(x+hsize,y+size)
#define POINTI get_vertex(x+size ,y+size)

  if (_triangles == 0) {
    open_geom();
  }
  if (subdivide(scale, x, y)) {
    int xc = x+(size>>1);
    int yc = y+(size>>1);
    if (_triangle_totals[yc*_heightfield.get_x_size()+xc] > _max_triangles) {
      if (_next_index) close_geom(root);
      NodePath subroot = root.attach_new_node(get_name() + " interior");
      generate_square(subroot, scale-1, x,  y , true);
      generate_square(subroot, scale-1, xc, y , true);
      generate_square(subroot, scale-1, xc, yc, true);
      generate_square(subroot, scale-1, x,  yc, true);
    } else {
      generate_square(root, scale-1, x,  y , false);
      generate_square(root, scale-1, xc, y , false);
      generate_square(root, scale-1, xc, yc, false);
      generate_square(root, scale-1, x,  yc, false);
    }
  } else if (subdivide(scale, x+size, y)) {
    add_quad(POINTG,POINTA,POINTI,POINTF);
    add_quad(POINTA,POINTA,POINTF,POINTC);
  } else if (subdivide(scale, x-size, y)) {
    add_quad(POINTG,POINTD,POINTI,POINTI);
    add_quad(POINTD,POINTA,POINTI,POINTC);
  } else if (subdivide(scale, x, y+size)) {
    add_quad(POINTG,POINTA,POINTH,POINTA);
    add_quad(POINTH,POINTA,POINTI,POINTC);
  } else if (subdivide(scale, x, y-size)) {
    add_quad(POINTG,POINTA,POINTI,POINTB);
    add_quad(POINTI,POINTB,POINTI,POINTC);
  } else {
    add_quad(POINTG,POINTA,POINTI,POINTC);
  }
  if (forceclose || (_next_index > _max_triangles)) {
    close_geom(root);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::count_triangles
//       Access: Private
//  Description: Calculates how many triangles are inside
//               the given region.  The result is stored in
//               the _poly_totals array, in the center of the
//               square.
////////////////////////////////////////////////////////////////////
int HeightfieldTesselator::
count_triangles(int scale, int x, int y) {
  int size = 1<<scale;
  if (subdivide(scale, x, y)) {
    int xc = x + (size>>1);
    int yc = y + (size>>1);
    int n = 0;
    n += count_triangles(scale-1, x,  y );
    n += count_triangles(scale-1, xc, y );
    n += count_triangles(scale-1, x,  yc);
    n += count_triangles(scale-1, xc, yc);
    _triangle_totals[yc*_heightfield.get_x_size() + xc] = n;
    return n;
  } else if (subdivide(scale, x+size, y)) {
    return 3;
  } else if (subdivide(scale, x-size, y)) {
    return 3;
  } else if (subdivide(scale, x, y+size)) {
    return 3;
  } else if (subdivide(scale, x, y-size)) {
    return 3;
  } else {
    return 2;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::add_quad_to_strip
//       Access: Private
//  Description: Adds a quad to the current triangle strip.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
add_quad_to_strip(int v1, int v2, int v3, int v4) {
  if ((v1 != v2)&&(v2 != v3)&&(v1 != v3)) {
    _triangles->add_vertices(v1,v3,v2);
  }
  if ((v3 != v2)&&(v2 != v4)&&(v4 != v3)) {
    _triangles->add_vertices(v3,v4,v2);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::add_quad
//       Access: Private
//  Description: Adds a quad to the current geom.
//
//               Eventually, I plan to reimplement this.  It is
//               going to add a quad to a table of quads.  A
//               post-processing pass is going to traverse the
//               table, calling add_quad_to_strip in the optimal
//               order.  For now, though, this routine just calls
//               add_quad_to_strip directly, which is quite
//               inefficient.
//
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
add_quad(int v1, int v2, int v3, int v4) {
  add_quad_to_strip(v1, v2, v3, v4);
}


////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::open_geom
//       Access: Private
//  Description: Initiates the construction of a geom.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
open_geom() {
  _vdata = new GeomVertexData
    ("heightfield", GeomVertexFormat::get_v3n3(), Geom::UH_static);
  _vertex_writer = new GeomVertexWriter(_vdata, InternalName::get_vertex());
  _normal_writer = new GeomVertexWriter(_vdata, InternalName::get_normal());
  _triangles = new GeomTriangles(Geom::UH_static);
  _triangles->set_shade_model(Geom::SM_uniform);

  _next_index = 0;
  _last_vertex_a = -1;
  _last_vertex_b = -1;
}

////////////////////////////////////////////////////////////////////
//     Function: HeightfieldTesselator::close_geom
//       Access: Private
//  Description: Completes the construction of a geom.
////////////////////////////////////////////////////////////////////
void HeightfieldTesselator::
close_geom(NodePath root) {
  if (_triangles == 0) {
    return;
  }
  _triangles->close_primitive();
  PT(Geom) geom = new Geom(_vdata);
  geom->add_primitive(_triangles);
  PT(GeomNode) gnode = new GeomNode(get_name() + " patch");
  gnode->add_geom(geom);
  root.attach_new_node(gnode);
  delete _vertex_writer;
  delete _normal_writer;
  
  for (int i=0; i<_next_index; i++) {
    _vertex_index[_dirty_vertices[i]] = -1;
  }

  _next_index = 0;
  _last_vertex_a = -1;
  _last_vertex_b = -1;
  _vertex_writer = 0;
  _normal_writer = 0;
  _triangles = 0;
}
