package org.geogebra.common.geogebra3D.kernel3D.geos; import java.util.ArrayList; import org.geogebra.common.geogebra3D.kernel3D.transform.MirrorableAtPlane; 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.StringTemplate; import org.geogebra.common.kernel.Matrix.CoordMatrix; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.arithmetic.ExpressionNode; 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.geos.Dilateable; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoVector; import org.geogebra.common.kernel.geos.Transformable; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoDirectionND; 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.kernelND.RotateableND; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.debug.Log; /** * 3D vector class * * @author ggb3D * */ public class GeoVector3D extends GeoVec4D implements GeoVectorND, RotateableND, MirrorableAtPlane, Transformable, Dilateable, MatrixTransformable { private GeoPointND startPoint; private CoordMatrix matrix; private Coords labelPosition = new Coords(0, 0, 0, 0); /** * simple constructor * * @param c * construction */ public GeoVector3D(Construction c) { super(c); matrix = new CoordMatrix(4, 2); setCartesian3D(); } /** * simple constructor with (x,y,z) coords * * @param c * construction * @param x * x-coord * @param y * y-coord * @param z * z-coord */ public GeoVector3D(Construction c, double x, double y, double z) { super(c, x, y, z, 0); matrix = new CoordMatrix(4, 2); setCartesian3D(); } @Override public void setCoords(double[] vals) { super.setCoords(vals); if (matrix == null) { matrix = new CoordMatrix(4, 2); } // sets the drawing matrix matrix.set(getCoords(), 1); } /** * update the start point position */ @Override public void updateStartPointPosition() { if (startPoint != null) { matrix.set(startPoint.getInhomCoordsInD3(), 2); } else { for (int i = 1; i < 4; i++) { matrix.set(i, 2, 0.0); } matrix.set(4, 2, 1.0); } labelPosition = matrix.getOrigin().add(matrix.getVx().mul(0.5)); } @Override public Coords getLabelPosition() { return labelPosition; } @Override public GeoElement copy() { GeoVector3D ret = new GeoVector3D(getConstruction()); ret.set(this); return ret; } @Override public GeoClass getGeoClassType() { return GeoClass.VECTOR3D; } @Override public boolean isDefined() { return (!(Double.isNaN(getX()) || Double.isNaN(getY()) || Double.isNaN(getZ()) || Double.isNaN(getW()))); } @Override public boolean isEqual(GeoElementND geo) { if (!geo.isGeoVector()) { return false; } GeoVectorND v1 = (GeoVectorND) geo; if (!(isFinite() && v1.isFinite())) { return false; } Coords c1 = getCoords(); Coords c2 = v1.getCoordsInD3(); return Kernel.isEqual(c1.getX(), c2.getX()) && Kernel.isEqual(c1.getY(), c2.getY()) && Kernel.isEqual(c1.getZ(), c2.getZ()); } @Override final public boolean isInfinite() { Coords v1 = getCoords(); return Double.isInfinite(v1.getX()) || Double.isInfinite(v1.getY()) || Double.isInfinite(v1.getZ()); } @Override final public boolean isFinite() { return !isInfinite(); } @Override public void set(GeoElementND geo) { if (geo.isGeoPoint()) { GeoPointND p = (GeoPointND) geo; setCoords(p.getCoordsInD3().get()); } else if (geo.isGeoVector()) { GeoVectorND vec = (GeoVectorND) geo; setCoords(vec.getCoordsInD3().get()); // don't set start point for macro output // see AlgoMacro.initRay() if (geo.getConstruction() != cons && isAlgoMacroOutput()) { return; } try { GeoPointND sp = vec.getStartPoint(); if (sp != null) { if (vec.hasAbsoluteLocation()) { // create new location point setStartPoint(sp.copy()); } else { // take existing location point setStartPoint(sp); } } } catch (CircularDefinitionException e) { Log.debug("set GeoVector3D: CircularDefinitionException"); } } } @Override public void setUndefined() { setCoords(Double.NaN, Double.NaN, Double.NaN, Double.NaN); } @Override public boolean showInAlgebraView() { return true; } @Override protected boolean showInEuclidianView() { return isDefined() && !isInfinite(); } @Override public boolean evaluatesTo3DVector() { return true; } // for properties panel @Override public boolean isPath() { return true; } @Override public boolean isGeoVector() { return true; } // ///////////////////////////////////////////// // TO STRING // ///////////////////////////////////////////// @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(" = "); } sbToString.append(buildValueString(tpl)); return sbToString.toString(); } private StringBuilder sbToString = new StringBuilder(50); @Override final public String toValueString(StringTemplate tpl) { return buildValueString(tpl).toString(); } @Override public boolean evaluatesToVectorNotPoint() { return true; } private StringBuilder buildValueString(StringTemplate tpl) { sbBuildValueString.setLength(0); switch (tpl.getStringType()) { case GIAC: sbBuildValueString.append("ggbvect["); sbBuildValueString.append(kernel.format(getX(), tpl)); sbBuildValueString.append(','); sbBuildValueString.append(kernel.format(getY(), tpl)); sbBuildValueString.append(','); sbBuildValueString.append(kernel.format(getZ(), tpl)); sbBuildValueString.append("]"); return sbBuildValueString; default: // continue below } /* * switch (toStringMode) { * * * case AbstractKernel.COORD_POLAR: sbBuildValueString.append("("); * sbBuildValueString.append(kernel.format(GeoVec2D.length(x, y))); * sbBuildValueString.append("; "); * sbBuildValueString.append(kernel.formatAngle(Math.atan2(y, x))); * sbBuildValueString.append(")"); break; * * case AbstractKernel.COORD_COMPLEX: * sbBuildValueString.append(kernel.format(x)); * sbBuildValueString.append(" "); * sbBuildValueString.append(kernel.formatSigned(y)); * sbBuildValueString.append("i"); break; * * default: // CARTESIAN sbBuildValueString.append("("); * sbBuildValueString.append(kernel.format(x)); switch * (kernel.getCoordStyle()) { case AbstractKernel.COORD_STYLE_AUSTRIAN: * sbBuildValueString.append(" | "); break; * * default: sbBuildValueString.append(", "); } * sbBuildValueString.append(kernel.format(y)); * sbBuildValueString.append(")"); break; } */ sbBuildValueString.append("("); sbBuildValueString.append(kernel.format(getX(), tpl)); setCoordSep(tpl); sbBuildValueString.append(kernel.format(getY(), tpl)); setCoordSep(tpl); sbBuildValueString.append(kernel.format(getZ(), tpl)); sbBuildValueString.append(")"); return sbBuildValueString; } private void setCoordSep(StringTemplate tpl) { switch (tpl.getCoordStyle(kernel.getCoordStyle())) { case Kernel.COORD_STYLE_AUSTRIAN: sbBuildValueString.append(" | "); break; default: sbBuildValueString.append(", "); } } private StringBuilder sbBuildValueString = new StringBuilder(50); private StringBuilder sb; @Override public String toLaTeXString(boolean symbolic, StringTemplate tpl) { if (sb == null) { sb = new StringBuilder(); } else { sb.setLength(0); } if (getMode() == Kernel.COORD_CARTESIAN_3D) { GeoVector.buildLatexValueStringCoordCartesian3D(kernel, tpl, getX(), getY(), getZ(), sb, this, symbolic); return sb.toString(); } if (getMode() == Kernel.COORD_SPHERICAL) { GeoPoint.buildValueStringCoordSpherical(kernel, tpl, getX(), getY(), getZ(), sb); return sb.toString(); } // cartesian 2D / polar / complex not possible if (!Kernel.isZero(getZ())) { if (getMode() == Kernel.COORD_POLAR) { GeoPoint.buildValueStringCoordSpherical(kernel, tpl, getX(), getY(), getZ(), sb); } else { GeoVector.buildLatexValueStringCoordCartesian3D(kernel, tpl, getX(), getY(), getZ(), sb, this, symbolic); } return sb.toString(); } // cartesian 2D / polar / complex are possible return GeoVector.buildLatexString(kernel, sb, symbolic, tpl, toStringMode, getX(), getY(), this); } /** * returns all class-specific xml tags for saveXML */ @Override protected void getXMLtags(StringBuilder sbXml) { super.getXMLtags(sbXml); // polar or cartesian coords switch (toStringMode) { case Kernel.COORD_POLAR: sbXml.append("\t<coordStyle style=\"polar\"/>\n"); break; case Kernel.COORD_COMPLEX: sbXml.append("\t<coordStyle style=\"complex\"/>\n"); break; case Kernel.COORD_CARTESIAN: sbXml.append("\t<coordStyle style=\"cartesian\"/>\n"); break; case Kernel.COORD_SPHERICAL: sbXml.append("\t<coordStyle style=\"spherical\"/>\n"); break; default: // don't save default (Kernel.COORD_CARTESIAN_3D) } // line thickness and type getLineStyleXML(sbXml); // startPoint of vector if (startPoint != null) { sbXml.append(startPoint.getStartPointXML()); } } // ///////////////////////////////////////////// // LOCATEABLE INTERFACE // ///////////////////////////////////////////// @Override public GeoPointND getStartPoint() { return startPoint; } @Override public void setStartPoint(GeoPointND p) throws CircularDefinitionException { // Application.debug("point : "+((GeoElement) pI).getLabel()); // GeoPoint3D p = (GeoPoint3D) pI; if (startPoint == p) { return; } // macro output uses initStartPoint() only if (isAlgoMacroOutput()) { return; } // check for circular definition if (isParentOf(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); } // update position matrix // updateStartPointPosition(); } @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 initStartPoint(GeoPointND p, int number) { startPoint = p; } @Override public boolean isAlwaysFixed() { return false; } @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, int number) throws CircularDefinitionException { setStartPoint(p); } @Override public void setWaitForStartPoint() { // TODO Auto-generated method stub } @Override public Geo3DVec getVector() { return new Geo3DVec(kernel, v.getX(), v.getY(), v.getZ()); } @Override public double[] getPointAsDouble() { double[] ret = { v.getX(), v.getY(), v.getZ() }; return ret; } @Override public Coords getCoordsInD2() { Coords ret = new Coords(3); ret.setValues(v, 3); return ret; } @Override public Coords getCoordsInD3() { Coords ret = new Coords(4); ret.setValues(v, 4); return ret; } private boolean trace; @Override public boolean isTraceable() { return true; } @Override public void setTrace(boolean trace) { this.trace = trace; } @Override public boolean getTrace() { return trace; } @Override public Coords getDirectionInD3() { return getCoordsInD3(); } @Override public boolean isLaTeXDrawableGeo() { return true; } @Override public void getInhomCoords(double[] coords) { coords[0] = v.getX(); coords[1] = v.getY(); coords[2] = v.getZ(); } @Override public double[] getInhomCoords() { double[] coords = new double[3]; getInhomCoords(coords); return coords; } @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())))); // ")" spreadsheetColumnHeadings.add(getColumnHeadingText(new ExpressionNode( kernel, kernel.getAlgebraProcessor().getZBracket(), // "z(" 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 sb1 = new StringBuilder(); sb1.append("x("); sb1.append(name); sb1.append("), y("); sb1.append(name); sb1.append("), z("); sb1.append(name); sb1.append(")"); return sb1.toString(); } @Override public void addToSpreadsheetTraceList( ArrayList<GeoNumeric> spreadsheetTraceList) { GeoNumeric xx = new GeoNumeric(cons, v.getX()); spreadsheetTraceList.add(xx); GeoNumeric yy = new GeoNumeric(cons, v.getY()); spreadsheetTraceList.add(yy); GeoNumeric zz = new GeoNumeric(cons, v.getZ()); spreadsheetTraceList.add(zz); } @Override final public boolean isCasEvaluableObject() { return true; } @Override public void updateLocation() { updateGeo(false); kernel.notifyUpdateLocation(this); } @Override final public void rotate(NumberValue phiValue) { double phi = phiValue.getDouble(); double cos = Math.cos(phi); double sin = Math.sin(phi); double x = getX(); double y = getY(); double z = getZ(); setCoords(x * cos - y * sin, x * sin + y * cos, z, getW()); } @Override final public void rotate(NumberValue phiValue, GeoPointND Q) { rotate(phiValue); } @Override public void rotate(NumberValue phiValue, GeoPointND S, GeoDirectionND orientation) { // origin ignored Coords vn = orientation.getDirectionInD3(); rotate(phiValue, vn); } private void rotate(NumberValue phiValue, Coords vn) { if (vn.isZero()) { setUndefined(); return; } // Coords v = getCoordsInD3(); double phi = phiValue.getDouble(); double cos = Math.cos(phi); double sin = Math.sin(phi); Coords vn2 = vn.normalized(); Coords v2 = vn2.crossProduct4(v); Coords v1 = v2.crossProduct4(vn2); setCoords(v1.mul(cos).add(v2.mul(sin)).add(vn2.mul(v.dotproduct(vn2)))); } @Override public void rotate(NumberValue phiValue, GeoLineND line) { // origin ignored Coords vn = line.getDirectionInD3(); rotate(phiValue, vn); } @Override public void mirror(Coords Q) { setCoords(v.mul(-1)); } @Override public void mirror(GeoLineND line) { Coords vn = line.getDirectionInD3().normalized(); setCoords(vn.mul(2 * v.dotproduct(vn)).add(v.mul(-1))); } @Override public void mirror(GeoCoordSys2D plane) { Coords vn = plane.getDirectionInD3().normalized(); setCoords(v.add(vn.mul(-2 * v.dotproduct(vn)))); } // ////////////////////// // DILATE // ////////////////////// @Override public void dilate(NumberValue rval, Coords S) { setCoords(v.mul(rval.getDouble())); } @Override public boolean isMatrixTransformable() { return true; } @Override public void matrixTransform(double a, double b, double c, double d) { double x = getX(); double y = getY(); Double x1 = a * x + b * y; Double y1 = c * x + d * y; setCoords(x1, y1, getZ(), getW()); } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { double x = getX(); double y = getY(); double z = getZ(); double x1 = a00 * x + a01 * y + a02 * z; double y1 = a10 * x + a11 * y + a12 * z; double z1 = a20 * x + a21 * y + a22 * z; setCoords(x1, y1, z1, getW()); } @Override public void setCartesian() { setMode(Kernel.COORD_CARTESIAN); } @Override public void setCartesian3D() { setMode(Kernel.COORD_CARTESIAN_3D); } @Override public void setSpherical() { setMode(Kernel.COORD_SPHERICAL); } @Override public void setPolar() { setMode(Kernel.COORD_POLAR); } @Override public void setComplex() { setMode(Kernel.COORD_COMPLEX); } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } @Override protected boolean moveVector(Coords rwTransVec, Coords endPosition) { boolean movedGeo = false; if (endPosition != null) { // setCoords(endPosition.x, endPosition.y, 1); // movedGeo = true; } // translate point else { Coords coords; Coords current = getCoords(); if (current.getLength() < rwTransVec.getLength()) { coords = current.add(rwTransVec); } else { coords = current.addSmaller(rwTransVec); } setCoords(coords); movedGeo = true; } return movedGeo; } @Override public ValueType getValueType() { return ValueType.VECTOR3D; } @Override public int getDimension() { return 3; } @Override public ValidExpression toValidExpression() { return getVector(); } }