/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.draw.shapes;
import java.awt.Graphics;
import java.awt.geom.GeneralPath;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.apache.commons.collections15.list.UnmodifiableList;
import com.cburch.draw.model.CanvasObject;
import com.cburch.draw.model.Handle;
import com.cburch.draw.model.HandleGesture;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import static com.cburch.logisim.util.LocaleString.*;
public class Poly extends FillableCanvasObject {
private boolean closed;
// "handles" should be immutable - create a new array and change using
// setHandles rather than changing contents
private Handle[] handles;
private GeneralPath path;
private double[] lens;
private Bounds bounds;
public Poly(boolean closed, List<Location> locations) {
Handle[] hs = new Handle[locations.size()];
int i = -1;
for (Location loc : locations) {
i++;
hs[i] = new Handle(this, loc.getX(), loc.getY());
}
this.closed = closed;
handles = hs;
recomputeBounds();
}
@Override
public Poly clone() {
Poly ret = (Poly) super.clone();
Handle[] hs = this.handles.clone();
for (int i = 0, n = hs.length; i < n; i++) {
Handle oldHandle = hs[i];
hs[i] = new Handle(ret, oldHandle.getX(), oldHandle.getY());
}
ret.handles = hs;
return ret;
}
@Override
public boolean matches(CanvasObject other) {
if (other instanceof Poly) {
Poly that = (Poly) other;
Handle[] a = this.handles;
Handle[] b = that.handles;
if (this.closed != that.closed || a.length != b.length) {
return false;
} else {
for (int i = 0, n = a.length; i < n; i++) {
if (!a[i].equals(b[i])) {
return false;
}
}
return super.matches(that);
}
} else {
return false;
}
}
@Override
public int matchesHashCode() {
int ret = super.matchesHashCode();
ret = ret * 3 + (closed ? 1 : 0);
Handle[] hs = handles;
for (int i = 0, n = hs.length; i < n; i++) {
ret = ret * 31 + hs[i].hashCode();
}
return ret;
}
@Override
public String getDisplayName() {
if (closed) {
return getFromLocale("shapePolygon");
} else {
return getFromLocale("shapePolyline");
}
}
@Override
public List<Attribute<?>> getAttributes() {
return DrawAttr.getFillAttributes(getPaintType());
}
@Override
public final boolean contains(Location loc, boolean assumeFilled) {
Object type = getPaintType();
if (assumeFilled && type == DrawAttr.PAINT_STROKE) {
type = DrawAttr.PAINT_STROKE_FILL;
}
if (type == DrawAttr.PAINT_STROKE) {
int thresh = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2);
PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc,
closed, handles);
return result.getDistanceSq() < thresh * thresh;
} else if (type == DrawAttr.PAINT_FILL) {
GeneralPath path = getPath();
return path.contains(loc.getX(), loc.getY());
// fill and stroke
} else {
GeneralPath path = getPath();
if (path.contains(loc.getX(), loc.getY())) {
return true;
}
int width = getStrokeWidth();
PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc,
closed, handles);
return result.getDistanceSq() < (width * width) / 4;
}
}
@Override
public final Location getRandomPoint(Bounds bds, Random rand) {
if (getPaintType() == DrawAttr.PAINT_STROKE) {
Location ret = getRandomBoundaryPoint(bds, rand);
int w = getStrokeWidth();
if (w > 1) {
int dx = rand.nextInt(w) - w / 2;
int dy = rand.nextInt(w) - w / 2;
ret = ret.translate(dx, dy);
}
return ret;
} else {
return super.getRandomPoint(bds, rand);
}
}
private Location getRandomBoundaryPoint(Bounds bds, Random rand) {
Handle[] hs = handles;
double[] ls = lens;
if (ls == null) {
ls = new double[hs.length + (closed ? 1 : 0)];
double total = 0.0;
for (int i = 0; i < ls.length; i++) {
int j = (i + 1) % hs.length;
total += LineUtil.distance(hs[i].getX(), hs[i].getY(),
hs[j].getX(), hs[j].getY());
ls[i] = total;
}
lens = ls;
}
double pos = ls[ls.length - 1] * rand.nextDouble();
for (int i = 0; true; i++) {
if (pos < ls[i]) {
Handle p = hs[i];
Handle q = hs[(i + 1) % hs.length];
double u = Math.random();
int x = (int) Math.round(p.getX() + u * (q.getX() - p.getX()));
int y = (int) Math.round(p.getY() + u * (q.getY() - p.getY()));
return Location.create(x, y);
}
}
}
@Override
public Bounds getBounds() {
return bounds;
}
@Override
public void translate(int dx, int dy) {
Handle[] hs = handles;
Handle[] is = new Handle[hs.length];
for(int i = 0; i < hs.length; i++) {
is[i] = new Handle(this, hs[i].getX() + dx, hs[i].getY() + dy);
}
setHandles(is);
}
public boolean isClosed() {
return closed;
}
@Override
public List<Handle> getHandles(HandleGesture gesture) {
Handle[] hs = handles;
if (gesture == null) {
return UnmodifiableList.decorate(Arrays.asList(hs));
} else {
Handle g = gesture.getHandle();
Handle[] ret = new Handle[hs.length];
for (int i = 0, n = hs.length; i < n; i++) {
Handle h = hs[i];
if (h.equals(g)) {
int x = h.getX() + gesture.getDeltaX();
int y = h.getY() + gesture.getDeltaY();
Location r;
if (gesture.isShiftDown()) {
Location prev = hs[(i + n - 1) % n].getLocation();
Location next = hs[(i + 1) % n].getLocation();
if (!closed) {
if (i == 0) {
prev = null;
}
if (i == n - 1) {
next = null;
}
}
if (prev == null) {
r = LineUtil.snapTo8Cardinals(next, x, y);
} else if (next == null) {
r = LineUtil.snapTo8Cardinals(prev, x, y);
} else {
Location to = Location.create(x, y);
Location a = LineUtil.snapTo8Cardinals(prev, x, y);
Location b = LineUtil.snapTo8Cardinals(next, x, y);
int ad = a.manhattanDistanceTo(to);
int bd = b.manhattanDistanceTo(to);
r = ad < bd ? a : b;
}
} else {
r = Location.create(x, y);
}
ret[i] = new Handle(this, r);
} else {
ret[i] = h;
}
}
return UnmodifiableList.decorate(Arrays.asList(ret));
}
}
@Override
public boolean canMoveHandle(Handle handle) {
return true;
}
@Override
public Handle moveHandle(HandleGesture gesture) {
List<Handle> hs = getHandles(gesture);
Handle[] is = new Handle[hs.size()];
Handle ret = null;
int i = -1;
for (Handle h : hs) {
i++;
is[i] = h;
}
setHandles(is);
return ret;
}
@Override
public Handle canInsertHandle(Location loc) {
PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc, closed,
handles);
int thresh = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2);
if (result.getDistanceSq() < thresh * thresh) {
Location resLoc = result.getLocation();
if (result.getPreviousHandle().isAt(resLoc)
|| result.getNextHandle().isAt(resLoc)) {
return null;
} else {
return new Handle(this, result.getLocation());
}
} else {
return null;
}
}
@Override
public Handle canDeleteHandle(Location loc) {
int minHandles = closed ? 3 : 2;
Handle[] hs = handles;
if (hs.length <= minHandles) {
return null;
} else {
int qx = loc.getX();
int qy = loc.getY();
int w = Math.max(Line.ON_LINE_THRESH, getStrokeWidth() / 2);
for (Handle h : hs) {
int hx = h.getX();
int hy = h.getY();
if (LineUtil.distance(qx, qy, hx, hy) < w * w) {
return h;
}
}
return null;
}
}
@Override
public void insertHandle(Handle desired, Handle previous) {
Location loc = desired.getLocation();
Handle[] hs = handles;
Handle prev;
if (previous == null) {
PolyUtil.ClosestResult result = PolyUtil.getClosestPoint(loc,
closed, hs);
prev = result.getPreviousHandle();
} else {
prev = previous;
}
Handle[] is = new Handle[hs.length + 1];
boolean inserted = false;
for(int i = 0; i < hs.length; i++) {
if (inserted) {
is[i + 1] = hs[i];
} else if (hs[i].equals(prev)) {
inserted = true;
is[i] = hs[i];
is[i + 1] = desired;
} else {
is[i] = hs[i];
}
}
if (!inserted) {
throw new IllegalArgumentException("no such handle");
}
setHandles(is);
}
@Override
public Handle deleteHandle(Handle handle) {
Handle[] hs = handles;
int n = hs.length;
Handle[] is = new Handle[n - 1];
Handle previous = null;
boolean deleted = false;
for (int i = 0; i < n; i++) {
if (deleted) {
is[i - 1] = hs[i];
} else if (hs[i].equals(handle)) {
if (previous == null) {
previous = hs[n - 1];
}
deleted = true;
} else {
previous = hs[i];
is[i] = hs[i];
}
}
setHandles(is);
return previous;
}
@Override
public void paint(Graphics g, HandleGesture gesture) {
List<Handle> hs = getHandles(gesture);
int[] xs = new int[hs.size()];
int[] ys = new int[hs.size()];
int i = -1;
for (Handle h : hs) {
i++;
xs[i] = h.getX();
ys[i] = h.getY();
}
if (setForFill(g)) {
g.fillPolygon(xs, ys, xs.length);
}
if (setForStroke(g)) {
if (closed) {
g.drawPolygon(xs, ys, xs.length);
} else {
g.drawPolyline(xs, ys, xs.length);
}
}
}
private void setHandles(Handle[] hs) {
handles = hs;
lens = null;
path = null;
recomputeBounds();
}
private void recomputeBounds() {
Handle[] hs = handles;
int x0 = hs[0].getX();
int y0 = hs[0].getY();
int x1 = x0;
int y1 = y0;
for(int i = 1; i < hs.length; i++) {
int x = hs[i].getX();
int y = hs[i].getY();
if (x < x0) {
x0 = x;
}
if (x > x1) {
x1 = x;
}
if (y < y0) {
y0 = y;
}
if (y > y1) {
y1 = y;
}
}
Bounds bds = Bounds.create(x0, y0, x1 - x0 + 1, y1 - y0 + 1);
int stroke = getStrokeWidth();
bounds = stroke < 2 ? bds : bds.expand(stroke / 2);
}
private GeneralPath getPath() {
GeneralPath p = path;
if (p == null) {
p = new GeneralPath();
Handle[] hs = handles;
if (hs.length > 0) {
boolean first = true;
for (Handle h : hs) {
if (first) {
p.moveTo(h.getX(), h.getY());
first = false;
} else {
p.lineTo(h.getX(), h.getY());
}
}
}
path = p;
}
return p;
}
}