/* 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. */ package org.geogebra.common.kernel.kernelND; import java.util.ArrayList; import java.util.TreeSet; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.MatrixTransformable; import org.geogebra.common.kernel.Path; import org.geogebra.common.kernel.PathMover; import org.geogebra.common.kernel.PathMoverGeneric; import org.geogebra.common.kernel.PathNormalizer; import org.geogebra.common.kernel.PathParameter; import org.geogebra.common.kernel.RegionParameters; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.CoordMatrix; import org.geogebra.common.kernel.Matrix.CoordSys; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoConicFivePoints; import org.geogebra.common.kernel.algos.AlgoEllipseFociLength; import org.geogebra.common.kernel.algos.AlgoEllipseHyperbolaFociPoint; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.Function; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.geos.Dilateable; import org.geogebra.common.kernel.geos.FromMeta; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.geos.GeoVec2D; import org.geogebra.common.kernel.geos.GeoVector; import org.geogebra.common.kernel.geos.Mirrorable; import org.geogebra.common.kernel.geos.PointRotateable; import org.geogebra.common.kernel.geos.Transformable; import org.geogebra.common.kernel.geos.Translateable; import org.geogebra.common.kernel.implicit.GeoImplicit; import org.geogebra.common.kernel.integration.EllipticArcLength; import org.geogebra.common.main.Feature; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.ExtendedBoolean; import org.geogebra.common.util.GgbMat; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * Class for conic in any dimension. * * @author Mathieu * */ public abstract class GeoConicND extends GeoQuadricND implements Path, Translateable, GeoConicNDConstants, MatrixTransformable, PointRotateable, Transformable, Mirrorable, Dilateable, GeoCoordSys2D, FromMeta { /** avoid very large and small coefficients for numerical stability */ protected static final double MAX_COEFFICIENT_SIZE = 100000; /** avoid very large and small coefficients for numerical stability */ protected static final double MIN_COEFFICIENT_SIZE = 1; /** mode for equations like ax^2+bxy+cy^2+dx+ey+f=0 */ public static final int EQUATION_IMPLICIT = 0; /** mode for equations like y=ax^2+bx+c */ public static final int EQUATION_EXPLICIT = 1; /** mode for equations like (x-m)^2/a^2+(y-n)^2/b^2=1 */ public static final int EQUATION_SPECIFIC = 2; /** X=(1,1)+(sin(t),cos(t)) */ public static final int EQUATION_PARAMETRIC = 3; /** user defined equation form */ public static final int EQUATION_USER = 4; /** vertex form **/ public static final int EQUATION_VERTEX = 5; /** conic form **/ public static final int EQUATION_CONICFORM = 6; /** variable strings for default output */ final private static String[] vars = { "x\u00b2", "x y", "y\u00b2", "x", "y" }; /** variable strings for LaTeX output */ final private static String[] varsLateX = { "x^{2}", "x y", "y^{2}", "x", "y" }; /** variable strings for CAS output */ final private static String[] varsCAS = { "x^2", "x*y", "y^2", "x", "y" }; /** enable negative sign of first coefficient in implicit equations */ protected final static boolean KEEP_LEADING_SIGN = false; /** point in case of single point degenerate conic */ protected GeoPoint singlePoint; /** lines of which this conic consists in case it's degenerate */ public GeoLine[] lines; /** two Eigenvectors (unit vectors), set by setEigenvectors() */ public final GeoVec2D[] eigenvec = { new GeoVec2D(kernel, 1, 0), new GeoVec2D(kernel, 0, 1) }; /** * (eigenvecX, eigenvecY) are coords of currently calculated first * eigenvector (eigenvecX, eigenvecY) is not a unit vector */ protected double eigenvecX; /** @see #eigenvecX */ protected double eigenvecY; /** eigenvector-real world transformation */ protected GAffineTransform transform; /** old value of transform */ protected GAffineTransform oldTransform; /** translation vector (midpoint, vertex) */ public GeoVec2D b = new GeoVec2D(kernel); /** start points for lines in degenerate cases */ protected GeoPoint[] startPoints; /** points on this conic */ protected ArrayList<GeoPointND> pointsOnConic; // for classification transient private double detS, length, temp, temp1, temp2, nx, ny, lambda; private int index = 0; private GeoVec2D c = new GeoVec2D(kernel); /** error DetS */ public double errDetS = Kernel.STANDARD_PRECISION; private boolean isShape = false; /** * * @param i * index of eigenvector * @return eigen vector in native dimension of the conic */ public Coords getEigenvec(int i) { return new Coords(eigenvec[i].getCoords()); } /** * * @param i * index of eigenvector * @return eigen vector in dimension 3 */ @Override abstract public Coords getEigenvec3D(int i); /** * If 2D conic, return identity (xOy plane) * * @return coord sys where the conic lies */ @Override abstract public CoordSys getCoordSys(); /** * * @param i * index of line * @return the direction in case of line(s) */ abstract public Coords getDirection3D(int i); /** * * @param i * index of line * @return the origin of lines in case of parallel lines */ abstract public Coords getOrigin3D(int i); /* * private CoordMatrix eigenMatrix2D = new CoordMatrix(3,3); * * /* update the 2D eigen matrix (should be called each * * public void updateEigenMatrix2D(){ * * eigenMatrix2D.setOrigin(getMidpoint()); * eigenMatrix2D.setVx(getEigenvec(0)); eigenMatrix2D.setVy(getEigenvec(1)); * } */ /** * default constructor * * @param c * construction * @param dimension * dimension */ public GeoConicND(Construction c, int dimension) { this(c, dimension, false); } /** * default constructor * * @param c * construction * @param dimension * dimension * @param isIntersection * if this is an intersection curve */ public GeoConicND(Construction c, int dimension, boolean isIntersection) { super(c, dimension, isIntersection); toStringMode = EQUATION_IMPLICIT; } /** * @return the matrix representation of the conic in its 2D sub space */ @Override protected CoordMatrix getSymetricMatrix(double[] vals) { CoordMatrix ret = new CoordMatrix(3, 3); ret.set(1, 1, vals[0]); ret.set(2, 2, vals[1]); ret.set(3, 3, vals[2]); ret.set(1, 2, vals[3]); ret.set(2, 1, vals[3]); ret.set(1, 3, vals[4]); ret.set(3, 1, vals[4]); ret.set(2, 3, vals[5]); ret.set(3, 2, vals[5]); return ret; } /** * makes this conic a circle with midpoint M and radius r * * @param M * center * @param r * radius */ final public void setCircle(GeoPoint M, double r) { setSphereND(M, r); } /** * makes this conic a circle with midpoint M through Point P * * @param M * center * @param P * point */ final public void setCircle(GeoPoint M, GeoPoint P) { defined = M.isDefined() && P.isDefined() && !P.isInfinite(); if (!defined) { return; } if (M.isInfinite()) { // midpoint at infinity -> parallelLines // one through P, the other through infinite point M /* * b.x = P.inhomX; b.y = P.inhomY; */ double[] coords = new double[3]; P.getCoords(coords); setMidpoint(coords); // M is normalvector of double line eigenvecX = -M.y; eigenvecY = M.x; findEigenvectors(); halfAxes[0] = Double.POSITIVE_INFINITY; halfAxes[1] = Double.POSITIVE_INFINITY; mu[0] = 0.0; // line at infinity is not drawn parallelLines(mu); // set line at infinity 0 = 1 lines[1].x = Double.NaN; lines[1].y = Double.NaN; lines[1].z = Double.NaN; // set degenerate matrix matrix[0] = 0.0d; matrix[1] = 0.0d; matrix[2] = lines[0].z; matrix[3] = 0.0d; matrix[4] = lines[0].x / 2.0; matrix[5] = lines[0].y / 2.0; } else { setCircleMatrix(M, M.distance(P)); } setAffineTransform(); } @Override public void setSphereND(GeoPointND M, double r) { defined = ((GeoElement) M).isDefined() && !M.isInfinite(); // check // midpoint setSphereND(M.getInhomCoordsInD2(), r); } ////////////////////////////////////// // PATH INTERFACE ////////////////////////////////////// @Override public boolean isPath() { return true; } private double[] tmpDouble4; /** * compute closest t parameter to point P * * @param P * point * @return t parameter */ public double getClosestParameterForParabola(GeoPointND P) { Coords coords = P.getCoordsInD2(getCoordSys()); coordsRWtoEV(coords); double x = coords.getX(); double y = coords.getY(); if (tmpDouble4 == null) { tmpDouble4 = new double[4]; } // solve PM.dM=0 tmpDouble4[3] = p / 2; tmpDouble4[2] = 0; tmpDouble4[1] = p - x; tmpDouble4[0] = -y; int nRoots = cons.getKernel().getEquationSolver().solveCubic(tmpDouble4, tmpDouble4, Kernel.STANDARD_PRECISION); // find closest root double dist = Double.POSITIVE_INFINITY; double param = 0; for (int i = 0; i < nRoots; i++) { double t = tmpDouble4[i]; double yt = p * t; double xt = yt * t / 2.0; double dx = xt - x; double dy = yt - y; double d = dx * dx + dy * dy; if (d < dist) { dist = d; param = t; } // //debug // coords.setX(xt); // coords.setY(yt); // coords.setZ(1); // coordsEVtoRW(coords); // Log.debug("root #" + i + ": (" + coords.getX() + "," // + coords.getY() + ") , d=" + d); } return param; } /** * * @param t * parameter on parabola * @return curvature value */ public double evaluateCurvatureForParabola(double t) { double s = Math.sqrt(1 + t * t); return 1 / (p * s * s * s); } /** * evaluate first derivative for parameter t * * @param t * parameter * @param result * (x,y) first derivative */ public void evaluateFirstDerivativeForParabola(double t, double[] result) { Coords eigenvec0 = getEigenvec(0); Coords eigenvec1 = getEigenvec(1); result[0] = p * (t * eigenvec0.getX() + eigenvec1.getX()); result[1] = p * (t * eigenvec0.getY() + eigenvec1.getY()); } /** * evaluate second derivative for parameter t * * @param t * parameter * @param result * (x,y) second derivative */ public void evaluateSecondDerivativeForParabola(double t, double[] result) { Coords eigenvec0 = getEigenvec(0); result[0] = p * eigenvec0.getX(); result[1] = p * eigenvec0.getY(); } @Override public void pointChanged(GeoPointND P) { Coords coords = P.getCoordsInD2(getCoordSys()); PathParameter pp = P.getPathParameter(); pointChanged(coords, pp); P.setCoords2D(coords.getX(), coords.getY(), coords.getZ()); P.updateCoordsFrom2D(false, getCoordSys()); P.updateCoords(); } /** * Edited by: Kai Chung Tam Date: 4/6/2011 Fixed case CONIC_ELLIPSE, * CONIC_HYPERBOLA and CONIC_PARABOLA * * @param P * a point * @param pp * path parameter of the point */ public void pointChanged(Coords P, PathParameter pp) { pointChanged(P, pp, true); } /** * Edited by: Kai Chung Tam Date: 4/6/2011 Fixed case CONIC_ELLIPSE, * CONIC_HYPERBOLA and CONIC_PARABOLA * * @param P * a point * @param pp * path parameter of the point * @param checkSection * check the section (if exists) */ public void pointChanged(Coords P, PathParameter pp, boolean checkSection) { double px, py, ha, hb, hc_2; double abspx, abspy; // for parabola and hyperbola double tolerance = Kernel.STANDARD_PRECISION; // required precision // (robustness not // proven) pp.setPathType(type); switch (type) { default: case CONIC_EMPTY: P.setX(Double.NaN); P.setY(Double.NaN); P.setZ(Double.NaN); break; case CONIC_SINGLE_POINT: P.setX(singlePoint.x); P.setY(singlePoint.y); P.setZ(singlePoint.z); break; case CONIC_INTERSECTING_LINES: case CONIC_PARALLEL_LINES: /* * For line conics, we use the parameter ranges first line: t = (-1, * 1) second line: t = (1, 3) and convert this to s = (-inf, inf) * using first line: s = t /(1 - abs(t)) second line: s = (t-2) /(1 * - abs(t-2)) which allows us to use the line's path parameter s */ // choose closest line boolean firstLine = lines[0].distanceHom(P) <= lines[1] .distanceHom(P); GeoLine line = firstLine ? lines[0] : lines[1]; // compute line path parameter line.doPointChanged(P, pp); // convert line parameter to (-1,1) pp.setT(PathNormalizer.inverseInfFunction(pp.getT())); if (!firstLine) { pp.setT(pp.getT() + 2);// convert from (-1,1) to (1,3) } break; case CONIC_LINE: case CONIC_DOUBLE_LINE: getLines(); lines[0].doPointChanged(P, pp); break; case CONIC_CIRCLE: // transform to eigenvector coord-system coordsRWtoEV(P); // calc parameter px = P.getX() / P.getZ(); py = P.getY() / P.getZ(); // Application.debug("px,py="+px+","+py); // relation between the internal parameter t and the angle theta: // t = atan(a/b tan(theta)) where tan(theta) = py / px pp.setT(Math.atan2(halfAxes[0] * py, halfAxes[1] * px)); // calc Point on conic using this parameter P.setX(halfAxes[0] * Math.cos(pp.getT())); P.setY(halfAxes[1] * Math.sin(pp.getT())); P.setZ(1.0); // transform back to real world coord system coordsEVtoRW(P); break; case CONIC_ELLIPSE: // transform to eigenvector coord-system coordsRWtoEV(P); // calc parameter px = P.getX() / P.getZ(); py = P.getY() / P.getZ(); abspx = Math.abs(px); abspy = Math.abs(py); ha = halfAxes[0]; hb = halfAxes[1]; hc_2 = ha * ha - hb * hb; // special case handling from: // http://cdserv1.wbut.ac.in/81-8147-617-4/Linux/MagicSoftware/WildMagic2/Documentation/DistancePointToEllipse2.pdf if (abspx < Kernel.STANDARD_PRECISION) { // pp.setT(Math.asin(Math.max(-1,-hb*abspy/hc_2))); if (abspy < Kernel.STANDARD_PRECISION) { if (hb < ha) { pp.setT(Math.PI / 2); } else { pp.setT(0); } } else { if (hb < ha) { pp.setT(Math.PI / 2); } else { if (abspy * hb < hc_2) { pp.setT(Math.asin(hb * abspy / hc_2)); } else { pp.setT(Math.PI / 2); } } } } else if (abspy < Kernel.STANDARD_PRECISION) { // pp.setT(Math.acos(Math.min(1,ha*abspx/hc_2))); if (ha < hb) { pp.setT(0); } else { if (abspx * ha < hc_2) { pp.setT(Math.acos(ha * abspx / hc_2)); } else { pp.setT(0); } } } else { // To solve (1-u^2)*(b*py + (a^2-b^2)*u)^2-a^2*px^2*u^2 = 0, // where u = sin(theta) double roots[] = getPerpendicularParams(abspx, abspy); if (roots[0] > 0) { pp.setT(Math.asin(roots[0])); } else if (roots[1] > 0) { pp.setT(Math.asin(roots[1])); } else if (roots[2] > 0) { pp.setT(Math.asin(roots[2])); } else { pp.setT(Math.asin(roots[3])); } } // transform the parameter if (px,py) is not in the first quadrant. if (px < 0) { pp.setT(Math.PI - pp.getT()); } if (py < 0) { pp.setT(-pp.getT()); } P.setX(ha * Math.cos(pp.getT())); P.setY(hb * Math.sin(pp.getT())); P.setZ(1.0); // transform back to real world coord system coordsEVtoRW(P); break; case CONIC_HYPERBOLA: /* * For hyperbolas, we use the parameter ranges right branch: t = * (-1, 1) left branch: t = (1, 3) and get this from s = (-inf, inf) * using right branch: s = t /(1 - abs(t)) where we use the * parameter form (a*cosh(s), b*sinh(s)) for the right branch of the * hyperbola. */ // transform to eigenvector coord-system coordsRWtoEV(P); // calc parameter px = P.getX() / P.getZ(); py = P.getY() / P.getZ(); abspx = Math.abs(px); abspy = Math.abs(py); ha = halfAxes[0]; hb = halfAxes[1]; hc_2 = ha * ha + hb * hb; double s; if (abspy < Kernel.STANDARD_PRECISION) { s = MyMath.acosh(Math.max(1, ha * abspx / hc_2)); } else { // To solve (1+u^2)*(-(b^2+a^2)*u +b*py)^2 - a^2*px^2, where // u=sinh(t) double[] roots = getPerpendicularParams(abspx, abspy); if (roots[0] > 0) { s = MyMath.asinh(roots[0]); } else if (roots[1] > 0) { s = MyMath.asinh(roots[1]); } else if (roots[2] > 0) { s = MyMath.asinh(roots[2]); } else { s = MyMath.asinh(roots[3]); } } // transform the s-parameter if (px,py) is not in the first // quadrant. if (py < 0) { // lower-half plane s = -s; } // compute t in (-1,1) from s in (-inf, inf) pp.setT(PathNormalizer.inverseInfFunction(s)); P.setX(ha * Math.cosh(s)); P.setY(hb * Math.sinh(s)); P.setZ(1.0); if (px < 0) { // left branch pp.setT(pp.getT() + 2); // convert (-1,1) to (1,3) P.setX(-P.getX()); } // transform back to real world coord system coordsEVtoRW(P); break; case CONIC_PARABOLA: // transform to eigenvector coord-system coordsRWtoEV(P); // calculate parameters. consider only the upper-half plane. px = P.getX() / P.getZ(); py = P.getY() / P.getZ(); abspy = Math.abs(py); if (abspy < tolerance) { // Point is on x-axis pp.setT(Math.sqrt(Math.max(0, 2 * (px - p) / p))); } else { // binary search double[] eqn = { abspy, -p + px, 0, -p / 2 }; double[] roots = { 0, 0, 0 }; cons.getKernel().getEquationSolver().solveCubic(eqn, roots, Kernel.STANDARD_PRECISION); if (roots[0] > 0) { pp.setT(roots[0]); } else if (roots[1] > 0) { pp.setT(roots[1]); } else { pp.setT(roots[2]); } if (py < 0) { pp.setT(-pp.getT()); } } P.setX(p * pp.getT() * pp.getT() / 2.0); P.setY(p * pp.getT()); P.setZ(1.0); // transform back to real world coord system coordsEVtoRW(P); break; } } /** * @param P * point * @return array of parameters t such that Line[point[this,t],P] is * perpendicular to this */ public double[] getPerpendicularParams(Coords P) { coordsRWtoEV(P); // calc parameter double px = P.getX() / P.getZ(); double py = P.getY() / P.getZ(); double abspx = Math.abs(px); double abspy = Math.abs(py); return getPerpendicularParams(abspx, abspy); } private double[] getPerpendicularParams(double abspx, double abspy) { double ha = halfAxes[0]; double hb = halfAxes[1]; double bpy = hb * abspy; double[] roots = { 0, 0, 0, 0 }; double[] eqn; if (type == CONIC_ELLIPSE) { double hc_2 = ha * ha - hb * hb; eqn = new double[] { bpy * bpy, 2 * bpy * hc_2, -bpy * bpy + hc_2 * hc_2 - ha * ha * abspx * abspx, -2 * bpy * hc_2, -hc_2 * hc_2 }; } else { double hc_2 = ha * ha + hb * hb; eqn = new double[] { bpy * bpy, -2 * bpy * hc_2, bpy * bpy + hc_2 * hc_2 - ha * ha * abspx * abspx, -2 * bpy * hc_2, hc_2 * hc_2 }; } cons.getKernel().getEquationSolver().solveQuartic(eqn, roots, Kernel.STANDARD_PRECISION); return roots; } /* * Edited by Kai Chung Tam */ @Override public void pathChanged(GeoPointND P) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(P)) { pointChanged(P); return; } // Application.debug(getEigenvec(0)); Coords coords = P.getCoordsInD2(getCoordSys()); PathParameter pp = P.getPathParameter(); // Application.debug(P.getCoordsInD3()+"\n2D:\n"+coords+"\npp="+pp.getT()); pathChanged(coords, pp); P.setCoords2D(coords.getX(), coords.getY(), coords.getZ()); P.updateCoordsFrom2D(false, getCoordSys()); // Application.debug("after:\n"+P.getCoordsInD3()+"\n2D:\n"+coords); } private boolean compatibleType(int t) { if (type == t) { return true; } // the conic type change temporarily to point or empty conic -- // once the conic returns back, we want the old parameter to be used if (t == CONIC_EMPTY || t == CONIC_SINGLE_POINT) { return true; } return false; } /** * check if compatible types * * @param P * point * @param pp * path parameter */ protected void pathChanged(Coords P, PathParameter pp) { // if type of path changed (other conic) then we // have to recalc the parameter with pointChanged() if (!compatibleType(pp.getPathType()) || Double.isNaN(pp.getT())) { pointChanged(P, pp); return; } pathChangedWithoutCheck(P, pp, true); } /** * @param P * point * @param pp * path parameter * @param checkSection * check section parts (if exist) */ protected void pathChangedWithoutCheckEllipse(Coords P, PathParameter pp, boolean checkSection) { // calc Point on conic using this parameter (in eigenvector space) P.setX(halfAxes[0] * Math.cos(pp.getT())); P.setY(halfAxes[1] * Math.sin(pp.getT())); P.setZ(1.0); // transform back to real world coord system coordsEVtoRW(P); } /** * @param P * point * @param pp * path parameter * @param checkSection * check section parts (if exist) */ protected void pathChangedWithoutCheckParabola(Coords P, PathParameter pp, boolean checkSection) { P.setY(p * pp.getT()); P.setX(P.getY() * pp.getT() / 2.0); P.setZ(1.0); // transform back to real world coord system coordsEVtoRW(P); } /** * @param P * point * @param pp * path parameter * @param checkSection * check section parts (if exist) */ protected void pathChangedWithoutCheckHyperbola(Coords P, PathParameter pp, boolean checkSection) { /* * For hyperbolas, we use the parameter ranges right branch: t = (-1, 1) * left branch: t = (1, 3) and convert this to s = (-inf, inf) using * right branch: s = t /(1 - abs(t)) left branch: s = (t-2) /(1 - * abs(t-2)) which allows us to use the parameter form (a*cosh(s), * b*sinh(s)) for the right branch of the hyperbola. */ boolean leftBranch = pp.getT() > 1; double t = leftBranch ? pp.getT() - 2 : pp.getT(); double s = t / (1 - Math.abs(t)); P.setX(halfAxes[0] * MyMath.cosh(s)); P.setY(halfAxes[1] * MyMath.sinh(s)); P.setZ(1.0); if (leftBranch) { P.setX(-P.getX()); } // transform back to real world coord system coordsEVtoRW(P); } /** * @param P * point * @param pp * path parameter * @param checkSection * check section parts (if exist) */ public void pathChangedWithoutCheck(Coords P, PathParameter pp, boolean checkSection) { switch (type) { default: case CONIC_EMPTY: P.setX(Double.NaN); P.setY(Double.NaN); P.setZ(Double.NaN); break; case CONIC_SINGLE_POINT: P.setX(singlePoint.x); P.setY(singlePoint.y); P.setZ(singlePoint.z); break; case CONIC_INTERSECTING_LINES: case CONIC_PARALLEL_LINES: /* * For line conics, we use the parameter ranges first line: t = (-1, * 1) second line: t = (1, 3) and convert this to s = (-inf, inf) * using first line: s = t /(1 - abs(t)) second line: s = (t-2) /(1 * - abs(t-2)) which allows us to use the line's path parameter s */ double pathParam = pp.getT(); boolean leftBranch = pathParam > 1; pp.setT(leftBranch ? pathParam - 2 : pathParam); // convert from (-1,1) to (-inf, inf) line path parameter pp.setT(pp.getT() / (1 - Math.abs(pp.getT()))); if (leftBranch) { lines[1].pathChanged(P, pp); } else { lines[0].pathChanged(P, pp); } // set our path parameter again pp.setT(pathParam); break; case CONIC_LINE: case CONIC_DOUBLE_LINE: lines[0].pathChanged(P, pp); break; case CONIC_CIRCLE: case CONIC_ELLIPSE: pathChangedWithoutCheckEllipse(P, pp, checkSection); break; case CONIC_HYPERBOLA: pathChangedWithoutCheckHyperbola(P, pp, checkSection); break; case CONIC_PARABOLA: pathChangedWithoutCheckParabola(P, pp, checkSection); break; } } /** * Returns the largest possible parameter value for this path * * @return the largest possible parameter value for this path (may be * Double.POSITIVE_INFINITY) */ @Override public double getMaxParameter() { switch (type) { case CONIC_DOUBLE_LINE: case CONIC_PARABOLA: case CONIC_LINE: return Double.POSITIVE_INFINITY; case CONIC_CIRCLE: case CONIC_ELLIPSE: return Math.PI; case CONIC_HYPERBOLA: case CONIC_INTERSECTING_LINES: case CONIC_PARALLEL_LINES: // For hyperbolas and line conics, we use the parameter ranges // right branch: t = (-1, 1) // left branch: t = (1, 3) return 3; case CONIC_EMPTY: case CONIC_SINGLE_POINT: default: return 0; } } /** * Returns the smallest possible parameter value for this path * * @return the smallest possible parameter value for this path (may be * Double.NEGATIVE_INFINITY) */ @Override public double getMinParameter() { switch (type) { case CONIC_PARABOLA: case CONIC_DOUBLE_LINE: case CONIC_LINE: return Double.NEGATIVE_INFINITY; case CONIC_HYPERBOLA: case CONIC_INTERSECTING_LINES: case CONIC_PARALLEL_LINES: // For hyperbolas and line conics, we use the parameter ranges // right branch: t = (-1, 1) // left branch: t = (1, 3) return -1; case CONIC_CIRCLE: case CONIC_ELLIPSE: return -Math.PI; case CONIC_EMPTY: case CONIC_SINGLE_POINT: default: return 0; } } @Override public boolean isClosedPath() { switch (type) { case CONIC_CIRCLE: case CONIC_ELLIPSE: return true; default: return false; } } @Override public boolean isOnPath(GeoPointND P, double eps) { if (P.getPath() == this) { return true; } return isOnFullConic(P, eps); } /** * states wheter P lies on this conic or not. Note: this method is not * overwritten by subclasses like isIntersectionPointIncident() * * @return true P lies on this conic * @param P * point * @param eps * precision */ public final boolean isOnFullConic(GeoPointND P, double eps) { if (!P.isDefined()) { return false; } return isOnFullConic(P.getCoordsInD2(), eps); } /** * @param P * point * @param eps * precision * @return true if point is on path with given precision */ public final boolean isOnFullConic(Coords P, double eps) { switch (type) { case GeoConicNDConstants.CONIC_SINGLE_POINT: Coords singlePointCoords = new Coords(singlePoint.x, singlePoint.y, singlePoint.z); return P.distance(singlePointCoords) < eps; case GeoConicNDConstants.CONIC_INTERSECTING_LINES: case GeoConicNDConstants.CONIC_DOUBLE_LINE: case GeoConicNDConstants.CONIC_PARALLEL_LINES: return lines[0].isOnFullLine(P, eps) || lines[1].isOnFullLine(P, eps); case GeoConicNDConstants.CONIC_LINE: return lines[0].isOnFullLine(P, eps); case GeoConicNDConstants.CONIC_EMPTY: return false; } // if we get here let's handle the remaining cases // remember coords of P double Px = P.getX(); double Py = P.getY(); double Pz = P.getZ(); // convert P to eigenvector coord system if (type == GeoConicNDConstants.CONIC_CIRCLE) { translateByCenter(P); } else { coordsRWtoEV(P); } double px = P.getX() / P.getZ(); double py = P.getY() / P.getZ(); boolean result = false; switch (type) { default: // do nothing break; case GeoConicNDConstants.CONIC_CIRCLE: // x^2 + y^2 = r^2 double radius2 = halfAxes[0] * halfAxes[0]; result = Kernel.isEqual(px * px / radius2 + py * py / radius2, 1, eps); break; case GeoConicNDConstants.CONIC_ELLIPSE: // x^2/a^2 + y^2/b^2 = 1 result = Kernel.isEqual(px * px / (halfAxes[0] * halfAxes[0]) + py * py / (halfAxes[1] * halfAxes[1]), 1, eps); break; case GeoConicNDConstants.CONIC_HYPERBOLA: // x^2/a^2 - y^2/b^2 = 1 result = Kernel.isEqual(px * px / (halfAxes[0] * halfAxes[0]), 1 + py * py / (halfAxes[1] * halfAxes[1]), eps); break; case GeoConicNDConstants.CONIC_PARABOLA: // y^2 = 2 p x result = Kernel.isEqual(py * py, 2 * p * px, eps); break; } // restore coords of P P.setX(Px); P.setY(Py); P.setZ(Pz); return result; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } /** * Transforms coords of point P from Eigenvector space to real world space. * * @param P * 2D point in EV coords */ protected final void coordsEVtoRW(Coords P) { // rotate by alpha double px = P.getX(); double py = P.getY(); Coords eigenvec0 = getEigenvec(0); Coords eigenvec1 = getEigenvec(1); P.setX(px * eigenvec0.getX() + py * eigenvec1.getX()); P.setY(px * eigenvec0.getY() + py * eigenvec1.getY()); // translate by b Coords mid = getMidpoint(); P.setX(P.getX() + P.getZ() * mid.getX()); P.setY(P.getY() + P.getZ() * mid.getY()); } /** * Transforms coords of point P from real world space to Eigenvector space. * * @param P * 2D point in EV coords */ private void coordsRWtoEV(Coords P) { Coords mid = getMidpoint(); // translate by -b P.setX(P.getX() - P.getZ() * mid.getX()); P.setY(P.getY() - P.getZ() * mid.getY()); // rotate by -alpha double px = P.getX(); double py = P.getY(); Coords eigenvec0 = getEigenvec(0); Coords eigenvec1 = getEigenvec(1); P.setX(px * eigenvec0.getX() + py * eigenvec0.getY()); P.setY(px * eigenvec1.getX() + py * eigenvec1.getY()); } private void translateByCenter(Coords P) { Coords mid = getMidpoint(); // translate by -b P.setX(P.getX() - P.getZ() * mid.getX()); P.setY(P.getY() - P.getZ() * mid.getY()); } @Override public boolean isFillable() { return true; } //////////////////////////////////// // MATRIX AND EQUATION //////////////////////////////////// /** * sets the matrix values from the symmetric matrix m * * @param m * matrix */ @Override final public void setMatrix(CoordMatrix m) { setDefinition(null); matrix[0] = m.get(1, 1); matrix[1] = m.get(2, 2); matrix[2] = m.get(3, 3); matrix[3] = (m.get(1, 2) + m.get(2, 1)) / 2.0; matrix[4] = (m.get(1, 3) + m.get(3, 1)) / 2.0; matrix[5] = (m.get(2, 3) + m.get(3, 2)) / 2.0; classifyConic(); } //////////////////////////////////// // FROM GEOCONIC //////////////////////////////////// /** * Returns a list of points that this conic passes through. May return null. * * @return list of points that this conic passes through. */ public final ArrayList<GeoPointND> getPointsOnConic() { return pointsOnConic; } /** * Sets a list of points that this conic passes through. This method should * only be used by AlgoMacro. * * @param points * list of points that this conic passes through */ public final void setPointsOnConic(ArrayList<GeoPointND> points) { pointsOnConic = points; } /** * Adds a point to the list of points that this conic passes through. * * @param pt * point */ public final void addPointOnConic(GeoPointND pt) { if (pointsOnConic == null) { pointsOnConic = new ArrayList<GeoPointND>(); } if (!pointsOnConic.contains(pt)) { pointsOnConic.add(pt); } } /** * Removes a point from the list of points that this conic passes through. * * @param pt * Point to be removed */ public final void removePointOnConic(GeoPointND pt) { if (pointsOnConic != null) { pointsOnConic.remove(pt); } } /** * geo is expected to be a conic. make deep copy of member vars of geo. */ @Override public void set(GeoElementND geo) { if (geo instanceof GeoLine) { GeoLine line = (GeoLine) geo; this.setCoeffs(0, 0, 0, line.getX(), line.getY(), line.getZ()); reuseDefinition(geo); return; } GeoConicND co = (GeoConicND) geo; // copy everything toStringMode = co.toStringMode; type = co.type; for (int i = 0; i < 6; i++) { matrix[i] = co.matrix[i]; // flat matrix A } if (co.transform != null) { GAffineTransform at = getAffineTransform(); at.setTransform(co.transform); } eigenvec[0].setCoords(co.eigenvec[0]); eigenvec[1].setCoords(co.eigenvec[1]); // b.setCoords(co.b); setMidpoint(co.getMidpoint().get()); halfAxes[0] = co.halfAxes[0]; halfAxes[1] = co.halfAxes[1]; linearEccentricity = co.linearEccentricity; eccentricity = co.eccentricity; p = co.p; mu[0] = co.mu[0]; mu[1] = co.mu[1]; setLines(co); if (co.singlePoint != null) { if (singlePoint == null) { singlePoint = new GeoPoint(cons); } singlePoint.setCoords(co.singlePoint); } if (co.startPoints != null) { if (startPoints == null) { startPoints = new GeoPoint[2]; for (int i = 0; i < 2; i++) { startPoints[i] = new GeoPoint(cons); } } for (int i = 0; i < 2; i++) { startPoints[i].set(co.startPoints[i]); } } defined = co.defined; super.set(geo); } private void setLines(GeoConicND co) { if (co.lines != null) { getLines(); for (int i = 0; i < 2; i++) { lines[i].setCoords(co.lines[i]); if (co.lines[i].startPoint != null) { lines[i].setStartPoint(co.lines[i].startPoint.copy()); } if (co.lines[i].endPoint != null) { lines[i].setEndPoint(co.lines[i].endPoint.copy()); } } } } /** * Updates this conic. If the transform has changed, we call * makePathParametersInvalid() to force an update of all path parameters of * all points on this conic. */ @Override public void update(boolean drag) { makePathParametersInvalid(); super.update(drag); } /** * Sets equation mode to specific, implicit or explicit * * @param mode * equation mode (one of EQUATION_* constants) */ final public void setToStringMode(int mode) { switch (mode) { case EQUATION_SPECIFIC: case EQUATION_EXPLICIT: case EQUATION_USER: case EQUATION_PARAMETRIC: case EQUATION_VERTEX: case EQUATION_CONICFORM: this.toStringMode = mode; break; default: this.toStringMode = EQUATION_IMPLICIT; } } /** * Returns equation mode (specific, implicit or explicit) * * @return equation mode (one of EQUATION_* constants) */ final public int getToStringMode() { return toStringMode; } /** * returns true if this conic is a circle Michael Borcherds 2008-03-23 * * @return true iff this conic is circle */ public final boolean isCircle() { return (type == CONIC_CIRCLE); } /** * returns true if this conic is a parabola * * @return true iff this conic is parabola */ public boolean isParabola() { return (type == CONIC_PARABOLA); } /** * returns true if this conic is an ellipse * * @return true iff this conic is ellipse */ public boolean isEllipse() { return (type == CONIC_ELLIPSE); } /** * returns true if this conic is a hyperbola * * @return true iff this conic is hyperbola */ public boolean isHyperbola() { return (type == CONIC_HYPERBOLA); } /** * returns true if this conic is a double line * * @return true iff this conic is double line */ public boolean isDoubleLine() { return (type == CONIC_DOUBLE_LINE); } /** Changes equation mode to Specific */ final public void setToSpecific() { setToStringMode(EQUATION_SPECIFIC); } /** Changes equation mode to Implicit */ final public void setToImplicit() { setToStringMode(EQUATION_IMPLICIT); } /** Changes equation mode to Explicit */ final public void setToExplicit() { setToStringMode(EQUATION_EXPLICIT); } /** Changes equation mode to Explicit */ final public void setToParametric() { setToStringMode(EQUATION_PARAMETRIC); } /** Changes equation mode to Explicit */ final public void setToUser() { setToStringMode(EQUATION_USER); } /** Changes equation mode to Vertex form */ final public void setToVertexform() { setToStringMode(EQUATION_VERTEX); } /** Changes equation mode to Vertex form */ final public void setToConicform() { setToStringMode(EQUATION_CONICFORM); } /** * Returns whether specific equation representation is possible. * * @return true iff specific equation representation is possible. */ final public boolean isSpecificPossible() { switch (type) { case CONIC_CIRCLE: case CONIC_DOUBLE_LINE: case CONIC_INTERSECTING_LINES: case CONIC_PARALLEL_LINES: return true; case CONIC_ELLIPSE: case CONIC_HYPERBOLA: // xy vanished return (Kernel.isZero(matrix[3])); case CONIC_PARABOLA: // x\u00b2 or y\u00b2 vanished return Kernel.isZero(matrix[0]) || Kernel.isZero(matrix[1]); default: case CONIC_LINE: return false; } } /** * Returns wheter explicit parabola equation representation (y = a x\u00b2 + * b x + c) is possible. * * @return true iff explicit equation is possible */ final public boolean isExplicitPossible() { if (type == CONIC_LINE) { return false; } return !Kernel.isZero(matrix[5]) && Kernel.isZero(matrix[3]) && Kernel.isZero(matrix[1]); } /** * Returns wheter vertex form of parabola equation representation (y = a * (x-h)\u00b2 + k) is possible. * * @return true if vertex form equation is possible */ final public boolean isVertexformPossible() { return !Kernel.isZero(matrix[0]) && !Kernel.isZero(matrix[5]) && Kernel.isZero(matrix[1]) && Kernel.isZero(matrix[3]); } /** * Returns wheter conic form of parabola equation representation ( 4p(y - k) * = (x - h)^2 is possible. * * @return true if conic form equation is possible */ final public boolean isConicformPossible() { // directrix parallel with xAxis if (!Kernel.isZero(matrix[0]) && !Kernel.isZero(matrix[5]) && Kernel.isZero(matrix[1]) && Kernel.isZero(matrix[3])) { return true; } // directrix parallel with yAxis if (!Kernel.isZero(matrix[1]) && !Kernel.isZero(matrix[4]) && Kernel.isZero(matrix[0]) && Kernel.isZero(matrix[3])) { return true; } return false; } /** * returns false if conic's matrix is the zero matrix or has infinite or NaN * values */ final private boolean checkDefined() { boolean allZero = true; double maxCoeffAbs = 0; for (int i = 0; i < 6; i++) { if (Double.isNaN(matrix[i]) || Double.isInfinite(matrix[i])) { return false; } double abs = Math.abs(matrix[i]); if (abs > Kernel.STANDARD_PRECISION) { allZero = false; } // check max only on coeffs x*x, y*y, x*y if ((i == 0 || i == 1 || i == 3) && maxCoeffAbs < abs) { maxCoeffAbs = abs; } } if (allZero) { return false; } // eg y = a x^2 if (maxCoeffAbs == 0) { return true; } // huge or tiny coefficients? double factor = 1.0; if (maxCoeffAbs < MIN_COEFFICIENT_SIZE) { factor = 2; while (maxCoeffAbs * factor < MIN_COEFFICIENT_SIZE) { factor *= 2; } } else if (maxCoeffAbs > MAX_COEFFICIENT_SIZE) { factor = 0.5; while (maxCoeffAbs * factor > MAX_COEFFICIENT_SIZE) { factor *= 0.5; } } // multiply matrix with factor to avoid huge and tiny coefficients if (factor != 1.0 && !Double.isInfinite(factor) && !Double.isNaN(factor)) { for (int i = 0; i < 6; i++) { matrix[i] *= factor; } } return true; } @Override final protected boolean showInEuclidianView() { return isDefined() && (type != CONIC_EMPTY || isInverseFill()); } @Override public final boolean showInAlgebraView() { // return defined; return true; } /** * Returns whether this conic consists of lines * * @return true for line conics */ final public boolean isLineConic() { switch (type) { case CONIC_DOUBLE_LINE: case CONIC_PARALLEL_LINES: case CONIC_INTERSECTING_LINES: case CONIC_LINE: return true; default: return false; } } /** * Returns whether this conic is degenerate * * @return true iff degenerate */ final public boolean isDegenerate() { switch (type) { case CONIC_CIRCLE: case CONIC_ELLIPSE: case CONIC_HYPERBOLA: case CONIC_PARABOLA: return false; default: return true; } } /** * sets conic's matrix from coefficients of equation from array * * @param coeffs * Array of coefficients */ final public void setCoeffs(double[] coeffs) { setCoeffs(coeffs[0], coeffs[1], coeffs[2], coeffs[3], coeffs[4], coeffs[5]); } /** * sets conic's matrix from coefficients of equation a x\u00b2 + b xy + c * y\u00b2 + d x + e y + f = 0 * * @param a * coeff at x^2 * @param b * coeff at xy * @param c * coeff at y^2 * @param d * coeff at x * @param e * coeff at y * @param f * constant coeff */ final public void setCoeffs(double a, double b, double c, double d, double e, double f) { setDefinition(null); matrix[0] = a; // x\u00b2 matrix[1] = c; // y\u00b2 matrix[2] = f; // constant matrix[3] = b / 2.0; // xy matrix[4] = d / 2.0; // x matrix[5] = e / 2.0; // y classifyConic(); } @Override public String toValueStringMinimal(StringTemplate tpl) { return getXMLtagsMinimal(); } @Override public String toStringMinimal(StringTemplate tpl) { return getXMLtagsMinimal(); } /** * returns some class-specific xml tags for getConstructionRegressionOut * * @return some class-specific xml tags for getConstructionRegressionOut */ protected String getXMLtagsMinimal() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append(regrFormat(matrix[i])); sb.append(" "); } sb.append(regrFormat(matrix[5])); return sb.toString(); } // I'm not sure if this is the right place for the *Minimal() methods. // In v3.2 they were put into kernel.GeoConic. It seems both are OK. // -- Zoltan, 2011-08-01 private double[] coeffs = new double[6]; @Override protected StringBuilder buildValueString(StringTemplate tpl) { return buildValueString(tpl, matrix); } /** * * @param tpl * string template * @param matrix1 * matrix * @return the value string regarding the given matrix (used for views) */ protected StringBuilder buildValueString(StringTemplate tpl, double[] matrix1) { StringBuilder sbToValueString = new StringBuilder(); if (!isDefined()) { sbToValueString.append("?"); return sbToValueString; } coeffs[0] = matrix1[0]; // x\u00b2 coeffs[2] = matrix1[1]; // y\u00b2 coeffs[5] = matrix1[2]; // constant coeffs[1] = 2 * matrix1[3]; // xy coeffs[3] = 2 * matrix1[4]; // x coeffs[4] = 2 * matrix1[5]; // y if (tpl.hasCASType()) { StringBuilder sb = sbToValueString; sb.setLength(0); String x = tpl.printVariableName("x"); String y = tpl.printVariableName("y"); sb.append("("); sb.append(kernel.format(coeffs[0], tpl)); sb.append(")*"); sb.append(x); sb.append("^2+("); sb.append(kernel.format(coeffs[1], tpl)); sb.append(")*"); sb.append(x); sb.append("*"); sb.append(y); sb.append("+("); sb.append(kernel.format(coeffs[2], tpl)); sb.append(")*"); sb.append(y); sb.append("^2+("); sb.append(kernel.format(coeffs[3], tpl)); sb.append(")*"); sb.append(x); sb.append("+("); sb.append(kernel.format(coeffs[4], tpl)); sb.append(")*"); sb.append(y); sb.append("="); sb.append(kernel.format(-coeffs[5], tpl)); return sb; } if (toStringMode == GeoConicND.EQUATION_PARAMETRIC) { return this.buildParametricValueString(tpl, 2); } if (getDefinition() != null && (!kernel.getApplication().getSettings() .getCasSettings().isEnabled() || toStringMode == GeoConicND.EQUATION_USER)) { return sbToValueString.append(getDefinition().toString(tpl)); } if (type == CONIC_LINE) { lines[0].toStringLHS(sbToValueString, tpl); sbToValueString.append(" = 0"); return sbToValueString; } String squared; String[] myVars; switch (tpl.getStringType()) { case LATEX: squared = "^{2}"; myVars = varsLateX; break; case GIAC: squared = "^2"; myVars = varsCAS; break; default: squared = "\u00b2"; myVars = vars; } switch (toStringMode) { case EQUATION_SPECIFIC: if (!isSpecificPossible()) { return kernel.buildImplicitEquation(coeffs, myVars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); } switch (type) { case CONIC_CIRCLE: buildSphereNDString(sbToValueString, tpl); return sbToValueString; case CONIC_ELLIPSE: if (Kernel.isZero(coeffs[1])) { // xy coeff = 0 double coeff0, coeff1; // we have to check the first eigenvector: it could be (1,0) // or (0,1) // if it is (0,1) we have to swap the coefficients of x^2 // and y^2 if (Kernel.isZero(eigenvec[0].getY())) { coeff0 = halfAxes[0]; coeff1 = halfAxes[1]; } else { coeff0 = halfAxes[1]; coeff1 = halfAxes[0]; } if (Kernel.isZero(b.getX())) { sbToValueString.append("x"); sbToValueString.append(squared); } else { sbToValueString.append("(x "); kernel.formatSigned(-b.getX(), sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); } sbToValueString.append(" / "); sbToValueString.append(kernel.format(coeff0 * coeff0, tpl)); sbToValueString.append(" + "); if (Kernel.isZero(b.getY())) { sbToValueString.append("y"); sbToValueString.append(squared); } else { sbToValueString.append("(y "); kernel.formatSigned(-b.getY(), sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); } sbToValueString.append(" / "); sbToValueString.append(kernel.format(coeff1 * coeff1, tpl)); sbToValueString.append(" = 1"); return sbToValueString; } return kernel.buildImplicitEquation(coeffs, myVars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); case CONIC_HYPERBOLA: if (Kernel.isZero(coeffs[1])) { // xy coeff = 0 char firstVar, secondVar; double b1, b2; // we have to check the first eigenvector: it could be (1,0) // or (0,1) // if it is (0,1) we have to swap the x and y, needs isZero // for #4248 if (Kernel.isZero(eigenvec[0].getY())) { firstVar = 'x'; secondVar = 'y'; b1 = b.getX(); b2 = b.getY(); } else { firstVar = 'y'; secondVar = 'x'; b1 = b.getY(); b2 = b.getX(); } if (Kernel.isZero(b1)) { sbToValueString.append(firstVar); sbToValueString.append(squared); } else { sbToValueString.append('('); sbToValueString.append(firstVar); sbToValueString.append(' '); kernel.formatSigned(-b1, sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); } sbToValueString.append(" / "); sbToValueString.append( kernel.format(halfAxes[0] * halfAxes[0], tpl)); sbToValueString.append(" - "); if (Kernel.isZero(b2)) { sbToValueString.append(secondVar); sbToValueString.append(squared); } else { sbToValueString.append('('); sbToValueString.append(secondVar); sbToValueString.append(' '); kernel.formatSigned(-b2, sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); } sbToValueString.append(" / "); sbToValueString.append( kernel.format(halfAxes[1] * halfAxes[1], tpl)); sbToValueString.append(" = 1"); return sbToValueString; } return kernel.buildImplicitEquation(coeffs, myVars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); case CONIC_PARABOLA: if (!Kernel.isZero(coeffs[2])) { return kernel.buildExplicitConicEquation(coeffs, myVars, 2, KEEP_LEADING_SIGN, tpl); } else if (!Kernel.isZero(coeffs[0])) { return kernel.buildExplicitConicEquation(coeffs, myVars, 0, KEEP_LEADING_SIGN, tpl); } else { return kernel.buildImplicitEquation(coeffs, myVars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); } case CONIC_DOUBLE_LINE: sbToValueString.append('('); lines[0].toStringLHS(sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); sbToValueString.append(" = 0"); return sbToValueString; case CONIC_PARALLEL_LINES: case CONIC_INTERSECTING_LINES: sbToValueString.append('('); lines[0].toStringLHS(sbToValueString, tpl); sbToValueString.append(") ("); lines[1].toStringLHS(sbToValueString, tpl); sbToValueString.append(") = 0"); return sbToValueString; } case EQUATION_EXPLICIT: if (isExplicitPossible()) { return kernel.buildExplicitConicEquation(coeffs, myVars, 4, KEEP_LEADING_SIGN, tpl); } case EQUATION_VERTEX: if (isVertexformPossible()) { return kernel.buildVertexformEquation(coeffs, myVars, tpl); } case EQUATION_CONICFORM: if (isConicformPossible()) { return kernel.buildConicformEquation(coeffs, myVars, tpl); } default: // implicit return kernel.buildImplicitEquation(coeffs, myVars, KEEP_LEADING_SIGN, true, false, '=', tpl, true); } } /** * Returns the halfaxes * * @return lengths of halfaxes */ final public double[] getHalfAxes() { return halfAxes; } /** * for intersecting lines, parallel lines * * @return lines the conic consists of */ final public GeoLine[] getLines() { if (lines == null) { lines = new GeoLine[2]; lines[0] = new GeoLine(cons); lines[1] = new GeoLine(cons); } return lines; } /** * Returns the point (in case this conic is a single point) * * @return the single point */ final public GeoPoint getSinglePoint() { return singlePoint; } /** * Returns the eigenvector-real worl transformation * * @return eigenvector-real worl transformation */ final public GAffineTransform getAffineTransform() { if (transform == null) { transform = AwtFactory.getPrototype().newAffineTransform(); } return transform; } @Override final protected void setAffineTransform() { GAffineTransform at = getAffineTransform(); /* * ( v1x v2x bx ) ( v1y v2y by ) ( 0 0 1 ) */ at.setTransform(eigenvec[0].getX(), eigenvec[0].getY(), eigenvec[1].getX(), eigenvec[1].getY(), b.getX(), b.getY()); } /** * Returns midpoint or vertex * * @return midpoint or vertex */ final public GeoVec2D getTranslationVector() { return b; } /** * return the radius of the circle (if the conic is a circle) * * @return the radius of the circle */ final public double getCircleRadius() { return getHalfAxis(0); } /** * Transforms coords of point P from Eigenvector space to real world space. * Note: P.setCoords() is not called here! * * @param P * point in EV coords */ protected final void coordsEVtoRW(GeoPoint P) { // rotate by alpha double px = P.x; P.x = px * eigenvec[0].getX() + P.y * eigenvec[1].getX(); P.y = px * eigenvec[0].getY() + P.y * eigenvec[1].getY(); // translate by b P.x = P.x + P.z * b.getX(); P.y = P.y + P.z * b.getY(); } /** * Transforms coords of point P from real world space to Eigenvector space. * Note: P.setCoords() is not called here! */ /* * private void coordsRWtoEV(GeoPoint P) { TODO delete if not needed // * translate by -b P.x = P.x - P.z * b.getX(); P.y = P.y - P.z * b.getY(); * * // rotate by -alpha double px = P.x; P.x = px * eigenvec[0].getX() + P.y * * eigenvec[0].getY(); P.y = px * eigenvec[1].getX() + P.y * * eigenvec[1].getY(); } */ /** @return copy of flat matrix */ final public double[] getMatrix() { double[] ret = { matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5] }; return ret; } /** * set out with flat matrix of this conic * * @param out * array in which the flat matrix should be stored */ final public void getMatrix(double[] out) { for (int i = 0; i < 6; i++) { out[i] = matrix[i]; } } /** * set conic's matrix from flat matrix * * @param matrix * array from which the flat matrix should be read */ final public void setMatrix(double[] matrix) { setDefinition(null); for (int i = 0; i < 6; i++) { this.matrix[i] = matrix[i]; } classifyConic(); } /** * Set conic's matrix from flat matrix (array of length 6). * * @param matrix * array from which the flat matrix should be read */ final public void setDegenerateMatrixFromArray(double[] matrix) { for (int i = 0; i < 6; i++) { this.matrix[i] = matrix[i]; } classifyConic(true); } /** * set conic's matrix from 3x3 matrix (not necessarily be symmetric). * * @param C * matrix */ final public void setMatrix(double[][] C) { matrix[0] = C[0][0]; matrix[1] = C[1][1]; matrix[2] = C[2][2]; matrix[3] = (C[0][1] + C[1][0]) / 2.0; matrix[4] = (C[0][2] + C[2][0]) / 2.0; matrix[5] = (C[1][2] + C[2][1]) / 2.0; classifyConic(); } /** * makes this conic a circle with midpoint M and radius BC Michael Borcherds * 2008-03-13 * * @param M * midpoint * @param B * first radius endpoint * @param C * second radius endpoint */ final public void setCircle(GeoPoint M, GeoPoint B, GeoPoint C) { defined = M.isDefined() && !M.isInfinite() && B.isDefined() && !B.isInfinite() && C.isDefined() && !C.isInfinite(); double r = B.distance(C); // check radius if (Kernel.isZero(r)) { r = 0; } else if (r < 0) { defined = false; } if (defined) { setCircleMatrix(M, r); setAffineTransform(); } } @Override public void setSphereND(GeoPointND M, GeoSegmentND segment) { setCircle((GeoPoint) M, (GeoSegment) segment); } /** * makes this conic a circle with midpoint M and radius geoSegment Michael * Borcherds 2008-03-13 * * @param M * center of circle * @param geoSegment * length of geoSegment is radius of the circle */ final public void setCircle(GeoPoint M, GeoSegment geoSegment) { defined = M.isDefined() && !M.isInfinite() && geoSegment.isDefined(); double r = geoSegment.getLength(); // check radius if (Kernel.isZero(r)) { r = 0; } else if (r < 0) { defined = false; } if (defined) { setCircleMatrix(M, r); setAffineTransform(); } } @Override public void setSphereND(GeoPointND M, GeoPointND P) { setCircle((GeoPoint) M, (GeoPoint) P); } /** * Sets matrix to matrix of a circle with given center and radius * * @param M * center * @param r * radius */ protected void setCircleMatrix(GeoPoint M, double r) { setSphereNDMatrix(M.getInhomCoordsInD2(), r); } /** * set Parabola from focus and line * * @param F * focus * @param g * line */ final public void setParabola(GeoPoint F, GeoLine g) { defined = F.isDefined() && !F.isInfinite() && g.isDefined(); if (!defined) { return; } // set parabola's matrix double fx = F.inhomX; double fy = F.inhomY; matrix[0] = g.y * g.y; matrix[1] = g.x * g.x; double lsq = matrix[0] + matrix[1]; matrix[2] = lsq * (fx * fx + fy * fy) - g.z * g.z; matrix[3] = -g.x * g.y; matrix[4] = -(lsq * fx + g.x * g.z); matrix[5] = -(lsq * fy + g.y * g.z); normalizeMatrix(); classifyConic(); // avoid flip if (defined && eigenvecX * g.x + eigenvecY * g.y < 0) { eigenvecX *= -1; eigenvecY *= -1; c.setX(matrix[4] * eigenvecX + matrix[5] * eigenvecY); c.setY(matrix[5] * eigenvecX - matrix[4] * eigenvecY); parabola(); } } private void normalizeMatrix() { double norm = 1; for (int i = 0; i < 6; i++) { double entry = Math.abs(matrix[i]); if (Math.abs(norm) < entry && 1000 < entry) { norm = entry; } if (Math.abs(norm) > entry && 0.001 > entry && !Kernel.isZero(entry)) { norm = entry; } } if (norm != 1) { for (int i = 0; i < 6; i++) { matrix[i] = matrix[i] / norm; } } } /** * set Parabola from focus (0,0) and line y = y0 * * @param y0 * y coord for line */ final public void setParabola(double y0) { // set parabola's matrix matrix[0] = 1; matrix[1] = 0; matrix[2] = -y0 * y0; matrix[3] = 0; matrix[4] = 0; matrix[5] = y0; classifyConic(); } /** * set the matrix of ellipse or hyperbola * * @param B * first focus * @param C * second focus * @param a * first half axis */ final public void setEllipseHyperbola(GeoPoint B, GeoPoint C, double a) { if (B.isInfinite() || C.isInfinite() || a < -Kernel.STANDARD_PRECISION) { defined = false; return; } defined = true; // set conics's matrix double b1 = B.inhomX; double b2 = B.inhomY; double c1 = C.inhomX; double c2 = C.inhomY; /* * removed again, we want Ellipse[(-3,3), (3,-5), 5] to give a * double-line (also see #1297) double halfLengthBC = * Math.sqrt((b1-c1)*(b1-c1)+(b2-c2)*(b2-c2))/2; * * if (AbstractKernel.isEqual(halfLengthBC, a)) { defined = false; * return; } */ // precalculations double diff1 = b1 - c1; double diff2 = b2 - c2; double sqsumb = b1 * b1 + b2 * b2; double sqsumc = c1 * c1 + c2 * c2; double sqsumdiff = sqsumb - sqsumc; double a2 = 2.0 * a; double asq4 = a2 * a2; double asq = a * a; double afo = asq * asq; matrix[0] = 4.0 * (a2 - diff1) * (a2 + diff1); matrix[3] = -4.0 * diff1 * diff2; matrix[1] = 4.0 * (a2 - diff2) * (a2 + diff2); matrix[4] = -2.0 * (asq4 * (b1 + c1) - diff1 * sqsumdiff); matrix[5] = -2.0 * (asq4 * (b2 + c2) - diff2 * sqsumdiff); matrix[2] = -16.0 * afo - sqsumdiff * sqsumdiff + 8.0 * asq * (sqsumb + sqsumc); // set eigenvectors' directions (B -> C and normalvector) // this is needed, so that setEigenvectors() (called by classifyConic) // will surely take the right direction // normalizing is not needed at this point eigenvec[0].setX(c1 - b1); eigenvec[0].setY(c2 - b2); eigenvec[1].setX(-eigenvec[0].getY()); eigenvec[1].setY(eigenvec[0].getX()); classifyConic(); // check if we got an ellipse or hyperbola if (!(type == CONIC_HYPERBOLA || type == CONIC_ELLIPSE || type == CONIC_CIRCLE || type == CONIC_DOUBLE_LINE)) { defined = false; } } /************************************* * MOVEMENTS *************************************/ /** * translate conic by vector v * * @param v * translation vector */ @Override public void translate(Coords v) { doTranslate(v); // classifyConic(); setAffineTransform(); updateDegenerates(); // for degenerate conics } @Override final public boolean isTranslateable() { return true; } /** * translate this conic by vector (vx, vy) * * @param vx * x-coord of translation vector * @param vy * y-coord of translation vector */ final public void translate(double vx, double vy) { doTranslate(vx, vy); setAffineTransform(); updateDegenerates(); // for degenerate conics } /** * @param v * translation vector */ protected void doTranslate(Coords v) { doTranslate(v.getX(), v.getY()); } /** * @param vx * translation in horizontal direction * @param vy * translation in vertical dyrection */ protected final void doTranslate(double vx, double vy) { // calc translated matrix translateMatrix(matrix, vx, vy); // avoid classification and set changes by hand: setMidpoint(new Coords(new double[] { vx, vy, 0 }) .addInside(getMidpoint()).get()); /* * b.x += vx; b.y += vy; */ } /** * @param mat * matrix * @param vx * translation in horizontal direction * @param vy * translation in vertical direction */ protected void translateMatrix(double[] mat, double vx, double vy) { setDefinition(null); mat[2] = mat[2] + vx * (mat[0] * vx - 2.0 * mat[4]) + vy * (mat[1] * vy - 2.0 * mat[5] + 2.0 * mat[3] * vx); mat[4] = mat[4] - mat[0] * vx - mat[3] * vy; mat[5] = mat[5] - mat[3] * vx - mat[1] * vy; } /** * rotate this conic by angle phi around (0,0) * * @param phiVal * angle */ @Override public void rotate(NumberValue phiVal) { double phi = phiVal.getDouble(); rotate(phi); setAffineTransform(); updateDegenerates(); // for degenerate conics } /** * rotate this conic by angle phi around Q * * @param phiVal * angle * @param point * rotation center */ @Override public void rotate(NumberValue phiVal, GeoPointND point) { Coords Q = point.getInhomCoords(); double phi = phiVal.getDouble(); double qx = Q.getX(); double qy = Q.getY(); // first translate to new origin Q doTranslate(-qx, -qy); // rotate around new origin Q rotate(phi); // translate back to old origin (0,0) doTranslate(qx, qy); setAffineTransform(); updateDegenerates(); // for degenerate conics } @Override public boolean isMatrixTransformable() { return true; } @Override public void matrixTransform(double a00, double a01, double a10, double a11) { double det = a00 * a11 - a01 * a10; double det2 = det * det; double A0 = a11 * (a11 * matrix[0] - a10 * matrix[3]) - a10 * (a11 * matrix[3] - a10 * matrix[1]); double A3 = a00 * (a11 * matrix[3] - a10 * matrix[1]) - a01 * (a11 * matrix[0] - a10 * matrix[3]); double A1 = a00 * (a00 * matrix[1] - a01 * matrix[3]) - a01 * (a00 * matrix[3] - a01 * matrix[0]); double A4 = a11 * matrix[4] - a10 * matrix[5]; matrix[5] = (a00 * matrix[5] - a01 * matrix[4]) / det; matrix[0] = A0 / det2; matrix[1] = A1 / det2; matrix[3] = A3 / det2; matrix[4] = A4 / det; classifyConic(); } /** * rotate this conic by angle phi around (0,0) [ cos -sin 0 ] [ sin cos 0 ] * [ 0 0 1 ] */ final private void rotate(double phi) { // set rotated matrix rotateMatrix(matrix, phi); // avoid classification: make changes by hand eigenvec[0].rotate(phi); eigenvec[1].rotate(phi); b.rotate(phi); setMidpoint(new double[] { b.getX(), b.getY() }); } /** * rotate the matrix * * @param matrix * matrix * @param phi * angle */ final protected static void rotateMatrix(double[] matrix, double phi) { double sum = matrix[0] + matrix[1]; double diff = matrix[0] - matrix[1]; double cos = Math.cos(phi); double sin = Math.sin(phi); // cos(2 phi) = cos(phi)\u00b2 - sin(phi)\u00b2 = (cos + sin)*(cos - // sin) double cos2 = (cos + sin) * (cos - sin); // cos(2 phi) = 2 cos sin double sin2 = 2.0 * cos * sin; double temp = diff * cos2 - 2.0 * matrix[3] * sin2; double A0 = (sum + temp) / 2.0; double A1 = (sum - temp) / 2.0; double A3 = matrix[3] * cos2 + diff * cos * sin; double A4 = matrix[4] * cos - matrix[5] * sin; matrix[5] = matrix[5] * cos + matrix[4] * sin; matrix[0] = A0; matrix[1] = A1; matrix[3] = A3; matrix[4] = A4; } /** * dilate this conic from point (0,0) by factor r * * @param r * ratio */ final protected void dilate(double r) { // remember Eigenvector orientation boolean oldOrientation = hasPositiveEigenvectorOrientation(); // do dilation doDilate(r); // classify as type may have change classifyConic(); // make sure we preserve old Eigenvector orientation setPositiveEigenvectorOrientation(oldOrientation); } /** * Dilate the conic wrt point (0,0) * * @param factor * dilation factor */ protected final void doDilate(double factor) { // calc dilated matrix double r = 1d / factor; double r2 = r * r; matrix[0] *= r2; matrix[1] *= r2; matrix[3] *= r2; matrix[4] *= r; matrix[5] *= r; } /** * to avoid classification in movements this method is called to update the * lines (point) of degenerate conics */ protected final void updateDegenerates() { // update lines of degenerate conic switch (type) { default: // do nothing break; case CONIC_SINGLE_POINT: singlePoint(); break; case CONIC_INTERSECTING_LINES: intersectingLines(mu); // coefficient mu unchanged break; case CONIC_DOUBLE_LINE: doubleLine(); break; case CONIC_PARALLEL_LINES: parallelLines(mu); // coefficient mu unchanged break; case CONIC_LINE: singleLine(mu); // coefficient mu unchanged break; } } /************************************* * CONIC CLASSIFICATION *************************************/ /** * Sets both eigenvectors to e0, e1 on file load. (note: needed for * "near-to-relationship" after loading a file) * * @param x0 * homogenous x-coord of e0 * @param y0 * homogenous y-coord of e0 * @param z0 * homogenous z-coord of e0 * @param x1 * homogenous x-coord of e1 * @param y1 * homogenous y-coord of e1 * @param z1 * homogenous z-coord of e1 * */ final public void setEigenvectors(double x0, double y0, double z0, double x1, double y1, double z1) { eigenvec[0].setX(x0 / z0); eigenvec[0].setY(y0 / z0); eigenvec[1].setX(x1 / z1); eigenvec[1].setY(y1 / z1); eigenvectorsSetOnLoad = true; } private boolean eigenvectorsSetOnLoad = false; @Override protected void setFirstEigenvector(double[] coords) { eigenvecX = coords[0]; eigenvecY = coords[1]; } @Override final protected void findEigenvectors() { // newly calculated first eigenvector = (eigenvecX, eigenvecY) // old eigenvectors: eigenvec[0], eigenvec[1] // set direction of eigenvectors with respect to old values: // take inner product >= 0 (small angle change) // make (eigenvecX, eigenvecY) a unit vector length = MyMath.length(eigenvecX, eigenvecY); if (length != 1.0d) { eigenvecX = eigenvecX / length; eigenvecY = eigenvecY / length; } if (kernel.isContinuous()) { // first eigenvector if (eigenvec[0].getX() * eigenvecX < -eigenvec[0].getY() * eigenvecY) { eigenvec[0].setX(-eigenvecX); eigenvec[0].setY(-eigenvecY); } else { eigenvec[0].setX(eigenvecX); eigenvec[0].setY(eigenvecY); } // second eigenvector (compared to normalvector (-eigenvecY, // eigenvecX) if (eigenvec[1].getY() * eigenvecX < eigenvec[1].getX() * eigenvecY) { eigenvec[1].setX(eigenvecY); eigenvec[1].setY(-eigenvecX); } else { eigenvec[1].setX(-eigenvecY); eigenvec[1].setY(eigenvecX); } } // non-continous else if (!eigenvectorsSetOnLoad) { eigenvec[0].setX(eigenvecX); eigenvec[0].setY(eigenvecY); eigenvec[1].setX(-eigenvecY); eigenvec[1].setY(eigenvecX); } eigenvectorsSetOnLoad = false; } final private void setParabolicEigenvectors() { // fist eigenvector of parabola must not be changed // newly calculated first eigenvector = (eigenvecX, eigenvecY) // old eigenvectors: eigenvec[0], eigenvec[1] // set direction of eigenvectors with respect to old values: // take inner product >= 0 (small angle change) // make (eigenvecX, eigenvecY) a unit vector length = MyMath.length(eigenvecX, eigenvecY); if (length != 1.0d) { eigenvecX = eigenvecX / length; eigenvecY = eigenvecY / length; } // first eigenvector eigenvec[0].setX(eigenvecX); eigenvec[0].setY(eigenvecY); if (kernel.isContinuous()) { // second eigenvector (compared to normalvector (-eigenvecY, // eigenvecX) if (eigenvec[1].getY() * eigenvecX < eigenvec[1].getX() * eigenvecY) { eigenvec[1].setX(eigenvecY); eigenvec[1].setY(-eigenvecX); } else { eigenvec[1].setX(-eigenvecY); eigenvec[1].setY(eigenvecX); } } else if (!eigenvectorsSetOnLoad) { // non-continous eigenvec[1].setX(-eigenvecY); eigenvec[1].setY(eigenvecX); } eigenvectorsSetOnLoad = false; } /** * Makes all path parameters of points on this conic invalid if the * Eigenvectors have changed. This will force recalculation of the path * parameters on the next call of pointChanged(). */ private void makePathParametersInvalid() { if (pointsOnConic == null) { return; } // eigenvectors have changed: we need to force an update of the // path parameters of all points on this conic. getAffineTransform(); if (oldTransform == null) { oldTransform = AwtFactory.getPrototype().newAffineTransform(); } boolean eigenVectorsSame = Kernel.isEqual(transform.getScaleX(), oldTransform.getScaleX(), Kernel.MIN_PRECISION) || Kernel.isEqual(transform.getScaleY(), oldTransform.getScaleY(), Kernel.MIN_PRECISION) || Kernel.isEqual(transform.getShearX(), oldTransform.getShearX(), Kernel.MIN_PRECISION) || Kernel.isEqual(transform.getShearY(), oldTransform.getShearY(), Kernel.MIN_PRECISION); if (!eigenVectorsSame) { // updated old transform oldTransform.setTransform(transform); int size = pointsOnConic.size(); for (int i = 0; i < size; i++) { GeoPointND point = pointsOnConic.get(i); if (point.getPath() == this) { point.getPathParameter().setT(Double.NaN); } } } } /** * Update conic type and properties */ protected void classifyConic() { classifyConic(false); } /** * @param degenerate * true to allow classification as degenerate */ public void classifyConic(boolean degenerate) { defined = degenerate || checkDefined(); if (!defined) { return; } // det of S lets us distinguish between // parabolic and midpoint conics // det(S) = A[0] * A[1] - A[3] * A[3] if (isDetSzero()) { classifyParabolicConic(degenerate); } else { // det(S) = A[0] * A[1] - A[3] * A[3] detS = matrix[0] * matrix[1] - matrix[3] * matrix[3]; classifyMidpointConic(degenerate); } setAffineTransform(); // Application.debug("conic: " + this.getLabel() + " type " + // getTypeString() ); // Application.debug(" detS: " + (A0A1 - A3A3));ELLIPSE } /** * Returns whether det(S) = A[0] * A[1] - A[3] * A[3] is zero. This method * takes care of possibly large coefficients and adapts the precision used * for the zero test automatically. */ private boolean isDetSzero() { // get largest abs of A0, A1, A3 // double maxAbs = Math.abs(matrix[0]); // double abs = Math.abs(matrix[1]); // if (abs > maxAbs) { // maxAbs = abs; // } // abs = Math.abs(matrix[3]); // det(S) = 0 // A[0] * A[1] = A[3] * A[3] // normalized: A[0]/maxAbs * A[1]/maxAbs = A[3]/maxAbs * A[3]/maxAbs // use precision: eps * maxAbs^2 /* * double eps; * * if (maxAbs > 1) { eps = kernel.getSTANDARD_PRECISION() * maxAbs * * maxAbs; } else { eps = kernel.getSTANDARD_PRECISION() * maxAbs * * maxAbs; //TODO: Also need to care for small coeff } */ return Kernel.isEqual(matrix[0] * matrix[1], matrix[3] * matrix[3], this.errDetS); } /************************************* * midpoint conics *************************************/ final private void classifyMidpointConic(boolean degenerate) { // calc eigenvalues and eigenvectors if (Kernel.isZero(matrix[3])) { // special case: submatrix S is allready diagonal eigenval[0] = matrix[0]; eigenval[1] = matrix[1]; eigenvecX = 1.0d; eigenvecY = 0.0d; } else { // eigenvalues are solutions of // 0 = det(S - x E) = x\u00b2 - spurS x + detS // detS was computed in classifyConic() // init array for solver eigenval[0] = detS; eigenval[1] = -(matrix[0] + matrix[1]); // -spurS eigenval[2] = 1.0d; int nRoots = cons.getKernel().getEquationSolver().solveQuadratic( eigenval, eigenval, Kernel.STANDARD_PRECISION); if (nRoots == 1) { eigenval[1] = eigenval[0]; } // set first eigenvector eigenvecX = -matrix[3]; eigenvecY = -eigenval[0] + matrix[0]; } // calc translation vector b = midpoint // b = -Inverse[S] . a, where a = (A[4], A[5]) /* * b.x = (matrix[3] * matrix[5] - matrix[1] * matrix[4]) / detS; b.y = * (matrix[3] * matrix[4] - matrix[0] * matrix[5]) / detS; */ setMidpoint(new double[] { (matrix[3] * matrix[5] - matrix[1] * matrix[4]) / detS, (matrix[3] * matrix[4] - matrix[0] * matrix[5]) / detS }); // beta = a . b + alpha, where alpha = A[2] double beta = matrix[4] * b.getX() + matrix[5] * b.getY() + matrix[2]; // beta lets us distinguish between Ellipse, Hyperbola, // single singlePoint and intersecting lines // if (Kernel.isZero(beta)) { if (degenerate || Kernel.isZero(beta)) { findEigenvectors(); // single point or intersecting lines mu[0] = eigenval[0] / eigenval[1]; if (Kernel.isZero(mu[0])) { mu[0] = 0.0; intersectingLines(mu); } else if (mu[0] < 0.0d) { mu[0] = Math.sqrt(-mu[0]); intersectingLines(mu); } else { singlePoint(); } } else { // Hyperbola, Ellipse, empty mu[0] = -eigenval[0] / beta; mu[1] = -eigenval[1] / beta; if (detS < 0) { hyperbola(mu); } else { if (mu[0] > 0 && mu[1] > 0) { ellipse(mu); } else { empty(); } } } } @Override final protected void singlePoint() { type = GeoConicNDConstants.CONIC_SINGLE_POINT; if (singlePoint == null) { singlePoint = new GeoPoint(cons); } singlePoint.setCoords(b.getX(), b.getY(), 1.0d); // Application.debug("singlePoint : " + b); halfAxes[0] = 0; halfAxes[1] = 0; } final private void intersectingLines(double[] mu1) { type = GeoConicNDConstants.CONIC_INTERSECTING_LINES; // set intersecting lines getLines(); // n = T . (-mu, 1) temp1 = eigenvec[0].getX() * mu1[0]; temp2 = eigenvec[0].getY() * mu1[0]; nx = eigenvec[1].getX() - temp1; ny = eigenvec[1].getY() - temp2; // take line with smallest change of direction if (Math.abs(nx * lines[0].x + ny * lines[0].y) < Math .abs(nx * lines[1].x + ny * lines[1].y)) { index = 1; } else { index = 0; } lines[index].x = nx; lines[index].y = ny; lines[index].z = -(nx * b.getX() + ny * b.getY()); // n = T . (mu, 1) nx = eigenvec[1].getX() + temp1; ny = eigenvec[1].getY() + temp2; index = 1 - index; lines[index].x = nx; lines[index].y = ny; lines[index].z = -(nx * b.getX() + ny * b.getY()); setStartPointsForLines(); // Application.debug("intersectingLines: " + lines[0] + ", " + // lines[1]); } final private void ellipse(double[] mu1) { // circle if (Kernel.isEqual(mu1[0] / mu1[1], 1.0)) { // sets eigen vecs parallel to Ox and Oy eigenvecX = 1; eigenvecY = 0; findEigenvectors(); type = GeoConicNDConstants.CONIC_CIRCLE; halfAxes[0] = Math.sqrt(1.0d / mu1[0]); halfAxes[1] = halfAxes[0]; linearEccentricity = 0.0d; eccentricity = 0.0d; // Application.debug("circle: M = " + b + ", r = " + halfAxes[0]); } else { // elipse if (mu1[0] > mu1[1]) { // swap eigenvectors and mu temp = mu1[0]; mu1[0] = mu1[1]; mu1[1] = temp; // rotate eigenvector 90 temp = eigenvecX; eigenvecX = -eigenvecY; eigenvecY = temp; } findEigenvectors(); type = GeoConicNDConstants.CONIC_ELLIPSE; mu1[0] = 1.0d / mu1[0]; mu1[1] = 1.0d / mu1[1]; halfAxes[0] = Math.sqrt(mu1[0]); halfAxes[1] = Math.sqrt(mu1[1]); linearEccentricity = Math.sqrt(mu1[0] - mu1[1]); eccentricity = linearEccentricity / Math.sqrt(mu1[0]); /* * Application.debug("Ellipse"); Application.debug("a : " + * halfAxes[0]); Application.debug("b : " + halfAxes[1]); * Application.debug("e : " + excent); */ } } final private void hyperbola(double[] mu1) { type = GeoConicNDConstants.CONIC_HYPERBOLA; if (mu1[0] < 0) { // swap eigenvectors and mu temp = mu1[0]; mu1[0] = mu1[1]; mu1[1] = temp; // rotate eigenvector 90 temp = eigenvecX; eigenvecX = -eigenvecY; eigenvecY = temp; } findEigenvectors(); mu1[0] = 1.0d / mu1[0]; mu1[1] = -1.0d / mu1[1]; halfAxes[0] = Math.sqrt(mu1[0]); halfAxes[1] = Math.sqrt(mu1[1]); linearEccentricity = Math.sqrt(mu1[0] + mu1[1]); eccentricity = linearEccentricity / Math.sqrt(mu1[0]); /* * Application.debug("Hyperbola"); Application.debug("a : " + * halfAxes[0]); Application.debug("b : " + halfAxes[1]); * Application.debug("e : " + excent); */ } /* * final private void empty() { type = GeoConic.CONIC_EMPTY; // * Application.debug("empty conic"); } */ /************************************* * parabolic conics *************************************/ final private void classifyParabolicConic(boolean degenerate) { // calc eigenvalues and first eigenvector if (Kernel.isZero(matrix[3])) { // special cases: submatrix S is allready diagonal // either A[0] or A[1] have to be zero (due to detS = 0) if (Kernel.isZero(matrix[0])) { // special case: the submatrix S is zero!!! if (Kernel.isZero(matrix[1])) { handleSzero(); return; } // else lambda = matrix[1]; // set first eigenvector eigenvecX = 1.0d; eigenvecY = 0.0d; // c = a . T = a, // where T is the matrix of the eigenvectors and a = (A[4], // A[5]) c.setX(matrix[4]); c.setY(matrix[5]); } else { // A[1] is zero lambda = matrix[0]; eigenvecX = 0.0d; // set first eigenvector eigenvecY = 1.0d; // c = a . T, // where T is the matrix of the eigenvectors and a = (A[4], // A[5]) c.setX(matrix[5]); c.setY(-matrix[4]); } } else { // A[3] != 0 // eigenvalues are solutions of // 0 = det(S - x E) = x^2 - spurS x + detS = x (x - spurS) lambda = matrix[0] + matrix[1]; // spurS // set first eigenvector as a unit vector (needed fo computing // vector c) length = MyMath.length(matrix[3], matrix[0]); eigenvecX = matrix[3] / length; eigenvecY = -matrix[0] / length; // c = a . T, // where T is the matrix of the eigenvectors and a = (A[4], A[5]) c.setX(matrix[4] * eigenvecX + matrix[5] * eigenvecY); c.setY(matrix[5] * eigenvecX - matrix[4] * eigenvecY); } if (degenerate || Kernel.isZero(c.getX())) { findEigenvectors(); // b = T . (0, -c.y/lambda) temp = c.getY() / lambda; /* * b.x = temp * eigenvecY; b.y = -temp * eigenvecX; */ setMidpoint(new double[] { temp * eigenvecY, -temp * eigenvecX }); mu[0] = -temp * temp + matrix[2] / lambda; if (Kernel.isZero(mu[0])) { doubleLine(); } else if (mu[0] < 0) { mu[0] = Math.sqrt(-mu[0]); parallelLines(mu); } else { empty(); } } else { // parabola parabola(); } } final private void doubleLine() { type = GeoConicNDConstants.CONIC_DOUBLE_LINE; // set double line getLines(); nx = -eigenvec[0].getY(); ny = eigenvec[0].getX(); lines[0].x = nx; lines[0].y = ny; lines[0].z = -(b.getX() * nx + b.getY() * ny); lines[1].x = lines[0].x; lines[1].y = lines[0].y; lines[1].z = lines[0].z; setStartPointsForDoubleLine(); // setStartPointsForLines(); // Log.debug("\ndouble line : " + lines[0]+"\nstart point : // "+startPoints[0]); } /** * Change this conic to double line */ final public void enforceDoubleLine() { defined = true; doubleLine(); } // if S is the zero matrix, set conic as double line or empty final private void handleSzero() { // conic is line 2*A[4] * x + 2*A[5] * y + A[2] = 0 if (Kernel.isZero(matrix[4])) { if (Kernel.isZero(matrix[5])) { empty(); return; } // A[5] not zero // make b a point on the line /* * b.x = 0; b.y = -matrix[2] / (2*matrix[5]); */ setMidpoint(new double[] { 0, -matrix[2] / (2 * matrix[5]) }); } else { // A[4] not zero // make b a point on the line /* * b.x = -matrix[2] / (2*matrix[4]); b.y = 0; */ setMidpoint(new double[] { -matrix[2] / (2 * matrix[4]), 0 }); } eigenvecX = matrix[5]; eigenvecY = -matrix[4]; findEigenvectors(); doubleLine(); } /** * @param mu1 * mu */ protected final void parallelLines(double[] mu1) { type = GeoConicNDConstants.CONIC_PARALLEL_LINES; // set double line getLines(); nx = -eigenvec[0].getY(); ny = eigenvec[0].getX(); temp1 = b.getX() * nx + b.getY() * ny; lines[0].x = nx; lines[0].y = ny; lines[1].x = nx; lines[1].y = ny; // smallest change: temp2 = mu1[0] - temp1; if (Math.abs(lines[0].z - temp2) < Math.abs(lines[1].z - temp2)) { lines[0].z = temp2; lines[1].z = -temp1 - mu1[0]; } else { lines[0].z = -temp1 - mu1[0]; lines[1].z = temp2; } setStartPointsForLines(); // Application.debug("parallel lines : " + lines[0] + ", " + lines[1]); // Application.debug("coeff : " + mu[0]); } /** * @param mu1 * sqrt(-eigenval[0] / eigenval[1]) */ protected final void singleLine(double[] mu1) { type = GeoConicNDConstants.CONIC_LINE; // set double line getLines(); nx = -eigenvec[0].getY(); ny = eigenvec[0].getX(); temp1 = b.getX() * nx + b.getY() * ny; lines[0].x = nx; lines[0].y = ny; // smallest change: temp2 = mu1[0] - temp1; lines[0].z = temp2; setStartPointsForLines(); // Application.debug("parallel lines : " + lines[0] + ", " + lines[1]); // Application.debug("coeff : " + mu[0]); } private void setStartPointsForLines() { // make sure we have a start point to compute line parameter if (startPoints == null) { startPoints = new GeoPoint[2]; for (int i = 0; i < 2; i++) { startPoints[i] = new GeoPoint(cons); } } // update start points for (int i = 0; i < 2; i++) { lines[i].setStartPoint(null); lines[i].getPointOnLine(startPoints[i]); lines[i].setStartPoint(startPoints[i]); } } private void setStartPointsForDoubleLine() { // make sure we have a start point to compute line parameter if (startPoints == null) { startPoints = new GeoPoint[2]; for (int i = 0; i < 2; i++) { startPoints[i] = new GeoPoint(cons); } } // update start points lines[0].setStartPoint(null); lines[0].getPointOnLine(startPoints[0]); lines[0].setStartPoint(startPoints[0]); lines[1].setStartPoint(startPoints[0]); } final private void parabola() { type = GeoConicNDConstants.CONIC_PARABOLA; // calc vertex = b // b = T . ((c.y\u00b2/lambda - A2)/(2 c.x) , -c.y/lambda) temp2 = c.getY() / lambda; temp1 = (c.getY() * temp2 - matrix[2]) / (2 * c.getX()); /* * b.x = eigenvecY * temp2 + eigenvecX * temp1; b.y = eigenvecY * temp1 * - eigenvecX * temp2; */ setMidpoint(new double[] { eigenvecY * temp2 + eigenvecX * temp1, eigenvecY * temp1 - eigenvecX * temp2 }); setParabolicEigenvectors(); // parameter p of parabola p = -c.getX() / lambda; if (p < 0) { // change orientation of first eigenvector eigenvec[0].setX(-eigenvec[0].getX()); eigenvec[0].setY(-eigenvec[0].getY()); p = -p; } linearEccentricity = p / 2; eccentricity = 1; /* * Application.debug("parabola"); Application.debug("Vertex: " + b); * Application.debug("p = " + p); */ } /********************************************************** * CACLCULATIONS ON CONIC (det, evaluate, intersect, ...) **********************************************************/ /** * Computes the determinant of a conic's 3x3 matrix. * * @param matrix * flat matrix of conic section * @return matrix determinant */ public static double det(double[] matrix) { return matrix[0] * (matrix[1] * matrix[2] - matrix[5] * matrix[5]) - matrix[2] * matrix[3] * matrix[3] - matrix[1] * matrix[4] * matrix[4] + 2 * matrix[3] * matrix[4] * matrix[5]; } /** * Returns true iff the determinant of 2x2 matrix of eigenvectors is * positive. * * @return true iff the determinant of 2x2 matrix of eigenvectors is * positive. */ protected final boolean hasPositiveEigenvectorOrientation() { // return eigenvec[0].x * eigenvec[1].y - eigenvec[0].y * eigenvec[1].x // > 0; return eigenvec[0].getX() * eigenvec[1].getY() > eigenvec[0].getY() * eigenvec[1].getX(); } /** * Sets orientation of eigenvectors to positive or negative * * @param flag * true for positive, false for negative */ protected final void setPositiveEigenvectorOrientation(boolean flag) { if (flag != hasPositiveEigenvectorOrientation()) { eigenvec[1].setX(-eigenvec[1].getX()); eigenvec[1].setY(-eigenvec[1].getY()); setAffineTransform(); } } /** * states wheter P lies on this conic or not * * @return true iff P lies on this conic * @param P * point * @param eps * precision */ public boolean isIntersectionPointIncident(GeoPoint P, double eps) { return isOnFullConic(P, eps); } /** * states wheter P lies on this conic or not * * @return true iff P lies on this conic * @param P * point (2D coords in conic coordsys) * @param eps * precision */ public boolean isIntersectionPointIncident(Coords P, double eps) { return isOnFullConic(P, eps); } /** * return wheter this conic represents the same conic as c (this = lambda * * c). */ @Override public boolean isEqual(GeoElementND geo) { if (!geo.isGeoConic()) { return false; } GeoConicND conic = (GeoConicND) geo; double[] B = conic.matrix; double lambda1 = 0.0; boolean aZero, bZero, equal = true; for (int i = 0; i < 6; i++) { aZero = Kernel.isZero(matrix[i]); bZero = Kernel.isZero(B[i]); // A[i] == 0 and B[i] != 0 => not equal if (aZero && !bZero) { equal = false; } else if (bZero && !aZero) { equal = false; } else if (!aZero && !bZero) { // init lambda? if (lambda1 == 0.0) { lambda1 = matrix[i] / B[i]; // check equality } else { equal = Kernel.isEqual(matrix[i], lambda1 * B[i]); } } // leaf loop if (!equal) { break; } } return equal; } /** * evaluates P . A . P * * @param P * point for the conic to be evaluated at * @return 0 iff P lies on conic */ final public double evaluate(GeoPoint P) { return P.x * (matrix[0] * P.x + matrix[3] * P.y + matrix[4] * P.z) + P.y * (matrix[3] * P.x + matrix[1] * P.y + matrix[5] * P.z) + P.z * (matrix[4] * P.x + matrix[5] * P.y + matrix[2] * P.z); } /** * evaluates (p.x, p.y, 1) . A . (p.x, p.y, 1) * * @param pt * inhomogenous coords of a point * @return 0 iff (p.x, p.y, 1) lies on conic */ public final double evaluate(GeoVec2D pt) { return evaluate(pt.getX(), pt.getY()); } /** * evaluates (x, y, 1) . A . (x, y, 1) * * @param x * inhomogenous x-coord of a point * @param y * inhomogenous y-coord of a point * @return 0 iff (p.x, p.y, 1) lies on conic */ public final double evaluate(double x, double y) { return matrix[2] + matrix[4] * x + matrix[5] * y + y * (matrix[5] + matrix[3] * x + matrix[1] * y) + x * (matrix[4] + matrix[0] * x + matrix[3] * y); } /** * Sets the GeoLine polar to A.P, the polar line of P relativ to this conic. * * @param P * point to which we want the polar * @param polar * GeoLine in which the result should be stored */ final public void polarLine(GeoPoint P, GeoLine polar) { if (!isDefined()) { polar.setUndefined(); } else { polar.x = matrix[0] * P.x + matrix[3] * P.y + matrix[4] * P.z; polar.y = matrix[3] * P.x + matrix[1] * P.y + matrix[5] * P.z; polar.z = matrix[4] * P.x + matrix[5] * P.y + matrix[2] * P.z; } } private GgbMat polarMatrix; /** * Sets the GeoPoint polar to the pole of line * * @param line * to which we want the pole * @param polar * GeoPoint in which the result should be stored */ final public void polarPoint(GeoLine line, GeoPoint polar) { polarPoint(line.x, line.y, line.z, polar); } /** * @param equation * line coefficients * @param polar * output point */ final public void polarPoint(Coords equation, GeoPoint polar) { polarPoint(equation.getX(), equation.getY(), equation.getZ(), polar); } /** * @param x * line x coefficient * @param y * line y coefficient * @param z * line z coefficient * @param polar * output point */ final public void polarPoint(double x, double y, double z, GeoPoint polar) { if (!isDefined()) { polar.setUndefined(); return; } if (polarMatrix == null) { polarMatrix = new GgbMat(3, 3); } polarMatrix.set3x3fromConic(matrix); polarMatrix.inverseImmediate(); if (polarMatrix.isUndefined()) { polar.setUndefined(); return; } polar.setCoords( polarMatrix.getEntry(0, 0) * x + polarMatrix.getEntry(0, 1) * y + polarMatrix.getEntry(0, 2) * z, polarMatrix.getEntry(1, 0) * x + polarMatrix.getEntry(1, 1) * y + polarMatrix.getEntry(1, 2) * z, polarMatrix.getEntry(2, 0) * x + polarMatrix.getEntry(2, 1) * y + polarMatrix.getEntry(2, 2) * z); } /** * Sets the GeoLine polar to A.P, the polar line of P relativ to this conic. * * @param project * 2D projected coords of the point to which we want the polar * @param polar * GeoLine in which the result should be stored */ final public void polarLine(Coords project, GeoLine polar) { if (!isDefined()) { polar.setUndefined(); } else { polar.x = matrix[0] * project.getX() + matrix[3] * project.getY() + matrix[4] * project.getZ(); polar.y = matrix[3] * project.getX() + matrix[1] * project.getY() + matrix[5] * project.getZ(); polar.z = matrix[4] * project.getX() + matrix[5] * project.getY() + matrix[2] * project.getZ(); } } /** * Sets the GeoLine diameter to X.S.v + a.v (v is a direction), the diameter * line parallel to v relativ to this conic. * * @param v * direction of diameter * @param diameter * GeoLine for storing the result */ final public void diameterLine(GeoVector v, GeoLine diameter) { diameterLine(v.x, v.y, diameter); } /** * Sets the GeoLine diameter to X.S.v + a.v (v is a direction), the diameter * line parallel to v relativ to this conic. * * @param vx * vector x coord * @param vy * vector y coord * @param diameter * GeoLine for storing the result */ final public void diameterLine(double vx, double vy, GeoLine diameter) { diameter.x = matrix[0] * vx + matrix[3] * vy; diameter.y = matrix[3] * vx + matrix[1] * vy; diameter.z = matrix[4] * vx + matrix[5] * vy; } /** * returns all class-specific xml tags for saveXML */ @Override protected void getXMLtags(StringBuilder sb) { super.getXMLtags(sb); // line thickness and type getLineStyleXML(sb); sb.append("\t<eigenvectors "); sb.append(" x0=\"" + eigenvec[0].getX() + "\""); sb.append(" y0=\"" + eigenvec[0].getY() + "\""); sb.append(" z0=\"1.0\""); sb.append(" x1=\"" + eigenvec[1].getX() + "\""); sb.append(" y1=\"" + eigenvec[1].getY() + "\""); sb.append(" z1=\"1.0\""); sb.append("/>\n"); // matrix must be saved after eigenvectors // as only <matrix> will cause a call to classifyConic() // see geogebra.io.MyXMLHandler: handleMatrix() and handleEigenvectors() sb.append("\t<matrix"); for (int i = 0; i < 6; i++) { sb.append(" A" + i + "=\"" + matrix[i] + "\""); } sb.append("/>\n"); // implicit or specific mode switch (toStringMode) { case GeoConicND.EQUATION_SPECIFIC: Equation.appendType(sb, "specific"); break; case GeoConicND.EQUATION_EXPLICIT: Equation.appendType(sb, "explicit"); break; case GeoConicND.EQUATION_USER: Equation.appendType(sb, "user"); break; case GeoConicND.EQUATION_VERTEX: Equation.appendType(sb, "vertex"); break; case GeoConicND.EQUATION_PARAMETRIC: sb.append("\t<eqnStyle style=\"parametric\"/>\n"); break; default: Equation.appendType(sb, "implicit"); } } /** * Returns description of current specific equation * * @return description of current specific equation */ public String getSpecificEquation() { String ret = null; switch (type) { default: // do nothing break; case GeoConicNDConstants.CONIC_CIRCLE: ret = getLoc().getPlain("CircleEquation"); break; case GeoConicNDConstants.CONIC_ELLIPSE: ret = getLoc().getPlain("EllipseEquation"); break; case GeoConicNDConstants.CONIC_HYPERBOLA: ret = getLoc().getPlain("HyperbolaEquation"); break; case GeoConicNDConstants.CONIC_PARABOLA: ret = getLoc().getPlain("ParabolaEquation"); break; case GeoConicNDConstants.CONIC_DOUBLE_LINE: ret = getLoc().getPlain("DoubleLineEquation"); break; case GeoConicNDConstants.CONIC_PARALLEL_LINES: case GeoConicNDConstants.CONIC_INTERSECTING_LINES: ret = getLoc().getPlain("ConicLinesEquation"); break; case GeoConicNDConstants.CONIC_LINE: ret = getLoc().getPlain("DoubleLineEquation"); break; } return ret; } @Override public boolean isNumberValue() { return false; } @Override final public boolean isGeoConic() { return true; } @Override public void setZero() { setCoeffs(1, 0, 1, 0, 0, 0); } @Override protected void setMidpoint(double[] coords) { b.setX(coords[0]); b.setY(coords[1]); // GeoQuadridND compatibility super.setMidpoint(coords[0], coords[1]); } @Override protected void setMidpoint(double x, double y) { b.setX(x); b.setY(y); // GeoQuadridND compatibility super.setMidpoint(x, y); } /** * @return : */ @Override public String getAssignmentOperator() { return ": "; } /* * Region interface implementation */ @Override public boolean isRegion() { return true; } @Override public boolean isInRegion(GeoPointND PI) { Coords coords = PI.getCoordsInD2IfInPlane(getCoordSys()); if (coords == null) { // point is not in plane containing the polygon return false; } return isInRegion(coords.getX(), coords.getY()); } /** * Returns true if point is in circle/ellipse. Coordinates of PointInterface * are used directly to avoid rounding errors. * * @author Michael Borcherds * @version 2010-05-17 * @param x0 * x-coord * @param y0 * y-coord * @return true if point (x0,y0) is inside the connic */ @Override public boolean isInRegion(double x0, double y0) { return Kernel.isGreaterEqual( evaluate(x0, y0) / evaluateInSignificantPoint(), 0); } /** * @return value of this quadratic form in significant point (midpoint, * focus) */ public double evaluateInSignificantPoint() { switch (type) { case CONIC_INTERSECTING_LINES: return evaluate(b.getX() + lines[0].x + lines[1].x, b.getY() + lines[0].y + lines[1].y); case CONIC_HYPERBOLA: return -evaluate(b.getX(), b.getY()); case CONIC_PARABOLA: return evaluate(b.getX() + p * eigenvec[0].getX(), b.getY() + p * eigenvec[0].getY()); case CONIC_SINGLE_POINT: return evaluate(b.getX() + 1, b.getY()); default: return evaluate(b.getX(), b.getY()); } } /** * Point's parameters are set to its EV coordinates * * @version 2010-07-30 * @param PI * point */ @Override public void pointChangedForRegion(GeoPointND PI) { PI.updateCoords2D(); RegionParameters rp = PI.getRegionParameters(); Coords coords = PI.getCoordsInD2(getCoordSys()); if (!isInRegion(coords.getX(), coords.getY())) { moveBackToRegion(PI, rp); } else { rp.setIsOnPath(false); coordsRWtoEV(coords); if (type != CONIC_PARABOLA) { rp.setT1(coords.getX() / this.halfAxes[0]); rp.setT2(coords.getY() / this.halfAxes[1]); } else { rp.setT1(coords.getX()); rp.setT2(coords.getY() / Math.sqrt(this.p)); } coordsEVtoRW(coords); } PI.updateCoordsFrom2D(false, null); } /** * Move a point back to region * * @param pi * point * @param rp * region parameters */ protected void moveBackToRegion(GeoPointND pi, RegionParameters rp) { pointChanged(pi); rp.setIsOnPath(true); } /** * When elipse is moved, the points moves as well and its EV coordinates * remain the same * * @version 2010-07-30 * @param PI * point */ @Override public void regionChanged(GeoPointND PI) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(PI) || PI.getRegionParameters().isNaN()) { pointChangedForRegion(PI); return; } // GeoPoint P = (GeoPoint) PI; RegionParameters rp = PI.getRegionParameters(); if (rp.isOnPath()) { pathChanged(PI); } else { Coords P = new Coords(3); if (P.isDefined()) { if (type != CONIC_PARABOLA) { P.setX(rp.getT1() * halfAxes[0]); P.setY(rp.getT2() * halfAxes[1]); } else { P.setX(rp.getT1()); P.setY(rp.getT2() * Math.sqrt(this.p)); } P.setZ(1.0); coordsEVtoRW(P); PI.setCoords2D(P.getX(), P.getY(), P.getZ()); PI.updateCoordsFrom2D(false, getCoordSys()); } // in some cases (e.g. ellipse becomes an hyperbola), point goes // outside if (!isInRegion(PI)) { moveBackToRegion(PI, rp); } } } /** * Sets curve to this conic * * @param curve * curve for storing this conic */ public void toGeoCurveCartesian(GeoCurveCartesianND curve) { FunctionVariable fv = new FunctionVariable(kernel, "t"); ExpressionNode evX = null, evY = null; double min = 0, max = 0; if (type == CONIC_CIRCLE && curve.getDimension() == 2) { evX = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.COS, null), Operation.MULTIPLY, new MyDouble(kernel, halfAxes[0])); evY = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.SIN, null), Operation.MULTIPLY, new MyDouble(kernel, halfAxes[1])); ExpressionNode rwX = new ExpressionNode(kernel, evX, Operation.PLUS, new MyDouble(kernel, b.getX())); ExpressionNode rwY = new ExpressionNode(kernel, evY, Operation.PLUS, new MyDouble(kernel, b.getY())); curve.setFun(0, new Function(rwX, fv)); curve.setFun(1, new Function(rwY, fv)); curve.setInterval(0, 2 * Math.PI); if (curve.getDimension() == 3) { curve.setFun(2, new Function(new ExpressionNode(kernel, 0.0), fv)); } return; } if (type == CONIC_ELLIPSE || type == CONIC_CIRCLE && curve.getDimension() == 3) { evX = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.COS, null), Operation.MULTIPLY, new MyDouble(kernel, -halfAxes[0])); evY = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.SIN, null), Operation.MULTIPLY, new MyDouble(kernel, halfAxes[1])); min = 0; max = 2 * Math.PI; } else if (type == CONIC_HYPERBOLA) { evX = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.COSH, null), Operation.MULTIPLY, new MyDouble(kernel, halfAxes[0])); evY = new ExpressionNode(kernel, new ExpressionNode(kernel, fv, Operation.SINH, null), Operation.MULTIPLY, new MyDouble(kernel, halfAxes[1])); min = -2 * Math.PI; max = 2 * Math.PI; } else if (type == CONIC_PARABOLA) { evY = new ExpressionNode(kernel, new ExpressionNode(kernel, fv), Operation.MULTIPLY, new MyDouble(kernel, Math.sqrt(2 * p))); evX = new ExpressionNode(kernel, fv, Operation.MULTIPLY, fv); min = kernel.getXminForFunctions(); max = kernel.getXmaxForFunctions(); } else { return; } if (curve.getDimension() == 3) { Coords e0 = this.getEigenvec3D(0); Coords e1 = this.getEigenvec3D(1); Coords m = getMidpoint3D(); ExpressionNode rwX = linComb(evX, evY, e0.getX(), e1.getX(), m.getX()); ExpressionNode rwY = linComb(evX, evY, e0.getY(), e1.getY(), m.getY()); ExpressionNode rwZ = linComb(evX, evY, e0.getZ(), e1.getZ(), m.getZ()); curve.setFun(0, new Function(rwX, fv)); curve.setFun(1, new Function(rwY, fv)); curve.setFun(2, new Function(rwZ, fv)); } else { ExpressionNode rwX = linComb(evX, evY, eigenvec[0].getX(), eigenvec[0].getY(), b.getX()); ExpressionNode rwY = linComb(evX, evY, eigenvec[0].getY(), -eigenvec[0].getX(), b.getY()); curve.setFun(0, new Function(rwX, fv)); curve.setFun(1, new Function(rwY, fv)); } curve.setInterval(min, max); } private ExpressionNode linComb(ExpressionNode evX, ExpressionNode evY, double cX, double cY, double c1) { return new ExpressionNode(kernel, new ExpressionNode(kernel, new ExpressionNode(kernel, evX, Operation.MULTIPLY, new MyDouble(kernel, cX)), Operation.PLUS, new ExpressionNode(kernel, evY, Operation.MULTIPLY, new MyDouble(kernel, cY))), Operation.PLUS, new MyDouble(kernel, c1)); } /** * Set implicit curve to this conic * * @param curve * Implicit curve to store this conic */ public void toGeoImplicitCurve(GeoImplicit curve) { FunctionVariable v1 = new FunctionVariable(kernel, "x"); FunctionVariable v2 = new FunctionVariable(kernel, "y"); MyDouble d = new MyDouble(kernel, 1.0); ExpressionNode x = new ExpressionNode(kernel, v1, Operation.MULTIPLY, d); ExpressionNode y = new ExpressionNode(kernel, v2, Operation.MULTIPLY, d); ExpressionNode lhs = x.square().multiply(matrix[0]) .plus(y.square().multiply(matrix[1])).plus(matrix[2]) .plus(x.multiply(y).multiply(2 * matrix[3])) .plus(x.multiply(2 * matrix[4])) .plus(y.multiply(2 * matrix[5])); ExpressionNode rhs = new ExpressionNode(kernel, 0.0); double coeff[][] = new double[3][3]; coeff[0][0] = matrix[2]; coeff[1][1] = 2 * matrix[3]; coeff[2][2] = 0; coeff[1][0] = 2 * matrix[4]; coeff[0][1] = 2 * matrix[5]; coeff[2][0] = matrix[0]; coeff[0][2] = matrix[1]; coeff[2][1] = coeff[1][2] = 0; curve.fromEquation(new Equation(kernel, lhs, rhs), coeff); } /** * Some ellipses might be circles by accident. This method tells us whether * we can rely on this conic being circle after some points are moved. * * @return true iff the conic will remain circle after changing parent * inputs */ public boolean keepsType() { if (getParentAlgorithm() == null) { return true; } if (getParentAlgorithm() instanceof AlgoConicFivePoints) { return false; } if (getParentAlgorithm() instanceof AlgoEllipseHyperbolaFociPoint) { return false; } if (getParentAlgorithm() instanceof AlgoEllipseFociLength) { return false; } return true; } /** * Calculates the euclidian distance between this GeoConic and GeoPoint P. * used for compound paths */ @Override public double distance(GeoPoint pt) { // if (!isCircle()) return Double.POSITIVE_INFINITY; /* * double xC = - matrix[4]; double yC = - matrix[5]; double r = * halfAxes[0]; * * double x = p.inhomX; double y = p.inhomY; * * double d = MyMath.length(x - xC, y - yC); return Math.abs(d - r); */ boolean tempLabeling = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); GeoPoint closestPoint = new GeoPoint(cons, null, pt.x, pt.y, pt.z); cons.setSuppressLabelCreation(tempLabeling); closestPoint.setPath(this); pointChanged(closestPoint); closestPoint.updateCoords(); // Application.debug("closest point = // "+closestPoint.inhomX+","+closestPoint.inhomY); // Application.debug("distance = "+p.distance(closestPoint)); return pt.distance(closestPoint); } @Override public String getTypeString() { switch (type) { case GeoConicNDConstants.CONIC_CIRCLE: return "Circle"; case GeoConicNDConstants.CONIC_DOUBLE_LINE: return "DoubleLine"; case GeoConicNDConstants.CONIC_ELLIPSE: return "Ellipse"; case GeoConicNDConstants.CONIC_EMPTY: return "EmptySet"; case GeoConicNDConstants.CONIC_HYPERBOLA: return "Hyperbola"; case GeoConicNDConstants.CONIC_INTERSECTING_LINES: return "IntersectingLines"; case GeoConicNDConstants.CONIC_LINE: return "Line"; case GeoConicNDConstants.CONIC_PARABOLA: return "Parabola"; case GeoConicNDConstants.CONIC_PARALLEL_LINES: return "ParallelLines"; case GeoConicNDConstants.CONIC_SINGLE_POINT: return "Point"; default: return "Conic"; } } ////////////////////////// // AREA ////////////////////////// // for 3D private boolean isEndOfQuadric = false; /** * set if this is end of a quadric * * @param flag * end of quadric */ public void setIsEndOfQuadric(boolean flag) { isEndOfQuadric = flag; } /** * * @return true if this is end of a quadric */ public boolean isEndOfQuadric() { return isEndOfQuadric; } @Override public void doRemove() { if (pointsOnConic != null) { for (int i = 0; i < pointsOnConic.size(); ++i) { GeoPointND pt = pointsOnConic.get(i); pt.removeIncidence(this); } } super.doRemove(); } ////////////////////////////////////////////// // HIT STUFF ////////////////////////////////////////////// private HitType lastHitType = HitType.NONE; /** * @param type * hit type */ final public void setLastHitType(HitType type) { lastHitType = type; } @Override final public HitType getLastHitType() { return lastHitType; } // make sure all Conics go in same group in AlgebraView @Override public String getTypeStringForAlgebraView() { // return getTypeString(); return "Conic"; } private Coords labelPosition, labelPosition3D; private PathParameter labelParameter; @Override public Coords getLabelPosition() { if (labelPosition == null) { labelPosition = new Coords(0, 0, 1); labelParameter = new PathParameter(0); labelPosition3D = Coords.createInhomCoorsInD3(); } pathChangedWithoutCheck(labelPosition, labelParameter, true); return getCoordSys().getPoint(labelPosition, labelPosition3D); } // ////////////////////////////////////// // REGION3D INTERFACE // ////////////////////////////////////// @Override public Coords[] getNormalProjection(Coords coords) { return getCoordSys().getNormalProjection(coords); } @Override public Coords getPoint(double x2d, double y2d, Coords coords) { return getCoordSys().getPoint(x2d, y2d, coords); } @Override public Coords[] getProjection(Coords oldCoords, Coords willingCoords, Coords willingDirection) { Coords[] result = new Coords[] { new Coords(4), new Coords(4) }; willingCoords.projectPlaneThruVIfPossible( getCoordSys().getMatrixOrthonormal(), oldCoords, willingDirection, result[0], result[1]); return result; } @Override public int getMetasLength() { if (metas == null) { return 0; } return metas.size(); } @Override public GeoElement[] getMetas() { GeoElement[] ret = new GeoElement[metas.size()]; metas.toArray(ret); return ret; } private TreeSet<GeoElement> metas; private String parameter = "t"; /** * add the limited quadric has meta geo for this * * @param limitedQuadric * polyhedron */ public void addMeta(GeoElement limitedQuadric) { if (metas == null) { metas = new TreeSet<GeoElement>(); } metas.add(limitedQuadric); } /** * remove limited quadric as meta for this * * @param limitedQuadric * limited quadric */ public void removeMeta(GeoElement limitedQuadric) { if (metas != null) { metas.remove(limitedQuadric); } } /** * * @return midpoint in correct dimension */ public Coords getMidpointND() { return getMidpoint2D(); } /** * @param geo * input * @return true - if input is congruent with this conic false - otherwise */ @Override public ExtendedBoolean isCongruent(GeoElement geo) { if (!geo.isGeoConic()) { return ExtendedBoolean.FALSE; } GeoConicND conic = (GeoConicND) geo; // Circles are congruent if their radius are of equal length: if (this.isCircle() && conic.isCircle()) { return ExtendedBoolean.newExtendedBoolean((Kernel .isEqual(this.getCircleRadius(), conic.getCircleRadius()))); } // Two parabolas are congruent if they have the same distance between // the focus and directrix: if (this.isParabola() && conic.isParabola()) { GeoElement[] ge1 = (this.getParentAlgorithm().input); GeoElement[] ge2 = (conic.getParentAlgorithm().input); if (ge1.length == 2 && ge2.length == 2) { // Easy case: definition by focus and directrix: GeoPoint F = (GeoPoint) ge1[0]; GeoLine d = (GeoLine) ge1[1]; double d1 = getKernel().getAlgoDispatcher() .getNewAlgoClosestPoint(cons, d, F).getP().distance(F); F = (GeoPoint) ge2[0]; d = (GeoLine) ge2[1]; double d2 = getKernel().getAlgoDispatcher() .getNewAlgoClosestPoint(cons, d, F).getP().distance(F); return ExtendedBoolean .newExtendedBoolean(Kernel.isEqual(d1, d2)); } // TODO: Handle the other case(s). } // Two ellipses are congruent if they have the same distances between // the two foci, // and also the sum of generatrixes are equal: if (this.isEllipse() && conic.isEllipse()) { GeoElement[] ge1 = (this.getParentAlgorithm().input); GeoElement[] ge2 = (conic.getParentAlgorithm().input); if (ge1.length == 3 && ge2.length == 3) { // Easy case: definition with 2 foci and one point on the // ellipse: GeoPoint F1 = (GeoPoint) ge1[0]; GeoPoint F2 = (GeoPoint) ge1[1]; GeoPoint P = (GeoPoint) ge1[2]; double d1 = F1.distance(F2); double d2 = F1.distance(P) + F2.distance(P); F1 = (GeoPoint) ge2[0]; F2 = (GeoPoint) ge2[1]; P = (GeoPoint) ge2[2]; double d1_ = F1.distance(F2); double d2_ = F1.distance(P) + F2.distance(P); return ExtendedBoolean.newExtendedBoolean( (Kernel.isEqual(d1, d1_)) && (Kernel.isEqual(d2, d2_))); } // TODO: Handle the other case(s). } // Two hyperbolas are congruent if they have the same distances between // the two foci, // and also the absolute value of the subtraction of generatrixes are // equal: if ((this.isHyperbola() && conic.isHyperbola()) || (this.isDoubleLine() && conic.isDoubleLine())) { GeoElement[] ge1 = (this.getParentAlgorithm().input); GeoElement[] ge2 = (conic.getParentAlgorithm().input); if (ge1.length == 3 && ge2.length == 3) { // Easy case: definition with 2 foci and one point on the // hyperbola: GeoPoint F1 = (GeoPoint) ge1[0]; GeoPoint F2 = (GeoPoint) ge1[1]; GeoPoint P = (GeoPoint) ge1[2]; double d1 = F1.distance(F2); double d2 = Math.abs(F1.distance(P) - F2.distance(P)); F1 = (GeoPoint) ge2[0]; F2 = (GeoPoint) ge2[1]; P = (GeoPoint) ge2[2]; double d1_ = F1.distance(F2); double d2_ = Math.abs(F1.distance(P) - F2.distance(P)); return ExtendedBoolean.newExtendedBoolean( (Kernel.isEqual(d1, d1_)) && (Kernel.isEqual(d2, d2_))); // TODO: Consider unifying this case with the ellipse case. } // TODO: Handle the other case(s). } return ExtendedBoolean.FALSE; } private Coords tmpCoords1, tmpCoords2; final private void createTmpCoords() { if (tmpCoords1 == null) { tmpCoords1 = new Coords(3); tmpCoords2 = new Coords(3); } } /** * @param tpl * demplate * @param dim * dimension * @return parametric description of this conic in given dimension */ protected StringBuilder buildParametricValueString(StringTemplate tpl, int dim) { StringBuilder sbBuildValueString = new StringBuilder(); if (!isDefined()) { sbBuildValueString.append("?"); return sbBuildValueString; } sbBuildValueString.append("X = "); switch (getType()) { case CONIC_CIRCLE: case CONIC_ELLIPSE: buildValueStringMidpointConic(false, "cos(", "sin(", tpl, sbBuildValueString, dim); break; case CONIC_HYPERBOLA: buildValueStringMidpointConic(true, "cosh(", "sinh(", tpl, sbBuildValueString, dim); break; case CONIC_PARABOLA: buildValueString(false, parameter + "\u00b2", parameter, linearEccentricity, 2 * linearEccentricity, tpl, sbBuildValueString, dim); break; case CONIC_SINGLE_POINT: Coords center = getMidpoint3D(); GeoPoint.buildValueStringCoordCartesian3D(kernel, tpl, center.getX(), center.getY(), center.getZ(), sbBuildValueString); break; case CONIC_INTERSECTING_LINES: center = getMidpoint3D(); Coords d1 = getDirection3D(0); Coords d2 = getDirection3D(1); createTmpCoords(); tmpCoords1.setAdd(d1, d2).mulInside(0.5); tmpCoords2.setSub(d2, d1).mulInside(0.5); tmpCoords2.checkReverseForFirstValuePositive(); sbBuildValueString.append("("); sbBuildValueString.append(kernel.format(center.getX(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(center.getY(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(center.getZ(), tpl)); sbBuildValueString.append(") + "); sbBuildValueString.append(Unicode.lambda); sbBuildValueString.append(" ("); kernel.appendTwoCoeffs(tmpCoords1.getX(), tmpCoords2.getX(), tpl, sbBuildValueString); sbBuildValueString.append(", "); kernel.appendTwoCoeffs(tmpCoords1.getY(), tmpCoords2.getY(), tpl, sbBuildValueString); sbBuildValueString.append(", "); kernel.appendTwoCoeffs(tmpCoords1.getZ(), tmpCoords2.getZ(), tpl, sbBuildValueString); sbBuildValueString.append(")"); break; case CONIC_PARALLEL_LINES: Coords c1 = getOrigin3D(0); Coords c2 = getOrigin3D(1); Coords d = getDirection3D(0); createTmpCoords(); tmpCoords1.setAdd(c1, c2).mulInside(0.5); tmpCoords2.setSub(c2, c1).mulInside(0.5); tmpCoords2.checkReverseForFirstValuePositive(); sbBuildValueString.append("("); kernel.appendTwoCoeffs(tmpCoords1.getX(), tmpCoords2.getX(), tpl, sbBuildValueString); sbBuildValueString.append(", "); kernel.appendTwoCoeffs(tmpCoords1.getY(), tmpCoords2.getY(), tpl, sbBuildValueString); sbBuildValueString.append(", "); kernel.appendTwoCoeffs(tmpCoords1.getZ(), tmpCoords2.getZ(), tpl, sbBuildValueString); sbBuildValueString.append(") + "); sbBuildValueString.append(Unicode.lambda); sbBuildValueString.append(" ("); sbBuildValueString.append(kernel.format(d.getX(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(d.getY(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(d.getZ(), tpl)); sbBuildValueString.append(")"); break; case CONIC_DOUBLE_LINE: center = getMidpoint3D(); d = getDirection3D(0); sbBuildValueString.append("("); sbBuildValueString.append(kernel.format(center.getX(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(center.getY(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(center.getZ(), tpl)); sbBuildValueString.append(") + "); sbBuildValueString.append(Unicode.lambda); sbBuildValueString.append(" ("); sbBuildValueString.append(kernel.format(d.getX(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(d.getY(), tpl)); sbBuildValueString.append(", "); sbBuildValueString.append(kernel.format(d.getZ(), tpl)); sbBuildValueString.append(")"); break; case CONIC_EMPTY: sbBuildValueString.append("?"); break; default: Log.debug("unknown conic type"); Log.debug(this.coeffs); sbBuildValueString.append("?"); break; } return sbBuildValueString; } private void buildValueStringMidpointConic(boolean plusMinusX, String s1, String s2, StringTemplate tpl, StringBuilder sbBuildValueString, int dim) { buildValueString(plusMinusX, s1 + parameter + ")", s2 + parameter + ")", getHalfAxis(0), getHalfAxis(1), tpl, sbBuildValueString, dim); } private void buildValueString(boolean plusMinusX, String s1, String s2, double r1, double r2, StringTemplate tpl, StringBuilder sbBuildValueString, int dim) { if (dim < 3) { Coords center = getMidpoint(); GeoPoint.buildValueString(kernel, tpl, Kernel.COORD_CARTESIAN, center.getX(), center.getY(), sbBuildValueString); } else { Coords center = getMidpoint3D(); GeoPoint.buildValueStringCoordCartesian3D(kernel, tpl, center.getX(), center.getY(), center.getZ(), sbBuildValueString); } Coords ev1 = getEigenvec3D(0); Coords ev2 = getEigenvec3D(1); String separator = GeoPoint.buildValueStringSeparator(kernel, tpl); sbBuildValueString.append(" + ("); kernel.appendTwoCoeffs(plusMinusX, r1 * ev1.getX(), r2 * ev2.getX(), s1, s2, tpl, sbBuildValueString); sbBuildValueString.append(separator); sbBuildValueString.append(" "); kernel.appendTwoCoeffs(plusMinusX, r1 * ev1.getY(), r2 * ev2.getY(), s1, s2, tpl, sbBuildValueString); if (dim > 2) { sbBuildValueString.append(separator); sbBuildValueString.append(" "); kernel.appendTwoCoeffs(plusMinusX, r1 * ev1.getZ(), r2 * ev2.getZ(), s1, s2, tpl, sbBuildValueString); } sbBuildValueString.append(')'); } /** * @return circumfernece assuming this is an ellipse */ public double getEllipseCircumference() { return EllipticArcLength.getEllipseCircumference(halfAxes[0], halfAxes[1]); } /** * @param param * parameter name */ public void toParametric(String param) { this.toStringMode = GeoConicND.EQUATION_PARAMETRIC; this.parameter = param; } @Override public boolean isShape() { return kernel.getApplication().has(Feature.BOUNDING_BOXES) && isShape; } @Override public void setIsShape(boolean isShape) { this.isShape = isShape; } }