package bezierdemo;

import java.awt.*;
import java.awt.event.*;

/**
 * <p>Title: Bezier Demo</p>
 * <p>Description: demonstrates the basic drawing techniques used to render Beziers (2D)</p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: Carleton University</p>
 * @author Pat Beirne
 * @version 1.0
 */

public class BezierPanel extends Panel implements MouseListener, MouseMotionListener {
  Point[] node = new Point[4];
  int nodeCount = 0;
  int rectDelta = 2;
  int nodeTracking = -1;
  boolean drawCubic = true; // for cubic, false for quadratic
  int drawDepth = 5;

  int drawMode = 0; // for polygon, 1 for linesplit, 2 for c.o.p.
  static final int POLYLINE = 0;
  static final int LINESPLIT = 1;
  static final int COP = 2;

  public BezierPanel() {
  }

  public void setDrawCubic(boolean newType) {
    if (newType != drawCubic) {
      drawCubic = newType;
      repaint();
    }
  }
  public void setDrawMode(int newMode) {
    if (newMode != drawMode) {
      drawMode = newMode;
      repaint();
    }
  }
  public void setDrawDepth(int newDepth) {
    if (newDepth != drawDepth) {
      drawDepth = newDepth;
      repaint();
    }
  }


  /************ painting ***********/
  public void paint(Graphics g) {
    //Rectangle r = g.getClipBounds();
    //System.out.println("redraw time "+nodeCount+" "+r);
    super.paintComponents(g);
    //g.drawRect((int)r.getX(),(int)r.getY(),
    //           (int)r.getWidth(),(int)r.getHeight());
    if (nodeCount>0) {
      for (int i=0; i<nodeCount; i++) {
        g.draw3DRect(node[i].x-rectDelta,node[i].y-rectDelta,
                     2*rectDelta,2*rectDelta,false);
      }
    }
    // the outline
    g.setColor(Color.GRAY);
    if (drawCubic ? nodeCount>=4 : nodeCount>=3) {
      for (int i=0; i<(drawCubic ? 3 : 2); i++) {
        g.drawLine(node[i].x,node[i].y,node[i+1].x,node[i+1].y);
      }
    }
    if (nodeCount<4)
      return;
    g.setColor(Color.BLACK);
    // the curve
    if (drawMode==POLYLINE) {
      if (drawCubic)
        drawPolylineCubic(g,drawDepth);
      else
        drawPolylineQuad(g,drawDepth);
    } else if (drawMode==LINESPLIT) {
      if (drawCubic)
        drawLinesplitCubic(g,node[0],node[1],node[2],node[3],drawDepth);
      else
        drawLinesplitQuad(g,node[0],node[1],node[2],drawDepth);
    } else
      drawCOP(g);
  }


  private void drawPolylineCubic(Graphics g, int depth) {
    double t;
    int i;
    int[] x = new int[33];
    int[] y = new int[33];
    for (i=0, t=0.0;
         t<=1.0;
         i++, t+=1./(1<<depth)) {
      x[i] = (int) (node[0].x*(1-t)*(1-t)*(1-t) +
          3*node[1].x*(1-t)*(1-t)*t +
          3*node[2].x*(1-t)*t*t +
          node[3].x*t*t*t);

      y[i] = (int) (node[0].y*(1-t)*(1-t)*(1-t) +
                3*node[1].y*(1-t)*(1-t)*t +
                3*node[2].y*(1-t)*t*t +
                node[3].y*t*t*t);
    }
    g.drawPolyline(x,y,i);
  }

  private void drawPolylineQuad(Graphics g, int depth) {
    double t;
    int i;
    int[] x = new int[33];
    int[] y = new int[33];
    for (i=0, t=0.0;
         t<=1.0;
         i++, t+=1./(1<<depth)) {
      x[i] = (int) (node[0].x*(1-t)*(1-t) +
          2*node[1].x*(1-t)*t +
          node[2].x*t*t);

      y[i] = (int) (node[0].y*(1-t)*(1-t) +
                2*node[1].y*(1-t)*t +
                node[2].y*t*t);
    }
    g.drawPolyline(x,y,i);
  }

