
// AnimSurface.java
// Andrew Davison, ad@coe.psu.ac.th, Feb. 2023

/* An animated surface plot of the equation
     z = 10*Math.sin(x*p) * Math.cos(y*p) * x
   where p starts at 0.9 and is changed every DELAY ms, which
   causes the surface to be redrawn.


   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 AnimSurface.java
   > run AnimSurface
*/

import java.awt.BorderLayout;
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.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.painters.*;

import org.jzy3d.maths.*;




public class AnimSurface extends JPanel
{
  // dimensions of the panel
  private static final int P_WIDTH = 700;
  private static final int P_HEIGHT = 600;

  private static final int DELAY = 100;  // ms

  private Shape surface;
  private ParametrizedMapper mapper;

  // thread for updating the plot
  private Thread updateThread = null;
  private volatile boolean isRunning = true;


  public AnimSurface()
  {
    setLayout( new BorderLayout());
    setPreferredSize(new java.awt.Dimension(P_WIDTH, P_HEIGHT));


    // parameterized plot function (p starts == 0.9)
    mapper = new ParametrizedMapper(0.9) {
      public double f(double x, double y)
      {  return 10*Math.sin(x*p) * Math.cos(y*p) * x; }
    };

    // build surface
    surface = new SurfaceBuilder().orthonormal(mapper, 
                                     new Range(-150, 150), 50);

    surface.setColorMapper( new ColorMapper(new ColorMapRainbow(),
                 surface.getBounds().getZmin(),
                 surface.getBounds().getZmax(),
                 new Color(1, 1, 1, 0.5f)));  // semi-transparent
    surface.setFaceDisplayed(true);
    surface.setWireframeDisplayed(true);
    surface.setWireframeColor(Color.BLACK);

    // 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(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);

    startUpdater();
  } // end of AnimSurface()



  public void startUpdater()
  // modify the mapping function every DELAY ms
  {
    updateThread = new Thread(){
      public void run() 
      {
        while(isRunning) {
          pause(DELAY);
          mapper.setParam(mapper.getParam() + 0.001); 
                   // change p parameter in mapper function
          remap(surface, mapper);
        }
      } /* run */
    };
    updateThread.start();
  } /* start */


  public void stopUpdater()
  {
    if (updateThread != null)
      isRunning = false;
  }


  private void remap(Shape shape, Mapper mapper)
  // update the z-values of the surface using mapper func
  {
    List<Drawable> polygons = shape.getDrawables();
    for (Drawable d: polygons)
      // modify every polygon in the surface
      if (d instanceof Polygon) {
        Polygon poly = (Polygon) d;
        for(int i=0; i < poly.size(); i++) {
          // modify z-value of each point of the polygon
          Point pt = poly.get(i);
          Coord3d c = pt.xyz; 
          c.z = (float) mapper.f(c.x, c.y);
        }
      }
  } /* remap */


  private void pause(int ms)
  {
    try {
      Thread.sleep(ms);
    }
    catch(InterruptedException ex) { }
  }  // end of pause()



  // -------------------------------------------

  public static void main(String[] args)
  {
    AnimSurface pane = new AnimSurface();

    JFrame f = new JFrame("AnimSurface");
    f.getContentPane().add(pane);

    f.addWindowListener( new WindowAdapter() {
        public void windowClosing(WindowEvent e) 
        { pane.stopUpdater();
          f.dispose();
          System.exit(0);
        }
      }
    );
    // f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    f.pack();
    f.setResizable(false);
    f.setVisible(true);
  } // end of main()


}  // end of AnimSurface class