package org.geogebra.common.geogebra3D.kernel3D.geos; import org.geogebra.common.euclidianForPlane.EuclidianViewForPlaneCompanionInterface; import org.geogebra.common.geogebra3D.kernel3D.transform.MirrorableAtPlane; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; 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.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.EquationValue; import org.geogebra.common.kernel.arithmetic.Functional2Var; import org.geogebra.common.kernel.arithmetic.NumberValue; 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.GeoLine; import org.geogebra.common.kernel.geos.Traceable; import org.geogebra.common.kernel.geos.Transformable; import org.geogebra.common.kernel.geos.Translateable; 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.GeoPlaneND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.RotateableND; import org.geogebra.common.kernel.kernelND.ViewCreator; import org.geogebra.common.plugin.GeoClass; /** * Plane * */ public class GeoPlane3D extends GeoElement3D implements Functional2Var, ViewCreator, GeoCoords4D, GeoPlaneND, Translateable, Traceable, RotateableND, MirrorableAtPlane, Transformable, Dilateable, EquationValue { /** default labels */ private static final char[] Labels = { 'p', 'q', 'r' }; private static boolean KEEP_LEADING_SIGN = true; private double xmin, xmax, ymin, ymax; // values for grid and interactions private double xPlateMin, xPlateMax, yPlateMin, yPlateMax; // values for // plate // grid and plate private boolean plateVisible = true; private double dx = Double.NaN; // distance between two marks on the grid // //TODO use // object // properties private double dy = Double.NaN; /** coord sys */ protected CoordSys coordsys; /** string repre of coordinates */ private static final String[] VAR_STRING = { "x", "y", "z" }; /** * creates an empty plane * * @param c * construction */ public GeoPlane3D(Construction c) { super(c); // 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 coordsys = new CoordSys(2); this.xmin = -2.5; this.xmax = 2.5; this.ymin = -2.5; this.ymax = 2.5; // grid // setGridVisible(true); } /** * @param cons * construction * @param a * x-coeeficient * @param b * y-coefficient * @param c * z-coefficient * @param d * constant coefficient */ public GeoPlane3D(Construction cons, double a, double b, double c, double d) { this(cons); setEquation(a, b, c, d); } /** * @param a * x-coeeficient * @param b * y-coefficient * @param c * z-coefficient * @param d * constant coefficient */ public void setEquation(double a, double b, double c, double d) { setEquation(a, b, c, d, true); } @Override public void setCoords(double x, double y, double z, double w) { setEquation(x, y, z, w, false); } private void setEquation(double a, double b, double c, double d, boolean makeCoordSys) { if (makeCoordSys || !getCoordSys().isDefined()) { setDefinition(null); getCoordSys().makeCoordSys(a, b, c, d); getCoordSys().makeOrthoMatrix(true, true); } } // ///////////////////////////////// // REGION INTERFACE @Override public boolean isRegion() { return true; } @Override public Coords[] getNormalProjection(Coords coords) { return getCoordSys().getNormalProjection(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 boolean isInRegion(GeoPointND P) { Coords planeCoords = getNormalProjection(P.getInhomCoordsInD3())[1]; // Application.debug(P.getLabel()+":\n"+planeCoords); return Kernel.isEqual(planeCoords.get(3), 0, Kernel.STANDARD_PRECISION); } @Override public boolean isInRegion(double x0, double y0) { return true; } @Override public void pointChangedForRegion(GeoPointND P) { P.updateCoords2D(); P.updateCoordsFrom2D(false, null); } @Override public void regionChanged(GeoPointND P) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(P) || P.getRegionParameters().isNaN()) { pointChangedForRegion(P); return; } // pointChangedForRegion(P); RegionParameters rp = P.getRegionParameters(); P.setCoords(getPoint(rp.getT1(), rp.getT2(), new Coords(4)), false); } @Override public Coords getPoint(double x2d, double y2d, Coords coords) { return getCoordSys().getPoint(x2d, y2d, coords); } // ///////////////////////////////// // GRID AND PLATE /** * sets corners of the plate * @param x1 x-min * * @param x1 * x-min * @param y1 * y-min * @param x2 * x-max * @param y2 * y-max */ public void setPlateCorners(double x1, double y1, double x2, double y2) { if (x1 < x2) { this.xPlateMin = x1; this.xPlateMax = x2; } else { this.xPlateMin = x2; this.xPlateMax = x1; } if (y1 < y2) { this.yPlateMin = y1; this.yPlateMax = y2; } else { this.yPlateMin = y2; this.yPlateMax = y1; } } /** * sets corners of the grid * * @param x1 * x-min * @param y1 * y-min * @param x2 * x-max * @param y2 * y-max */ public void setGridCorners(double x1, double y1, double x2, double y2) { if (x1 < x2) { this.xmin = x1; this.xmax = x2; } else { this.xmin = x2; this.xmax = x1; } if (y1 < y2) { this.ymin = y1; this.ymax = y2; } else { this.ymin = y2; this.ymax = y1; } } /** * set grid distances (between two ticks) * * @param dx * grid x distance * @param dy * grid y distance */ public void setGridDistances(double dx, double dy) { this.dx = dx; this.dy = dy; } /** @return x min */ public double getXmin() { return xmin; } /** @return y min */ public double getYmin() { return ymin; } /** @return x max */ public double getXmax() { return xmax; } /** @return y max */ public double getYmax() { return ymax; } /** @return plate x min */ public double getXPlateMin() { return xPlateMin; } /** @return plate y min */ public double getYPlateMin() { return yPlateMin; } /** @return plate x max */ public double getXPlateMax() { return xPlateMax; } /** @return plate y max */ public double getYPlateMax() { return yPlateMax; } /** @return if there is a grid to plot or not */ public boolean isGridVisible() { return getLineThickness() > 0 && isEuclidianVisible(); } /** @return if there is a plate visible */ public boolean isPlateVisible() { return plateVisible && isEuclidianVisible(); } /** * @param flag * if there is a plate visible */ public void setPlateVisible(boolean flag) { plateVisible = flag; } /** @return x delta for the grid */ public double getGridXd() { return dx; } /** @return y delta for the grid */ public double getGridYd() { return dy; } // ///////////////////////////////// // GEOELEMENT3D @Override public Coords getMainDirection() { return getCoordSys().getNormal(); } @Override public Coords getLabelPosition() { return getCoordSys().getPoint(0.5, 0.5); } @Override public GeoClass getGeoClassType() { return GeoClass.PLANE3D; } @Override public GeoPlane3D copy() { GeoPlane3D p = new GeoPlane3D(cons); // TODO move this elsewhere CoordSys cs = p.getCoordSys(); cs.set(this.getCoordSys()); return p; } @Override public boolean isEqual(GeoElementND geo) { if (!geo.isDefined() || !isDefined()) { return false; } if (geo.isGeoPlane()) { Coords ev1 = getCoordSys().getEquationVector(); Coords ev2 = ((GeoPlane3D) geo).getCoordSys().getEquationVector(); return !ev1.isLinearIndependentAllCoords(ev2); } return false; } /** * Also allow setting from line x+y=1, which may come from user or CAS * instead of x+y+0z=1 */ @Override public void set(GeoElementND geo) { if (geo instanceof GeoPlane3D) { GeoPlane3D plane = (GeoPlane3D) geo; getCoordSys().set(plane.getCoordSys()); reuseDefinition(geo); return; } setDefinition(null); if (geo instanceof GeoLine) { GeoLine line = (GeoLine) geo; setEquation(line.getX(), line.getY(), 0, line.getZ()); } } /** * @param cs * coordinate system */ public void setCoordSys(CoordSys cs) { getCoordSys().set(cs); } @Override public void setVisualStyle(GeoElement geo) { super.setVisualStyle(geo); if (geo.isGeoPlane()) { setFading(((GeoPlaneND) geo).getFading()); } } @Override public void setUndefined() { coordsys.setUndefined(); } private boolean showUndefinedInAlgebraView = false; /** * Set whether this line should be visible in AV when undefined * * @param flag * true to show undefined */ public void showUndefinedInAlgebraView(boolean flag) { showUndefinedInAlgebraView = flag; } @Override public boolean showInAlgebraView() { return isDefined() || showUndefinedInAlgebraView; } @Override protected boolean showInEuclidianView() { return isDefined(); } @Override public String toValueString(StringTemplate tpl) { return buildValueString(tpl).toString(); } @Override final public String toString(StringTemplate tpl) { StringBuilder sbToString = getSbToString(); sbToString.setLength(0); sbToString.append(label); sbToString.append(": "); // TODO use kernel property sbToString.append(buildValueString(tpl)); return sbToString.toString(); } private StringBuilder buildValueString(StringTemplate tpl) { // we need to keep 0z in equation to be sure that y+0z=1 will be loaded // as a plane StringBuilder ret = buildValueString(tpl, kernel, getCoordSys().getEquationVector(), !isLabelSet()); // fix for GGB-1116 // we don't need this since equation is already // wrapped in Ggb2giac // if (tpl.hasCASType()) { // StringBuilder sbTemp = new StringBuilder(); // Giac // sbTemp.append("plane("); // sbTemp.append(ret); // sbTemp.append(")"); // return sbTemp; // } return ret; } /** * @param tpl * template * @param kernel * kernel * @param coords * coefficients * @param needsZ * whether to force +0z * @return value as stringbuilder */ static public final StringBuilder buildValueString(StringTemplate tpl, Kernel kernel, Coords coords, boolean needsZ) { return kernel.buildImplicitEquation(coords.get(), VAR_STRING, KEEP_LEADING_SIGN, true, needsZ, '=', tpl, true); } /** to be able to fill it with an alpha value */ @Override public boolean isFillable() { return true; } // /////////////////////////////////////////// // 2 VAR FUNCTION INTERFACE // ////////////////////////////////////////// @Override public Coords evaluateNormal(double u, double v) { return coordsys.getNormal(); } public void evaluatePoint(double u, double v, Coords point) { coordsys.getPointForDrawing(u, v, point); } @Override public double getMinParameter(int index) { return 0; // TODO } @Override public double getMaxParameter(int index) { return 0; // TODO } @Override public CoordSys getCoordSys() { return coordsys; } @Override public boolean isDefined() { return coordsys.isDefined(); } @Override public boolean isMoveable() { return false; } @Override public String getDefaultLabel() { return getDefaultLabel(Labels, false); } @Override protected void getXMLtags(StringBuilder sb) { super.getXMLtags(sb); Coords equation = getCoordSys().getEquationVector(); // equation sb.append("\t<coords x=\""); sb.append(equation.getX()); sb.append("\" y=\""); sb.append(equation.getY()); sb.append("\" z=\""); sb.append(equation.getZ()); sb.append("\" w=\""); sb.append(equation.getW()); sb.append("\"/>\n"); // fading sb.append("\t<fading val=\""); sb.append(getFading()); sb.append("\"/>\n"); // grid line style getLineStyleXML(sb); } @Override public boolean isGeoPlane() { return true; } // //////////////////////////////// // FADING private float fading = 0.10f; @Override public void setFading(float fading) { this.fading = fading; } @Override public float getFading() { return fading; } // //////////////////////////////// // 2D VIEW private EuclidianViewForPlaneCompanionInterface euclidianViewForPlane; @Override public int getViewID() { return euclidianViewForPlane.getId(); } @Override public void createView2D() { euclidianViewForPlane = kernel.getApplication().getCompanion() .createEuclidianViewForPlane(this, true); euclidianViewForPlane.setTransformRegardingView(); } @Override public void removeView2D() { euclidianViewForPlane.doRemove(); } @Override public void doRemove() { if (euclidianViewForPlane != null) { removeView2D(); } super.doRemove(); } @Override public boolean hasView2DVisible() { return euclidianViewForPlane != null && kernel.getApplication() .getGuiManager().showView(euclidianViewForPlane.getId()); } @Override public void setView2DVisible(boolean flag) { if (euclidianViewForPlane == null) { if (flag) { createView2D(); } return; } kernel.getApplication().getGuiManager().setShowView(flag, euclidianViewForPlane.getId()); } @Override public void update(boolean drag) { super.update(drag); if (euclidianViewForPlane != null) { euclidianViewForPlane.updateForPlane(); } } @Override public void setEuclidianViewForPlane( EuclidianViewForPlaneCompanionInterface view) { euclidianViewForPlane = view; } @Override public Coords getDirectionInD3() { return getCoordSys().getNormal(); } @Override public double getMeasure() { return Double.POSITIVE_INFINITY; } private Coords tmpCoords1, tmpCoords2; @Override public double distance(GeoPointND P) { return Math.abs(distanceWithSign(P)); } /** * * @param P * point * @return distance from point P to this plane, with sign */ public double distanceWithSign(GeoPointND P) { if (tmpCoords1 == null) { tmpCoords1 = new Coords(3); } if (tmpCoords2 == null) { tmpCoords2 = new Coords(3); } tmpCoords1.setSub(P.getInhomCoordsInD3(), getCoordSys().getOrigin()); tmpCoords2.setValues(getDirectionInD3(), 3); tmpCoords2.normalize(); return tmpCoords1.dotproduct(tmpCoords2); } @Override public double distanceWithSign(GeoPlaneND P) { if (tmpCoords1 == null) { tmpCoords1 = new Coords(3); } if (tmpCoords2 == null) { tmpCoords2 = new Coords(3); } tmpCoords2.setValues(getDirectionInD3(), 3); tmpCoords2.normalize(); tmpCoords1.setValues(P.getDirectionInD3(), 3); tmpCoords1.normalize(); if (!Kernel.isEqual(1, Math.abs(tmpCoords1.dotproduct(tmpCoords2)))) { return 0; } tmpCoords1.setSub(P.getCoordSys().getOrigin(), getCoordSys().getOrigin()); return tmpCoords1.dotproduct(tmpCoords2); } // /////////////////////////////////// // TRANSLATE // /////////////////////////////////// @Override public void translate(Coords v) { getCoordSys().translate(v); getCoordSys().translateEquationVector(v); } @Override public boolean isTranslateable() { return true; } // //////////////// // 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; } // ////////////////////// // ROTATIONS // ////////////////////// /** * rotate the plane * * @param rot * rotation matrix * @param center * rotation center */ final public void rotate(CoordMatrix rot, Coords center) { coordsys.rotate(rot, center); coordsys.makeEquationVector(); } @Override final public void rotate(NumberValue phiVal) { coordsys.rotate(phiVal.getDouble(), Coords.O); coordsys.makeEquationVector(); } @Override final public void rotate(NumberValue phiVal, GeoPointND Q) { coordsys.rotate(phiVal.getDouble(), Q.getInhomCoordsInD3()); coordsys.makeEquationVector(); } final private void rotate(NumberValue phiVal, Coords center, Coords direction) { coordsys.rotate(phiVal.getDouble(), center, direction.normalized()); coordsys.makeEquationVector(); } @Override public void rotate(NumberValue phiVal, GeoPointND Q, GeoDirectionND orientation) { rotate(phiVal, Q.getInhomCoordsInD3(), orientation.getDirectionInD3()); } @Override public void rotate(NumberValue phiVal, GeoLineND line) { rotate(phiVal, line.getStartInhomCoords(), line.getDirectionInD3()); } @Override public void mirror(Coords Q) { coordsys.mirror(Q); coordsys.mirrorEquationVector(Q); } @Override public void mirror(GeoLineND line) { Coords point = line.getStartInhomCoords(); Coords direction = line.getDirectionInD3().normalized(); coordsys.mirror(point, direction); coordsys.makeEquationVector(); } @Override public void mirror(GeoCoordSys2D plane) { coordsys.mirror(plane.getCoordSys()); coordsys.makeEquationVector(); } // ////////////////////// // DILATE // ////////////////////// @Override public void dilate(NumberValue rval, Coords S) { double r = rval.getDouble(); coordsys.dilate(r, S); coordsys.dilateEquationVector(r, S); } @Override final public HitType getLastHitType() { return HitType.ON_FILLING; } @Override public boolean is6dofMoveable() { return true; } @Override public ValueType getValueType() { return ValueType.EQUATION; } @Override protected void getXMLanimationTags(final StringBuilder sb) { // no need for planes } @Override public char getLabelDelimiter() { return ':'; } @Override public boolean showLineProperties() { return true; } @Override public int getMinimumLineThickness() { return 0; } @Override public Equation getEquation() { return kernel.getAlgebraProcessor().parseEquation(this); } @Override public boolean isRegion3D() { return true; } }