Ptidej-v5.2-master/PADL Serialiser DB4O Tests/rsc/JHotDraw v5.1/src/CH/ifa/draw/contrib/PolygonFigure.java
/*
 * Fri Feb 28 07:47:05 1997  Doug Lea  (dl at gee)
 * Based on PolyLineFigure
 */

package CH.ifa.draw.contrib;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

import CH.ifa.draw.figures.AttributeFigure;
import CH.ifa.draw.framework.Connector;
import CH.ifa.draw.framework.Figure;
import CH.ifa.draw.framework.Locator;
import CH.ifa.draw.standard.AbstractLocator;
import CH.ifa.draw.util.Geom;
import CH.ifa.draw.util.StorableInput;
import CH.ifa.draw.util.StorableOutput;

/**
 * A scalable, rotatable polygon with an arbitrary number of points
 */
public  class PolygonFigure extends AttributeFigure {

  /**
   * Distance threshold for smoothing away or locating points
   **/
  static final int TOO_CLOSE = 2;

  /*
   * Serialization support.
   */
  private static final long serialVersionUID = 6254089689239215026L;
  private int polygonFigureSerializedDataVersion = 1;

  protected Polygon fPoly = new Polygon();

  public PolygonFigure() {
	super();
  }  

  public PolygonFigure(int x, int y) {
	fPoly.addPoint(x, y);
  }  

  public PolygonFigure(Polygon p) {
	fPoly = new Polygon(p.xpoints, p.ypoints, p.npoints);
  }  

  public Rectangle displayBox() {
	return bounds(fPoly);
  }  


  public boolean isEmpty() {
	return (fPoly.npoints < 3 ||
			(size().width < TOO_CLOSE) && (size().height < TOO_CLOSE));
  }  

  public Vector handles() {
	Vector handles = new Vector(fPoly.npoints);
	for (int i = 0; i < fPoly.npoints; i++)
	  handles.addElement(new PolygonHandle(this, locator(i), i));
	handles.addElement(new PolygonScaleHandle(this));
	//handles.addElement(new PolygonPointAddHandle(this));
	return handles;
  }  


  public void basicDisplayBox(Point origin, Point corner) {
	Rectangle r = displayBox();
	int dx = origin.x - r.x;
	int dy = origin.y - r.y;
	fPoly.translate(dx, dy);
	r = displayBox();
	Point oldCorner = new Point(r.x + r.width, r.y + r.height);
	Polygon p = getPolygon();
	scaleRotate(oldCorner, p, corner);
  }  

  /**
   * return a copy of the raw polygon
   **/
  public Polygon getPolygon() {
	return new Polygon(fPoly.xpoints, fPoly.ypoints, fPoly.npoints);
  }  

  public Point center() {
	return center(fPoly);
  }  

  public Enumeration points() {
	Vector pts = new Vector(fPoly.npoints);
	for (int i = 0; i < fPoly.npoints; ++i)
	  pts.addElement(new Point(fPoly.xpoints[i], fPoly.ypoints[i]));
	return pts.elements();
  }  

  public int pointCount() {
	return fPoly.npoints;
  }  

  public void basicMoveBy(int dx, int dy) {
	fPoly.translate(dx, dy);
  }  

  public void drawBackground(Graphics g) {
	  g.fillPolygon(fPoly);
  }  

  public void drawFrame(Graphics g) {
	  g.drawPolygon(fPoly);
  }  

  public boolean containsPoint(int x, int y) {
	return fPoly.contains(x, y);
  }  

  public Connector connectorAt(int x, int y) {
	return new ChopPolygonConnector(this);
  }  

  /**
   * Adds a node to the list of points.
   */
  public  void addPoint(int x, int y) {
	fPoly.addPoint(x, y);
	changed();
  }  


  /**
   * Changes the position of a node.
   */
  public  void setPointAt(Point p, int i) {
	willChange();
	fPoly.xpoints[i] = p.x;
	fPoly.ypoints[i] = p.y;
	changed();
  }  

  /**
   * Insert a node at the given point.
   */
  public  void insertPointAt(Point p, int i) {
	willChange();
	int n = fPoly.npoints + 1;
	int[] xs = new int[n];
	int[] ys = new int[n];
	for (int j = 0; j < i; ++j) {
	  xs[j] = fPoly.xpoints[j];
	  ys[j] = fPoly.ypoints[j];
	}
	xs[i] = p.x;
	ys[i] = p.y;
	for (int j = i; j < fPoly.npoints; ++j) {
	  xs[j+1] = fPoly.xpoints[j];
	  ys[j+1] = fPoly.ypoints[j];
	}

	fPoly = new Polygon(xs, ys, n);
	changed();
  }  

  public  void removePointAt(int i) {
	willChange();
	int n = fPoly.npoints - 1;
	int[] xs = new int[n];
	int[] ys = new int[n];
	for (int j = 0; j < i; ++j) {
	  xs[j] = fPoly.xpoints[j];
	  ys[j] = fPoly.ypoints[j];
	}
	for (int j = i; j < n; ++j) {
	  xs[j] = fPoly.xpoints[j+1];
	  ys[j] = fPoly.ypoints[j+1];
	}
	fPoly = new Polygon(xs, ys, n);
	changed();
  }  

