
// PolygonDemo.java
// Andrew Davison, ad@coe.psu.ac.th, Feb. 2023

/* Plot a polygon surface. The heights of the polygon vertices
   are read in from a text file (e.g. heights1.txt, heights2.txt).


   Using Jzy3d 2.2.0 (http://www.jzy3d.org/)

   Based on an example at:
   https://www.doc.jzy3d.org/guide/docs/getting-started-charts.html

   Usage:
   > compile PolygonDemo.java
   > run PolygonDemo  <height text file>
   e.g.
     > run PolygonDemo  heights1.txt      // small
     > run PolygonDemo  heights2.txt      
                    // big; generated by ReadHeightMap.java

   ReadHeightMap.java reads a grayscale image, and can store the pixel
   values as doubles in a text file. One row of pixels becomes one
   row of doubles in the file. 
*/

import java.awt.BorderLayout;
import java.io.*;
import javax.swing.*;
import java.util.*;

import org.jzy3d.chart.*;
import org.jzy3d.chart.factories.*;
import org.jzy3d.chart.controllers.mouse.camera.*;
import org.jzy3d.chart.controllers.keyboard.camera.*;

import org.jzy3d.colors.*;
import org.jzy3d.colors.colormaps.*;

import org.jzy3d.plot3d.builder.*;
import org.jzy3d.plot3d.builder.concrete.*;

import org.jzy3d.plot3d.primitives.*;
import org.jzy3d.plot3d.primitives.axis.layout.*;
import org.jzy3d.plot3d.primitives.axis.layout.renderers.*;

import org.jzy3d.plot3d.rendering.canvas.*;
import org.jzy3d.plot3d.rendering.view.*;
import org.jzy3d.plot3d.rendering.legends.overlay.*;

import org.jzy3d.painters.*;

import org.jzy3d.maths.*;



public class PolygonDemo extends JPanel
{
  // dimensions of the panel
  private static final int P_WIDTH = 700;
  private static final int P_HEIGHT = 600;


  public PolygonDemo(String fnm)
  {
    setLayout( new BorderLayout());
    setPreferredSize(new java.awt.Dimension(P_WIDTH, P_HEIGHT));

    // load z-axis data points
    double[][] data = loadHeights(fnm);
    // printArray(data);
     /* the data is in row order, so the array has the format data[y][x]
     */

    // build polygon list using loaded heights
    List<Polygon> polys = new ArrayList<>();
    for(int y = 0; y < data.length-1; y++){
      for(int x = 0; x < data[y].length-1; x++){
        Polygon p = new Polygon();
        p.add( new Point( new Coord3d(x, y, data[y][x]) ));
        p.add( new Point( new Coord3d(x, y+1, data[y+1][x]) ));
        p.add( new Point( new Coord3d(x+1, y+1, data[y+1][x+1]) ));
        p.add( new Point( new Coord3d(x+1, y, data[y][x+1]) ));
        polys.add(p);
      }
    }

    // build surface using a grayscale color map
    Shape surface = new Shape(polys);
    surface.setColorMapper( new ColorMapper(new ColorMapGrayscale(),
                      surface.getBounds().getZmin(),
                      surface.getBounds().getZmax(),
                      new Color(1, 1, 1, 1f)));  // no transparency
    surface.setFaceDisplayed(true);
    surface.setWireframeDisplayed(true);
    surface.setWireframeColor(Color.GRAY);

    // create the chart
    IChartFactory factory = new SwingChartFactory();
    Chart chart = factory.newChart(Quality.Advanced());
    chart.getScene().getGraph().add(surface);

    // modify the chart axes
    AxisLayout axl = chart.getAxisLayout();
    axl.setFont(Font.Helvetica_18);
    axl.setMainColor(Color.GRAY);

    FixedDecimalTickRenderer ren2dp = 
                    new FixedDecimalTickRenderer(2);  // 2 dp
    axl.setXTickRenderer(ren2dp);
    axl.setYTickRenderer(ren2dp);
    axl.setZTickRenderer(ren2dp);

    // add user viewing controls
    chart.addMouse();
    chart.addKeyboard();

    add((JPanel)chart.getCanvas(), BorderLayout.CENTER);
  } // end of PolygonDemo()



  private double[][] loadHeights(String fnm)
  /* Load heights into a 2D array.
     The file format are lines of doubles which may also
     contain blank lines and comment lines beginning with "//"
     which are ignored.

     The doubles are separated by spaces, and a warning is issued
     if a row does not have the same number of values as the first
     row read in.
  */
  { 
    System.out.println("Reading heights from " + fnm);

    // build a list of lists of doubles
    ArrayList<ArrayList<Double>> rowsList = new ArrayList<>();
    int numRows = 0;
    int numColumns = -1;
    try {
      BufferedReader br = new BufferedReader( new FileReader(fnm));
      String line;
      while((line = br.readLine()) != null) {
        if (line.length() == 0)      // empty line
          continue;
        if (line.startsWith("//"))   // comment
          continue;

        ArrayList<Double> colVals = parseRow(line);  // get one row
        rowsList.add(colVals);
        int numCols = colVals.size();
        if (numColumns == -1)
          numColumns = numCols;
        else {
          if (numColumns != numCols)
            System.out.println("Column mismatch on row " + (numRows+1));
        }
        numRows++;
      }
      br.close();
    } 
    catch (IOException e) 
    { System.out.println("Error reading file: " + fnm);  }

    if ((numRows == 0) || (numColumns == 0)) {
      System.out.println("No data read in");
      return null;
    }

    // convert list of lists to 2D array
    double[][] heights = new double[numRows][numColumns];
    for (int i=0; i < numRows; i++) {
      ArrayList<Double> currRow = rowsList.get(i);
      for (int j=0; j < numColumns; j++) {
         heights[i][j] = currRow.get(j);
      }
    }
    return heights;
  }  // end of loadHeights()


  private ArrayList<Double> parseRow(String line)
  // convert a line into a list of doubles
  {
    String[] tokens = line.trim().split("\\s+");
    ArrayList<Double> colVals = new ArrayList<>();
    for (int i=0; i < tokens.length; i++) 
       colVals.add( getDouble(tokens[i]));
    return colVals;
  }  // end of getColumnChar()


  private static double getDouble(String s)
  {
    double val = 0;
    try {
      val = Double.parseDouble(s);
    }
    catch (NumberFormatException e)
    { System.out.println(s + " is not a double; using 0");  }

    return val;
  }  // end of getDouble()


  private void printArray(double[][] arr)
  {
    System.out.println("------------------------------\n");
    for(int i = 0; i < arr.length; i++) {
      for(int j = 0; j < arr[i].length; j++)
        System.out.printf("%.2f ", arr[i][j]);
      System.out.println();
    }
    System.out.println("\n------------------------------");
  }  // end of printArr()



  // -------------------------------------------

  public static void main(String[] args)
  {
    if (args.length != 1) {
      System.out.println("Usage: run PolygonDemo <Height map file>");
      return;
    }

    JFrame f = new JFrame("PolygonDemo " + args[0]);
    f.getContentPane().add(new PolygonDemo(args[0]));

    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.pack();
    f.setResizable(false);
    f.setVisible(true);
  } // end of main()



}  // end of PolygonDemo class