/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.fge.geom; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.openflexo.fge.FGEUtils; import org.openflexo.fge.geom.area.FGEArea; import org.openflexo.fge.geom.area.FGEBand; import org.openflexo.fge.geom.area.FGEEmptyArea; import org.openflexo.fge.geom.area.FGEExclusiveOrArea; import org.openflexo.fge.geom.area.FGEHalfBand; import org.openflexo.fge.geom.area.FGEHalfLine; import org.openflexo.fge.geom.area.FGEHalfPlane; import org.openflexo.fge.geom.area.FGEIntersectionArea; import org.openflexo.fge.geom.area.FGESubstractionArea; import org.openflexo.fge.geom.area.FGEUnionArea; import org.openflexo.fge.graphics.FGEGraphics; public class FGEArc extends Arc2D.Double implements FGEGeometricObject<FGEArc>, FGEShape<FGEArc> { private static final Logger logger = Logger.getLogger(FGEArc.class.getPackage().getName()); public static enum ArcType { /** * The closure type for an open arc with no path segments connecting the two ends of the arc segment. */ OPEN, /** * The closure type for an arc closed by drawing a straight line segment from the start of the arc segment to the end of the arc * segment. */ CHORD, /** * The closure type for an arc closed by drawing straight line segments from the start of the arc segment to the center of the full * ellipse and from that point to the end of the arc segment. */ PIE } public FGEArc() { super(ArcType.OPEN.ordinal()); } public FGEArc(ArcType type) { super(type.ordinal()); } /** * * @param x * @param y * @param w * @param h * @param start * angle in degree * @param extent * angle in degree * @param type */ public FGEArc(double x, double y, double w, double h, double start, double extent, ArcType type) { super(x, y, w, h, start, extent, type.ordinal()); } /** * * @param center * @param size * @param start * angle in degree * @param extent * angle in degree * @param type */ public FGEArc(FGEPoint center, FGEDimension size, double start, double extent, ArcType type) { this(center.x - size.width / 2, center.y - size.height / 2, size.width, size.height, start, extent, type); } public FGEArc(double x, double y, double w, double h, double start, double extent) { super(x, y, w, h, start, extent, ArcType.OPEN.ordinal()); if (extent >= 360) { logger.warning("Arc created instead of ellips or circle (extent >=360) "); } } public FGEArc(FGEPoint center, FGEDimension size, double start, double extent) { this(center.x - size.width / 2, center.y - size.height / 2, size.width, size.height, start, extent, ArcType.OPEN); } public FGEArc(FGEPoint center, FGEDimension size) { this(center, size, 0, 360, ArcType.OPEN); } @Override public boolean getIsFilled() { return getFGEArcType() == ArcType.PIE; } @Override public void setIsFilled(boolean filled) { if (!filled) { setFGEArcType(ArcType.OPEN); } else { setFGEArcType(ArcType.PIE); } } @Override public FGEPoint getCenter() { return new FGEPoint(getCenterX(), getCenterY()); } public FGERectangle getFGEBounds() { return new FGERectangle(getX(), getY(), getWidth(), getHeight(), Filling.FILLED); } public ArcType getFGEArcType() { if (getArcType() == ArcType.OPEN.ordinal()) { return ArcType.OPEN; } else if (getArcType() == ArcType.CHORD.ordinal()) { return ArcType.CHORD; } else if (getArcType() == ArcType.PIE.ordinal()) { return ArcType.PIE; } return null; } public void setFGEArcType(ArcType arcType) { if (arcType == ArcType.OPEN) { setArcType(ArcType.OPEN.ordinal()); } else if (arcType == ArcType.CHORD) { setArcType(ArcType.CHORD.ordinal()); } else if (arcType == ArcType.PIE) { setArcType(ArcType.PIE.ordinal()); } } public static void main(String[] args) { // FGESegment s = new FGESegment(new FGEPoint(0,5),new FGEPoint(0,2)); // FGEHalfLine hl = new FGEHalfLine(new FGEPoint(0,0),new FGEPoint(0,1)); FGESegment s = new FGESegment(new FGEPoint(0.11718749999999999, 0.7722772277227723), new FGEPoint(0.1171875, 0.5742574257425743)); FGEHalfLine hl = new FGEHalfLine(new FGEPoint(0.11718749999999999, 0.22277227722772278), new FGEPoint(0.11718749999999999, 1.2227722772277227)); // FGESegment s = new FGESegment(new FGEPoint(0.1171875,0.7722772277227723),new FGEPoint(0.1171875,0.5742574257425743)); // FGEHalfLine hl = new FGEHalfLine(new FGEPoint(0.1171875,0.22277227722772278),new FGEPoint(0.1171875,1.2227722772277227)); System.out.println("intersect: " + s.intersect(hl)); // System.out.println("intersect: "+hl.intersect(s)); FGEArc arc = new FGEArc(new FGEPoint(0, 0), new FGEDimension(100, 100)); /*Area area1 = new Area(arc); Area area2 = new Area(line); System.out.println("area1="+area1); System.out.println("area2="+area2); Area intersect = new Area(area1); intersect.intersect(area2); System.out.println("intersect="+intersect); PathIterator i = intersect.getPathIterator(null); System.out.println("PathIterator="+i); while (!i.isDone()) { double[] coords = null; i.next(); i.currentSegment(coords); System.out.println("coords="+coords); }*/ /*FGELine line = new FGELine(new FGEPoint(-100,-100),new FGEPoint(100,100)); System.out.println("1: "+arc.computeIntersection(line)); FGELine line2 = new FGELine(new FGEPoint(50,0),new FGEPoint(50,100)); System.out.println("2: "+arc.computeIntersection(line2)); FGELine line3 = new FGELine(new FGEPoint(0,0),new FGEPoint(0,100)); System.out.println("3: "+arc.computeIntersection(line3)); FGELine line4 = new FGELine(new FGEPoint(-100,50),new FGEPoint(100,50)); System.out.println("4: "+arc.computeIntersection(line4));*/ } /** * Returns the angular extent of the arc, expressed in radians, in the range -2.PI/2.PI * * @return A double value that represents the angular extent of the arc in radians. */ public double getRadianAngleExtent() { double angdeg = getAngleExtent(); // while (angdeg < -360) angdeg += 360; // while (angdeg > 360) angdeg -= 360; return Math.toRadians(angdeg); } /** * Returns the starting angle of the arc expressed in radians, in the range -PI / PI * * @return a double value that represents the starting angle of the arc in degrees. */ public double getRadianAngleStart() { return normalizeDegAngle(getAngleStart()); } /** * Returns the ending angle of the arc expressed in radians, in the range -PI / PI * * @return a double value that represents the ending angle of the arc in degrees. */ public double getRadianAngleEnd() { return normalizeDegAngle(getAngleStart() + getAngleExtent()); } /** * Returns the supplied angle of the arc expressed in radians, in the range -PI / PI * * @return a double value that represents the normalized angle in radians */ public static double normalizeRadianAngle(double radianAngle) { while (radianAngle <= -Math.PI) { radianAngle += 2 * Math.PI; } while (radianAngle > Math.PI) { radianAngle -= 2 * Math.PI; } return radianAngle; } /** * Returns the supplied angle of the arc expressed in radians, in the range -PI / PI * * @return a double value that represents the normalized angle in radians */ public static double normalizeDegAngle(double degAngle) { return normalizeRadianAngle(Math.toRadians(degAngle)); } /** * Returns the starting point of the arc. This point is the intersection of the ray from the center defined by the starting angle and * the elliptical boundary of the arc. * * @return A <CODE>FGEPoint</CODE> object representing the x,y coordinates of the starting point of the arc. */ @Override public FGEPoint getStartPoint() { double angle = Math.toRadians(-getAngleStart()); double x = getX() + (Math.cos(angle) * 0.5 + 0.5) * getWidth(); double y = getY() + (Math.sin(angle) * 0.5 + 0.5) * getHeight(); return new FGEPoint(x, y); } /** * Returns the ending point of the arc. This point is the intersection of the ray from the center defined by the starting angle plus the * angular extent of the arc and the elliptical boundary of the arc. * * @return A <CODE>FGEPoint</CODE> object representing the x,y coordinates of the ending point of the arc. */ @Override public FGEPoint getEndPoint() { double angle = Math.toRadians(-getAngleStart() - getAngleExtent()); double x = getX() + (Math.cos(angle) * 0.5 + 0.5) * getWidth(); double y = getY() + (Math.sin(angle) * 0.5 + 0.5) * getHeight(); return new FGEPoint(x, y); } /** * Returns boolean indicating if supplied angle (expressed in radians) in contained in this arc * * @param radianAngle * @return */ public boolean includeAngle(double radianAngle) { while (radianAngle < -Math.PI - EPSILON) { radianAngle += 2 * Math.PI; } while (radianAngle > Math.PI + EPSILON) { radianAngle -= 2 * Math.PI; } double angle_start = getRadianAngleStart(); double angle_extent = getRadianAngleExtent(); if (angle_extent > 0) { return radianAngle >= angle_start - EPSILON && radianAngle <= angle_start + angle_extent + EPSILON || radianAngle + 2 * Math.PI >= angle_start - EPSILON && radianAngle + 2 * Math.PI <= angle_start + angle_extent + EPSILON || radianAngle - 2 * Math.PI >= angle_start - EPSILON && radianAngle - 2 * Math.PI <= angle_start + angle_extent + EPSILON; } else if (angle_extent < 0) { return radianAngle <= angle_start + EPSILON && radianAngle >= angle_start + angle_extent - EPSILON || radianAngle + 2 * Math.PI <= angle_start + EPSILON && radianAngle + 2 * Math.PI >= angle_start + angle_extent - EPSILON || radianAngle - 2 * Math.PI <= angle_start + EPSILON && radianAngle - 2 * Math.PI >= angle_start + angle_extent - EPSILON; } return false; } /*private FGEArea computeIntersection(FGEAbstractLine line) { double a,b,c; a = line.getA()*x+line.getB()*y+line.getB()*height/2.0+line.getC(); b = line.getB()*height; c = line.getA()*x+line.getB()*y+line.getA()*width+line.getB()*height/2.0+line.getC(); double delta = b*b-4*a*c; if (Math.abs(a) > EPSILON && Math.abs(delta) < EPSILON) { double t = -b/(2*a); double alpha = Math.atan(t)*2.0; // One solution, with angle alpha return new FGEPoint(width/2.0*Math.cos(alpha)+x+width/2.0,height/2.0*Math.sin(alpha)+y+height/2.0); } double alpha1; // in the range -PI / PI double alpha2; // in the range -PI / PI if (Math.abs(a) < EPSILON) { double t = -c/b; // Two solutions, with angle -alpha and PI-alpha alpha1 = -Math.atan(t)*2.0; alpha2 = Math.PI-Math.atan(t)*2.0; } else if (delta > 0) { double t1,t2; t1 = -(b+Math.sqrt(delta))/(2.0*a); t2 = -(b-Math.sqrt(delta))/(2.0*a); alpha1 = -Math.atan(t1)*2.0; // in the range -PI / PI alpha2 = -Math.atan(t2)*2.0; // in the range -PI / PI // Two solutions, with angle alpha1 and alpha2 } else { // No solution return new FGEEmptyArea(); } FGEPoint p1 = new FGEPoint(width/2.0*Math.cos(-alpha1)+x+width/2.0,height/2.0*Math.sin(-alpha1)+y+height/2.0); FGEPoint p2 = new FGEPoint(width/2.0*Math.cos(-alpha2)+x+width/2.0,height/2.0*Math.sin(-alpha2)+y+height/2.0); boolean includeP1 = includeAngle(alpha1); boolean includeP2 = includeAngle(alpha2); //logger.info("Found angle "+Math.toDegrees(alpha1)+" "+(includeP1?"INCLUDED":"- not included - ")); //logger.info("Found angle "+Math.toDegrees(alpha2)+" "+(includeP2?"INCLUDED":"- not included - ")); if (includeP1 && includeP2) { if (getFGEArcType() == ArcType.OPEN) { return FGEUnionArea.makeUnion(p1,p2); } else { return new FGESegment(p1,p2); } } FGEArea pp1 = (new FGESegment(getCenter(),getStartPoint())).intersect(line); FGEArea pp2 = (new FGESegment(getCenter(),getEndPoint())).intersect(line); if (includeP1) { if (getFGEArcType() == ArcType.OPEN) { return p1; } else { if (pp1 instanceof FGEPoint) return new FGESegment((FGEPoint)pp1,p1); if (pp2 instanceof FGEPoint) return new FGESegment((FGEPoint)pp2,p1); logger.warning("Unexpected situation encoutered here: arc="+this+" line="+line+" pp1="+pp1+" pp2="+pp2); } } else if (includeP2) { if (getFGEArcType() == ArcType.OPEN) { return p2; } else { if (pp1 instanceof FGEPoint) return new FGESegment((FGEPoint)pp1,p2); if (pp2 instanceof FGEPoint) return new FGESegment((FGEPoint)pp2,p2); logger.warning("Unexpected situation encoutered here: arc="+this+" line="+line+" pp1="+pp1+" pp2="+pp2); } } else { if (getFGEArcType() == ArcType.OPEN || getFGEArcType() == ArcType.CHORD) return new FGEEmptyArea(); // ArcType is ArcType.PIE if (pp1 instanceof FGEPoint && pp2 instanceof FGEPoint) { if (pp1.equals(pp2)) { return pp1; } else { // NOTE: // if is filled (not implemented yet) // return segment : return new FGESegment((FGEPoint)pp1,(FGEPoint)pp2); return FGEUnionArea.makeUnion(pp1,pp2); } } } return new FGEEmptyArea(); }*/ private static class LineIntersectionResult { private static enum SolutionType { NO_SOLUTION, ONE_SOLUTION, TWO_SOLUTIONS } SolutionType solutionType; double alpha1; // in the range -PI / PI double alpha2; // in the range -PI / PI @Override public String toString() { return "LineIntersectionResult solutionType=" + solutionType + " alpha1=" + alpha1 + " alpha2=" + alpha2; } } private LineIntersectionResult _computeIntersection(FGEAbstractLine line) { LineIntersectionResult result = new LineIntersectionResult(); double a, b, c; a = line.getA() * x + line.getB() * y + line.getB() * height / 2.0 + line.getC(); b = line.getB() * height; c = line.getA() * x + line.getB() * y + line.getA() * width + line.getB() * height / 2.0 + line.getC(); double delta = b * b - 4 * a * c; if (Math.abs(a) > EPSILON && Math.abs(delta) < EPSILON) { double t = -b / (2 * a); double alpha = Math.atan(t) * 2.0; // One solution, with angle alpha result.solutionType = org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.ONE_SOLUTION; result.alpha1 = alpha; result.alpha2 = alpha; return result; } if (Math.abs(a) < EPSILON) { double t = -c / b; // Two solutions, with angle -alpha and PI-alpha result.solutionType = org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.TWO_SOLUTIONS; result.alpha1 = -Math.atan(t) * 2.0; result.alpha2 = Math.PI - Math.atan(t) * 2.0; return result; } else if (delta > 0) { double t1, t2; t1 = -(b + Math.sqrt(delta)) / (2.0 * a); t2 = -(b - Math.sqrt(delta)) / (2.0 * a); // Two solutions, with angle alpha1 and alpha2 result.solutionType = org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.TWO_SOLUTIONS; result.alpha1 = -Math.atan(t1) * 2.0; // in the range -PI / PI result.alpha2 = -Math.atan(t2) * 2.0; // in the range -PI / PI return result; } else { // No solution result.solutionType = org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.NO_SOLUTION; return result; } } private FGEArea computeIntersection(FGEAbstractLine line) { FGEArea a = computeLineIntersection(new FGELine(line.getP1(), line.getP2())); // logger.info("computeLineIntersection(): "+a+" line="+line+" intersect="+(a.intersect(line))); return a.intersect(line); } private FGEArea computeLineIntersection(FGELine line) { LineIntersectionResult result = _computeIntersection(line); if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.NO_SOLUTION) { return new FGEEmptyArea(); } else if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.ONE_SOLUTION) { // One solution, with angle alpha return new FGEPoint(width / 2.0 * Math.cos(result.alpha1) + x + width / 2.0, height / 2.0 * Math.sin(result.alpha1) + y + height / 2.0); } else if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.TWO_SOLUTIONS) { FGEPoint p1 = new FGEPoint(width / 2.0 * Math.cos(-result.alpha1) + x + width / 2.0, height / 2.0 * Math.sin(-result.alpha1) + y + height / 2.0); FGEPoint p2 = new FGEPoint(width / 2.0 * Math.cos(-result.alpha2) + x + width / 2.0, height / 2.0 * Math.sin(-result.alpha2) + y + height / 2.0); boolean includeP1 = includeAngle(result.alpha1); boolean includeP2 = includeAngle(result.alpha2); // logger.info("Found angle "+Math.toDegrees(alpha1)+" "+(includeP1?"INCLUDED":"- not included - ")); // logger.info("Found angle "+Math.toDegrees(alpha2)+" "+(includeP2?"INCLUDED":"- not included - ")); if (includeP1 && includeP2) { if (getFGEArcType() == ArcType.OPEN) { return FGEUnionArea.makeUnion(p1, p2); } else { return new FGESegment(p1, p2); } } FGEArea pp1 = new FGESegment(getCenter(), getStartPoint()).intersect(line); FGEArea pp2 = new FGESegment(getCenter(), getEndPoint()).intersect(line); if (pp1 instanceof FGEEmptyArea && pp2 instanceof FGEEmptyArea) { // Nothing return new FGEEmptyArea(); } if (includeP1) { if (getFGEArcType() == ArcType.OPEN) { return p1; } else { if (pp1 instanceof FGESegment && pp2 instanceof FGESegment) { // They are aligned return FGEUnionArea.makeUnion(pp1, pp2); } else if (pp1 instanceof FGESegment) { return pp1; } else if (pp2 instanceof FGESegment) { return pp2; } else if (pp1 instanceof FGEPoint) { return new FGESegment((FGEPoint) pp1, p1); } else if (pp2 instanceof FGEPoint) { return new FGESegment((FGEPoint) pp2, p1); } logger.warning("Unexpected situation encoutered here: arc=" + this + " line=" + line + " pp1=" + pp1 + " pp2=" + pp2 + " p1=" + p1 + " p2=" + p2); } } else if (includeP2) { if (getFGEArcType() == ArcType.OPEN) { return p2; } else { if (pp1 instanceof FGESegment && pp2 instanceof FGESegment) { // They are aligned return FGEUnionArea.makeUnion(pp1, pp2); } else if (pp1 instanceof FGESegment) { return pp1; } else if (pp2 instanceof FGESegment) { return pp2; } else if (pp1 instanceof FGEPoint) { return new FGESegment((FGEPoint) pp1, p2); } if (pp2 instanceof FGEPoint) { return new FGESegment((FGEPoint) pp2, p2); } logger.warning("Unexpected situation encoutered here: arc=" + this + " line=" + line + " pp1=" + pp1 + " pp2=" + pp2); } } else { if (getFGEArcType() == ArcType.OPEN || getFGEArcType() == ArcType.CHORD) { return new FGEEmptyArea(); } // ArcType is ArcType.PIE if (pp1 instanceof FGEPoint && pp2 instanceof FGEPoint) { if (pp1.equals(pp2)) { return pp1; } else { // NOTE: // if is filled (not implemented yet) // return segment : return new FGESegment((FGEPoint)pp1,(FGEPoint)pp2); return FGEUnionArea.makeUnion(pp1, pp2); } } } logger.warning("Unexpected situation here " + result); logger.warning("p1=" + p1); logger.warning("p2=" + p2); logger.warning("includeP1=" + includeP1); logger.warning("includeP2=" + includeP2); logger.warning("pp1=" + pp1); logger.warning("pp2=" + pp2); } logger.warning("Unexpected situation here " + result); return new FGEEmptyArea(); } /** * Creates a new object of the same class and with the same contents as this object. * * @return a clone of this instance. * @exception OutOfMemoryError * if there is not enough memory. * @see java.lang.Cloneable * @since 1.2 */ @Override public FGEArc clone() { return (FGEArc) super.clone(); } @Override public FGEPoint getNearestPoint(FGEPoint aPoint) { if (getIsFilled() && containsPoint(aPoint)) { return aPoint.clone(); } return nearestOutlinePoint(aPoint); } @Override public FGEPoint nearestOutlinePoint(FGEPoint aPoint) { double angle = angleForPoint(aPoint); if (!includeAngle(angle)) { // System.out.println("Hop, "+aPoint+" est en dehors de "+this+", je choisis entre: "); // System.out.println("angle: "+Math.toDegrees(getRadianAngleStart())+" is: "+getPointAtRadianAngle(getRadianAngleStart())); // System.out.println("angle2: "+Math.toDegrees(getRadianAngleEnd())+" is: "+getPointAtRadianAngle(getRadianAngleEnd())); FGEPoint returned = FGEPoint.getNearestPoint(aPoint, getPointAtRadianAngle(getRadianAngleStart()), getPointAtRadianAngle(getRadianAngleEnd())); if (!containsPoint(returned)) { logger.warning("Returning point not beeing in area " + this); } return returned; // System.out.println("C'est: "+returned); // return returned; } FGEArea intersection = computeIntersection(new FGELine(aPoint, getCenter())); // System.out.println("aPoint="+aPoint); // System.out.println("Intersection="+intersection); // System.out.println("angle: "+Math.toDegrees(angle)+" is: "+getPointAtRadianAngle(angle)); Vector<FGEPoint> pts = new Vector<FGEPoint>(); if (intersection instanceof FGEPoint) { pts.add((FGEPoint) intersection); } else if (intersection instanceof FGESegment) { pts.add(((FGESegment) intersection).getP1()); pts.add(((FGESegment) intersection).getP2()); } else if (intersection instanceof FGEUnionArea) { for (FGEArea a : ((FGEUnionArea) intersection).getObjects()) { if (a instanceof FGEPoint) { pts.add((FGEPoint) a); } } } if (pts.size() > 0) { FGEPoint returned = null; double minimalDistanceSq = java.lang.Double.POSITIVE_INFINITY; for (FGEPoint p : pts) { double currentDistanceSq = FGEPoint.distanceSq(p, aPoint); if (currentDistanceSq < minimalDistanceSq) { returned = p.clone(); minimalDistanceSq = currentDistanceSq; } } return returned; } if (getFGEArcType() == ArcType.OPEN || getFGEArcType() == ArcType.CHORD) { if (FGEPoint.distanceSq(aPoint, getStartPoint()) < FGEPoint.distanceSq(aPoint, getEndPoint())) { return getStartPoint(); } else { return getEndPoint(); } } // NOTE: CHORD not well handled here: also handle segment [getStartPoint();getEndPoint()] else { // PIE FGESegment s1 = new FGESegment(getCenter(), getStartPoint()); FGESegment s2 = new FGESegment(getCenter(), getEndPoint()); FGEPoint pp1 = s1.getNearestPointOnSegment(aPoint); FGEPoint pp2 = s2.getNearestPointOnSegment(aPoint); if (FGEPoint.distanceSq(aPoint, pp1) < FGEPoint.distanceSq(aPoint, pp2)) { return pp1; } else { return pp2; } } } /** * Return nearest point from point "from" following supplied orientation * * Returns null if no intersection was found * * @param from * point from which we are coming to area * @param orientation * orientation we are coming from * @return */ @Override public FGEPoint nearestPointFrom(FGEPoint from, SimplifiedCardinalDirection orientation) { // logger.info("Request for nearestPointFrom: "+from+" orientation="+orientation+" for arc "+this); FGEHalfLine hl = FGEHalfLine.makeHalfLine(from, orientation); FGEArea intersect = intersect(hl); // logger.info("hl="+hl); // logger.info("intersect="+intersect); if (intersect instanceof FGEEmptyArea) { return null; } if (intersect instanceof FGEPoint) { return (FGEPoint) intersect; } if (intersect instanceof FGESegment) { FGEPoint p1, p2; p1 = ((FGESegment) intersect).getP1(); p2 = ((FGESegment) intersect).getP2(); if (FGEPoint.distanceSq(from, p1) < FGEPoint.distanceSq(from, p2)) { return p1; } else { return p2; } } if (intersect instanceof FGEUnionArea) { FGEPoint returned = null; double minimalDistanceSq = java.lang.Double.POSITIVE_INFINITY; for (FGEArea o : ((FGEUnionArea) intersect).getObjects()) { if (o instanceof FGEPoint) { double distSq = FGEPoint.distanceSq(from, (FGEPoint) o); if (distSq < minimalDistanceSq) { returned = (FGEPoint) o; minimalDistanceSq = distSq; } } } return returned; } return null; } @Override public FGEArea exclusiveOr(FGEArea area) { return new FGEExclusiveOrArea(this, area); } @Override public FGEArea intersect(FGEArea area) { if (area.containsArea(this)) { return this.clone(); } if (containsArea(area)) { return area.clone(); } if (area instanceof FGEAbstractLine) { return computeIntersection((FGEAbstractLine) area); } if (area instanceof FGEArc && ((FGEArc) area).overlap(this)) { return computeArcIntersection((FGEArc) area); } if (area instanceof FGEBand || area instanceof FGEHalfBand) { // TODO please really implement this FGERectangle boundingBox = getBoundingBox(); boundingBox.setIsFilled(true); FGEArea boundsIntersect = boundingBox.intersect(area); if (boundsIntersect instanceof FGEEmptyArea) { return new FGEEmptyArea(); } FGEArea returned = intersect(boundsIntersect); if (returned instanceof FGEIntersectionArea) { // Cannot do better, sorry if (logger.isLoggable(Level.FINE)) { logger.fine("Unable to compute intersection between " + this + " and " + area); } } return returned; } if (area instanceof FGEHalfPlane) { FGEHalfPlane hp = (FGEHalfPlane) area; FGELine line = hp.line; FGEArea intersect = intersect(line); FGEPoint p1 = null; FGEPoint p2 = null; if (intersect instanceof FGEUnionArea && ((FGEUnionArea) intersect).isUnionOfPoints() && ((FGEUnionArea) intersect).getObjects().size() > 1) { p1 = (FGEPoint) ((FGEUnionArea) intersect).getObjects().get(0); p2 = (FGEPoint) ((FGEUnionArea) intersect).getObjects().get(1); } else if (intersect instanceof FGESegment) { p1 = ((FGESegment) intersect).getP1(); p2 = ((FGESegment) intersect).getP2(); } else if (intersect instanceof FGEPoint) { return intersect; } if (p1 != null && p2 != null) { ArcType type = getFGEArcType(); if (type == ArcType.PIE) { type = line.contains(getCenter()) ? ArcType.PIE : ArcType.CHORD; } double startAngle = Math.toDegrees(angleForPoint(p1)); double endAngle = Math.toDegrees(angleForPoint(p2)); FGEArc arc1 = new FGEArc(x, y, width, height, startAngle, endAngle - startAngle + (endAngle >= startAngle ? 0 : 360), type); FGEArc arc2 = new FGEArc(x, y, width, height, endAngle, startAngle - endAngle + (startAngle >= endAngle ? 0 : 360), type); if (hp.containsPoint(arc1.getMiddle())) { return arc1; } else { return arc2; } } } FGEIntersectionArea returned = new FGEIntersectionArea(this, area); if (returned.isDevelopable()) { return returned.makeDevelopped(); } else { return returned; } } @Override public FGEArea substract(FGEArea area, boolean isStrict) { return new FGESubstractionArea(this, area, isStrict); } @Override public FGEArea union(FGEArea area) { if (containsArea(area)) { return clone(); } if (area.containsArea(this)) { return area.clone(); } if (area instanceof FGEArc && ((FGEArc) area).overlap(this)) { return computeArcUnion((FGEArc) area); } return new FGEUnionArea(this, area); } @Override public boolean containsPoint(FGEPoint p) { if (!getFGEBounds().containsPoint(p)) { return false; } double radianAngle = angleForPoint(p); if (!includeAngle(radianAngle)) { return false; } FGEPoint outlinePoint = getPointAtRadianAngle(radianAngle); if (getFGEArcType() == ArcType.OPEN) { return outlinePoint.equals(p); } else if (new FGESegment(getCenter(), outlinePoint).containsPoint(p)) { if (getFGEArcType() == ArcType.PIE) { return true; } else if (getFGEArcType() == ArcType.CHORD) { return !new FGEPolygon(Filling.FILLED, getCenter(), getStartPoint(), getEndPoint()).contains(p); } } return false; // FGEArea intersection = computeIntersection(new FGELine(getCenter(),p)); // System.out.println("p= "+p+" intersection="+intersection+" intersection.containsPoint(p)="+intersection.containsPoint(p)); // return (intersection.containsPoint(p)); } @Override public boolean containsLine(FGEAbstractLine l) { return containsPoint(l.getP1()) && containsPoint(l.getP2()) && l instanceof FGESegment; } @Override public boolean containsArea(FGEArea a) { if (a instanceof FGEArc && ((FGEArc) a).overlap(this)) { FGEArea arc1 = _equivalentForArcIn1D(this, false); FGEArea arc2 = _equivalentForArcIn1D((FGEArc) a, false); if (arc1.containsArea(arc2)) { return true; } arc1 = _equivalentForArcIn1D(this, true); arc2 = _equivalentForArcIn1D((FGEArc) a, true); if (arc1.containsArea(arc2)) { return true; } return false; } if (a instanceof FGEPoint) { return containsPoint((FGEPoint) a); } if (a instanceof FGESegment) { return containsPoint(((FGESegment) a).getP1()) && containsPoint(((FGESegment) a).getP2()); } if (a instanceof FGEShape) { return FGEShape.AreaComputation.isShapeContainedInArea((FGEShape<?>) a, this); } return false; } @Override public FGEArc transform(AffineTransform t) { // logger.info("Not well implemented, do it later"); // TODO: this implementation is not correct if AffineTransform contains rotation FGEPoint p1 = new FGEPoint(getX(), getY()).transform(t); FGEPoint p2 = new FGEPoint(getX() + getWidth(), getY() + getHeight()).transform(t); // TODO: if transformation contains a rotation, change angle start, with and heigth are not correct either return new FGEArc(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y), Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y), getAngleStart(), getAngleExtent(), getFGEArcType()); } @Override public List<FGEPoint> getControlPoints() { Vector<FGEPoint> returned = new Vector<FGEPoint>(); returned.add(getCenter()); returned.add(getStartPoint()); returned.add(getEndPoint()); /*returned.add(new FGEPoint(x+width/2.0,y)); returned.add(new FGEPoint(x,y+height/2.0)); returned.add(new FGEPoint(x+width/2.0,y+height)); returned.add(new FGEPoint(x+width,y+height/2.0));*/ return returned; } @Override public void paint(FGEGraphics g) { if (getFGEArcType() == ArcType.CHORD || getFGEArcType() == ArcType.PIE) { g.useDefaultBackgroundStyle(); g.fillArc(getX(), getY(), getWidth(), getHeight(), getAngleStart(), getAngleExtent(), getFGEArcType() == ArcType.CHORD); } g.useDefaultForegroundStyle(); Stroke defaultForegroundStroke = null; if (g.getGraphicalRepresentation().getSpecificStroke() != null) { defaultForegroundStroke = g.getGraphics().getStroke(); g.getGraphics().setStroke(g.getGraphicalRepresentation().getSpecificStroke()); } g.drawArc(getX(), getY(), getWidth(), getHeight(), getAngleStart(), getAngleExtent()); if (defaultForegroundStroke != null) { g.getGraphics().setStroke(defaultForegroundStroke); } } @Override public String toString() { return "FGEArc: (" + x + "," + y + "," + width + "," + height + "," + getAngleStart() + "," + getAngleExtent() + " type=" + getFGEArcType() + ")"; } @Override public String getStringRepresentation() { return toString(); } /*public FGERectangle getBoundingBox() { return new FGERectangle(x,y,width,height,Filling.FILLED); }*/ @Override public FGERectangle getBoundingBox() { double minX = java.lang.Double.POSITIVE_INFINITY; double minY = java.lang.Double.POSITIVE_INFINITY; double maxX = java.lang.Double.NEGATIVE_INFINITY; double maxY = java.lang.Double.NEGATIVE_INFINITY; Vector<java.lang.Double> angles = new Vector<java.lang.Double>(); if (includeAngle(0)) { angles.add(0.0); } if (includeAngle(Math.PI / 2)) { angles.add(Math.PI / 2); } if (includeAngle(Math.PI)) { angles.add(Math.PI); } if (includeAngle(3 * Math.PI / 2)) { angles.add(3 * Math.PI / 2); } angles.add(getRadianAngleStart()); angles.add(getRadianAngleStart() + getRadianAngleExtent()); for (java.lang.Double angle : angles) { FGEPoint p = getPointAtRadianAngle(angle); if (p.x < minX) { minX = p.x; } if (p.x > maxX) { maxX = p.x; } if (p.y < minY) { minY = p.y; } if (p.y > maxY) { maxY = p.y; } } return new FGERectangle(new FGEPoint(minX, minY), new FGEPoint(maxX, maxY), Filling.FILLED); } @Override public boolean equals(Object obj) { if (obj instanceof FGEArc) { FGEArc p = (FGEArc) obj; if (getFGEArcType() != p.getFGEArcType()) { return false; } return Math.abs(getX() - p.getX()) <= EPSILON && Math.abs(getY() - p.getY()) <= EPSILON && Math.abs(getWidth() - p.getWidth()) <= EPSILON && Math.abs(getHeight() - p.getHeight()) <= EPSILON && Math.abs(normalizeDegAngle(getAngleStart()) - normalizeDegAngle(p.getAngleStart())) <= EPSILON && Math.abs(getAngleExtent() - p.getAngleExtent()) <= EPSILON; } return super.equals(obj); } public boolean overlap(FGEArc arc) { if (getFGEArcType() != arc.getFGEArcType()) { return false; } return Math.abs(getX() - arc.getX()) <= EPSILON && Math.abs(getY() - arc.getY()) <= EPSILON && Math.abs(getWidth() - arc.getWidth()) <= EPSILON && Math.abs(getHeight() - arc.getHeight()) <= EPSILON; } @Override public final FGEArea getOrthogonalPerspectiveArea(SimplifiedCardinalDirection orientation) { if (orientation == null) { return new FGEEmptyArea(); } FGERectangle boundingBox; FGEArea anchorArea = getAnchorAreaFrom(orientation); if (anchorArea instanceof FGEArc) { boundingBox = ((FGEArc) anchorArea).getBoundingBox(); } else { boundingBox = getBoundingBox(); } // System.out.println("Orientation: "+orientation+" Bounding box for "+anchorArea+" is "+boundingBox); FGEArea hb = null; switch (orientation) { case EAST: hb = boundingBox.getWest().getOrthogonalPerspectiveArea(orientation); break; case WEST: hb = boundingBox.getEast().getOrthogonalPerspectiveArea(orientation); break; case NORTH: hb = boundingBox.getSouth().getOrthogonalPerspectiveArea(orientation); break; case SOUTH: hb = boundingBox.getNorth().getOrthogonalPerspectiveArea(orientation); break; default: return new FGEEmptyArea(); } FGEArc filledArc = clone(); filledArc.setIsFilled(true); return new FGESubstractionArea(hb, filledArc, false); } @Override public final FGEArea getAnchorAreaFrom(SimplifiedCardinalDirection orientation) { FGEArc returned; switch (orientation) { case EAST: returned = new FGEArc(x, y, width, height, -90, 180, ArcType.OPEN); break; case WEST: returned = new FGEArc(x, y, width, height, 90, 180, ArcType.OPEN); break; case NORTH: returned = new FGEArc(x, y, width, height, 0, 180, ArcType.OPEN); break; case SOUTH: returned = new FGEArc(x, y, width, height, -180, 180, ArcType.OPEN); break; default: return null; } return computeArcIntersection(returned); } /** * Compute angle extent given start and end angle * * @param startAngle * , expressed in radians * @param endAngle * , expressed in radians * @return extent in radians */ public static double angleExtent(double startAngle, double endAngle) { double start = normalizeRadianAngle(startAngle); double end = normalizeRadianAngle(endAngle); if (start <= end) { return end - start; } else { return end + 2 * Math.PI - start; } } /** * Compute an equivalent for an arc in the 1-D model Angles are normalized and a segment (or a union of 2 segments mapping 2.PI-modulo * is retrieved ) * * @param anArc * @return */ private static FGEArea _equivalentForArcIn1D(FGEArc anArc, boolean forIntersection) { double startAngle; double endAngle; if (anArc instanceof FGEEllips || anArc.getRadianAngleExtent() >= 2 * Math.PI || anArc.getRadianAngleExtent() <= -2 * Math.PI) { return new FGEUnionArea(new FGESegment(new FGEPoint(-Math.PI, 0), new FGEPoint(Math.PI, 0)), new FGESegment(new FGEPoint( Math.PI, 0), new FGEPoint(3 * Math.PI, 0))); } if (anArc.getAngleExtent() >= 0) { startAngle = anArc.getRadianAngleStart(); endAngle = normalizeRadianAngle(anArc.getRadianAngleStart() + anArc.getRadianAngleExtent()); } else { endAngle = anArc.getRadianAngleStart(); startAngle = normalizeRadianAngle(anArc.getRadianAngleStart() + anArc.getRadianAngleExtent()); } if (endAngle < startAngle) { endAngle += 2 * Math.PI; } FGESegment segment = new FGESegment(new FGEPoint(startAngle, 0), new FGEPoint(endAngle, 0)); if (anArc.includeAngle(Math.PI)) { if (forIntersection) { return new FGEUnionArea(segment, new FGESegment(new FGEPoint(startAngle + 2 * Math.PI, 0), new FGEPoint(endAngle + 2 * Math.PI, 0))); } } return segment; } /** * Compute an equivalent for an area in the 1-D model to arc model * * @param anArc * @return */ private FGEArea _equivalentToArcIn1D(FGEArea anArea) { if (anArea instanceof FGEEmptyArea) { return new FGEEmptyArea(); } else if (anArea instanceof FGESegment) { FGESegment s = (FGESegment) anArea; if (s.getP2().x >= s.getP1().x + 2 * Math.PI || s.getP2().x <= s.getP1().x - 2 * Math.PI) { return new FGEEllips(x, y, width, height, getIsFilled() ? Filling.FILLED : Filling.NOT_FILLED); } return new FGEArc(x, y, width, height, Math.toDegrees(normalizeRadianAngle(s.getP1().x)), Math.toDegrees(s.getP2().x - s.getP1().x), getFGEArcType()); } else if (anArea instanceof FGEPoint) { FGEPoint p = (FGEPoint) anArea; if (getIsFilled()) { return new FGESegment(getCenter(), getPointAtRadianAngle(p.x)); } else { return getPointAtRadianAngle(p.x); } } else if (anArea instanceof FGEUnionArea) { FGEUnionArea u = (FGEUnionArea) anArea; // if (u.isUnionOfSegments() || u.isUnionOfPoints()) { Vector<FGEArea> returned = new Vector<FGEArea>(); for (FGEArea a : u.getObjects()) { FGEArea newArea = _equivalentToArcIn1D(a); if (!returned.contains(newArea)) { returned.add(newArea); } } if (returned.size() == 1) { return returned.firstElement(); } if (returned.size() == 2 && returned.firstElement() instanceof FGEArc && returned.elementAt(1) instanceof FGEArc) { // There is still a case where there can be 2 adjascent arcs // At point corresponding to -180 degree FGEArc arc1 = (FGEArc) returned.firstElement(); FGEArc arc2 = (FGEArc) returned.elementAt(1); boolean arc1StartFromPI = FGEUtils.doubleEquals(arc1.getRadianAngleStart(), Math.PI) || FGEUtils.doubleEquals(arc1.getRadianAngleStart(), -Math.PI); boolean arc1EndToPI = FGEUtils.doubleEquals(arc1.getRadianAngleEnd(), Math.PI) || FGEUtils.doubleEquals(arc1.getRadianAngleEnd(), -Math.PI); boolean arc2StartFromPI = FGEUtils.doubleEquals(arc2.getRadianAngleStart(), Math.PI) || FGEUtils.doubleEquals(arc2.getRadianAngleStart(), -Math.PI); boolean arc2EndToPI = FGEUtils.doubleEquals(arc2.getRadianAngleEnd(), Math.PI) || FGEUtils.doubleEquals(arc2.getRadianAngleEnd(), -Math.PI); if (arc1EndToPI && arc2StartFromPI) { return _equivalentToArcIn1D(new FGESegment(new FGEPoint(arc1.getRadianAngleStart(), 0), new FGEPoint( arc2.getRadianAngleEnd() + 2 * Math.PI, 0))); } if (arc2EndToPI && arc1StartFromPI) { return _equivalentToArcIn1D(new FGESegment(new FGEPoint(arc2.getRadianAngleStart(), 0), new FGEPoint( arc1.getRadianAngleEnd() + 2 * Math.PI, 0))); } } FGEUnionArea union = new FGEUnionArea(returned); if (returned.size() == 2 && returned.get(0) instanceof FGEArc && returned.get(1) instanceof FGEPoint) { new Exception("------------ Ca vient de la ce truc bizarre !!!").printStackTrace(); logger.info("Area=" + anArea); } if (union.getObjects().size() == 1) { return union.getObjects().firstElement(); } return union; // } } logger.warning("Unexpected result: " + anArea); return new FGEEmptyArea(); } /** * Compute arc union, asserting that this arc and supplied arc differs only because of their start angles and/or angle extent * * @param anArc * @return */ protected FGEArea computeArcUnion(FGEArc anArc) { if (anArc instanceof FGEEllips || anArc.getRadianAngleExtent() >= 2 * Math.PI || anArc.getRadianAngleExtent() <= -2 * Math.PI || this instanceof FGEEllips || getRadianAngleExtent() >= 2 * Math.PI || getRadianAngleExtent() <= -2 * Math.PI) { // Trivial case return new FGEEllips(x, y, width, height, getIsFilled() ? Filling.FILLED : Filling.NOT_FILLED); } FGEArea equ1 = _equivalentForArcIn1D(this, false); FGEArea equ2 = _equivalentForArcIn1D(anArc, false); FGEArea union = equ1.union(equ2); /*System.out.println("arc1="+this); System.out.println("arc2="+anArc); System.out.println("equ1="+equ1); System.out.println("equ2="+equ2); System.out.println("union="+union);*/ return _equivalentToArcIn1D(union); } /** * Compute arc intersection, asserting that this arc and supplied arc differs only because of their start angles and/or angle extent * * @param anArc * @return */ protected FGEArea computeArcIntersection(FGEArc anArc) { if (anArc instanceof FGEEllips || anArc.getRadianAngleExtent() >= 2 * Math.PI || anArc.getRadianAngleExtent() <= -2 * Math.PI) { // Trivial case return clone(); } if (this instanceof FGEEllips || getRadianAngleExtent() >= 2 * Math.PI || getRadianAngleExtent() <= -2 * Math.PI) { // Trivial case return anArc.clone(); } FGEArea equ1 = _equivalentForArcIn1D(this, true); FGEArea equ2 = _equivalentForArcIn1D(anArc, true); FGEArea intersection = equ1.intersect(equ2); /*System.out.println("arc1="+this); System.out.println("arc2="+anArc); System.out.println("equ1="+equ1); System.out.println("arc2="+equ2); System.out.println("intersection="+intersection);*/ return _equivalentToArcIn1D(intersection); } // Old (buggy implementation) /*protected FGEArea computeArcIntersection2(FGEArc anArc) { double startAngle1 = getRadianAngleStart(); double endAngle1 = normalizeRadianAngle(getRadianAngleStart()+getRadianAngleExtent()); double startAngle2 = anArc.getRadianAngleStart(); double endAngle2 = normalizeRadianAngle(anArc.getRadianAngleStart()+anArc.getRadianAngleExtent()); double startAngle; double endAngle; if (anArc.includeAngle(startAngle1)) { if (anArc.includeAngle(endAngle1)) { System.out.println("anArc="+anArc+" include angle "+Math.toDegrees(endAngle1)); double midAngle1 = (startAngle1+endAngle1)/2; //if (anArc.includeAngle(midAngle1)) { if (getRadianAngleExtent() < anArc.getRadianAngleExtent()) { System.out.println("Hop1"); System.out.println("anArc="+anArc); System.out.println("this="+this); System.out.println("getRadianAngleExtent()="+getRadianAngleExtent()); System.out.println("anArc.getRadianAngleExtent()="+anArc.getRadianAngleExtent()); return this.clone(); } else { double a1 = Math.toDegrees(startAngle1); double e1 = Math.toDegrees(angleExtent(startAngle1,endAngle2)); double a2 = Math.toDegrees(startAngle2); double e2 = Math.toDegrees(angleExtent(startAngle2,endAngle1)); FGEArc arc1 = new FGEArc(x,y,width,height,a1,e1,getFGEArcType()); FGEArc arc2 = new FGEArc(x,y,width,height,a2,e2,getFGEArcType()); System.out.println("Hop2"); return new FGEUnionArea(arc1,arc2); } } else { startAngle = startAngle1; endAngle = endAngle2; System.out.println("Hop3"); } } else { if (anArc.includeAngle(endAngle1)) { startAngle = startAngle2; endAngle = endAngle1; System.out.println("Hop4"); } else { double midAngle2 = (startAngle2+endAngle2)/2; if (includeAngle(midAngle2)) { System.out.println("Hop5"); return anArc.clone(); } else { System.out.println("Hop6"); return new FGEEmptyArea(); } } } double angleExtent = Math.toDegrees(angleExtent(startAngle,endAngle)); startAngle = Math.toDegrees(startAngle); return new FGEArc(x,y,width,height,startAngle,angleExtent,getFGEArcType()); }*/ public FGEPoint getPointAtAngle(double degAngle) { return getPointAtRadianAngle(Math.toRadians(degAngle)); } public FGEPoint getPointAtRadianAngle(double radianAngle) { return new FGEPoint(getCenter().x + getWidth() / 2 * Math.cos(radianAngle), getCenter().y - getHeight() / 2 * Math.sin(radianAngle)); } /** * * @param p * @return angle in radians */ public double angleForPoint(FGEPoint p) { FGEHalfLine hl = new FGEHalfLine(getCenter(), p); LineIntersectionResult result = _computeIntersection(hl); if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.NO_SOLUTION) { logger.warning("Unexpected situation here"); return java.lang.Double.NaN; } else if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.ONE_SOLUTION) { return result.alpha1; } else if (result.solutionType == org.openflexo.fge.geom.FGEArc.LineIntersectionResult.SolutionType.TWO_SOLUTIONS) { FGEPoint p1 = new FGEPoint(width / 2.0 * Math.cos(-result.alpha1) + x + width / 2.0, height / 2.0 * Math.sin(-result.alpha1) + y + height / 2.0); FGEPoint p2 = new FGEPoint(width / 2.0 * Math.cos(-result.alpha2) + x + width / 2.0, height / 2.0 * Math.sin(-result.alpha2) + y + height / 2.0); if (hl.containsPoint(p1)) { return result.alpha1; } else { /*if (hl.containsPoint(p2))*/return result.alpha2; } } logger.warning("Unexpected situation while retrieving angle for point " + p + " on " + this); logger.warning("result=" + result); return java.lang.Double.NaN; } public boolean isClockWise() { return extent < 0; } /** * This area is finite, so always return true */ @Override public final boolean isFinite() { return true; } /** * This area is finite, so always return null */ @Override public final FGERectangle getEmbeddingBounds() { // TODO: sub-optimal implementation, you can do better job return new FGERectangle(x, y, width, height, Filling.FILLED); } public FGEPoint getMiddle() { if (this instanceof FGEEllips) { logger.warning("getMiddle() invoked for an ellips"); return getCenter(); } else { double angle = getAngleStart() + getAngleExtent() / 2; return getPointAtAngle(angle); } } }