/* 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 org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.CoordMatrix; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.geos.ChangeableCoordParent; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.Traceable; /** * Abstract class describing quadrics in n-dimension space. Extended by * GeoConic, GeoQuadric3D * * @author Mathieu * */ public abstract class GeoQuadricND extends GeoElement implements GeoQuadricNDConstants, Traceable { private int dimension; /** matrix dimension */ protected int matrixDim; // types /** quadric type */ public int type = GeoQuadricNDConstants.QUADRIC_NOT_CLASSIFIED; // of // quadric /** * flat matrix * * @see org.geogebra.common.kernel.geos.GeoConic Also see GeoQuadric3D in * Desktop */ protected double[] matrix; /** * * @return flat matrix */ public double[] getFlatMatrix() { return matrix; } /** * half axes */ public double[] halfAxes; /** linear eccentricity */ public double linearEccentricity; /** (relative) eccentricity */ public double eccentricity; /** parabola parameter */ public double p; /** flag for isDefined() */ protected boolean defined = true; /** midpoint */ protected Coords midpoint; /** TODO merge with 2D eigenvec */ protected Coords[] eigenvecND; /** numbers on matrix diagonal */ protected double[] diagonal; /** variable string */ private static final char[] VAR_STRING = { 'x', 'y', 'z' }; /** eigenvalues */ protected double[] eigenval; /** mu TODO better javadoc */ protected double[] mu = new double[2]; /** flag for intersect(quadric, quadric) */ protected boolean isIntersection; /** * default constructor * * @param c * construction * @param dimension * dimension of the space (2D or 3D) */ public GeoQuadricND(Construction c, int dimension) { this(c, dimension, false); } /** * @param c * construction */ public GeoQuadricND(Construction c) { super(c); } /** * default constructor * * @param c * construction * @param dimension * dimension of the space (2D or 3D) * @param isIntersection * if this is an intersection curve */ public GeoQuadricND(Construction c, int dimension, boolean isIntersection) { this(c); this.isIntersection = isIntersection; // moved from GeoElement's constructor // must be called from the subclass, see // http://benpryor.com/blog/2008/01/02/dont-call-subclass-methods-from-a-superclass-constructor/ setConstructionDefaults(); // init visual settings createFields(dimension); } /** * @param dim * conic dimension */ protected void createFields(int dim) { this.dimension = dim; matrixDim = (dim + 1) * (dim + 2) / 2; matrix = new double[matrixDim]; halfAxes = new double[dim]; midpoint = new Coords(dim + 1); midpoint.set(dim + 1, 1); eigenval = new double[dim + 1]; mu = new double[dim]; } @Override public void set(GeoElementND geo) { GeoQuadricND quadric = (GeoQuadricND) geo; if (quadric.hasChangeableCoordParentNumbers()) { setChangeableCoordParent(quadric.changeableCoordParent.getNumber(), quadric.changeableCoordParent.getDirector()); } reuseDefinition(geo); } //////////////////////////////// // EIGENVECTORS /** * * @param i * index * @return i-th eigenvector */ public Coords getEigenvec3D(int i) { return eigenvecND[i]; } ///////////////////////////////// // MATRIX REPRESENTATION ///////////////////////////////// private CoordMatrix symetricMatrix; /** * @param vals * flat matrix * @return the matrix representation of the quadric in its dimension * regarding vals */ protected CoordMatrix getSymetricMatrix(double[] vals) { if (symetricMatrix == null) { symetricMatrix = new CoordMatrix(4, 4); } symetricMatrix.set(1, 1, vals[0]); symetricMatrix.set(2, 2, vals[1]); symetricMatrix.set(3, 3, vals[2]); symetricMatrix.set(4, 4, vals[3]); symetricMatrix.set(1, 2, vals[4]); symetricMatrix.set(2, 1, vals[4]); symetricMatrix.set(1, 3, vals[5]); symetricMatrix.set(3, 1, vals[5]); symetricMatrix.set(2, 3, vals[6]); symetricMatrix.set(3, 2, vals[6]); symetricMatrix.set(1, 4, vals[7]); symetricMatrix.set(4, 1, vals[7]); symetricMatrix.set(2, 4, vals[8]); symetricMatrix.set(4, 2, vals[8]); symetricMatrix.set(3, 4, vals[9]); symetricMatrix.set(4, 3, vals[9]); return symetricMatrix; } /** * @return the matrix representation of the quadric in its dimension */ public CoordMatrix getSymetricMatrix() { return getSymetricMatrix(matrix); } private CoordMatrix tmpEigenMatrix; /** * sets the matrix values from eigenvectors, midpoint and "diagonal" values */ protected final void setMatrixFromEigen() { setMatrixFromEigen(0); } /** * @param m21 * element (4,1) of diagonalized matrix */ protected final void setMatrixFromEigen(double m21) { if (tmpEigenMatrix == null) { tmpEigenMatrix = new CoordMatrix(4, 4); } tmpEigenMatrix.set(eigenvecND); tmpEigenMatrix.set(getMidpoint(), 4); CoordMatrix diagonalizedMatrix = CoordMatrix.diagonalMatrix(diagonal); CoordMatrix eigenMatrixInv = tmpEigenMatrix.inverse(); diagonalizedMatrix.set(1, 4, m21); diagonalizedMatrix.set(4, 1, m21); CoordMatrix finalMatrix = eigenMatrixInv.transposeCopy() .mul(diagonalizedMatrix).mul(eigenMatrixInv); setMatrix(finalMatrix); } /** * sets the matrix values from the symmetric matrix m * * @param m * matrix */ protected void setMatrix(CoordMatrix m) { matrix[0] = m.get(1, 1); matrix[1] = m.get(2, 2); matrix[2] = m.get(3, 3); matrix[3] = m.get(4, 4); matrix[4] = m.get(1, 2); matrix[5] = m.get(1, 3); matrix[6] = m.get(2, 3); matrix[7] = m.get(1, 4); matrix[8] = m.get(2, 4); matrix[9] = m.get(3, 4); } ///////////////////////////////// // SPECIAL CASES SETTERS ///////////////////////////////// /** * set the center and radius (as segment) of the N-sphere * * @param M * center * @param segment * radius */ abstract public void setSphereND(GeoPointND M, GeoSegmentND segment); /** * makes this quadric a sphere with midpoint M and radius r * * @param M * center * @param r * radius */ public void setSphereND(GeoPointND M, double r) { defined = ((GeoElement) M).isDefined() && !M.isInfinite(); // check // midpoint setSphereND(M.getInhomCoordsInD3(), r); } /** * makes this quadric a sphere with midpoint M and radius r * * @param M * center * @param rad * radius */ public void setSphereND(Coords M, double rad) { double r = rad; // check radius if (Kernel.isZero(r)) { r = 0; } else if (r < 0) { defined = false; } if (defined) { setSphereNDMatrix(M, r); setAffineTransform(); } } /** * @param M * center * @param P * point on sphere */ abstract public void setSphereND(GeoPointND M, GeoPointND P); /** * @param M * matrix * @param r * radius */ protected void setSphereNDMatrix(Coords M, double r) { double[] coords = M.get(); // set midpoint setMidpoint(coords); // set halfAxes = radius for (int i = 0; i < dimension; i++) { halfAxes[i] = r; } // set quadric's matrix with M(mx, my, mz) and r // [ 1 0 -m ] // [ 0 1 -n ] // [ -m -n m\u00b2+n\u00b2-r\u00b2 ] for (int i = 0; i < dimension; i++) { matrix[i] = 1.0d; } matrix[dimension] = -r * r; for (int i = 0; i < dimension; i++) { matrix[dimension] += coords[i] * coords[i]; } for (int i = dimension + 1; i < matrixDim - dimension; i++) { matrix[i] = 0.0; } for (int i = matrixDim - dimension; i < matrixDim; i++) { matrix[i] = -coords[i - (matrixDim - dimension)]; } if (r >= Kernel.STANDARD_PRECISION) { // radius not zero if (type != QUADRIC_SPHERE) { type = QUADRIC_SPHERE; linearEccentricity = 0.0d; eccentricity = 0.0d; // set first eigenvector and eigenvectors setFirstEigenvector(new double[] { 1, 0 }); findEigenvectors(); } } else if (Kernel.isZero(r)) { // radius == 0 singlePoint(); } else { // radius < 0 or radius = infinite empty(); } } /** * turn type of quadric to empty */ public void empty() { type = QUADRIC_EMPTY; } @Override public void setUndefined() { defined = false; empty(); } /** * mark as defined */ final public void setDefined() { defined = true; } /** * @param coord1 * x-coord of midpoint * @param coord2 * y-coord of midpoint */ protected void setMidpoint(double coord1, double coord2) { midpoint.set(1, coord1); midpoint.set(2, coord2); } /** * @param coords * coords of midpoint */ protected void setMidpoint(double[] coords) { midpoint.set(coords); } /** * @return midpoint 2D */ public Coords getMidpoint2D() { return midpoint; } /** * @return midpoint 2D */ public Coords getMidpoint() { return getMidpoint2D(); } /** * @return midpoint 3D */ public Coords getMidpoint3D() { Coords ret = new Coords(4); for (int i = 1; i < midpoint.getLength(); i++) { ret.set(i, midpoint.get(i)); } ret.setW(midpoint.getLast()); return ret; } /** * @param i * index * @return i-th half axis */ public double getHalfAxis(int i) { return halfAxes[i]; } @Override public boolean isDefined() { return defined; } /** * @return quadric type */ final public int getType() { return type; } /** * @param type * quadric type */ final public void setType(int type) { this.type = type; } ////////////////////////////////////// : // STRING ////////////////////////////////////// : /** * returns equation of conic. in implicit mode: a x\u00b2 + b xy + c y\u00b2 * + d x + e y + f = 0. in specific mode: y\u00b2 = ... , (x - m)\u00b2 + (y * - n)\u00b2 = r\u00b2, ... */ @Override public String toString(StringTemplate tpl) { StringBuilder sbToString = new StringBuilder(); sbToString.setLength(0); sbToString.append(label); sbToString.append(": "); sbToString.append(buildValueString(tpl).toString()); return sbToString.toString(); } @Override public String toValueString(StringTemplate tpl) { return buildValueString(tpl).toString(); } /** * @param tpl * string template * @return value string as string builder */ abstract protected StringBuilder buildValueString(StringTemplate tpl); /** * Appends value string of this (if this isa sphere) to given builder * * @param sbToValueString * string builder * @param tpl * string template */ protected void buildSphereNDString(StringBuilder sbToValueString, StringTemplate tpl) { String squared; switch (tpl.getStringType()) { case LATEX: squared = "^{2}"; break; case GIAC: squared = "^2"; break; default: squared = "\u00b2"; } for (int i = 0; i < dimension; i++) { if (Kernel.isZero(getMidpoint().get(i + 1))) { sbToValueString.append(VAR_STRING[i]); sbToValueString.append(squared); } else { sbToValueString.append("("); sbToValueString.append(VAR_STRING[i]); sbToValueString.append(" "); kernel.formatSigned(-getMidpoint().get(i + 1), sbToValueString, tpl); sbToValueString.append(")"); sbToValueString.append(squared); } if (i < dimension - 1) { sbToValueString.append(" + "); } else { sbToValueString.append(" = "); } } sbToValueString .append(kernel.format(getHalfAxis(0) * getHalfAxis(0), tpl)); } /** * @param coords * coords of first eigenvector */ // TODO turn methods below to abstract, implement it in GeoQuadric3D protected void setFirstEigenvector(double[] coords) { // do nothing,overriden in some classes } /** * Updates eigenvectors */ protected void findEigenvectors() { // do nothing,overriden in some classes } /** * Update to become single point */ abstract protected void singlePoint(); /** * Update affine transform */ protected void setAffineTransform() { // AffineTransform transform = getAffineTransform(); /* * ( v1x v2x bx ) ( v1y v2y by ) ( 0 0 1 ) */ /* * transform.setTransform( eigenvec[0].x, eigenvec[0].y, eigenvec[1].x, * eigenvec[1].y, b.x, b.y); */ } // //////////////////////////////////////////////////// // PARENT NUMBER (HEIGHT OF A PRISM, ...) // //////////////////////////////////////////////////// private ChangeableCoordParent changeableCoordParent = null; /** * sets the parents for changing coords * * @param number * number * @param direction * direction * */ final public void setChangeableCoordParent(GeoNumeric number, GeoElement direction) { changeableCoordParent = new ChangeableCoordParent(number, direction); } @Override public boolean hasChangeableCoordParentNumbers() { return (changeableCoordParent != null); } @Override public void recordChangeableCoordParentNumbers(EuclidianView view) { changeableCoordParent.record(view); } @Override public boolean moveFromChangeableCoordParentNumbers(Coords rwTransVec, Coords endPosition, Coords viewDirection, ArrayList<GeoElement> updateGeos, ArrayList<GeoElement> tempMoveObjectList, EuclidianView view) { if (changeableCoordParent == null) { return false; } return changeableCoordParent.move(rwTransVec, endPosition, viewDirection, updateGeos, tempMoveObjectList, view); } /** * * @return dimension (2 for conic, 3 for quadric) */ public int getDimension() { return dimension; } ////////////////// // TRACE ////////////////// private boolean trace; @Override public boolean isTraceable() { return true; } @Override public void setTrace(boolean trace) { this.trace = trace; } @Override public boolean getTrace() { return trace; } }