/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program 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.
*/
/*
* DrawConic.java
*
* Created on 16. Oktober 2001, 15:13
*/
package org.geogebra.common.euclidian.draw;
import java.util.ArrayList;
import org.geogebra.common.awt.GAffineTransform;
import org.geogebra.common.awt.GArc2D;
import org.geogebra.common.awt.GArea;
import org.geogebra.common.awt.GEllipse2DDouble;
import org.geogebra.common.awt.GGeneralPath;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GPoint2D;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.awt.GRectangularShape;
import org.geogebra.common.awt.GShape;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianBoundingBoxHandler;
import org.geogebra.common.euclidian.EuclidianConstants;
import org.geogebra.common.euclidian.EuclidianStatic;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.GeneralPathClipped;
import org.geogebra.common.euclidian.Previewable;
import org.geogebra.common.euclidian.clipping.ClipShape;
import org.geogebra.common.euclidian.event.AbstractEvent;
import org.geogebra.common.factories.AwtFactory;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.Matrix.CoordMatrix;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoCirclePointRadius;
import org.geogebra.common.kernel.algos.AlgoCircleThreePoints;
import org.geogebra.common.kernel.algos.AlgoCircleTwoPoints;
import org.geogebra.common.kernel.algos.AlgoConicFivePoints;
import org.geogebra.common.kernel.algos.AlgoEllipseHyperbolaFociPoint;
import org.geogebra.common.kernel.algos.AlgoParabolaPointLine;
import org.geogebra.common.kernel.arithmetic.Equation;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.FunctionVariable;
import org.geogebra.common.kernel.arithmetic.ValidExpression;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElement.HitType;
import org.geogebra.common.kernel.geos.GeoLine;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoVec2D;
import org.geogebra.common.kernel.kernelND.GeoConicND;
import org.geogebra.common.kernel.kernelND.GeoConicNDConstants;
import org.geogebra.common.kernel.kernelND.GeoLineND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.kernel.kernelND.GeoSegmentND;
import org.geogebra.common.main.MyError;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.Log;
/**
*
* @author Markus
*/
public class DrawConic extends Drawable implements Previewable {
/** plotpoints per quadrant for hyperbola */
protected static final int PLOT_POINTS = 32;
/** maximum number of plot points */
public static final int MAX_PLOT_POINTS = 300;
/**
* maximum of pixels for a standard circle radius bigger circles are drawn
* via Arc2D
*/
public static final double HUGE_RADIUS = 1E12;
/**
* the conic being drawn (not necessarily the same as geo, eg, for ineq
* drawing)
*/
protected GeoConicND conic;
/** whether this is euclidian visible and onscreen */
protected boolean isVisible;
/** whether the label is visible */
protected boolean labelVisible;
private int type;
/** label coordinates */
protected double[] labelCoords = new double[2];
// CONIC_SINGLE_POINT
private boolean firstPoint = true;
private GeoPoint point;
private DrawPoint drawPoint;
// CONIC_INTERSECTING_LINES
private boolean firstLines = true;
private GeoLine[] lines;
private DrawLine[] drawLines;
// CONIC_CIRCLE
private boolean firstCircle = true;
private GeoVec2D midpoint;
private GArc2D arc;
private GeneralPathClipped arcFiller, gp;
private GRectangularShape circle;
private double mx, my, radius, yradius, angSt, angEnd;
/** transform for ellipse, hyperbola, parabola */
protected GAffineTransform transform = AwtFactory.getPrototype()
.newAffineTransform();
/** shape to be filled (eg. ellipse, space between paralel lines) */
protected GShape shape;
// CONIC_ELLIPSE
private boolean firstEllipse = true;
/** lengths of half axes */
protected double[] halfAxes;
private GEllipse2DDouble ellipse;
// CONIC_PARABOLA
private boolean firstParabola = true;
/** x coord of start point for parabola/hyperbola */
protected double x0;
/** y coord of start point for parabola/hyperbola */
protected double y0;
private double k2;
private GeoVec2D vertex;
/** parabolic path */
protected GGeneralPath parabola;
private double[] parpoints = new double[8];
// CONIC_HYPERBOLA
/** whether this is the first time we draw a hyperbola */
protected boolean firstHyperbola = true;
/** first half-axis */
protected double a;
private double b;
private double tsq;
private double step;
private double t;
private double denom;
private double x, y;
private int index0, index1, n;
/** number of points used for hyperbola path */
protected int points = PLOT_POINTS;
private GeneralPathClipped hypLeft, hypRight;
private boolean hypLeftOnScreen, hypRightOnScreen;
// preview of circle (two points or three points)
private ArrayList<GeoPointND> prevPoints;
private ArrayList<GeoSegmentND> prevSegments;
private ArrayList<GeoLineND> prevLines;
private ArrayList<GeoConicND> prevConics;
private GeoPoint[] previewTempPoints;
private GeoLine previewTempLine;
private GeoNumeric previewTempRadius;
private int previewMode, neededPrevPoints;
private boolean isPreview = false;
private boolean ignoreSingularities;
private BoundingBox boundingBox;
private double fixCornerX = Double.NaN, fixCornerY = Double.NaN;
private double oldWidth = Double.NaN, oldHeight = Double.NaN;
private double proportion = Double.NaN;
private boolean isCircle = false;
/**
* preview of ellipse during resize with drag
*/
private GEllipse2DDouble prewEllipse = AwtFactory.getPrototype()
.newEllipse2DDouble(0, 0, 0, 0);
@Override
public GArea getShape() {
GArea area = super.getShape() != null ? super.getShape()
: (shape == null ? AwtFactory.getPrototype().newArea()
: AwtFactory.getPrototype().newArea(shape));
// Log.debug(conic.isInverseFill() + "," + shape +
// ","+super.getShape());
if (conic.isInverseFill()) {
GArea complement = AwtFactory.getPrototype()
.newArea(view.getBoundingPath());
if (arcFiller != null) {
complement = AwtFactory.getPrototype().newArea(arcFiller);
}
complement.subtract(area);
return complement;
}
return area;
}
/**
* Creates new DrawConic
*
* @param view
* view
* @param c
* conic
* @param ignoreSingularities
* true to avoid drawing points
*/
public DrawConic(EuclidianView view, GeoConicND c,
boolean ignoreSingularities) {
this.view = view;
isPreview = false;
this.ignoreSingularities = ignoreSingularities;
initConic(c);
update();
}
private void initConic(GeoConicND c) {
conic = c;
geo = c;
vertex = c.getTranslationVector(); // vertex
midpoint = vertex;
halfAxes = c.getHalfAxes();
c.getAffineTransform();
}
/**
* Creates a new DrawConic for preview of a circle
*
* @param view
* view
* @param mode
* preview mode
* @param points
* preview points
*/
public DrawConic(EuclidianView view, int mode,
ArrayList<GeoPointND> points) {
this.view = view;
prevPoints = points;
previewMode = mode;
Construction cons = view.getKernel().getConstruction();
switch (mode) {
default:
case EuclidianConstants.MODE_CIRCLE_TWO_POINTS:
case EuclidianConstants.MODE_CIRCLE_POINT_RADIUS:
neededPrevPoints = 1;
break;
case EuclidianConstants.MODE_CIRCLE_THREE_POINTS:
case EuclidianConstants.MODE_ELLIPSE_THREE_POINTS:
case EuclidianConstants.MODE_HYPERBOLA_THREE_POINTS:
neededPrevPoints = 2;
break;
case EuclidianConstants.MODE_CONIC_FIVE_POINTS:
neededPrevPoints = 4;
break;
}
// neededPrevPoints = mode == EuclidianConstants.MODE_CIRCLE_TWO_POINTS
// ? 1
// : 2;
previewTempPoints = new GeoPoint[neededPrevPoints + 1];
for (int i = 0; i < previewTempPoints.length; i++) {
previewTempPoints[i] = new GeoPoint(cons);
}
initPreview();
}
/**
* Creates a new DrawConic for preview of a parabola
*
* @param view
* view
* @param selectedLines
* possible directrix
* @param points
* preview points
*/
public DrawConic(EuclidianView view, ArrayList<GeoPointND> points,
ArrayList<GeoLineND> selectedLines) {
this.view = view;
prevPoints = points;
prevLines = selectedLines;
neededPrevPoints = 1;
previewMode = EuclidianConstants.MODE_PARABOLA;
Construction cons = view.getKernel().getConstruction();
if (selectedLines.size() == 0) {
previewTempLine = new GeoLine(cons);
} else {
previewTempLine = (GeoLine) selectedLines.get(0);
}
previewTempPoints = new GeoPoint[1];
previewTempPoints[0] = new GeoPoint(cons);
initPreview();
}
/**
* Creates a new DrawConic for preview of a compass circle (radius or
* segment first, then center point)
*
* @param view
* view
* @param mode
* preview mode
* @param points
* preview points
* @param segments
* preview segments
* @param conics
* preview conics
*/
public DrawConic(EuclidianView view, int mode, ArrayList<GeoPointND> points,
ArrayList<GeoSegmentND> segments, ArrayList<GeoConicND> conics) {
this.view = view;
prevPoints = points;
prevSegments = segments;
prevConics = conics;
previewMode = mode;
Construction cons = view.getKernel().getConstruction();
previewTempRadius = new GeoNumeric(cons);
previewTempPoints = new GeoPoint[1];
previewTempPoints[0] = new GeoPoint(cons);
initPreview();
}
@Override
final public void update() {
isVisible = geo.isEuclidianVisible();
if (!isVisible) {
return;
}
labelVisible = geo.isLabelVisible();
updateStrokes(conic);
type = conic.getType();
switch (type) {
default:
case GeoConicNDConstants.CONIC_EMPTY:
setShape(null);
shape = null;
break;
case GeoConicNDConstants.CONIC_SINGLE_POINT:
updateSinglePoint();
break;
case GeoConicNDConstants.CONIC_DOUBLE_LINE:
updateDoubleLine();
break;
case GeoConicNDConstants.CONIC_INTERSECTING_LINES:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
case GeoConicNDConstants.CONIC_LINE:
updateLines();
break;
case GeoConicNDConstants.CONIC_CIRCLE:
updateCircle();
break;
case GeoConicNDConstants.CONIC_ELLIPSE:
updateEllipse();
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
updateHyperbola();
break;
case GeoConicNDConstants.CONIC_PARABOLA:
updateParabola();
break;
}
if (!isVisible) {
return;
}
// shape on screen?
GRectangle viewRect = AwtFactory.getPrototype().newRectangle(0, 0,
view.getWidth(), view.getHeight());
switch (type) {
default:
// do nothing
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
case GeoConicNDConstants.CONIC_PARABOLA:
isVisible = checkCircleEllipseParabolaOnScreen(viewRect);
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
isVisible = checkHyperbolaOnScreen(viewRect);
break;
}
if (!isVisible) {
return;
}
// draw trace
if (conic.getTrace()) {
isTracing = true;
GGraphics2D g2 = view.getBackgroundGraphics();
if (g2 != null) {
drawTrace(g2);
}
} else {
if (isTracing) {
isTracing = false;
// view.updateBackground();
}
}
if (labelVisible) {
labelDesc = geo.getLabelDescription();
addLabelOffset();
}
if (geo.isShape()) {
if (getBounds() != null) {
getBoundingBox().setRectangle(getBounds());
if (Kernel.isEqual(getBoundingBox().getRectangle().getHeight(),
getBoundingBox().getRectangle().getWidth(), 2)) {
setIsCircle(true);
}
} else {
getBoundingBox().setRectangle(null);
setIsCircle(false);
}
}
}
/**
* check circle/ellipse/parabola intersects the screen
*
* @param viewRect
* view rectangle
* @return if hyperbola intersects the screen
*/
protected boolean checkCircleEllipseParabolaOnScreen(GRectangle viewRect) {
boolean includesScreenCompletely = shape.contains(viewRect);
// offScreen = includesScreenCompletely or the shape does not
// intersect the view rectangle
boolean offScreen = includesScreenCompletely
|| !shape.getBounds2D().intersects(viewRect);
if (!geo.isFilled()) {
// no filling
return !offScreen;
}
// filling
if (includesScreenCompletely) {
return true;
}
return !offScreen;
}
/**
* check hyperbola intersects the screen
*
* @param viewRect
* view rectangle
* @return if hyperbola intersects the screen
*/
protected boolean checkHyperbolaOnScreen(GRectangle viewRect) {
// hyperbola wings on screen?
hypLeftOnScreen = hypLeft
.intersects(AwtFactory.getPrototype().newRectangle(viewRect));
hypRightOnScreen = hypRight
.intersects(AwtFactory.getPrototype().newRectangle(viewRect));
if (!hypLeftOnScreen && !hypRightOnScreen) {
return false;
}
return true;
}
final private void updateSinglePoint() {
// we want to determine the sign of the result but we can't use fixed
// point
// as it may be equal to the single point. Point (b.x+1,0) differs in
// one coord.
shape = null;
if (firstPoint) {
firstPoint = false;
point = conic.getSinglePoint();
if (point == null) {
point = new GeoPoint(conic.getConstruction());
}
drawPoint = new DrawPoint(view, point, isPreview);
drawPoint.setGeoElement(conic);
// drawPoint.font = view.fontConic;
}
setShape(!ignoreSingularities ? drawPoint.getDot() : null);
// looks if it's on view
Coords p = view.getCoordsForView(conic.getMidpoint3D());
// Log.debug("\n"+view+"\n"+p);
if (!Kernel.isZero(p.getZ())) {
isVisible = false;
return;
}
double[] coords = new double[2];
coords[0] = p.getX();
coords[1] = p.getY();
point.copyLabel(conic);
point.setObjColor(conic.getObjectColor());
point.setPointSize(conic.getLineThickness());
drawPoint.update(coords);
}
/**
* Updates the double line and shape so that positive part is colored
*/
protected void updateDoubleLine() {
updateLines();
}
/**
* Updates the lines and shape so that positive part is colored
*/
protected void updateLines() {
shape = null;
if (firstLines) {
firstLines = false;
lines = conic.getLines();
drawLines = new DrawLine[2];
drawLines[0] = new DrawLine(view, lines[0]);
drawLines[1] = new DrawLine(view, lines[1]);
drawLines[0].setGeoElement(geo);
drawLines[1].setGeoElement(geo);
// drawLines[0].font = view.fontConic;
// drawLines[1].font = view.fontConic;
}
CoordMatrix m = null;
if (!isPreview) {
if (view.getMatrix() == null) {
if (conic.isGeoElement3D()) {
m = conic.getCoordSys().getMatrixOrthonormal().inverse();
}
} else {
if (conic.isGeoElement3D()) {
m = conic.getCoordSys().getMatrixOrthonormal().inverse()
.mul(view.getMatrix());
} else {
m = view.getMatrix();
}
}
}
for (int i = 0; i < 2; i++) {
drawLines[i].forceLineType(conic.lineType);
drawLines[i].update(m);
// thickness needs update #4087
drawLines[i].updateStrokesJustLineThickness(geo);
}
if (conic.type == GeoConicNDConstants.CONIC_PARALLEL_LINES
|| conic.type == GeoConicNDConstants.CONIC_INTERSECTING_LINES
|| conic.type == GeoConicNDConstants.CONIC_LINE) {
shape = drawLines[0].getShape(true);
if (conic.type != GeoConicNDConstants.CONIC_LINE)
{
((GArea) shape).exclusiveOr(drawLines[1].getShape(true));
// FIXME: buggy when conic(RW(0),RW(0))=0
}
if (negativeColored()) {
GArea complement = AwtFactory.getPrototype()
.newArea(view.getBoundingPath());
complement.subtract((GArea) shape);
shape = complement;
}
}
}
private boolean negativeColored() {
double[] xTry = new double[] { 0, 10, 20, 0, 10, 20 };
double[] yTry = new double[] { 0, 0, 0, 10, 10, 20 };
for (int i = 0; i < 6; i++) {
double val1 = conic.evaluate(view.toRealWorldCoordX(xTry[i]),
view.toRealWorldCoordY(yTry[i]));
if (conic.type == GeoConicNDConstants.CONIC_INTERSECTING_LINES) {
val1 *= conic.evaluate(conic.b.getX() + lines[0].x + lines[1].x,
conic.b.getY() + lines[0].y + lines[1].y);
}
if (conic.type == GeoConicNDConstants.CONIC_PARALLEL_LINES) {
val1 *= conic.evaluate(conic.b.getX(), conic.b.getY());
}
if (!Kernel.isZero(val1)) {
return (val1 > 0) ^ shape.contains(xTry[i], yTry[i]);
}
}
return false;
}
/**
* Update method for circles
*/
protected void updateCircle() {
setShape(null);
boolean fullAngle = false;
// calc screen pixel of radius
radius = halfAxes[0] * view.getXscale();
yradius = halfAxes[1] * view.getYscale(); // radius scaled in y
// direction
if (radius > DrawConic.HUGE_RADIUS || yradius > DrawConic.HUGE_RADIUS) {
Log.debug("ellipse fallback");
// ellipse drawing is handling those cases better
updateEllipse();
return;
}
if (firstCircle) {
firstCircle = false;
arc = AwtFactory.getPrototype().newArc2D();
if (ellipse == null) {
ellipse = AwtFactory.getPrototype().newEllipse2DDouble();
}
}
int i = -1; // bugfix
// if circle is very big, draw arc: this is very important
// for graphical continuity
// BIG RADIUS: larger than screen diagonal
int BIG_RADIUS = view.getWidth() + view.getHeight(); // > view's
// diagonal
if (radius < BIG_RADIUS && yradius < BIG_RADIUS) {
circle = ellipse;
arcFiller = null;
// calc screen coords of midpoint
Coords M;
if (isPreview) {
M = conic.getMidpoint3D().getInhomCoords();
} else {
M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
// check if eigen vec are in view
for (int j = 0; j < 2; j++) {
Coords evCoords = view
.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(evCoords.getZ())) {// check if in view
isVisible = false;
return;
}
}
}
mx = M.getX() * view.getXscale() + view.getXZero();
my = -M.getY() * view.getYscale() + view.getYZero();
ellipse.setFrame(mx - radius, my - yradius, 2.0 * radius,
2.0 * yradius);
} else {
// special case: really big circle
// draw arc according to midpoint position
// of the arc
Coords M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
// check if eigen vec are in view
for (int j = 0; j < 2; j++) {
Coords evCoords = view.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(evCoords.getZ())) {// check if in view
isVisible = false;
return;
}
}
mx = M.getX() * view.getXscale() + view.getXZero();
my = -M.getY() * view.getYscale() + view.getYZero();
angSt = Double.NaN;
// left
if (mx < 0.0) {
// top
if (my < 0.0) {
angSt = -Math.acos(-mx / radius);
angEnd = -Math.asin(-my / yradius);
i = 0;
}
// bottom
else if (my > view.getHeight()) {
angSt = Math.asin((my - view.getHeight()) / yradius);
angEnd = Math.acos(-mx / radius);
i = 2;
}
// middle
else {
angSt = -Math.asin((view.getHeight() - my) / yradius);
angEnd = Math.asin(my / yradius);
i = 1;
}
}
// right
else if (mx > view.getWidth()) {
// top
if (my < 0.0) {
angSt = Math.PI + Math.asin(-my / yradius);
angEnd = Math.PI
+ Math.acos((mx - view.getWidth()) / radius);
i = 6;
}
// bottom
else if (my > view.getHeight()) {
angSt = Math.PI
- Math.acos((mx - view.getWidth()) / radius);
angEnd = Math.PI
- Math.asin((my - view.getHeight()) / yradius);
i = 4;
}
// middle
else {
angSt = Math.PI - Math.asin(my / yradius);
angEnd = Math.PI
+ Math.asin((view.getHeight() - my) / yradius);
i = 5;
}
}
// top middle
else if (my < 0.0) {
angSt = Math.PI + Math.acos(mx / radius);
angEnd = 2 * Math.PI
- Math.acos((view.getWidth() - mx) / radius);
i = 7;
}
// bottom middle
else if (my > view.getHeight()) {
angSt = Math.acos((view.getWidth() - mx) / radius);
angEnd = Math.PI - Math.acos(mx / radius);
i = 3;
}
// center on screen
else {
// huge circle with center on screen: use screen rectangle
// instead of circle for possible filling
if (radius < BIG_RADIUS || yradius < BIG_RADIUS) {
updateEllipse();
return;
}
shape = circle = AwtFactory.getPrototype().newRectangle(-1, -1,
view.getWidth() + 2, view.getHeight() + 2);
arcFiller = null;
xLabel = -100;
yLabel = -100;
return;
}
if (Double.isNaN(angSt) || Double.isNaN(angEnd)) {
// to ensure drawing ...
angSt = 0.0d;
angEnd = 2 * Math.PI;
arcFiller = null;
fullAngle = true;
}
// set arc
circle = arc;
arc.setArc(mx - radius, my - yradius, 2.0 * radius, 2.0 * yradius,
Math.toDegrees(angSt), Math.toDegrees(angEnd - angSt),
GArc2D.OPEN);
// set general path for filling the arc to screen borders
if (conic.isFilled() && !fullAngle) {
if (gp == null) {
gp = new GeneralPathClipped(view);
} else {
gp.reset();
}
GPoint2D sp = arc.getStartPoint();
GPoint2D ep = arc.getEndPoint();
if (!conic.isInverseFill()) {
getArcFillerGP(sp, ep, i);
} else {
getInverseArcFillerGP(sp, ep, i);
}
// gp.
arcFiller = gp;
}
}
shape = circle;
// set label position
xLabel = (int) (mx - radius / 2.0);
yLabel = (int) (my - yradius * 0.85) + 20;
}
private void getArcFillerGP(GPoint2D sp, GPoint2D ep, int i) {
switch (i) { // case number
case 0: // left top
gp.moveTo(0, 0);
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
break;
case 1: // left middle
gp.moveTo(0, view.getHeight());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(0, 0);
break;
case 2: // left bottom
gp.moveTo(0, view.getHeight());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
break;
case 3: // middle bottom
gp.moveTo(view.getWidth(), view.getHeight());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(0, view.getHeight());
break;
case 4: // right bottom
gp.moveTo(view.getWidth(), view.getHeight());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
break;
case 5: // right middle
gp.moveTo(view.getWidth(), 0);
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(view.getWidth(), view.getHeight());
break;
case 6: // right top
gp.moveTo(view.getWidth(), 0);
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
break;
case 7: // top middle
gp.moveTo(0, 0);
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(view.getWidth(), 0);
break;
default:
gp = null;
}
}
private void getInverseArcFillerGP(GPoint2D sp, GPoint2D ep, int i) {
switch (i) { // case number
case 0: // left top
gp.moveTo(view.getWidth(), view.getHeight());
gp.lineTo(view.getWidth(), 0);
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(sp.getX(), view.getHeight());
break;
case 1: // left middle
getArcFillerGP(ep, sp, 5);
break;
case 2: // left bottom
gp.moveTo(view.getWidth(), 0);
gp.lineTo(view.getWidth(), view.getHeight());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(ep.getX(), 0);
break;
case 3: // middle bottom
this.getArcFillerGP(ep, sp, 7);
break;
case 4: // right bottom
gp.moveTo(0, 0);
gp.lineTo(0, view.getHeight());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(sp.getX(), 0);
break;
case 5: // right middle
getArcFillerGP(ep, sp, 1);
break;
case 6: // right top
gp.moveTo(0, view.getHeight());
gp.lineTo(0, 0);
gp.lineTo(sp.getX(), sp.getY());
gp.lineTo(ep.getX(), ep.getY());
gp.lineTo(ep.getX(), view.getHeight());
break;
case 7: // top middle
this.getArcFillerGP(ep, sp, 3);
break;
default:
gp = null;
}
}
/**
*
*/
protected Coords[] ev;
/**
* Update in case this draws an ellipse
*/
protected void updateEllipse() {
setShape(null);
// check for huge pixel radius
double xRadius = halfAxes[0] * view.getXscale();
double yRadius = halfAxes[1] * view.getYscale();
if (xRadius > DrawConic.HUGE_RADIUS
|| yRadius > DrawConic.HUGE_RADIUS) {
isVisible = false;
return;
}
// check if in view
Coords M;
if (isPreview) { // midpoint has been calculated in view coords
M = conic.getMidpoint3D().getInhomCoords();
} else {
M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
}
if (ev == null) {
ev = new Coords[2];
}
if (isPreview) { // calculations were in view coords
for (int j = 0; j < 2; j++) {
ev[j] = conic.getEigenvec(j);
}
} else {
for (int j = 0; j < 2; j++) {
ev[j] = view.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(ev[j].getZ())) {// check if in view
isVisible = false;
return;
}
}
}
if (firstEllipse) {
firstEllipse = false;
if (ellipse == null) {
ellipse = AwtFactory.getPrototype().newEllipse2DDouble();
}
}
// set transform
transform.setTransform(view.getCoordTransform());
transform.concatenate(view.getCompanion().getTransform(conic, M, ev));
// set ellipse
ellipse.setFrameFromCenter(0, 0, halfAxes[0], halfAxes[1]);
// BIG RADIUS: larger than screen diagonal
int BIG_RADIUS = view.getWidth() + view.getHeight(); // > view's
// diagonal
if (xRadius < BIG_RADIUS && yRadius < BIG_RADIUS) {
shape = transform.createTransformedShape(ellipse);
} else {
// clip big arc at screen
// shape=ClipShape.clipToRect(shape,ellipse, transform, new
// Rectangle(-1,
// -1, view.getWidth() + 2, view.getHeight() + 2));
shape = ClipShape.clipToRect(ellipse, transform,
AwtFactory.getPrototype().newRectangle(-1, -1,
view.getWidth() + 2, view.getHeight() + 2));
}
// set label coords
labelCoords[0] = -halfAxes[0] / 2.0d;
labelCoords[1] = halfAxes[1] * 0.85d - 20.0 / view.getYscale();
transform.transform(labelCoords, 0, labelCoords, 0, 1);
xLabel = (int) labelCoords[0];
yLabel = (int) labelCoords[1];
}
/**
* draw only one edge for the hyperbola section
*/
protected void updateHyperbolaEdge() {
// only used in DrawConicSection
isVisible = false;
}
/**
* Update method for hyperbolas
*/
protected void updateHyperbola() {
// check if in view
Coords M;
if (isPreview) { // midpoint has been calculated in view coords
M = conic.getMidpoint3D().getInhomCoords();
} else {
M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
}
if (ev == null) {
ev = new Coords[2];
}
if (isPreview) { // calculations were in view coords
for (int j = 0; j < 2; j++) {
ev[j] = conic.getEigenvec(j);
}
} else {
for (int j = 0; j < 2; j++) {
ev[j] = view.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(ev[j].getZ())) {// check if in view
isVisible = false;
return;
}
}
}
updateHyperbolaResetPaths();
a = halfAxes[0];
b = halfAxes[1];
updateHyperbolaX0();
// init step width
if (x0 <= a) { // hyperbola is not visible on screen
isVisible = false;
return;
}
// set number of plot points according to size of x0
// add ten points per screen width
n = PLOT_POINTS
+ (int) (Math.abs(x0 - a) / (view.getXmax() - view.getXmin()))
* 10;
// n < 0 might result from huge real
if (points != n && n > 0) {
points = Math.min(n, MAX_PLOT_POINTS);
}
// hyperbola is visible on screen
step = Math.sqrt((x0 - a) / (x0 + a)) / (points - 1);
// build Polyline of parametric hyperbola
// hyp(t) = 1/(1-t^2) {a(1+t^2), 2bt}, 0 <= t < 1
// this represents the first quadrant's wing of a hypberola
/*
* hypRight.addPoint(points - 1, a, 0); hypLeft.addPoint(points - 1, -a,
* 0);
*/
updateHyperbolaAddPoint(points - 1, a, 0);
t = step;
int i = 1;
index0 = points; // points ... 2*points - 2
index1 = points - 2; // points-2 ... 0
while (index1 >= 0) {
tsq = t * t;
denom = 1.0 - tsq;
// calc coords of first quadrant
x = (a * (1.0 + tsq) / denom);
y = (2.0 * b * t / denom);
// first and second quadrants
updateHyperbolaAddPoint(index0, x, y);
// third and fourth quadrants
updateHyperbolaAddPoint(index1, x, -y);
/*
* // first quadrant hypRight.addPoint(index0, x, y); // second
* quadrant hypLeft.addPoint(index0, -x, y); // third quadrant
* hypLeft.addPoint(index1, -x, -y); // fourth quadrant
* hypRight.addPoint(index1, x, -y);
*/
index0++;
index1--;
i++;
t = i * step;
}
updateHyperbolaClosePaths();
// set transform for Graphics2D
transform.setTransform(view.getCoordTransform());
transform.concatenate(view.getCompanion().getTransform(conic, M, ev));
updateHyperboalSetTransformToPaths();
updateHyperbolaLabelCoords();
transform.transform(labelCoords, 0, labelCoords, 0, 1);
xLabel = (int) labelCoords[0];
yLabel = (int) labelCoords[1];
updateHyperbolaSetShape();
}
/** set label coords */
protected void updateHyperbolaLabelCoords() {
labelCoords[0] = 2.0 * a;
// point on curve: y = b * sqrt(3) minus 20 pixels
labelCoords[1] = b * 1.7 - 20.0 / view.getYscale();
}
/**
* reset paths for hyperbola
*/
protected void updateHyperbolaResetPaths() {
if (firstHyperbola) {
firstHyperbola = false;
points = PLOT_POINTS;
hypRight = new GeneralPathClipped(view); // right wing
hypLeft = new GeneralPathClipped(view); // left wing
} else {
hypRight.reset();
hypLeft.reset();
}
}
/**
* updates hyperbola x maximum value
*/
protected void updateHyperbolaX0() {
// draw hyperbola wing from x=a to x=x0
// the drawn hyperbola must be larger than the screen
// get max distance from midpoint to screen edge
x0 = Math.max(
Math.max(Math.abs(midpoint.getX() - view.getXmin()),
Math.abs(midpoint.getX() - view.getXmax())),
Math.max(Math.abs(midpoint.getY() - view.getYmin()),
Math.abs(midpoint.getY() - view.getYmax())));
// ensure that rotated hyperbola is fully on screen:
x0 *= 1.5;
}
/**
* add point to paths for hyperbola
*
* @param index
* index for the point
* @param x1
* x coord
* @param y1
* y coord
*/
protected void updateHyperbolaAddPoint(int index, double x1, double y1) {
hypRight.addPoint(index, x1, y1);
hypLeft.addPoint(index, -x1, y1);
}
/** build general paths of hyperbola wings and transform them */
protected void updateHyperboalSetTransformToPaths() {
hypLeft.transform(transform);
hypRight.transform(transform);
}
/**
* close hyperbola branchs
*/
protected void updateHyperbolaClosePaths() {
// we have drawn the hyperbola from x=a to x=x0
// ensure correct filling by adding points at (2*x0, y)
if (conic.isFilled()) {
hypRight.lineTo(Float.MAX_VALUE, y);
hypRight.lineTo(Float.MAX_VALUE, -y);
hypLeft.lineTo(-Float.MAX_VALUE, y);
hypLeft.lineTo(-Float.MAX_VALUE, -y);
}
}
/**
* set shape for hyperbola
*/
protected void updateHyperbolaSetShape() {
setShape(AwtFactory.getPrototype().newArea(hypLeft));
// geogebra.awt.Area.getAWTArea(super.getShape()).add(new
// Area(geogebra.awt.GenericShape.getAwtShape(hypRight)));
super.getShape().add(AwtFactory.getPrototype().newArea(hypRight));
}
/**
* draw only one edge for the parabola section
*/
protected void updateParabolaEdge() {
// only used for conic section
isVisible = false;
}
/**
* Update method for parabolas
*/
protected void updateParabola() {
if (conic.p > DrawConic.HUGE_RADIUS) {
isVisible = false;
return;
}
// check if in view
Coords M;
if (isPreview) { // midpoint has been calculated in view coords
M = conic.getMidpoint3D().getInhomCoords();
} else {
M = view.getCoordsForView(conic.getMidpoint3D());
if (!Kernel.isZero(M.getZ())) {// check if in view
isVisible = false;
return;
}
}
if (ev == null) {
ev = new Coords[2];
}
if (isPreview) { // calculations were in view coords
for (int j = 0; j < 2; j++) {
ev[j] = conic.getEigenvec(j);
}
} else {
for (int j = 0; j < 2; j++) {
ev[j] = view.getCoordsForView(conic.getEigenvec3D(j));
if (!Kernel.isZero(ev[j].getZ())) {// check if in view
isVisible = false;
return;
}
}
}
if (firstParabola) {
firstParabola = false;
parabola = AwtFactory.getPrototype().newGeneralPath();
}
GAffineTransform conicTransform = view.getCompanion()
.getTransform(conic, M, ev);
updateParabolaX0Y0(conicTransform);
// set transform
transform.setTransform(view.getCoordTransform());
transform.concatenate(conicTransform);
// setCurve(P0, P1, P2)
// parabola.setCurve(x0, y0, -x0, 0.0, x0, -y0);
// shape = transform.createTransformedShape(parabola);
parpoints[0] = x0;
parpoints[1] = y0;
parpoints[2] = -x0 / 3;
parpoints[3] = y0 / 3;
parpoints[4] = -x0 / 3;
parpoints[5] = -y0 / 3;
parpoints[6] = x0;
parpoints[7] = -y0;
transform.transform(parpoints, 0, parpoints, 0, 4);
updateParabolaPath();
shape = parabola;
updateParabolaLabelCoords();
transform.transform(labelCoords, 0, labelCoords, 0, 1);
xLabel = (int) labelCoords[0];
yLabel = (int) labelCoords[1];
}
/**
* update label coords for parabola
*/
protected void updateParabolaLabelCoords() {
// set label coords
labelCoords[0] = 2 * conic.p;
// y = 2p minus 20 pixels
labelCoords[1] = labelCoords[0] - 20.0 / view.getYscale();
}
/**
* calc control points coords of parabola y^2 = 2 p x
*
* @param conicTransform
* transform from eigenvector CS to RW
*/
protected void updateParabolaX0Y0(GAffineTransform conicTransform) {
GAffineTransform inverse = null;
try {
inverse = conicTransform.createInverse();
} catch (Exception e) {
e.printStackTrace();
}
if (inverse == null) {
return;
}
double[] corners = new double[10];
inverse.transform(new double[] { vertex.getX(), vertex.getY(),
view.getXmin(), view.getYmin(), view.getXmin(), view.getYmax(),
view.getXmax(), view.getYmin(), view.getXmax(),
view.getYmax() }, 0, corners, 0, 5);
x0 = Math.max(Math.abs(corners[0] - corners[2]),
Math.abs(corners[0] - corners[4]));
x0 = Math.max(x0, Math.abs(corners[0] - corners[6]));
x0 = Math.max(x0, Math.abs(corners[0] - corners[8]));
y0 = Math.max(Math.abs(corners[1] - corners[3]),
Math.abs(corners[1] - corners[5]));
y0 = Math.max(y0, Math.abs(corners[1] - corners[7]));
y0 = Math.max(y0, Math.abs(corners[1] - corners[9]));
// we want to return either (y0^2/2/p,y0) or (x0,sqrt(2p*x0))
double xForY0 = y0 * y0 / 2 / conic.p;
if (x0 > xForY0) {
x0 = xForY0;
return;
}
x0 = 2 * x0 / conic.p;
// avoid sqrt by choosing x = k*p with
// i = 2*k is quadratic number
// make parabola big enough: k*p >= 2*x0 -> 2*k >= 4*x0/p
// changed these to doubles, see #654 y=x^2+100000x+1
double i = 2;
k2 = 4;
do {
i += 2;
k2 = i * i;
} while (k2 < x0);
x0 = k2 / 2 * conic.p; // x = k*p
y0 = i * conic.p; // y = sqrt(2k p^2) = i p
}
/**
* create path for parabola
*/
protected void updateParabolaPath() {
parabola.reset();
parabola.moveTo(parpoints[0], parpoints[1]);
parabola.curveTo(parpoints[2], parpoints[3], parpoints[4], parpoints[5],
parpoints[6], parpoints[7]);
}
@Override
final public void draw(GGraphics2D g2) {
if (!isVisible) {
return;
}
g2.setColor(getObjectColor());
switch (type) {
case GeoConicNDConstants.CONIC_SINGLE_POINT:
int pointType;
if ((conic == geo && conic.isInverseFill())
|| geo.isInverseFill() != conic.isInverseFill()) {
fill(g2, getShape());
pointType = EuclidianStyleConstants.POINT_STYLE_CIRCLE;
} else {
pointType = EuclidianStyleConstants.POINT_STYLE_DOT;
}
if (!ignoreSingularities) {
drawPoint.setPointStyle(pointType);
drawPoint.draw(g2);
}
break;
case GeoConicNDConstants.CONIC_INTERSECTING_LINES:
case GeoConicNDConstants.CONIC_DOUBLE_LINE:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
drawLines(g2);
break;
case GeoConicNDConstants.CONIC_LINE:
drawLines[0].draw(g2);
break;
default:
case GeoConicNDConstants.CONIC_EMPTY:
if (conic.isInverseFill()) {
fill(g2, getShape());
}
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
case GeoConicNDConstants.CONIC_PARABOLA:
fillEllipseParabola(g2);
if (geo.doHighlighting()) {
g2.setStroke(selStroke);
g2.setColor(geo.getSelColor());
g2.draw(shape);
}
g2.setStroke(objStroke);
g2.setColor(getObjectColor());
if (geo.getLineThickness() > 0) {
g2.draw(shape);
}
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setColor(geo.getLabelColor());
drawLabel(g2);
}
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
drawHyperbola(g2);
break;
}
}
private void fillEllipseParabola(GGraphics2D g2) {
if (conic.isInverseFill()) {
fill(g2, getShape());
} else {
fill(g2, shape); // fill using default/hatching/image as
// appropriate
}
if (arcFiller != null && !conic.isInverseFill())
{
fill(g2, arcFiller); // fill using default/hatching/image
// as appropriate
}
}
/**
* draw lines
*
* @param g2
* graphic context
*/
protected void drawLines(GGraphics2D g2) {
if (geo.getLineThickness() > 0) {
drawLines[0].draw(g2);
drawLines[1].draw(g2);
}
if (conic.isInverseFill()) {
fill(g2, getShape());
} else {
fill(g2, shape == null ? getShape() : shape);
}
}
/**
* draw hyperbola
*
* @param g2
* graphic context
*/
protected void drawHyperbola(GGraphics2D g2) {
fillHyperbola(g2);
if (geo.doHighlighting()) {
g2.setStroke(selStroke);
g2.setColor(geo.getSelColor());
if (hypLeftOnScreen) {
g2.draw(hypLeft);
}
if (hypRightOnScreen) {
g2.draw(hypRight);
}
}
g2.setStroke(objStroke);
g2.setColor(getObjectColor());
if (geo.getLineThickness() > 0) {
if (hypLeftOnScreen) {
g2.draw(hypLeft);
}
if (hypRightOnScreen) {
g2.draw(hypRight);
}
}
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setColor(geo.getLabelColor());
drawLabel(g2);
}
}
private void fillHyperbola(GGraphics2D g2) {
if (conic.isInverseFill()) {
GArea a1 = AwtFactory.getPrototype().newArea(hypLeft);
GArea a2 = AwtFactory.getPrototype().newArea(hypRight);
GArea complement = AwtFactory.getPrototype()
.newArea(view.getBoundingPath());
complement.subtract(a1);
complement.subtract(a2);
fill(g2, complement);
} else {
if (hypLeftOnScreen) {
fill(g2, hypLeft);
}
if (hypRightOnScreen) {
fill(g2, hypRight);
}
}
}
/**
* Returns the bounding box of this Drawable in screen coordinates.
*
* @return null when this Drawable is infinite or undefined
*/
@Override
final public GRectangle getBounds() {
if (!geo.isDefined() || !geo.isEuclidianVisible()) {
return null;
}
switch (type) {
case GeoConicNDConstants.CONIC_SINGLE_POINT:
return drawPoint.getBounds();
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
// shape is null for 3D ellipse
return shape == null ? null : shape.getBounds();
case GeoConicNDConstants.CONIC_PARABOLA:
case GeoConicNDConstants.CONIC_HYPERBOLA:
// might need another formula for flat hyperbolae, max() prevents
// flattening of xx-yy=1000
double focX = Math.max(
Math.abs(conic.linearEccentricity
* conic.eigenvec[0].getX()),
Math.abs(conic.linearEccentricity
* conic.eigenvec[0].getY()));
int xmin = view.toScreenCoordX(midpoint.getX() - focX);
int xmax = view.toScreenCoordX(midpoint.getX() + focX);
int ymin = view.toScreenCoordY(midpoint.getY() - focX);
int ymax = view.toScreenCoordY(midpoint.getY() + focX);
return AwtFactory.getPrototype().newRectangle(xmin, ymax,
xmax - xmin, ymin - ymax);
default:
return null;
}
}
@Override
final public void drawTrace(GGraphics2D g2) {
g2.setColor(getObjectColor());
switch (type) {
default:
// do nothing
break;
case GeoConicNDConstants.CONIC_SINGLE_POINT:
drawPoint.drawTrace(g2);
break;
case GeoConicNDConstants.CONIC_INTERSECTING_LINES:
case GeoConicNDConstants.CONIC_DOUBLE_LINE:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
drawLines[0].drawTrace(g2);
drawLines[1].drawTrace(g2);
break;
case GeoConicNDConstants.CONIC_LINE:
drawLines[0].drawTrace(g2);
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
case GeoConicNDConstants.CONIC_PARABOLA:
fillEllipseParabola(g2);
g2.setStroke(objStroke);
g2.setColor(getObjectColor());
g2.draw(shape);
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
fillHyperbola(g2);
g2.setStroke(objStroke);
g2.setColor(getObjectColor());
g2.draw(hypLeft);
g2.draw(hypRight);
break;
}
}
/**
*
* @return true if it has to check it's on filling
*/
protected boolean checkIsOnFilling() {
return geo.isFilled() && type != GeoConicNDConstants.CONIC_SINGLE_POINT
&& type != GeoConicNDConstants.CONIC_DOUBLE_LINE;
}
@Override
final public boolean hit(int hitX, int hitY, int hitThreshold) {
if (!isVisible) {
return false;
}
// set a flag that says if the point is on the filling
boolean isOnFilling = false;
if (checkIsOnFilling()) {
double realX = view.toRealWorldCoordX(hitX);
double realY = view.toRealWorldCoordY(hitY);
double x3 = view.toRealWorldCoordX(3) - view.toRealWorldCoordX(0);
double y3 = view.toRealWorldCoordY(3) - view.toRealWorldCoordY(0);
int insideNeigbors = (conic.isInRegion(realX, realY) ? 1 : 0)
+ (conic.isInRegion(realX - x3, realY - y3) ? 1 : 0)
+ (conic.isInRegion(realX + x3, realY - y3) ? 1 : 0)
+ (conic.isInRegion(realX - x3, realY + y3) ? 1 : 0)
+ (conic.isInRegion(realX + x3, realY + y3) ? 1 : 0);
if (conic.isInverseFill()) {
isOnFilling = (insideNeigbors < 5);
} else {
isOnFilling = (insideNeigbors > 0);
}
}
// set a flag to say if point is on the boundary
boolean isOnBoundary = false;
switch (type) {
default:
// do nothing
break;
case GeoConicNDConstants.CONIC_SINGLE_POINT:
isOnBoundary = drawPoint.hit(hitX, hitY, hitThreshold);
break;
case GeoConicNDConstants.CONIC_INTERSECTING_LINES:
case GeoConicNDConstants.CONIC_DOUBLE_LINE:
case GeoConicNDConstants.CONIC_PARALLEL_LINES:
isOnBoundary = hitLines(hitX, hitY, hitThreshold);
break;
case GeoConicNDConstants.CONIC_LINE:
isOnBoundary = drawLines[0].hit(hitX, hitY, hitThreshold);
break;
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_PARABOLA:
if (strokedShape == null) {
strokedShape = objStroke.createStrokedShape(shape);
}
isOnBoundary = strokedShape.intersects(hitX - hitThreshold,
hitY - hitThreshold, 2 * hitThreshold, 2 * hitThreshold);
if (!isOnBoundary && type == GeoConicNDConstants.CONIC_CIRCLE) {
isOnBoundary = getBoundingBox() != null
&& getBoundingBox().getRectangle() != null
&& getBoundingBox() == view.getBoundingBox()
&& getBoundingBox().getRectangle().intersects(
hitX - hitThreshold, hitY - hitThreshold,
2 * hitThreshold, 2 * hitThreshold)
&& getBoundingBox().hitSideOfBoundingBox(hitX, hitY,
hitThreshold);
}
break;
case GeoConicNDConstants.CONIC_ELLIPSE:
isOnBoundary = hitEllipse(hitX, hitY, hitThreshold);
break;
case GeoConicNDConstants.CONIC_HYPERBOLA:
isOnBoundary = hitHyperbola(hitX, hitY, hitThreshold);
break;
}
// Application.debug("isOnFilling="+isOnFilling+"\nisOnBoundary="+isOnBoundary);
if (isOnFilling) {
if (isOnBoundary) {
conic.setLastHitType(HitType.ON_BOUNDARY);
return true;
}
conic.setLastHitType(HitType.ON_FILLING);
return true;
}
if (isOnBoundary) {
conic.setLastHitType(HitType.ON_BOUNDARY);
return true;
}
conic.setLastHitType(HitType.NONE);
return false;
}
/**
* Says if the coords hit lines
*
* @param hitX
* x coord for hit
* @param hitY
* y coord for hit
* @param hitThreshold
* acceptable distance from line
* @return true if lines are hit
*/
public boolean hitLines(int hitX, int hitY, int hitThreshold) {
return drawLines[0].hit(hitX, hitY, hitThreshold)
|| drawLines[1].hit(hitX, hitY, hitThreshold);
}
/**
* Says if the coords hit hyperbola
*
* @param hitX
* x coord for hit
* @param hitY
* y coord for hit
* @return true if lines are hitted
* @param hitThreshold
* acceptable distance from line
*/
public boolean hitHyperbola(int hitX, int hitY, int hitThreshold) {
if (strokedShape == null) {
strokedShape = objStroke.createStrokedShape(hypLeft);
strokedShape2 = objStroke.createStrokedShape(hypRight);
}
return strokedShape.intersects(hitX - hitThreshold, hitY - hitThreshold,
2 * hitThreshold, 2 * hitThreshold)
|| strokedShape2.intersects(hitX - hitThreshold,
hitY - hitThreshold, 2 * hitThreshold,
2 * hitThreshold);
}
/**
* Says if the coords hit ellipse
*
* @param hitX
* x coord for hit
* @param hitY
* y coord for hit
* @return true if lines are hitted
* @param hitThreshold
* acceptable distance from line
*/
public boolean hitEllipse(int hitX, int hitY, int hitThreshold) {
if (strokedShape == null) {
strokedShape = objStroke.createStrokedShape(shape);
}
return (strokedShape.intersects(hitX - hitThreshold,
hitY - hitThreshold, 2 * hitThreshold, 2 * hitThreshold))
|| (getBoundingBox() != null
&& getBoundingBox().getRectangle() != null
&& getBoundingBox() == view.getBoundingBox()
&& getBoundingBox().getRectangle().intersects(
hitX - hitThreshold, hitY - hitThreshold,
2 * hitThreshold, 2 * hitThreshold)
&& getBoundingBox().hitSideOfBoundingBox(hitX, hitY,
hitThreshold));
}
@Override
final public boolean isInside(GRectangle rect) {
switch (type) {
case GeoConicNDConstants.CONIC_SINGLE_POINT:
return drawPoint.isInside(rect);
case GeoConicNDConstants.CONIC_CIRCLE:
case GeoConicNDConstants.CONIC_ELLIPSE:
return rect != null && shape != null
&& rect.contains(shape.getBounds());
}
return false;
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
if (type == GeoConicNDConstants.CONIC_SINGLE_POINT) {
return drawPoint.intersectsRectangle(rect);
}
if (type == GeoConicNDConstants.CONIC_DOUBLE_LINE) {
return drawLines[0].intersectsRectangle(rect)
|| drawLines[1].intersectsRectangle(rect);
}
if (geo.isFilled()) {
return super.intersectsRectangle(rect);
}
if (shape != null) {
return shape.intersects(rect) && !shape.contains(rect);
}
if (super.getShape() != null) {
return super.getShape().intersects(rect)
&& !super.getShape().contains(rect);
}
return false;
}
@Override
public GeoElement getGeoElement() {
return geo;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
if (drawLines != null) {
for (int i = 0; i < 2 && drawLines[i] != null; i++) {
drawLines[i].setGeoElement(geo);
}
}
}
private void initPreview() {
// init the conic for preview
Construction cons = previewTempPoints[0].getConstruction();
isPreview = true;
switch (previewMode) {
case EuclidianConstants.MODE_CIRCLE_TWO_POINTS:
AlgoCircleTwoPoints algo = new AlgoCircleTwoPoints(cons,
previewTempPoints[0], previewTempPoints[1]);
cons.removeFromConstructionList(algo);
initConic(algo.getCircle());
break;
case EuclidianConstants.MODE_CIRCLE_POINT_RADIUS:
Coords p = view
.getCoordsForView(prevPoints.get(0).getInhomCoordsInD3());
previewTempPoints[0].setCoords(p.projectInfDim(), false);
GeoNumberValue distance = new GeoNumeric(cons,
previewTempPoints[1].distance(previewTempPoints[0]));
AlgoCirclePointRadius algoCircleRadius = new AlgoCirclePointRadius(
cons, previewTempPoints[0], distance);
cons.removeFromConstructionList(algoCircleRadius);
initConic(algoCircleRadius.getCircle());
break;
case EuclidianConstants.MODE_CONIC_FIVE_POINTS:
GeoPoint[] pts = { previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2], previewTempPoints[3],
previewTempPoints[4] };
AlgoConicFivePoints algo0 = new AlgoConicFivePoints(cons, pts);
cons.removeFromConstructionList(algo0);
initConic(algo0.getConic());
break;
case EuclidianConstants.MODE_CIRCLE_THREE_POINTS:
AlgoCircleThreePoints algo2 = new AlgoCircleThreePoints(cons,
previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2]);
cons.removeFromConstructionList(algo2);
initConic(algo2.getCircle());
break;
case EuclidianConstants.MODE_ELLIPSE_THREE_POINTS:
AlgoEllipseHyperbolaFociPoint algo3 = new AlgoEllipseHyperbolaFociPoint(
cons, previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2], GeoConicNDConstants.CONIC_ELLIPSE);
cons.removeFromConstructionList(algo3);
initConic(algo3.getConic());
break;
case EuclidianConstants.MODE_HYPERBOLA_THREE_POINTS:
AlgoEllipseHyperbolaFociPoint algo4 = new AlgoEllipseHyperbolaFociPoint(
cons, previewTempPoints[0], previewTempPoints[1],
previewTempPoints[2], GeoConicNDConstants.CONIC_HYPERBOLA);
cons.removeFromConstructionList(algo4);
initConic(algo4.getConic());
break;
case EuclidianConstants.MODE_COMPASSES:
AlgoCirclePointRadius algo5 = new AlgoCirclePointRadius(cons,
previewTempPoints[0], previewTempRadius);
cons.removeFromConstructionList(algo5);
initConic(algo5.getCircle());
break;
case EuclidianConstants.MODE_PARABOLA:
AlgoParabolaPointLine algo6 = new AlgoParabolaPointLine(cons,
previewTempPoints[0], previewTempLine);
cons.removeFromConstructionList(algo6);
initConic(algo6.getParabola());
break;
default:
Log.debug("unknown conic type");
}
if (conic != null) {
conic.setLabelVisible(false);
}
}
// preview of circle with midpoint through a second point
@Override
final public void updatePreview() {
switch (previewMode) {
case EuclidianConstants.MODE_COMPASSES:
// compass: set radius of preview circle
// two points or one segment selected to define radius
isVisible = conic != null && (prevPoints.size() == 2
|| prevSegments.size() == 1 || prevConics.size() == 1);
if (isVisible) {
if (prevPoints.size() == 2) {
GeoPointND p1 = prevPoints.get(0);
GeoPointND p2 = prevPoints.get(1);
previewTempRadius.setValue(p1.distance(p2));
} else if (prevSegments.size() == 1) {
GeoSegmentND seg = prevSegments.get(0);
previewTempRadius.setValue(seg.getLength());
} else if (prevConics.size() == 1) {
GeoConicND prevCircle = prevConics.get(0);
previewTempRadius.setValue(prevCircle.getCircleRadius());
}
previewTempRadius.updateCascade();
}
break;
case EuclidianConstants.MODE_PARABOLA:
isVisible = prevLines.size() == 1;
if (prevLines.size() > 0) {
GeoLineND lND = prevLines.get(0);
Coords equation = lND
.getCartesianEquationVector(view.getMatrix());
previewTempLine.setCoords(equation.getX(), equation.getY(),
equation.getZ());
}
if (prevPoints.size() > 0) {
Coords p = view.getCoordsForView(
prevPoints.get(0).getInhomCoordsInD3());
// Application.debug("p["+i+"]=\n"+p);
previewTempPoints[0].setCoords(p.projectInfDim(), true);
previewTempPoints[0].updateCascade();
}
break;
case EuclidianConstants.MODE_CIRCLE_POINT_RADIUS:
isVisible = conic != null && prevPoints.size() == neededPrevPoints;
if (isVisible) {
Coords p = view.getCoordsForView(
prevPoints.get(0).getInhomCoordsInD3());
previewTempPoints[0].setCoords(p.projectInfDim(), false);
Construction cons = previewTempPoints[0].getConstruction();
GeoNumberValue distance = new GeoNumeric(cons,
previewTempPoints[1].distance(previewTempPoints[0]));
AlgoCirclePointRadius algoCircleRadius = new AlgoCirclePointRadius(
cons, previewTempPoints[0], distance);
cons.removeFromConstructionList(algoCircleRadius);
initConic(algoCircleRadius.getCircle());
this.conic.updateCascade();
}
break;
default:
// all other conic preview modes: use points to define preview conic
isVisible = conic != null && prevPoints.size() == neededPrevPoints;
if (isVisible) {
for (int i = 0; i < prevPoints.size(); i++) {
Coords p = view.getCoordsForView(
prevPoints.get(i).getInhomCoordsInD3());
// Log.debug("p["+i+"]=\n"+p);
previewTempPoints[i].setCoords(p.projectInfDim(), false);
}
previewTempPoints[0].updateCascade();
}
}
}
@Override
final public void updateMousePos(double xRW, double yRW) {
if (isVisible) {
// double xRW = view.toRealWorldCoordX(x);
// double yRW = view.toRealWorldCoordY(y);
previewTempPoints[previewTempPoints.length - 1].setCoords(xRW, yRW,
1.0);
previewTempPoints[previewTempPoints.length - 1].updateCascade();
update();
}
}
@Override
final public void drawPreview(GGraphics2D g2) {
draw(g2);
}
@Override
public void disposePreview() {
if (conic != null) {
conic.remove();
}
}
/**
* Returns the conic to be draw (might not be equal to geo, if this is part
* of bigger geo)
*
* @return conic
*/
public GeoConicND getConic() {
return conic;
}
/**
* @param ignore
* to avoid drawing single point if part of ineq
*/
public void setIgnoreSingularities(boolean ignore) {
this.ignoreSingularities = ignore;
}
/**
* update bounding box construction
*/
@Override
public void updateBoundingBox() {
if (getBoundingBox().getRectangle() == null) {
if (geo.isShape() && getBounds() != null) {
boundingBox.setRectangle(getBounds());
}
}
}
@Override
public BoundingBox getBoundingBox() {
if (boundingBox == null) {
boundingBox = new BoundingBox();
}
return boundingBox;
}
/**
* @return fixed x coord of corner
*/
public double getFixCornerX() {
return fixCornerX;
}
/**
* @param fixCornerX
* - x coord of fixed corner
*/
public void setFixCornerX(double fixCornerX) {
this.fixCornerX = fixCornerX;
}
/**
* @return fixed y coord of corner
*/
public double getFixCornerY() {
return fixCornerY;
}
/**
* @param fixCornerY
* - y coord of fixed corner
*/
public void setFixCornerY(double fixCornerY) {
this.fixCornerY = fixCornerY;
}
/**
* @return true if is circle
*/
public boolean isCircle() {
return isCircle;
}
/**
* @param isCircle
* - if it is circle
*/
public void setIsCircle(boolean isCircle) {
this.isCircle = isCircle;
}
private static boolean isCornerHandler(
EuclidianBoundingBoxHandler handler) {
return handler == EuclidianBoundingBoxHandler.BOTTOM_LEFT
|| handler == EuclidianBoundingBoxHandler.BOTTOM_RIGHT
|| handler == EuclidianBoundingBoxHandler.TOP_LEFT
|| handler == EuclidianBoundingBoxHandler.TOP_RIGHT;
}
private void fixCornerCoords(EuclidianBoundingBoxHandler hitHandler) {
if (Double.isNaN(fixCornerX)) {
switch (hitHandler) {
case BOTTOM_LEFT:
case TOP_LEFT:
fixCornerX = getBoundingBox().getRectangle().getMaxX();
break;
case LEFT:
fixCornerX = getBoundingBox().getRectangle().getMaxX();
fixCornerY = getBoundingBox().getRectangle().getMinY();
break;
case TOP_RIGHT:
case BOTTOM_RIGHT:
fixCornerX = getBoundingBox().getRectangle().getX();
break;
case RIGHT:
fixCornerX = getBoundingBox().getRectangle().getX();
fixCornerY = getBoundingBox().getRectangle().getMinY();
break;
default:
break;
}
}
if (Double.isNaN(fixCornerY)) {
switch (hitHandler) {
case TOP_LEFT:
case TOP_RIGHT:
fixCornerY = getBoundingBox().getRectangle().getMaxY();
break;
case TOP:
fixCornerX = getBoundingBox().getRectangle().getMinX();
fixCornerY = getBoundingBox().getRectangle().getMaxY();
break;
case BOTTOM_LEFT:
case BOTTOM_RIGHT:
fixCornerY = getBoundingBox().getRectangle().getMinY();
break;
case BOTTOM:
fixCornerX = getBoundingBox().getRectangle().getMinX();
fixCornerY = getBoundingBox().getRectangle().getMinY();
break;
default:
break;
}
}
if (Double.isNaN(proportion)) {
proportion = getBoundingBox().getRectangle().getWidth()
/ getBoundingBox().getRectangle().getHeight();
}
if (Double.isNaN(oldWidth)) {
oldWidth = getBoundingBox().getRectangle().getWidth();
}
if (Double.isNaN(oldHeight)) {
oldHeight = getBoundingBox().getRectangle().getHeight();
}
}
/**
* update ellipse/circle by dragging corner handler
*
* @param hitHandler
* - handler was hit
* @param event
* - mouse event
*/
protected void updateEllipseCorner(EuclidianBoundingBoxHandler hitHandler,
AbstractEvent event) {
if (prewEllipse == null) {
prewEllipse = AwtFactory.getPrototype().newEllipse2DDouble(0, 0, 0,
0);
}
fixCornerCoords(hitHandler);
/*
* if (conic.getParentAlgorithm() != null && conic .getParentAlgorithm()
* instanceof AlgoEllipseHyperbolaFociPoint) { translatePoints(event);
* this.update(); //
* etBoundingBox().setRectangle(prewEllipse.getBounds()); return; }
*/
int dx = (int) (event.getX() - fixCornerX);
int dy = (int) (event.getY() - fixCornerY);
int width = dx;
int height = dy;
if (height >= 0) {
if (width >= 0) {
if (isCircle) {
prewEllipse.setFrame(fixCornerX, fixCornerY, width,
width);
} else {
prewEllipse.setFrame(fixCornerX, fixCornerY, width,
(int) (width / proportion));
}
} else { // width < 0
if (isCircle) {
prewEllipse.setFrame(
fixCornerX + width, fixCornerY, -width,
-width);
} else {
prewEllipse.setFrame(
fixCornerX + width, fixCornerY, -width,
(int) (-width / proportion));
}
}
} else { // height < 0
if (width >= 0) {
if (isCircle) {
prewEllipse.setFrame(fixCornerX, fixCornerY - width,
width, width);
} else {
int newHeight = (int) (width / proportion);
prewEllipse.setFrame(fixCornerX, fixCornerY - newHeight,
width, newHeight);
}
} else { // width < 0
if (isCircle) {
prewEllipse.setFrame(
fixCornerX + width, fixCornerY + width,
-width, -width);
} else {
int newHeight = (int) (-width / proportion);
prewEllipse.setFrame(
fixCornerX + width, fixCornerY - newHeight, -width,
newHeight);
}
}
}
getBoundingBox().setRectangle(prewEllipse.getBounds());
}
/**
* update ellipse/circle by dragging side handler
*
* @param hitHandler
* - handler was hit
* @param event
* - mouse event
*/
protected void updateEllipseSide(EuclidianBoundingBoxHandler hitHandler,
AbstractEvent event) {
if (prewEllipse == null) {
prewEllipse = AwtFactory.getPrototype().newEllipse2DDouble(0, 0, 0,
0);
}
fixCornerCoords(hitHandler);
int width = (int) (event.getX() - fixCornerX);
int height = (int) (event.getY() - fixCornerY);
if (hitHandler == EuclidianBoundingBoxHandler.LEFT
|| hitHandler == EuclidianBoundingBoxHandler.RIGHT) {
if (width >= 0) {
prewEllipse.setFrame(fixCornerX,
getBoundingBox().getRectangle().getMinY(),
width, getBoundingBox().getRectangle().getHeight());
} else { // width < 0
prewEllipse.setFrame(
fixCornerX + width,
getBoundingBox().getRectangle().getMinY(), -width,
getBoundingBox().getRectangle().getHeight());
}
}
if (hitHandler == EuclidianBoundingBoxHandler.TOP
|| hitHandler == EuclidianBoundingBoxHandler.BOTTOM) {
if (height >= 0) {
prewEllipse.setFrame(getBoundingBox().getRectangle().getMinX(),
fixCornerY, getBoundingBox().getRectangle().getWidth(),
height);
} else { // height < 0
prewEllipse.setFrame(getBoundingBox().getRectangle().getMinX(),
fixCornerY + height,
getBoundingBox().getRectangle().getWidth(), -height);
}
}
// with side handler dragging no circle anymore
setIsCircle(false);
proportion = Double.NaN;
getBoundingBox().setRectangle(prewEllipse.getBounds());
}
@Override
public void updateByBoundingBoxResize(AbstractEvent e,
EuclidianBoundingBoxHandler handler) {
if (isCornerHandler(handler)) {
if (conic.getParentAlgorithm() != null) {
fixCornerCoords(handler);
translatePointsForCornerHandler(e);
setFixCornerX(Double.NaN);
setFixCornerY(Double.NaN);
oldHeight = Double.NaN;
oldWidth = Double.NaN;
proportion = Double.NaN;
view.repaintView();
} else {
conic.setEuclidianVisible(false);
conic.updateRepaint();
updateEllipseCorner(handler, e);
}
} else {
if (conic.getParentAlgorithm() != null) {
fixCornerCoords(handler);
translatePointsForSideHandler(e);
setFixCornerX(Double.NaN);
setFixCornerY(Double.NaN);
oldHeight = Double.NaN;
oldWidth = Double.NaN;
proportion = Double.NaN;
view.repaintView();
} else {
conic.setEuclidianVisible(false);
conic.updateRepaint();
updateEllipseSide(handler, e);
}
}
view.setShapeEllipse(prewEllipse);
view.setShapeFillCol(conic.getFillColor());
view.setShapeObjCol(conic.getObjectColor());
view.setShapeStroke(EuclidianStatic.getStroke(
conic.getLineThickness() / 2.0,
conic.getLineType()));
view.getEuclidianController().setDynamicStylebarVisible(false);
view.repaintView();
}
private void translatePointsForSideHandler(AbstractEvent e) {
if (conic.getParentAlgorithm() != null) {
double pointsX[] = new double[conic.getParentAlgorithm().getInput().length];
double pointsY[] = new double[conic.getParentAlgorithm().getInput().length];
if (view.getHitHandler() == EuclidianBoundingBoxHandler.RIGHT
|| view.getHitHandler() == EuclidianBoundingBoxHandler.LEFT) {
fixCornerY = Double.NaN;
} else {
fixCornerX = Double.NaN;
}
GeoElement[] algoInput = conic.getParentAlgorithm().getInput();
if (!Double.isNaN(fixCornerX)) {
int width = (int) (e.getX() - fixCornerX);
double[] coords = new double[2];
for (int i = 0; i < conic.getParentAlgorithm()
.getInput().length; i++) {
coords[0] = view.toScreenCoordXd(
((GeoPointND) algoInput[i]).getInhomX());
coords[1] = view.toScreenCoordYd(
((GeoPointND) algoInput[i]).getInhomY());
// left or right side was moved
pointsX[i] = fixCornerX
+ (Math.abs(coords[0] - fixCornerX)) * width
/ oldWidth;
pointsY[i] = coords[1];
}
}
if (!Double.isNaN(fixCornerY)) {
int height = (int) (e.getY() - fixCornerY);
double[] coords = new double[2];
for (int i = 0; i < conic.getParentAlgorithm()
.getInput().length; i++) {
coords[0] = view.toScreenCoordXd(
((GeoPointND) algoInput[i]).getInhomX());
coords[1] = view.toScreenCoordYd(
((GeoPointND) algoInput[i]).getInhomY());
// bottom or top side was moved
pointsX[i] = coords[0];
pointsY[i] = fixCornerY
+ (Math.abs(coords[1] - fixCornerY)) * height
/ oldHeight;
}
}
for (int i = 0; i < conic.getParentAlgorithm()
.getInput().length; i++) {
((GeoPointND) conic.getParentAlgorithm().getInput(i)).setCoords(
view.toRealWorldCoordX(pointsX[i]),
view.toRealWorldCoordY(pointsY[i]), 1);
((GeoPointND) conic.getParentAlgorithm().getInput(i)).update();
((GeoPointND) conic.getParentAlgorithm().getInput(i))
.updateCascade();
}
conic.getParentAlgorithm().update();
conic.update();
}
}
private void translatePointsForCornerHandler(AbstractEvent e) {
if (conic.getParentAlgorithm() != null) {
int newWidth = (int) (e.getX() - fixCornerX);
int height = (int) (e.getY() - fixCornerY);
int newHeight = (int) (newWidth * oldHeight / oldWidth);
double ratioWidth = newWidth / oldWidth;
double ratioHeight = newHeight / oldHeight;
double pointsX[] = new double[conic.getParentAlgorithm()
.getInput().length];
double pointsY[] = new double[conic.getParentAlgorithm()
.getInput().length];
GeoElement[] algoInput = conic.getParentAlgorithm().getInput();
double[] currCoords = new double[2];
for (int i = 0; i < conic.getParentAlgorithm()
.getInput().length; i++) {
currCoords[0] = view.toScreenCoordXd(
((GeoPointND) algoInput[i]).getInhomX());
currCoords[1] = view.toScreenCoordYd(
((GeoPointND) algoInput[i]).getInhomY());
if (height >= 0) {
pointsX[i] = fixCornerX
+ (Math.abs(currCoords[0] - fixCornerX))
* ratioWidth;
if (newWidth >= 0) {
pointsY[i] = fixCornerY
+ (Math.abs(currCoords[1] - fixCornerY))
* ratioHeight;
} else {
pointsY[i] = fixCornerY
- (Math.abs(currCoords[1] - fixCornerY))
* ratioHeight;
}
}
// bottom or top left corner was moved
else {
pointsX[i] = fixCornerX
+ (Math.abs(currCoords[0] - fixCornerX))
* ratioWidth;
if (newWidth >= 0) {
pointsY[i] = fixCornerY
- (Math.abs(currCoords[1] - fixCornerY))
* ratioHeight;
} else {
pointsY[i] = fixCornerY
+ (Math.abs(currCoords[1] - fixCornerY))
* ratioHeight;
}
}
}
for (int i = 0; i < conic.getParentAlgorithm()
.getInput().length; i++) {
((GeoPointND) conic.getParentAlgorithm().getInput(i))
.setCoords(view.toRealWorldCoordX(pointsX[i]),
view.toRealWorldCoordY(pointsY[i]), 1);
((GeoPointND) conic.getParentAlgorithm().getInput(i)).update();
((GeoPointND) conic.getParentAlgorithm().getInput(i))
.updateCascade();
}
conic.getParentAlgorithm()
.update();
conic.update();
}
}
private double[] getEndPointRealCoords(AbstractEvent event) {
double[] coord = new double[2];
double startPointX = fixCornerX;
double startPointY = fixCornerY;
if (startPointX >= event.getX()) {
if (startPointY >= event.getY()) {
coord[0] = view.toRealWorldCoordX(startPointX
- getBoundingBox().getRectangle().getWidth());
if (isCircle) {
coord[1] = view.toRealWorldCoordY(
startPointY - getBoundingBox().getRectangle().getWidth());
} else {
coord[1] = view.toRealWorldCoordY(
getBoundingBox().getRectangle().getMinY());
}
} else {
coord[0] = view.toRealWorldCoordX(
startPointX
- getBoundingBox().getRectangle().getWidth());
if (isCircle) {
coord[1] = view.toRealWorldCoordY(
startPointY
+ getBoundingBox().getRectangle().getWidth());
} else {
coord[1] = view.toRealWorldCoordY(
getBoundingBox().getRectangle().getMaxY());
}
}
} else {
if (startPointY >= event.getY()) {
coord[0] = view.toRealWorldCoordX(
startPointX
+ getBoundingBox().getRectangle().getWidth());
if (isCircle) {
coord[1] = view.toRealWorldCoordY(
startPointY
- getBoundingBox().getRectangle().getWidth());
} else {
coord[1] = view.toRealWorldCoordY(
getBoundingBox().getRectangle().getMinY());
}
} else {
coord[0] = view.toRealWorldCoordX(
startPointX + getBoundingBox().getRectangle().getWidth());
if (isCircle) {
coord[1] = view.toRealWorldCoordY(
startPointY
+ getBoundingBox().getRectangle().getWidth());
} else {
coord[1] = view.toRealWorldCoordY(
getBoundingBox().getRectangle().getMaxY());
}
}
}
return coord;
}
/**
* update geo of drawable
*
* @param event
* - mouse event
*/
@Override
public void updateGeo(AbstractEvent event) {
if (conic.getParentAlgorithm() != null) {
fixCornerCoords(view.getHitHandler());
if (isCornerHandler(view.getHitHandler())) {
translatePointsForCornerHandler(event);
} else {
translatePointsForSideHandler(event);
}
conic.setSelected(true);
conic.updateRepaint();
this.update();
setFixCornerX(Double.NaN);
setFixCornerY(Double.NaN);
oldHeight = Double.NaN;
oldWidth = Double.NaN;
proportion = Double.NaN;
view.repaintView();
return;
}
Equation equ = getEquationOfConic(event);
if (equ != null) {
equ.initEquation();
ValidExpression ve = equ.wrap();
ve.setLabel(conic.getLabelSimple());
try {
view.getKernel().getAlgebraProcessor()
.processValidExpression(ve);
} catch (MyError e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
conic.setEuclidianVisible(true);
conic.setSelected(true);
conic.updateRepaint();
this.update();
view.setShapeEllipse(null);
setFixCornerX(Double.NaN);
setFixCornerY(Double.NaN);
view.repaintView();
}
private Equation getEquationOfConic(AbstractEvent event) {
// real coords
double startX = view
.toRealWorldCoordX(fixCornerX);
double startY = view
.toRealWorldCoordY(fixCornerY);
double endX, endY;
double[] coords = getEndPointRealCoords(event);
endX = coords[0];
endY = coords[1];
if (Double.isNaN(startX) || Double.isNaN(startY) || Double.isNaN(endX)
|| Double.isNaN(endY)) {
return null;
}
// coords of center
double centerX = (startX + endX) / 2;
double centerY = (startY + endY) / 2;
// minor and major axis
double minAx = Math.hypot(centerX - centerX, centerY - endY);
double majAx = Math.hypot(centerX - endX, centerY - centerY);
// construct equation (x-center_x)^2 / b^2 + (y-center_y)^2 / a^2 = 1
FunctionVariable xx = new FunctionVariable(view.getKernel(), "x");
FunctionVariable yy = new FunctionVariable(view.getKernel(), "y");
ExpressionNode rhs = new ExpressionNode(view.getKernel(), 1);
ExpressionNode expCenterX = new ExpressionNode(view.getKernel(),
centerX);
ExpressionNode expCenterY = new ExpressionNode(view.getKernel(),
centerY);
ExpressionNode expA = new ExpressionNode(view.getKernel(), minAx);
ExpressionNode expB = new ExpressionNode(view.getKernel(), majAx);
ExpressionNode leftNumerator = new ExpressionNode(view.getKernel(), xx,
Operation.MINUS, expCenterX);
ExpressionNode leftNumeratorSqr = new ExpressionNode(view.getKernel(),
leftNumerator, Operation.POWER,
new ExpressionNode(view.getKernel(), 2));
ExpressionNode leftDenom = new ExpressionNode(view.getKernel(), expB,
Operation.POWER, new ExpressionNode(view.getKernel(), 2));
ExpressionNode leftLhs = new ExpressionNode(view.getKernel(),
leftNumeratorSqr, Operation.DIVIDE, leftDenom);
ExpressionNode rightNumerator = new ExpressionNode(view.getKernel(), yy,
Operation.MINUS, expCenterY);
ExpressionNode rightNumeatorSqr = new ExpressionNode(view.getKernel(),
rightNumerator, Operation.POWER,
new ExpressionNode(view.getKernel(), 2));
ExpressionNode rightDenom = new ExpressionNode(view.getKernel(), expA,
Operation.POWER, new ExpressionNode(view.getKernel(), 2));
ExpressionNode rightLhs = new ExpressionNode(view.getKernel(),
rightNumeatorSqr, Operation.DIVIDE, rightDenom);
ExpressionNode lhs = new ExpressionNode(view.getKernel(), leftLhs,
Operation.PLUS, rightLhs);
Equation equ = new Equation(view.getKernel(), lhs, rhs);
return equ;
}
}