  /**
   * Scale and rotate relative to anchor
   **/
  public  void scaleRotate(Point anchor, Polygon originalPolygon, Point p) {
	willChange();

	// use center to determine relative angles and lengths
	Point ctr = center(originalPolygon);
	double anchorLen = Geom.length(ctr.x, ctr.y, anchor.x, anchor.y);

	if (anchorLen > 0.0) {
	  double newLen = Geom.length(ctr.x, ctr.y, p.x, p.y);
	  double ratio = newLen / anchorLen;

	  double anchorAngle = Math.atan2(anchor.y - ctr.y, anchor.x - ctr.x);
	  double newAngle = Math.atan2(p.y - ctr.y, p.x - ctr.x);
	  double rotation = newAngle - anchorAngle;

	  int n = originalPolygon.npoints;
	  int[] xs = new int[n];
	  int[] ys = new int[n];

	  for (int i = 0; i < n; ++i) {
		int x = originalPolygon.xpoints[i];
		int y = originalPolygon.ypoints[i];
		double l = Geom.length(ctr.x, ctr.y, x, y) * ratio;
		double a = Math.atan2(y - ctr.y, x - ctr.x) + rotation;
		xs[i] = (int)(ctr.x + l * Math.cos(a) + 0.5);
		ys[i] = (int)(ctr.y + l * Math.sin(a) + 0.5);
	  }
	  fPoly =  new Polygon(xs, ys, n);
	}
	changed();
  }  


  /**
   * Remove points that are nearly colinear with others
   **/
  public void smoothPoints() {
	willChange();
	boolean removed = false;
	int n = fPoly.npoints;
	do {
	  removed = false;
	  int i = 0;
	  while (i < n && n >= 3) {
		int nxt = (i + 1) % n;
		int prv = (i - 1 + n) % n;

		if ((distanceFromLine(fPoly.xpoints[prv], fPoly.ypoints[prv],
							  fPoly.xpoints[nxt], fPoly.ypoints[nxt],
							  fPoly.xpoints[i], fPoly.ypoints[i]) < TOO_CLOSE)) {
		  removed = true;
		  --n;
		  for (int j = i; j < n; ++j) {
			fPoly.xpoints[j] = fPoly.xpoints[j+1];
			fPoly.ypoints[j] = fPoly.ypoints[j+1];
		  }
		}
		else
		  ++i;
	  }
	} while(removed);
	if (n != fPoly.npoints)
	  fPoly =  new Polygon(fPoly.xpoints, fPoly.ypoints, n);
	changed();
  }  

  /**
   * Splits the segment at the given point if a segment was hit.
   * @return the index of the segment or -1 if no segment was hit.
   */
  public int splitSegment(int x, int y) {
	int i = findSegment(x, y);
	if (i != -1) {
	  insertPointAt(new Point(x, y), i+1);
	  return i + 1;
	}
	else
	  return -1;
  }  

  public Point pointAt(int i) {
	return new Point(fPoly.xpoints[i], fPoly.ypoints[i]);
  }  

  /**
   * Return the point on the polygon that is furthest from the center
   **/
  public Point outermostPoint() {
	Point ctr = center();
	int outer = 0;
	long dist = 0;

	for (int i = 0; i < fPoly.npoints; ++i) {
	  long d = Geom.length2(ctr.x, ctr.y, fPoly.xpoints[i], fPoly.ypoints[i]);
	  if (d > dist) {
		dist = d;
		outer = i;
	  }
	}

	return new Point(fPoly.xpoints[outer], fPoly.ypoints[outer]);
  }  


  /**
   * Gets the segment that is hit by the given point.
   * @return the index of the segment or -1 if no segment was hit.
   */
  public int findSegment(int x, int y) {
	double dist = TOO_CLOSE;
	int best = -1;

	for (int i = 0; i < fPoly.npoints; i++) {
	  int n = (i + 1) % fPoly.npoints;
	  double d =  distanceFromLine(fPoly.xpoints[i], fPoly.ypoints[i],
								   fPoly.xpoints[n], fPoly.ypoints[n],
								   x, y);
	  if (d < dist) {
		dist = d;
		best = i;
	  }
	}
	return best;
  }  

  public Point chop(Point p) {
	return chop(fPoly, p);
  }  

  public void write(StorableOutput dw) {
	super.write(dw);
	dw.writeInt(fPoly.npoints);
	for (int i = 0; i < fPoly.npoints; ++i) {
	  dw.writeInt(fPoly.xpoints[i]);
	  dw.writeInt(fPoly.ypoints[i]);
	}
  }  

  public void read(StorableInput dr) throws IOException {
	super.read(dr);
	int size = dr.readInt();
	int[] xs = new int[size];
	int[] ys = new int[size];
	for (int i = 0; i < size; i++) {
	  xs[i] = dr.readInt();
	  ys[i] = dr.readInt();
	}
	fPoly = new Polygon(xs, ys, size);
  }  

