/* 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. */ /* * GeoConic.java * * Created on 10. September 2001, 08:52 */ package org.geogebra.common.kernel.geos; import java.util.ArrayList; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Matrix.CoordSys; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.EquationValue; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.kernelND.GeoConicND; import org.geogebra.common.kernel.kernelND.GeoConicNDConstants; import org.geogebra.common.kernel.kernelND.GeoElementND; import org.geogebra.common.kernel.kernelND.GeoLineND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.prover.NoSymbolicParametersException; import org.geogebra.common.kernel.prover.polynomial.PPolynomial; import org.geogebra.common.kernel.prover.polynomial.PVariable; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.MyMath; /** * Conics in 2D */ public class GeoConic extends GeoConicND implements ConicMirrorable, SymbolicParametersBotanaAlgo, EquationValue { /* * ( A[0] A[3] A[4] ) matrix = ( A[3] A[1] A[5] ) ( A[4] A[5] A[2] ) */ /** * Creates a conic * * @param c * construction */ public GeoConic(Construction c) { super(c, 2); } /** * Creates new GeoConic with Coordinate System for 3D * * @param c * construction * @param coeffs * coefficients */ public GeoConic(Construction c, double[] coeffs) { this(c); setCoeffs(coeffs); } /** * Creates copy of conic in construction of conic * * @param conic * conic to be copied */ public GeoConic(GeoConic conic) { this(conic.cons); set(conic); } @Override public GeoClass getGeoClassType() { return GeoClass.CONIC; } @Override public GeoElement copy() { return new GeoConic(this); } /** * Return angle of rotation from x-axis to the major axis of ellipse * * @return angle between x-axis and major axis of ellipse */ double getPhi() { if (matrix[3] == 0) { if (matrix[0] < matrix[1]) { return 0.0; } return 0.5 * Math.PI; } if (matrix[0] <= matrix[1]) { return 0.25 * Math.PI - 0.5 * Math.atan((matrix[0] - matrix[1]) / (2 * matrix[3])); } return 0.75 * Math.PI - 0.5 * Math.atan((matrix[0] - matrix[1]) / (2 * matrix[3])); } /** * Return n points on conic * * @param n * number of points * @return Array list of points */ public ArrayList<GeoPoint> getPointsOnConic(int n) { GeoCurveCartesian curve = new GeoCurveCartesian(cons); this.toGeoCurveCartesian(curve); double startInterval = -Math.PI, endInterval = Math.PI; if (this.type == CONIC_HYPERBOLA) { startInterval = -Math.PI / 2; endInterval = Math.PI / 2; } if (this.type == CONIC_PARABOLA) { startInterval = -1; endInterval = 1; } return curve.getPointsOnCurve(n, startInterval, endInterval); } /** * Invert circle or line in circle * * @version 2010-01-21 * @author Michael Borcherds * @param mirror * Circle used as mirror */ @Override final public void mirror(GeoConic mirror) { if (mirror.getType() == CONIC_SINGLE_POINT) { setUndefined(); return; } if (mirror.isCircle() && (type == CONIC_SINGLE_POINT || type == CONIC_CIRCLE)) { // Mirror // point // in // circle double r1 = mirror.getHalfAxes()[0]; GeoVec2D midpoint1 = mirror.getTranslationVector(); double x1 = midpoint1.getX(); double y1 = midpoint1.getY(); double r2 = getHalfAxes()[0]; GeoVec2D midpoint2 = getTranslationVector(); double x2 = midpoint2.getX(); double y2 = midpoint2.getY(); // distance between centers double dist = Math .sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); // Log.debug("dist ="+dist); // Log.debug("r1="+r1+" x1="+x1+"y1 ="+y1); // Log.debug("r2="+r2+" x2="+x2+"y2 ="+y2); // circle being reflected has zero radius // and it's at center of mirror if (Kernel.isZero(r2) && Kernel.isZero(dist)) { setUndefined(); update(); return; } // does circle being inverted pass through center of the other? if (Kernel.isEqual(dist, r2)) { double dx = x2 - x1; double dy = y2 - y1; // (x3,y3) is reflection of reflection of (x1+2dx,x1+2dy) an // thus lies on the line double k = r1 * r1 / 2 / r2 / r2; double x3 = x1 + k * dx; double y3 = y1 + k * dy; matrix[4] = dx * 0.5; matrix[5] = dy * 0.5; matrix[2] = -dx * x3 - dy * y3; matrix[0] = 0; matrix[1] = 0; matrix[3] = 0; // we update the eigenvectors etc. this.classifyConic(true); // classification yields CONIC_DOUBLE_LINE, we want a single // line type = GeoConicNDConstants.CONIC_LINE; return; } double x = r1 * r1 / (dist - r2); double y = r1 * r1 / (dist + r2); // radius of new circle double r3 = Math.abs(y - x) / 2.0; // center of new circle double centerX, centerY; if (Kernel.isZero(dist)) { // circle being mirrored has same centre as mirror -> centre // doesn't change centerX = x1; centerY = y1; } else { centerX = x1 + (x2 - x1) * (Math.min(x, y) + r3) / dist; centerY = y1 + (y2 - y1) * (Math.min(x, y) + r3) / dist; } // Log.debug("r3="+r3+" centerX="+centerX+"centerY ="+centerY); // double sf=r1*r1/((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)); // setCoords( x1+sf*(x2-x1), y1+sf*(y2-y1) ,1.0); GeoPoint tmp = new GeoPoint(cons, null, centerX, centerY, 1.0); setCircleMatrix(tmp, r3); tmp.removeOrSetUndefinedIfHasFixedDescendent(); } else if (mirror.isCircle() && (this .getType() == GeoConicNDConstants.CONIC_LINE || this.getType() == GeoConicNDConstants.CONIC_PARALLEL_LINES)) { // Mirror // point // in // circle if (mirror.getType() == GeoConicNDConstants.CONIC_CIRCLE) { // Mirror // point in // circle double r = mirror.getHalfAxes()[0]; GeoVec2D midPoint = mirror.getTranslationVector(); double mx = midPoint.getX(); double my = midPoint.getY(); double lx = (getLines()[0]).x; double ly = (getLines()[0]).y; double lz = (getLines()[0]).z; double perpY, perpX; if (lx == 0) { perpX = mx; perpY = -lz / ly; } else { perpY = -(lx * ly * mx - lx * lx * my + ly * lz) / (lx * lx + ly * ly); perpX = (-lz - ly * perpY) / lx; } double dist2 = ((perpX - mx) * (perpX - mx) + (perpY - my) * (perpY - my)); // if line goes through center, we keep it if (!Kernel.isZero(dist2)) { double sf = r * r / dist2; // GeoPoint p =new GeoPoint(cons,null,a+sf*(perpX-a), // b+sf*(perpY-b) ,1.0); GeoPoint m = new GeoPoint(cons); m.setCoords(mx + sf * (perpX - mx) / 2, my + sf * (perpY - my) / 2, 1.0); setSphereND(m, sf / 2 * Math.sqrt(((perpX - mx) * (perpX - mx) + (perpY - my) * (perpY - my)))); } else { type = GeoConicNDConstants.CONIC_LINE; } } else { setUndefined(); } } else if (mirror.getType() == GeoConicNDConstants.CONIC_PARALLEL_LINES) { GeoLine line = mirror.getLines()[0]; mirror(line); } else { setUndefined(); } setAffineTransform(); // updateDegenerates(); // for degenerate conics } /** * mirror this conic at point Q */ @Override final public void mirror(Coords Q) { double qx = Q.getX(); double qy = Q.getY(); matrix[2] = 4.0 * (qy * qy * matrix[1] + qx * (qx * matrix[0] + 2.0 * qy * matrix[3] + matrix[4]) + qy * matrix[5]) + matrix[2]; matrix[4] = -2.0 * (qx * matrix[0] + qy * matrix[3]) - matrix[4]; matrix[5] = -2.0 * (qx * matrix[3] + qy * matrix[1]) - matrix[5]; // change eigenvectors' orientation eigenvec[0].mult(-1.0); eigenvec[1].mult(-1.0); // mirror translation vector b b.mirror(Q); setMidpoint(new double[] { b.getX(), b.getY() }); setAffineTransform(); updateDegenerates(); // for degenerate conics } /** * mirror this point at line g */ @Override final public void mirror(GeoLineND g1) { GeoLine g = (GeoLine) g1; // Y = S(phi).(X - Q) + Q // where Q is a point on g, S(phi) is the mirror transform // and phi/2 is the line's slope angle // get arbitrary point of line double qx, qy; if (Math.abs(g.getX()) > Math.abs(g.getY())) { qx = -g.getZ() / g.getX(); qy = 0.0d; } else { qx = 0.0d; qy = -g.getZ() / g.getY(); } // translate -Q doTranslate(-qx, -qy); // do mirror transform mirror(2.0 * Math.atan2(-g.getX(), g.getY())); // translate +Q doTranslate(qx, qy); setAffineTransform(); updateDegenerates(); // for degenerate conics } /** * mirror transform with angle phi [ cos sin 0 ] [ sin -cos 0 ] [ 0 0 1 ] */ final private void mirror(double phi) { // set rotated matrix 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 tmp = diff * cos2 + 2.0 * matrix[3] * sin2; double A0 = (sum + tmp) / 2.0; double A1 = (sum - tmp) / 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; // avoid classification: make changes by hand eigenvec[0].mirror(phi); eigenvec[1].mirror(phi); b.mirror(phi); setMidpoint(new double[] { b.getX(), b.getY() }); } // ////////////////////////////////////// // FOR DRAWING IN 3D // ////////////////////////////////////// @Override public Coords getEigenvec3D(int i) { Coords ret = new Coords(4); ret.set(getEigenvec(i)); return ret; } @Override public boolean hasDrawable3D() { return true; } @Override public Coords getDirection3D(int i) { return new Coords(lines[i].y, -lines[i].x, 0, 0); } @Override public Coords getOrigin3D(int i) { return new Coords(startPoints[i].x, startPoints[i].y, 0, 1); } @Override final public boolean isCasEvaluableObject() { return true; } @Override public char getLabelDelimiter() { return ':'; } private CoordSys coordSys; @Override public CoordSys getCoordSys() { if (coordSys == null) { coordSys = CoordSys.Identity3D; } return coordSys; } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { // TODO Auto-generated method stub double[][] adj = MyMath.adjoint(a00, a01, a02, a10, a11, a12, a20, a21, a22); /* * ( A[0] A[3] A[4] ) matrix = ( A[3] A[1] A[5] ) ( A[4] A[5] A[2] ) * P=matrix*B */ double p00 = matrix[0] * adj[0][0] + matrix[3] * adj[0][1] + matrix[4] * adj[0][2]; double p01 = matrix[0] * adj[1][0] + matrix[3] * adj[1][1] + matrix[4] * adj[1][2]; double p02 = matrix[0] * adj[2][0] + matrix[3] * adj[2][1] + matrix[4] * adj[2][2]; double p10 = matrix[3] * adj[0][0] + matrix[1] * adj[0][1] + matrix[5] * adj[0][2]; double p11 = matrix[3] * adj[1][0] + matrix[1] * adj[1][1] + matrix[5] * adj[1][2]; double p12 = matrix[3] * adj[2][0] + matrix[1] * adj[2][1] + matrix[5] * adj[2][2]; double p20 = matrix[4] * adj[0][0] + matrix[5] * adj[0][1] + matrix[2] * adj[0][2]; double p21 = matrix[4] * adj[1][0] + matrix[5] * adj[1][1] + matrix[2] * adj[1][2]; double p22 = matrix[4] * adj[2][0] + matrix[5] * adj[2][1] + matrix[2] * adj[2][2]; matrix[0] = adj[0][0] * p00 + adj[0][1] * p10 + adj[0][2] * p20; matrix[3] = adj[0][0] * p01 + adj[0][1] * p11 + adj[0][2] * p21; matrix[4] = adj[0][0] * p02 + adj[0][1] * p12 + adj[0][2] * p22; matrix[1] = adj[1][0] * p01 + adj[1][1] * p11 + adj[1][2] * p21; matrix[5] = adj[1][0] * p02 + adj[1][1] * p12 + adj[1][2] * p22; matrix[2] = adj[2][0] * p02 + adj[2][1] * p12 + adj[2][2] * p22; this.classifyConic(false); } @Override public boolean isFillable() { return type != GeoConicNDConstants.CONIC_LINE; } @Override public boolean isInverseFillable() { return isFillable(); } /* * ( A[0] A[3] A[4] ) matrix = ( A[3] A[1] A[5] ) ( A[4] A[5] A[2] ) */ /** * @param coeff * matrix of coefficients */ public void setCoeffs(ExpressionValue[][] coeff) { matrix[0] = evalCoeff(coeff, 2, 0); matrix[1] = evalCoeff(coeff, 0, 2); matrix[2] = evalCoeff(coeff, 0, 0); matrix[3] = evalCoeff(coeff, 1, 1) / 2; matrix[4] = evalCoeff(coeff, 1, 0) / 2; matrix[5] = evalCoeff(coeff, 0, 1) / 2; classifyConic(false); if (coeff.length <= 2 && coeff[0].length <= 2 && Kernel.isZero(evalCoeff(coeff, 1, 1))) { type = CONIC_LINE; } } /** * @param ev * two-fold array of expressions * @param i * row * @param j * column * @return evaluated ev[i][j] or 0 if the array does not contain [i][j] */ public static double evalCoeff(ExpressionValue[][] ev, int i, int j) { if (ev.length > i && ev[i].length > j && ev[i][j] != null) { return ev[i][j].evaluateDouble(); } return 0; } /** * @return parameter of parabola */ public double getP() { return p; } /** * Set this conic from line (type will be CONIC_LINE) * * @param line * line */ public void fromLine(GeoLine line) { lines = new GeoLine[2]; lines[0] = line.copy(); lines[1] = line.copy(); type = GeoConicNDConstants.CONIC_LINE; eigenvec[0] = new GeoVec2D(kernel, -line.getY(), line.getX()); GeoPointND sp = line.getStartPoint(); if (sp == null) { sp = line.setStandardStartPoint(); } b = new GeoVec2D(kernel, sp.getInhomX(), sp.getInhomY()); this.setMidpoint(b.getX(), b.getY()); } @Override public PVariable[] getBotanaVars(GeoElementND geo) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersBotanaAlgo) { return ((SymbolicParametersBotanaAlgo) algoParent) .getBotanaVars(this); } return null; } @Override public PPolynomial[] getBotanaPolynomials(GeoElementND geo) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersBotanaAlgo) { return ((SymbolicParametersBotanaAlgo) algoParent) .getBotanaPolynomials(this); } throw new NoSymbolicParametersException(); } @Override public Coords getDirectionInD3() { return Coords.VZ; } /** * dilate this conic from point S by factor r * * @param rval * ratio * @param S * fixed point of dilation */ @Override final public void dilate(NumberValue rval, Coords S) { double r = rval.getDouble(); double sx = S.getX(); double sy = S.getY(); // remember Eigenvector orientation boolean oldOrientation = hasPositiveEigenvectorOrientation(); // translate -S doTranslate(-sx, -sy); // do dilation doDilate(r); // translate +S doTranslate(sx, sy); // classify as type may have change classifyConic(); // make sure we preserve old Eigenvector orientation setPositiveEigenvectorOrientation(oldOrientation); } @Override public boolean hasLineOpacity() { return true; } /** * set this to single point at m location * * @param conic * conic which will be single point * @param x * single point x coord * @param y * single point y coord */ static final public void setSinglePoint(GeoConic conic, double x, double y) { conic.setMidpoint(x, y); conic.singlePoint(); } @Override public ValueType getValueType() { return ValueType.EQUATION; } @Override public Equation getEquation() { return kernel.getAlgebraProcessor().parseEquation(this); } }