/* 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. */ /* * GeoVector.java * * The vector (x,y) has homogenous coordinates (x,y,0) * * Created on 30. August 2001, 17:39 */ package org.geogebra.common.kernel.geos; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import org.geogebra.common.kernel.CircularDefinitionException; 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.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoDependentVector; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.DependentAlgo; import org.geogebra.common.kernel.algos.SymbolicParameters; import org.geogebra.common.kernel.algos.SymbolicParametersAlgo; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionValue; import org.geogebra.common.kernel.arithmetic.MyVecNode; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValidExpression; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.arithmetic.VectorValue; 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.kernelND.GeoVectorND; 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.plugin.Operation; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.lang.Unicode; /** * * @author Markus */ final public class GeoVector extends GeoVec3D implements Path, VectorValue, Translateable, PointRotateable, Mirrorable, Dilateable, MatrixTransformable, Transformable, GeoVectorND, SpreadsheetTraceable, SymbolicParametersAlgo, SymbolicParametersBotanaAlgo { private GeoPointND startPoint; // for path interface we use a segment private GeoSegment pathSegment; private GeoPoint pathStartPoint, pathEndPoint; private boolean waitingForStartPoint = false; private HashSet<GeoPointND> waitingPointSet; /** * Creates new GeoVector * * @param c * construction */ public GeoVector(Construction c) { super(c); setConstructionDefaults(); } @Override public GeoClass getGeoClassType() { return GeoClass.VECTOR; } @Override final public boolean isCasEvaluableObject() { return true; } /** * Creates new GeoVector * * @param c * construction * @param label * label * @param x * x-coord * @param y * y-coord * @param z * z-coord */ public GeoVector(Construction c, String label, double x, double y, double z) { super(c, x, y, z); // GeoVec3D constructor setConstructionDefaults(); setLabel(label); // setEuclidianVisible(false); } /** * Copy constructor * * @param vector * vector to copy */ public GeoVector(GeoVector vector) { this(vector.cons); set(vector); // setEuclidianVisible(false); } @Override final public void setCoords(double x, double y, double z) { this.x = x; this.y = y; this.z = z; setDefinition(null); } @Override final public void setCoords(double[] c) { setCoords(c[0], c[1], c[2]); } @Override final public void setCoords(GeoVec3D v) { x = v.x; y = v.y; z = v.z; setDefinition(null); } @Override public void set(GeoElementND geo) { if (geo.isGeoPoint()) { GeoPointND p = (GeoPointND) geo; double[] coords = p.getCoordsInD3().get(); if (Kernel.isZero(coords[2])) { setCoords(coords); } else { setUndefined(); } } else { super.set(geo); } if (!geo.isGeoVector()) { return; } GeoVector vec = (GeoVector) geo; // don't set start point for macro output // see AlgoMacro.initRay() if (geo.getConstruction() != cons && isAlgoMacroOutput()) { return; } try { if (vec.startPoint != null) { if (vec.hasAbsoluteLocation()) { // create new location point setStartPoint(vec.startPoint.copy()); } else { // take existing location point setStartPoint(vec.startPoint); } } } catch (CircularDefinitionException e) { Log.debug("set GeoVector: CircularDefinitionException"); } } @Override public GeoElement copy() { return new GeoVector(this); } /** * @param r * radius * @param phi * phase */ final public void setPolarCoords(double r, double phi) { // convert angle to radiant x = r * Math.cos(phi); y = r * Math.sin(phi); z = 0.0d; } /** * Sets coords to (x,y,0) * * @param v * vector (x,y) */ final public void setCoords(GeoVec2D v) { x = v.getX(); y = v.getY(); z = 0.0d; } /** * Converts the homogeneous coordinates (x,y,z) of this GeoVec3D to the * inhomogeneous coordinates (x/z, y/z) of a new GeoVec2D. * * @return vector containing inhomogeneous coords */ final public GeoVec2D getInhomVec() { return new GeoVec2D(kernel, x, y); } /** * Retuns starting point of this vector or null. */ @Override final public GeoPointND getStartPoint() { return startPoint; } @Override public GeoPointND[] getStartPoints() { if (startPoint == null) { return null; } GeoPointND[] ret = new GeoPointND[1]; ret[0] = startPoint; return ret; } @Override public boolean hasAbsoluteLocation() { return startPoint == null || startPoint.isAbsoluteStartPoint(); } @Override public void setStartPoint(GeoPointND p, int number) throws CircularDefinitionException { setStartPoint(p); } /** * Sets the startpoint without performing any checks. This is needed for * macros. */ @Override public void initStartPoint(GeoPointND p, int number) { startPoint = p; } @Override public void removeStartPoint(GeoPointND p) { if (startPoint == p) { try { setStartPoint(null); } catch (Exception e) { // ignore circular definition here } } } @Override public void setStartPoint(GeoPointND p) throws CircularDefinitionException { if (startPoint == p) { return; } // macro output uses initStartPoint() only if (isAlgoMacroOutput()) { return; } // check for circular definition if (isParentOf(p)) { Log.debug(this + " startpoint " + p); // throw new CircularDefinitionException(); } // remove old dependencies if (startPoint != null) { startPoint.getLocateableList().unregisterLocateable(this); } // set new location startPoint = p; // add new dependencies if (startPoint != null) { startPoint.getLocateableList().registerLocateable(this); } // reinit path if (pathSegment != null) { initPathSegment(); } // update the waiting points if (waitingForStartPoint) { waitingForStartPoint = false; if (waitingPointSet != null) { updatePathSegment(); GeoPoint P; Iterator<GeoPointND> it = waitingPointSet.iterator(); while (it.hasNext()) { P = (GeoPoint) it.next(); pathSegment.pointChanged(P); P.updateCoords(); } } waitingPointSet = null; } } @Override public void setWaitForStartPoint() { // the startpoint should not be used as long // as waitingForStartPoint is true // This is important for points on this vector: // their coords should not be changed until // the startPoint was finally set waitingForStartPoint = true; } @Override public void doRemove() { super.doRemove(); // tell startPoint if (startPoint != null) { startPoint.getLocateableList().unregisterLocateable(this); } } @Override final public boolean isFinite() { return !isInfinite(); } @Override final public boolean isInfinite() { return Double.isInfinite(x) || Double.isInfinite(y); } @Override final protected boolean showInEuclidianView() { return isDefined() && !isInfinite(); } @Override public final boolean showInAlgebraView() { // independent or defined // return isIndependent() || isDefined(); return true; } /** * Yields true if the coordinates of this vector are equal to those of * vector v. Infinite points are checked for linear dependency. */ // Michael Borcherds 2008-05-01 @Override final public boolean isEqual(GeoElementND geo) { if (!geo.isGeoVector()) { return false; } GeoVector v = (GeoVector) geo; if (!(isFinite() && v.isFinite())) { return false; } return Kernel.isEqual(x, v.x) && Kernel.isEqual(y, v.y); } /*********************************************************** * MOVEMENTS ***********************************************************/ /** * rotate this vector by angle phi around (0,0) */ @Override final public void rotate(NumberValue phi) { rotateXY(phi); } /** * Called when transforming Ray[point,direction] -- doesn't do anything. */ @Override public void translate(Coords v) { // do nothing } @Override public void rotate(NumberValue r, GeoPointND S) { rotateXY(r); } @Override public void mirror(Coords Q) { setCoords(-x, -y, z); } @Override public void mirror(GeoLineND g1) { GeoLine g = (GeoLine) g1; mirrorXY(2.0 * Math.atan2(-g.getX(), g.getY())); } @Override public void dilate(NumberValue rval, Coords S) { double r = rval.getDouble(); setCoords(r * x, r * y, z); } @Override public void matrixTransform(double a, double b, double c, double d) { Double x1 = a * x + b * y; Double y1 = c * x + d * y; setCoords(x1, y1, z); } /*********************************************************************/ @Override final public String toString(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(label); switch (tpl.getCoordStyle(kernel.getCoordStyle())) { case Kernel.COORD_STYLE_FRENCH: // no equal sign sbToString.append(": "); break; case Kernel.COORD_STYLE_AUSTRIAN: // no equal sign break; default: sbToString.append(" = "); } // Without toString, there was an InvocationTargetException here String str = buildValueString(tpl).toString(); sbToString.append(str); return sbToString.toString(); } @Override final public String toStringMinimal(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(regrFormat(x)); sbToString.append(" "); sbToString.append(regrFormat(y)); return sbToString.toString(); } private StringBuilder sbToString = new StringBuilder(50); @Override final public String toValueString(StringTemplate tpl) { return buildValueString(tpl).toString(); } private StringBuilder buildValueString(StringTemplate tpl) { sbBuildValueString.setLength(0); switch (tpl.getStringType()) { case GIAC: sbBuildValueString.append("ggbvect["); sbBuildValueString.append(kernel.format(getInhomVec().getX(), tpl)); sbBuildValueString.append(','); sbBuildValueString.append(kernel.format(getInhomVec().getY(), tpl)); sbBuildValueString.append("]"); return sbBuildValueString; default: // continue below } switch (toStringMode) { case Kernel.COORD_POLAR: sbBuildValueString.append("("); sbBuildValueString.append(kernel.format(MyMath.length(x, y), tpl)); sbBuildValueString.append("; "); sbBuildValueString .append(kernel.formatAngle(Math.atan2(y, x), tpl, false)); sbBuildValueString.append(")"); break; case Kernel.COORD_COMPLEX: sbBuildValueString.append(kernel.format(x, tpl)); sbBuildValueString.append(" "); kernel.formatSigned(y, sbBuildValueString, tpl); sbBuildValueString.append(Unicode.IMAGINARY); break; case Kernel.COORD_CARTESIAN_3D: GeoPoint.buildValueStringCoordCartesian3D(kernel, tpl, x, y, 0, sbBuildValueString); break; case Kernel.COORD_SPHERICAL: GeoPoint.buildValueStringCoordSpherical(kernel, tpl, x, y, 0, sbBuildValueString); break; default: // CARTESIAN sbBuildValueString.append("("); sbBuildValueString.append(kernel.format(x, tpl)); switch (tpl.getCoordStyle(kernel.getCoordStyle())) { case Kernel.COORD_STYLE_AUSTRIAN: sbBuildValueString.append(" | "); break; default: sbBuildValueString.append(", "); } sbBuildValueString.append(kernel.format(y, tpl)); sbBuildValueString.append(")"); break; } return sbBuildValueString; } private StringBuilder sbBuildValueString = new StringBuilder(50); /** * interface VectorValue implementation */ @Override public GeoVec2D getVector() { GeoVec2D ret = new GeoVec2D(kernel, x, y); ret.setMode(toStringMode); return ret; } @Override public double[] getPointAsDouble() { return new double[] { x, y, 0 }; } /** POLAR or CARTESIAN */ /** * returns all class-specific xml tags for saveXML */ @Override protected void getXMLtags(StringBuilder xmlsb) { super.getXMLtags(xmlsb); // line thickness and type getLineStyleXML(xmlsb); // polar or cartesian coords switch (toStringMode) { case Kernel.COORD_POLAR: xmlsb.append("\t<coordStyle style=\"polar\"/>\n"); break; case Kernel.COORD_COMPLEX: xmlsb.append("\t<coordStyle style=\"complex\"/>\n"); break; case Kernel.COORD_CARTESIAN_3D: xmlsb.append("\t<coordStyle style=\"cartesian3d\"/>\n"); break; case Kernel.COORD_SPHERICAL: xmlsb.append("\t<coordStyle style=\"spherical\"/>\n"); break; default: xmlsb.append("\t<coordStyle style=\"cartesian\"/>\n"); } // startPoint of vector if (startPoint != null) { xmlsb.append(startPoint.getStartPointXML()); } } @Override public boolean isNumberValue() { return false; } @Override public boolean evaluatesToNonComplex2DVector() { return this.getMode() != Kernel.COORD_COMPLEX; } @Override public boolean evaluatesToVectorNotPoint() { return this.getMode() != Kernel.COORD_COMPLEX; } /* * Path interface */ @Override public boolean isClosedPath() { return false; } @Override public void pointChanged(GeoPointND P) { if (startPoint == null && waitingForStartPoint) { // remember waiting points if (waitingPointSet == null) { waitingPointSet = new HashSet<GeoPointND>(); } waitingPointSet.add(P); return; } if (pathSegment == null) { updatePathSegment(); } pathSegment.pointChanged(P); } @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; } updatePathSegment(); pathSegment.pathChanged(P); } @Override public boolean isOnPath(GeoPointND P, double eps) { updatePathSegment(); // Michael Borcherds 2008-06-10 bugfix return pathSegment.isOnPath(P, eps); } @Override public boolean isPath() { return true; } @Override public double getMinParameter() { return 0; } @Override public double getMaxParameter() { return 1; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } private void initPathSegment() { if (startPoint != null && !startPoint.isGeoElement3D()) { // TODO 3D // case pathStartPoint = (GeoPoint) startPoint; } else { pathStartPoint = new GeoPoint(cons); pathStartPoint.setCoords(0, 0, 1); } pathEndPoint = new GeoPoint(cons); pathSegment = new GeoSegment(cons, pathStartPoint, pathEndPoint); } private void updatePathSegment() { if (pathSegment == null) { initPathSegment(); } // update segment pathEndPoint.setCoords(pathStartPoint.inhomX + x, pathStartPoint.inhomY + y, 1.0); GeoVec3D.lineThroughPoints(pathStartPoint, pathEndPoint, pathSegment); // length is used in GeoSement.pointChanged() and // GeoSegment.pathChanged() pathSegment.calcLength(); } @Override public boolean isGeoVector() { return true; } @Override public boolean isAlwaysFixed() { return false; } @Override public boolean isMatrixTransformable() { return true; } /** * @param kernel * kernel * @param tpl * string template * @param x * x-coord * @param y * y-coord * @param z * z-coord * @param sb * string builder * @param vector * the vector * @param symbolic * if symbolic */ public static final void buildLatexValueStringCoordCartesian3D( Kernel kernel, StringTemplate tpl, double x, double y, double z, StringBuilder sb, GeoVectorND vector, boolean symbolic) { String[] inputs; if (symbolic && vector.getParentAlgorithm() instanceof DependentAlgo) { AlgoElement algo = vector.getParentAlgorithm(); String symbolicStr = algo.toString(tpl); // remove \left( and \right) int firstIndex = symbolicStr.indexOf("\\left("); int lastIndex = symbolicStr.lastIndexOf("\\right)"); if (firstIndex > -1 && lastIndex > -1) { inputs = symbolicStr.substring(firstIndex + 6, lastIndex) .split(","); } else { inputs = new String[3]; inputs[0] = kernel.format(x, tpl); inputs[1] = kernel.format(y, tpl); inputs[2] = kernel.format(z, tpl); } } else { inputs = new String[3]; inputs[0] = kernel.format(x, tpl); inputs[1] = kernel.format(y, tpl); inputs[2] = kernel.format(z, tpl); } buildTabular(inputs, sb); } private static void buildTabular(String[] inputs, StringBuilder sb) { boolean alignOnDecimalPoint = true; for (int i = 0; i < inputs.length; i++) { if (inputs[i].indexOf('.') == -1) { alignOnDecimalPoint = false; continue; } } if (alignOnDecimalPoint) { sb.append("\\left(\\hspace{-0.4em} \\begin{tabular}{r@{.}l}"); for (int i = 0; i < inputs.length; i++) { inputs[i] = inputs[i].replace('.', '&'); } } else { sb.append("\\left(\\hspace{-0.4em} \\begin{tabular}{r}"); } for (int i = 0; i < inputs.length; i++) { sb.append(inputs[i]); sb.append(" \\\\ "); } sb.append("\\end{tabular}\\hspace{-0.4em} \\right)"); } private StringBuilder sb; @Override public String toLaTeXString(boolean symbolic, StringTemplate tpl) { if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } return buildLatexString(kernel, sb, symbolic, tpl, toStringMode, x, y, this); } /** * @param kernel * kernel * @param sb * builder * @param symbolic * whether to replace variables * @param tpl * template * @param toStringMode * COORD_POLAR / COORD_CARTESIAN etc. * @param x * x-coord * @param y * y-coord * @param vector * vector coresponding to x,y (result depends on parent algo) * @return content of string builder */ static final public String buildLatexString(Kernel kernel, StringBuilder sb, boolean symbolic, StringTemplate tpl, int toStringMode, double x, double y, GeoVectorND vector) { switch (toStringMode) { case Kernel.COORD_POLAR: sb.append("("); sb.append(kernel.format(MyMath.length(x, y), tpl)); sb.append("; "); sb.append(kernel.formatAngle(Math.atan2(y, x), tpl, false)); sb.append(")"); break; case Kernel.COORD_COMPLEX: sb.append(kernel.format(x, tpl)); sb.append(" "); kernel.formatSigned(y, sb, tpl); sb.append(Unicode.IMAGINARY); break; case Kernel.COORD_CARTESIAN_3D: buildLatexValueStringCoordCartesian3D(kernel, tpl, x, y, 0, sb, vector, symbolic); break; case Kernel.COORD_SPHERICAL: GeoPoint.buildValueStringCoordSpherical(kernel, tpl, x, y, 0, sb); break; default: // CARTESIAN String[] inputs; if (symbolic && vector .getParentAlgorithm() instanceof AlgoDependentVector) { AlgoDependentVector algo = (AlgoDependentVector) vector .getParentAlgorithm(); // need to do something different for (xx,yy) and a (1,2) + c ExpressionNode en = algo.getExpression(); ExpressionValue ev = en.unwrap(); if (ev instanceof MyVecNode) { MyVecNode vn = (MyVecNode) ev; inputs = new String[2]; inputs[0] = vn.getX().toString(tpl); inputs[1] = vn.getY().toString(tpl); } else { return algo.toString(tpl); } } else { inputs = new String[2]; inputs[0] = kernel.format(x, tpl); inputs[1] = kernel.format(y, tpl); } // MathQuillGGB can't render v = \left( \begin{tabular}{r}-10 \\ 0 // \\ \end{tabular} \right) // so use eg \binom{ -10 }{ 0 } in web // see #1987 buildTabular(inputs, sb); break; } return sb.toString(); } @Override public Coords getCoordsInD2() { Coords ret = new Coords(3); ret.setX(getX()); ret.setY(getY()); ret.setZ(getZ()); return ret; } @Override public Coords getCoordsInD3() { Coords ret = new Coords(4); ret.setX(getX()); ret.setY(getY()); ret.setZ(getZ()); return ret; } @Override public boolean hasDrawable3D() { return true; } // only used for 3D @Override public void updateStartPointPosition() { // 3D only } @Override public Coords getDirectionInD3() { return getCoordsInD3(); } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { double x1 = a00 * x + a01 * y + a02 * 1; double y1 = a10 * x + a11 * y + a12 * 1; double z1 = a20 * x + a21 * y + a22 * 1; setCoords(x1 / z1, y1 / z1, 0); } @Override public boolean isLaTeXDrawableGeo() { return true; } @Override public void updateColumnHeadingsForTraceValues() { resetSpreadsheetColumnHeadings(); spreadsheetColumnHeadings.add(getColumnHeadingText(new ExpressionNode( kernel, kernel.getAlgebraProcessor().getXBracket(), // "x(" Operation.PLUS, new ExpressionNode(kernel, getNameGeo(), // Name[this] Operation.PLUS, kernel.getAlgebraProcessor().getCloseBracket())))); // ")" spreadsheetColumnHeadings.add(getColumnHeadingText(new ExpressionNode( kernel, kernel.getAlgebraProcessor().getYBracket(), // "y(" Operation.PLUS, new ExpressionNode(kernel, getNameGeo(), // Name[this] Operation.PLUS, kernel.getAlgebraProcessor().getCloseBracket())))); // ")" } @Override public TraceModesEnum getTraceModes() { return TraceModesEnum.SEVERAL_VALUES_OR_COPY; } @Override public String getTraceDialogAsValues() { String name = getLabelTextOrHTML(false); StringBuilder sbTrace = new StringBuilder(); sbTrace.append("x("); sbTrace.append(name); sbTrace.append("), y("); sbTrace.append(name); sbTrace.append(")"); return sbTrace.toString(); } @Override public void addToSpreadsheetTraceList( ArrayList<GeoNumeric> spreadsheetTraceList) { GeoNumeric xx = new GeoNumeric(cons, getInhomVec().getX()); spreadsheetTraceList.add(xx); GeoNumeric yy = new GeoNumeric(cons, getInhomVec().getY()); spreadsheetTraceList.add(yy); } @Override public SymbolicParameters getSymbolicParameters() { if (algoParent instanceof SymbolicParametersAlgo) { return new SymbolicParameters((SymbolicParametersAlgo) algoParent); } return null; } @Override public void getFreeVariables(HashSet<PVariable> variables) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersAlgo) { ((SymbolicParametersAlgo) algoParent).getFreeVariables(variables); return; } throw new NoSymbolicParametersException(); } @Override public int[] getDegrees() throws NoSymbolicParametersException { if (algoParent != null && (algoParent instanceof SymbolicParametersAlgo)) { return ((SymbolicParametersAlgo) algoParent).getDegrees(); } throw new NoSymbolicParametersException(); } @Override public BigInteger[] getExactCoordinates( final HashMap<PVariable, BigInteger> values) throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersAlgo) { return ((SymbolicParametersAlgo) algoParent) .getExactCoordinates(values); } throw new NoSymbolicParametersException(); } @Override public PPolynomial[] getPolynomials() throws NoSymbolicParametersException { if (algoParent instanceof SymbolicParametersAlgo) { return ((SymbolicParametersAlgo) algoParent).getPolynomials(); } throw new NoSymbolicParametersException(); } @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 double[] getInhomCoords() { double[] ret = new double[2]; ret[0] = getX(); ret[1] = getY(); return ret; } @Override public void updateLocation() { updateGeo(false); kernel.notifyUpdateLocation(this); } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } @Override public ValueType getValueType() { return getMode() == Kernel.COORD_COMPLEX ? ValueType.COMPLEX : ValueType.NONCOMPLEX2D; } @Override public boolean hasLineOpacity() { return true; } @Override public int getDimension() { return 2; } @Override public void setCoords(double x, double y, double z, double w) { setCoords(x, y, w); } @Override public ValidExpression toValidExpression() { return getVector(); } }