  /**
   * Creates a locator for the point with the given index.
   */
  public static Locator locator(final int pointIndex) {
	return new AbstractLocator() {
	  public Point locate(Figure owner) {
		PolygonFigure plf = (PolygonFigure)owner;
		// guard against changing PolygonFigures -> temporary hack
		if (pointIndex < plf.pointCount())
		  return ((PolygonFigure)owner).pointAt(pointIndex);
		return new Point(-1, -1);
	  }
	};
  }  

  /**
   * compute distance of point from line segment, or
   * Double.MAX_VALUE if perpendicular projection is outside segment; or
   * If pts on line are same, return distance from point
   **/
  public static double distanceFromLine(int xa, int ya,
										int xb, int yb,
										int xc, int yc) {


	// source:http://vision.dai.ed.ac.uk/andrewfg/c-g-a-faq.html#q7
	//Let the point be C (XC,YC) and the line be AB (XA,YA) to (XB,YB).
	//The length of the
	//      line segment AB is L:
	//
	//                    ___________________
	//                   |        2         2
	//              L = \| (XB-XA) + (YB-YA)
	//and
	//
	//                  (YA-YC)(YA-YB)-(XA-XC)(XB-XA)
	//              r = -----------------------------
	//                              L**2
	//
	//                  (YA-YC)(XB-XA)-(XA-XC)(YB-YA)
	//              s = -----------------------------
	//                              L**2
	//
	//      Let I be the point of perpendicular projection of C onto AB, the
	//
	//              XI=XA+r(XB-XA)
	//              YI=YA+r(YB-YA)
	//
	//      Distance from A to I = r*L
	//      Distance from C to I = s*L
	//
	//      If r < 0 I is on backward extension of AB
	//      If r>1 I is on ahead extension of AB
	//      If 0<=r<=1 I is on AB
	//
	//      If s < 0 C is left of AB (you can just check the numerator)
	//      If s>0 C is right of AB
	//      If s=0 C is on AB

	int xdiff = xb - xa;
	int ydiff = yb - ya;
	long l2 = xdiff*xdiff + ydiff*ydiff;

	if (l2 == 0) return Geom.length(xa, ya, xc, yc);

	double rnum =  (ya-yc) * (ya-yb) - (xa-xc) * (xb-xa);
	double r = rnum / l2;

	if (r < 0.0 || r > 1.0) return Double.MAX_VALUE;

	double xi = xa + r * xdiff;
	double yi = ya + r * ydiff;
	double xd = xc - xi;
	double yd = yc - yi;
	return Math.sqrt(xd * xd + yd * yd);

	/*
	  for directional version, instead use
	  double snum =  (ya-yc) * (xb-xa) - (xa-xc) * (yb-ya);
	  double s = snum / l2;

	  double l = Math.sqrt((double)l2);
	  return = s * l;
	  */
  }  

  /**
   * replacement for builtin Polygon.getBounds that doesn't always update?
   */

  public static Rectangle bounds(Polygon p) {
	int minx = Integer.MAX_VALUE;
	int miny = Integer.MAX_VALUE;
	int maxx = Integer.MIN_VALUE;
	int maxy = Integer.MIN_VALUE;
	int n = p.npoints;
	for (int i = 0; i < n; i++) {
	  int x = p.xpoints[i];
	  int y = p.ypoints[i];
	  if (x > maxx) maxx = x;
	  if (x < minx) minx = x;
	  if (y > maxy) maxy = y;
	  if (y < miny) miny = y;
	}

	return new Rectangle(minx, miny, maxx-minx, maxy-miny);
  }  

  public static Point center(Polygon p) {
	long sx = 0;
	long sy = 0;
	int n = p.npoints;
	for (int i = 0; i < n; i++) {
	  sx += p.xpoints[i];
	  sy += p.ypoints[i];
	}

	return new Point((int)(sx/n), (int)(sy/n));
  }  

  public static Point chop(Polygon poly, Point p) {
	Point ctr = center(poly);
	int cx = -1;
	int cy = -1;
	long len = Long.MAX_VALUE;

	// Try for points along edge

	for (int i = 0; i < poly.npoints; ++i) {
	  int nxt = (i + 1) % poly.npoints;
	  Point chop = Geom.intersect(poly.xpoints[i],
							 poly.ypoints[i],
							 poly.xpoints[nxt],
							 poly.ypoints[nxt],
							 p.x,
							 p.y,
							 ctr.x,
							 ctr.y);
	  if (chop != null) {
		long cl = Geom.length2(chop.x, chop.y, p.x, p.y);
		if (cl < len) {
		  len = cl;
		  cx = chop.x;
		  cy = chop.y;
		}
	  }
	}
	// if none found, pick closest vertex
	//if (len ==  Long.MAX_VALUE) {
	{ // try anyway
	  for (int i = 0; i < poly.npoints; ++i) {
		long l = Geom.length2(poly.xpoints[i], poly.ypoints[i], p.x, p.y);
		if (l < len) {
		  len = l;
		  cx = poly.xpoints[i];
		  cy = poly.ypoints[i];
		}
	  }
	}
	return new Point(cx, cy);
  }  

}

