/*
* (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.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.util.List;
import java.util.Vector;
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 abstract class FGEAbstractLine<L extends FGEAbstractLine> extends Line2D.Double implements FGEGeometricObject<L> {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(FGEAbstractLine.class.getPackage().getName());
// Equation is a*x + b*y + c = 0
private double a;
private double b;
private double c;
private FGEPoint p1;
private FGEPoint p2;
@SuppressWarnings("unchecked")
@Override
public FGEAbstractLine<L> clone() {
return (FGEAbstractLine<L>) super.clone();
}
public FGEAbstractLine(double X1, double Y1, double X2, double Y2) {
super(X1, Y1, X2, Y2);
}
public FGEAbstractLine(FGEPoint p1, FGEPoint p2) {
super(p1, p2);
}
public FGEAbstractLine(FGEAbstractLine line) {
super(line.getP1(), line.getP2());
}
public FGEAbstractLine() {
super();
}
public FGEAbstractLine(double pA, double pB, double pC) {
super();
if (pB != 0) {
FGEPoint p1 = new FGEPoint(0, -pC / pB);
FGEPoint p2 = new FGEPoint(1, -(pA + pC) / pB);
setLine(p1, p2);
} else {
FGEPoint p1 = new FGEPoint(-pC / pA, 0);
FGEPoint p2 = new FGEPoint(-pC / pA, 1);
setLine(p1, p2);
}
}
@Override
public List<FGEPoint> getControlPoints() {
Vector<FGEPoint> returned = new Vector<FGEPoint>();
returned.add(getP1());
returned.add(getP2());
return returned;
}
@Override
public FGEPoint getP1() {
return p1;
/*
* Point2D returned = super.getP1(); return new FGEPoint(returned.getX(),returned.getY());
*/
}
public void setP1(FGEPoint p1) {
setX1(p1.x);
setY1(p1.y);
}
@Override
public FGEPoint getP2() {
return p2;
/*
* Point2D returned = super.getP2(); return new FGEPoint(returned.getX(),returned.getY());
*/
}
public void setP2(FGEPoint p2) {
setX2(p2.x);
setY2(p2.y);
}
@Override
public final void setLine(double X1, double Y1, double X2, double Y2) {
super.setLine(X1, Y1, X2, Y2);
updateCoeffs();
// System.out.println("hop: "+(a*X1+b*Y1+c)+" et "+(a*X2+b*Y2+c));
}
public void setX1(double x1) {
this.x1 = x1;
updateCoeffs();
}
public void setX2(double x2) {
this.x2 = x2;
updateCoeffs();
}
public void setY1(double y1) {
this.y1 = y1;
updateCoeffs();
}
public void setY2(double y2) {
this.y2 = y2;
updateCoeffs();
}
public double getA() {
return a;
}
public double getB() {
return b;
}
public double getC() {
return c;
}
private void updateCoeffs() {
if (FGEUtils.doubleEquals(x1, x2) && FGEUtils.doubleEquals(y1, y2)) {
// if (x1==x2 && y1==y2) {
a = 0;
b = 0;
c = 0;
}
if (!FGEUtils.doubleEquals(x1, x2)) {
// if (x1 != x2) {
b = 1;
a = (y2 - y1) / (x1 - x2);
c = -(a * x1 + y1);
} else {
b = 0;
a = 1;
c = -x1;
}
p1 = new FGEPoint(x1, y1);
p2 = new FGEPoint(x2, y2);
}
public final boolean overlap(FGEAbstractLine anOtherLine) {
return isParallelTo(anOtherLine) && anOtherLine._containsPointIgnoreBounds(getP1());
}
public static FGEPoint getOppositePoint(FGEPoint p, FGEPoint pivot) {
return FGEPoint.getOppositePoint(p, pivot);
}
public static FGEPoint getMiddlePoint(FGEPoint p1, FGEPoint p2) {
return FGEPoint.getMiddlePoint(p1, p2);
}
/**
* Return a new line orthogonal to this instance and crossing supplied point
*
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public FGELine getOrthogonalLine(FGEPoint p) {
double a1 = -b;
double b1 = a;
double c1 = -(a1 * p.x + b1 * p.y);
return new FGELine(a1, b1, c1);
}
/**
* Return a new line orthogonal to reference line and crossing supplied point
*
* @param reference
* reference line
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public static FGELine getOrthogonalLine(FGEAbstractLine reference, FGEPoint p) {
return reference.getOrthogonalLine(p);
}
/**
* Return a new line parallel to this instance and crossing supplied point
*
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public FGELine getParallelLine(FGEPoint p) {
double a1 = a;
double b1 = b;
double c1 = -(a1 * p.x + b1 * p.y);
return new FGELine(a1, b1, c1);
}
/**
* Return a new line parallel to reference line and crossing supplied point
*
* @param reference
* reference line
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public static FGELine getParallelLine(FGEAbstractLine reference, FGEPoint p) {
return reference.getParallelLine(p);
}
/**
* Return a new line obtained by rotation of supplied angle (given in degree) and crossing supplied point
*
* @param angle
* (in degree)
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public FGELine getRotatedLine(double angle, FGEPoint p) {
FGEAbstractLine l = transform(AffineTransform.getRotateInstance(Math.toRadians(angle)));
return l.getParallelLine(p);
}
/**
* Return a new line obtained by rotation of supplied angle (given in degree) and crossing supplied point
*
* @param reference
* reference line
* @param angle
* (in degree)
* @param p
* point the returned line must cross
* @return a new FGELine instance
*/
public static FGELine getRotatedLine(FGEAbstractLine reference, double angle, FGEPoint p) {
return reference.getRotatedLine(angle, p);
}
public FGEPoint getLineIntersection(FGEAbstractLine aLine) throws ParallelLinesException {
double a1 = aLine.a;
double b1 = aLine.b;
double c1 = aLine.c;
double det = a1 * b - a * b1;
if (Math.abs(det) < EPSILON) { // parallel lines
throw new ParallelLinesException();
} else {
return new FGEPoint(-(b * c1 - b1 * c) / det, (a * c1 - a1 * c) / det);
}
}
public boolean isParallelTo(FGEAbstractLine<?> aLine) {
double a1 = aLine.a;
double b1 = aLine.b;
double det = a1 * b - a * b1;
if (Math.abs(det) < EPSILON) { // parallel lines
return true;
} else {
return false;
}
}
public boolean isOrthogonalTo(FGEAbstractLine<?> aLine) {
double a1 = aLine.a;
double b1 = aLine.b;
if (Math.abs(a * a1 - b * b1) < EPSILON) { // orthogonal lines
return true;
} else {
return false;
}
}
public static FGEPoint getLineIntersection(FGEAbstractLine line1, FGEAbstractLine line2) throws ParallelLinesException {
return line1.getLineIntersection(line2);
}
public abstract boolean contains(FGEPoint p);
protected final boolean _containsPointIgnoreBounds(FGEPoint p) {
return Math.abs(a * p.x + b * p.y + c) < EPSILON;
}
public int getPlaneLocation(FGEPoint p) {
double k = a * p.x + b * p.y + c;
if (Math.abs(k) < EPSILON) {
return 0; // We are on line
}
if (k > 0) {
return 1; // We are on one side
} else {
return -1; // We are on the other side
}
}
public FGEPoint getProjection(FGEPoint p) {
if (contains(p)) {
return new FGEPoint(p);
}
try {
return getOrthogonalLine(p).getLineIntersection(this);
} catch (ParallelLinesException e) {
// cannot happen
e.printStackTrace();
return null;
}
}
public static FGEPoint getProjection(FGEPoint p, FGEAbstractLine line) {
return line.getProjection(p);
}
public FGEPoint pointAtAbciss(double x) {
if (b != 0) {
return new FGEPoint(x, -(a * x + c) / b);
}
throw new IllegalArgumentException("Line does not pass through this abciss");
}
public FGEPoint pointAtOrdinate(double y) {
if (a != 0) {
return new FGEPoint(-(b * y + c) / a, y);
}
throw new IllegalArgumentException("Line does not pass through this ordinate");
}
@Override
public abstract FGEPoint getNearestPoint(FGEPoint p);
public boolean isHorizontal() {
return Math.abs(getY1() - getY2()) < EPSILON;
}
public boolean isVertical() {
return Math.abs(getX1() - getX2()) < EPSILON;
}
/**
* Compute and return angle formed by the abstract line (NOTE: assert that coordinate system is orthonormal !!!!!)
*
* @return angle in radians, in range -PI/2 - 3*PI/2
*/
public double getAngle() {
return FGEUtils.getSlope(getP1(), getP2());
}
public static FGEAbstractLine getNorthernLine(FGEAbstractLine source, FGEAbstractLine destination) {
if (source.isParallelTo(destination)) {
FGELine orthogonalLine = source.getOrthogonalLine(source.getP1());
FGEPoint p1 = orthogonalLine.getLineIntersection(source);
FGEPoint p2 = orthogonalLine.getLineIntersection(destination);
return p1.y <= p2.y ? source : destination;
} else {
throw new IllegalArgumentException("lines are not parallel");
}
}
public static FGEAbstractLine getSouthernLine(FGEAbstractLine source, FGEAbstractLine destination) {
if (source.isParallelTo(destination)) {
FGELine orthogonalLine = source.getOrthogonalLine(source.getP1());
FGEPoint p1 = orthogonalLine.getLineIntersection(source);
FGEPoint p2 = orthogonalLine.getLineIntersection(destination);
return p1.y >= p2.y ? source : destination;
} else {
throw new IllegalArgumentException("lines are not parallel");
}
}
public static FGEAbstractLine getEasternLine(FGEAbstractLine source, FGEAbstractLine destination) {
if (source.isParallelTo(destination)) {
FGELine orthogonalLine = source.getOrthogonalLine(source.getP1());
FGEPoint p1 = orthogonalLine.getLineIntersection(source);
FGEPoint p2 = orthogonalLine.getLineIntersection(destination);
return p1.x >= p2.x ? source : destination;
} else {
throw new IllegalArgumentException("lines are not parallel");
}
}
public static FGEAbstractLine getWesternLine(FGEAbstractLine source, FGEAbstractLine destination) {
if (source.isParallelTo(destination)) {
FGELine orthogonalLine = source.getOrthogonalLine(source.getP1());
FGEPoint p1 = orthogonalLine.getLineIntersection(source);
FGEPoint p2 = orthogonalLine.getLineIntersection(destination);
return p1.x <= p2.x ? source : destination;
} else {
throw new IllegalArgumentException("lines are not parallel");
}
}
protected abstract FGEArea computeLineIntersection(FGEAbstractLine line);
/*
* { logger.info("computeIntersection() between "+this+"\n and "+line+" overlap="+overlap(line)); if (overlap(line)) { if (this
* instanceof FGEHalfLine) { if (line instanceof FGEHalfLine) { return _compute_hl_hl_Intersection((FGEHalfLine)this,(FGEHalfLine)line);
* } else if (line instanceof FGESegment) { return _compute_hl_segment_Intersection((FGEHalfLine)this,(FGESegment)line); } else { return
* clone(); } } else if (this instanceof FGESegment) { if (line instanceof FGEHalfLine) { return
* _compute_hl_segment_Intersection((FGEHalfLine)line,(FGESegment)this); } else if (line instanceof FGESegment) { return
* _compute_segment_segment_Intersection((FGESegment)this,(FGESegment)line); } else { return clone(); } } else { return line.clone(); }
* } else if (isParallelTo(line)) { return new FGEEmptyArea(); } else { FGEPoint returned; try { returned = getLineIntersection(line);
* if (containsPoint(returned) && line.containsPoint(returned)) return returned; } catch (ParallelLinesException e) { // cannot happen }
* return new FGEEmptyArea(); } }
*/
@Override
public final FGEArea exclusiveOr(FGEArea area) {
return new FGEExclusiveOrArea(this, area);
}
@Override
public final FGEArea intersect(FGEArea area) {
if (area.containsArea(this)) {
return this.clone();
}
if (containsArea(area)) {
return area.clone();
}
if (area instanceof FGEPoint) {
FGEPoint p = (FGEPoint) area;
if (containsPoint(p)) {
return p.clone();
} else {
return new FGEEmptyArea();
}
}
if (area instanceof FGEAbstractLine) {
return computeLineIntersection((FGEAbstractLine) area);
}
if (area instanceof FGERectangle) {
return ((FGERectangle) area).intersect(this);
}
if (area instanceof FGERoundRectangle) {
return ((FGERoundRectangle) area).intersect(this);
}
if (area instanceof FGEHalfPlane) {
return ((FGEHalfPlane) area).intersect(this);
}
if (area instanceof FGEHalfBand) {
return ((FGEHalfBand) area).intersect(this);
}
if (area instanceof FGEBand) {
return ((FGEBand) area).intersect(this);
}
if (area instanceof FGEArc) {
return ((FGEArc) area).intersect(this);
}
if (area instanceof FGEPolygon) {
return ((FGEPolygon) area).intersect(this);
}
FGEIntersectionArea returned = new FGEIntersectionArea(this, area);
if (returned.isDevelopable()) {
return returned.makeDevelopped();
} else {
return returned;
}
}
@Override
public final 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();
}
return new FGEUnionArea(this, area);
}
@Override
public final boolean containsPoint(FGEPoint p) {
return contains(p);
}
@Override
public abstract boolean containsLine(FGEAbstractLine l);
@Override
public final boolean containsArea(FGEArea a) {
if (a instanceof FGEPoint) {
return containsPoint((FGEPoint) a);
}
if (a instanceof FGEAbstractLine) {
return containsLine((FGEAbstractLine) a);
}
return false;
}
@Override
public abstract boolean equals(Object obj);
@Override
public abstract FGEAbstractLine transform(AffineTransform t);
@Override
public abstract String toString();
@Override
public String getStringRepresentation() {
return toString();
}
@Override
public abstract void paint(FGEGraphics g);
public static FGEArea _compute_hl_segment_Intersection(FGEHalfLine hl, FGESegment s) {
if (hl.containsPoint(s.getP1())) {
if (hl.containsPoint(s.getP2())) {
return s.clone();
} else {
return new FGESegment(hl.getLimit(), s.getP1());
}
} else {
if (hl.containsPoint(s.getP2())) {
return new FGESegment(hl.getLimit(), s.getP2());
} else {
return new FGEEmptyArea();
}
}
}
/**
* 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) {
FGEHalfLine hl = FGEHalfLine.makeHalfLine(from, orientation);
FGEArea returned = hl.intersect(this);
if (hl.overlap(this)) {
if (returned instanceof FGEArea) {
return null;
} else {
return getNearestPoint(from);
}
}
if (returned instanceof FGEEmptyArea) {
return null;
} else if (returned instanceof FGEPoint) {
return (FGEPoint) returned;
} else {
logger.warning("Unexpected area: " + returned);
return null;
}
}
}