  private void drawLinesplitQuad(Graphics g, Point a, Point b, Point c, int depth) {
    g.setColor(Color.RED);
    Point d,e,f;
    d = new Point((a.x+b.x)/2,(a.y+b.y)/2);
    e = new Point((b.x+c.x)/2,(b.y+c.y)/2);
    f = new Point((d.x+e.x)/2,(d.y+e.y)/2);
    g.drawLine(d.x,d.y,e.x,e.y);
    g.drawRect(f.x-rectDelta,f.y-rectDelta,2*rectDelta,2*rectDelta);
    if (depth>1) {
      drawLinesplitQuad(g,a,d,f,depth-1);
      drawLinesplitQuad(g,f,e,c,depth-1);
    }
  }
  private void drawLinesplitCubic(Graphics gr, Point a, Point b, Point c, Point d, int depth) {
    gr.setColor(Color.BLUE);
    Point e,f,g,h,i,j;
    e = new Point((a.x+b.x)/2,(a.y+b.y)/2);
    f = new Point((b.x+c.x)/2,(b.y+c.y)/2);
    g = new Point((c.x+d.x)/2,(c.y+d.y)/2);
    h = new Point((e.x+f.x)/2,(e.y+f.y)/2);
    i = new Point((f.x+g.x)/2,(f.y+g.y)/2);
    j = new Point((h.x+i.x)/2,(h.y+i.y)/2);
    gr.drawLine(e.x,e.y,f.x,f.y);
    gr.drawLine(f.x,f.y,g.x,g.y);
    gr.drawLine(h.x,h.y,i.x,i.y);
    gr.drawRect(j.x-rectDelta,j.y-rectDelta,2*rectDelta,2*rectDelta);
    if (depth>1) {
      drawLinesplitCubic(gr,a,e,h,j,depth-1);
      drawLinesplitCubic(gr,j,i,g,d,depth-1);
    }
  }
  private void drawCOP(Graphics g)
  {
    Color push_color = g.getColor();
    int[] cax = new int[101];
    int[] cay = new int[101];
    int[] dax = new int[101];
    int[] day = new int[101];
    double bx,by;
    double cx = node[2].x, cy = node[2].y;
    double dx = node[3].x, dy = node[3].y;
    final int STEPS = 100;
    double e = 1.0/STEPS;
    try {
      for (int i=0; i<STEPS; i++) {
        double t = i*e;
        g.setColor(Color.RED);
        bx = node[1].x+t*(node[0].x-node[1].x);
        by = node[1].y+t*(node[0].y-node[1].y);
        g.drawLine(node[1].x,node[1].y,(int)bx,(int)by);
        g.setColor(Color.BLUE);
        cx = cx + (bx-cx)*2*e/(1-t);
        cy = cy + (by-cy)*2*e/(1-t);
        cax[i] = (int)cx;
        cay[i] = (int)cy;
        g.drawPolyline(cax,cay,i);
        g.setColor(Color.GREEN);
        dx = dx + (cx-dx)*3*e/(1-t);
        dy = dy + (cy-dy)*3*e/(1-t);
        dax[i] = (int)dx;
        day[i] = (int)dy;
        g.drawPolyline(dax,day,i);
        Thread.sleep(30);
      }
    } catch (InterruptedException ex) {}
    g.setColor(push_color);
  }

  public int hitTest(int x, int y) {
    for (int i = 0; i<nodeCount; i++) {
      if (x>node[i].x-2*rectDelta && x<node[i].x+2*rectDelta
        && y>node[i].y-2*rectDelta && y<node[i].y+2*rectDelta)
      return i;
    }
    return -1;
  }

  /*********** mouse listener *************/
  public void mouseClicked(MouseEvent e) {}
  public void mousePressed(MouseEvent e) {
    if (nodeCount < 4) {
      node[nodeCount++] = new Point(e.getX(), e.getY());
      this.repaint();//e.getX()-rectDelta,e.getY()-rectDelta,
                   //2*rectDelta,2*rectDelta);
      return;
    }
    //System.out.println("try to pick up :"+e);
    int hit = hitTest(e.getX(),e.getY());
    //System.out.println("hit test returned: "+hit);
    if (hit>=0) {
      nodeTracking = hit;
    }
  }
  public void mouseReleased(MouseEvent e) {
      nodeTracking = -1;
      if (drawMode==COP)
        repaint();
  }
  public void mouseEntered(MouseEvent e) {}
  public void mouseExited(MouseEvent e) {}

  public void mouseDragged(MouseEvent e) {
    if (nodeTracking==-1)
      return;
    node[nodeTracking] = new Point(e.getX(),e.getY());
    int i = nodeTracking;
    if (drawMode != COP)
      repaint();
  }
  public void mouseMoved(MouseEvent e) {}

  public static void main(String[] args) {
    BezierPanel bezierPanel1 = new BezierPanel();
  }

}