
// PickScatter.java
// Andrew Davison, ad@coe.psu.ac.th, Feb. 2023

/* Plot NUM_POINTS random points using their coordinates 
   (between 0 and 1) as their RGB colors.
   Based on Scatter3DDemo.java.

   Add picking capability: when the mouse is moved over a point,
   it will be highlighted in red and the point's coordinate in
   the graph is displayed in an overlay.

   ----------------

   I have two problems with this program.

   1. On a multiple monitor setup (one monitor has a
      different resolution than the other) then the wrong
      point is highlighted when I move the mouse when the window
      is on one screen, but the correct point is 
      selected when the window is on the other screen. 

    2. The other problem crops up when I've been picking points inside 
    the window for some time. When I click the close box, it becomes more
    unresponsive the longer that I've been running the program. 

    If I wait long enough, when I click the close box the following 
    exception is raised:

      Exception in thread "AWT-EventQueue-0" com.jogamp.opengl.GLException: Caught GLException: Context still current after 512 releases: WindowsWGLContext [Version 4.6 ....

   It seems to be originating from JOGL in forceNativeRelease().

   The WindowListener in main() is my attempt 
   to 'fix' this problem. The delay still occurs, but the
   exception is quietly caught, allowing the program
   to exit normally.

   -------------------

   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 PickScatter.java
   > run PickScatter
*/

import java.awt.BorderLayout;
import java.awt.Graphics2D;
import javax.swing.*;
import java.awt.event.*;
import java.util.*;

import org.jzy3d.chart.*;
import org.jzy3d.chart.factories.*;
import org.jzy3d.chart.controllers.mouse.camera.*;
import org.jzy3d.chart.controllers.mouse.picking.*;
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.primitives.pickable.*;

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 PickScatter extends JPanel
{
  // dimensions of the panel
  private static final int P_WIDTH = 700;
  private static final int P_HEIGHT = 600;

  private static final int NUM_POINTS = 500;  // in the scatter plot

  // used for point picking and color changes
  private Color[] scatterColors;
  private int pickIdx;
  private int currIdx = -1;
  private Color origColor;
  private String currentPt = "";


  public PickScatter()
  {
    setLayout( new BorderLayout());
    setPreferredSize(new java.awt.Dimension(P_WIDTH, P_HEIGHT));

    Random rand = new Random();
    Coord3d[] coords = new Coord3d[NUM_POINTS];
    Color[] colors = new Color[NUM_POINTS];
    PickablePoint[] ppts = new PickablePoint[NUM_POINTS];
              // will hold the point's coordinate and index

    // create random scatter plot data
    float x, y, z;
    for(int i = 0; i < NUM_POINTS; i++) {
      x = rand.nextFloat();
      y = rand.nextFloat();
      z = rand.nextFloat();
      coords[i] = new Coord3d(x, y, z);
      colors[i] = new Color(x, y, z, 0.25f);
                // use coordinates as semi-transparent colors
      ppts[i] =  new PickablePoint(coords[i]);
      ppts[i].setPickingId(i);
    }
    Scatter scatter = new Scatter(coords, colors, 10f);
                                 // increase point radius to 10
    scatterColors = scatter.getColors();  
         // get a reference to the colors in the scatter plot

    // create the chart
    Quality quality = Quality.Advanced();
    quality.setAnimated(true);   // so changes are drawn
    IChartFactory factory = new SwingChartFactory();
    Chart chart = factory.newChart(quality);
    chart.getScene().getGraph().add(scatter);

    // modify the chart axes
    AxisLayout axl = chart.getAxisLayout();
    axl.setFont( org.jzy3d.painters.Font.Helvetica_18);
    axl.setMainColor(org.jzy3d.colors.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();

    // report currently picked point
    ((AWTChart)chart).addRenderer(new OverlayLegendRenderer() {
      public void paint(java.awt.Graphics g, int w, int h)
      {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(java.awt.Color.LIGHT_GRAY);
        g2d.fillRect(20, 20, 160, 30);
        g2d.setColor(java.awt.Color.BLACK);
        g2d.setFont( new java.awt.Font("Helvetica",
                                java.awt.Font.BOLD, 18));
        g2d.drawString(currentPt, 30, 40);
      }
    });


    // set up mouse picking
    AWTMousePickingController mp = 
                     new AWTMousePickingController(chart);
    PickingSupport pickSupport = mp.getPickingSupport();

    // register all the coordinates for picking
    for(int i = 0; i < NUM_POINTS; i++)
      pickSupport.registerDrawableObject(ppts[i], ppts[i]);

    // listen for a pick; change point color and report
    pickSupport.addObjectPickedListener( new IObjectPickedListener() {
      public void objectPicked(List<? extends Object> verts, 
                               PickingSupport ps) 
      {
        for (Object v: verts) {
          PickablePoint pp = (PickablePoint)v;       
          pickIdx = pp.getPickingId();
          if (pickIdx != currIdx) {  // selected a new point?
            if (currIdx != -1) 
              scatterColors[currIdx] = origColor;  
                    // undo previous picked point's color change
            currIdx = pickIdx;    // store current point index and color
            origColor = scatterColors[currIdx];
            scatterColors[currIdx] = Color.RED;  // change picked point's color
            Coord3d coord = pp.getCoord();
            currentPt = String.format("(%.2f, %.2f, %.2f)",
                                      coord.x, coord.y, coord.z);
            // System.out.println("Picked: " + currentPt);   
          }
        }
      }  // end of objectPicked()
    });

    add((JPanel)chart.getCanvas(), BorderLayout.CENTER);
  } // end of PickScatter()



  // -------------------------------------------

  public static void main(String[] args)
  {
    PickScatter pane = new PickScatter();

    JFrame f = new JFrame("PickScatter");
    f.getContentPane().add(pane);

    f.addWindowListener( new WindowAdapter() {
        public void windowClosing(WindowEvent e) 
        { 
          try {
            f.dispose();
          }
          catch(com.jogamp.opengl.GLException ex) { }
          // {  System.out.println("Problem 2: " + ex.getMessage()); }
            
          System.exit(0);
        }
      }
    );
    // f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    f.pack();
    f.setResizable(false);
    f.setVisible(true);
  } // end of main()



}  // end of PickScatter class