/* 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.geos; import java.util.ArrayList; import java.util.TreeSet; import org.geogebra.common.awt.GColor; import org.geogebra.common.euclidian.EuclidianView; 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.PathParameter; import org.geogebra.common.kernel.RegionParameters; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.Matrix.CoordSys; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoAnglePolygon; import org.geogebra.common.kernel.algos.AlgoAnglePolygonND; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegment; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegmentInterface; import org.geogebra.common.kernel.algos.AlgoPolygon; import org.geogebra.common.kernel.algos.AlgoPolygonRegularND; import org.geogebra.common.kernel.algos.AlgoTransformation; import org.geogebra.common.kernel.algos.PolygonAlgo; import org.geogebra.common.kernel.algos.SymbolicParametersBotanaAlgo; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.ExpressionNodeConstants.StringType; import org.geogebra.common.kernel.arithmetic.ExpressionNodeEvaluator; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.arithmetic.NumberValue; import org.geogebra.common.kernel.arithmetic.ValueType; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.discrete.PolygonTriangulation; import org.geogebra.common.kernel.kernelND.GeoCoordSys2D; import org.geogebra.common.kernel.kernelND.GeoCurveCartesianND; 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.GeoSegmentND; import org.geogebra.common.kernel.kernelND.HasSegments; 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.main.Feature; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.ExtendedBoolean; import org.geogebra.common.util.MyMath; /** * Polygon through given points * * @author Markus Hohenwarter */ public class GeoPolygon extends GeoElement implements GeoNumberValue, GeoSurfaceFinite, Traceable, PointRotateable, MatrixTransformable, Mirrorable, Translateable, Dilateable, GeoCoordSys2D, GeoPoly, Transformable, SymbolicParametersBotanaAlgo, HasSegments, FromMeta { /** maximal number of vertices for polygon tool */ public static final int POLYGON_MAX_POINTS = 1000; /** polygon vertices */ protected GeoPointND[] points; /** polygon edges */ protected GeoSegmentND[] segments; /** first point for region coord sys */ protected GeoPoint p0; /** second point for region coord sys */ protected GeoPoint p1; /** third point for region coord sys */ protected GeoPoint p2; /** number of points in coord sys */ protected int numCS = 0; /** directed area */ protected double area; private boolean defined = false; /** @see #wasInitLabelsCalled() */ protected boolean initLabelsCalled = false; /** says if the polygon had created its segments itself (used for 3D) */ private boolean createSegments = true; private boolean isShape = false; /** * common constructor for 2D. * * @param c * the construction * @param points * vertices */ public GeoPolygon(Construction c, GeoPointND[] points) { this(c, points, null, true); } /** * common constructor for 3D. * * @param c * the construction * @param points * vertices * @param cs * for 3D stuff : 2D coord sys * @param createSegments * says if the polygon has to creates its edges */ public GeoPolygon(Construction c, GeoPointND[] points, CoordSys cs, boolean createSegments) { this(c); // Application.printStacktrace("poly"); this.createSegments = createSegments; setPoints(points, cs, createSegments); setLabelVisible(false); } /** true for polygons created by area intersection methods */ protected boolean isIntersection; /** * Creates new GeoPolygon * * @param cons * construction */ public GeoPolygon(Construction cons) { this(cons, false); } /** * Creates new GeoPolygon * * @param cons * construction * @param isIntersection * whether this is result of intersection; use different style in * such case */ public GeoPolygon(Construction cons, boolean isIntersection) { super(cons); 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 } /** * not used for 2D * * @param cs * GeoCoordSys2D */ public void setCoordSys(CoordSys cs) { // nothing to do here } /** * for 3D stuff (unused here) * * @param poly * polygon * */ public void setCoordSys(GeoPolygon poly) { // 3D only } /** * for 3D stuff (unused here) * * @param p * polygon * */ public void setCoordSysAndPoints3D(GeoPolygon p) { // 3D only } private boolean notFixedPointsLength = false; /** * set that this polygon hasn't fixed points length (e.g. for regular * polygons with slider). Used in getTypeString() to avoid bad type display * in algebra view, properties view, etc. * * @param flag * true if not fixed points length */ public void setNotFixedPointsLength(boolean flag) { notFixedPointsLength = flag; } @Override public String getTypeString() { if (notFixedPointsLength || points == null) { return "Polygon"; } switch (getPointsLength()) { case 3: return "Triangle"; case 4: return "Quadrilateral"; case 5: return "Pentagon"; case 6: return "Hexagon"; default: return "Polygon"; } } @Override public GeoClass getGeoClassType() { return GeoClass.POLYGON; } private HitType lastHitType = HitType.ON_FILLING; /** * @param type * hit type */ final public void setLastHitType(HitType type) { lastHitType = type; } @Override final public HitType getLastHitType() { return lastHitType; } /** * set the vertices to points * * @param points * the vertices */ final public void setPoints(GeoPointND[] points) { setPoints(points, null, true); } /** * set the vertices to points (cs is only used for 3D stuff) * * @param points * the vertices * @param cs * used for 3D stuff * @param createSegments * says if the polygon has to creates its edges */ public void setPoints(GeoPointND[] points, CoordSys cs, boolean createSegments) { this.points = points; setCoordSys(cs); if (createSegments) { updateSegments(cons); } // if (points != null) { // Application.debug("*** " + this + " *****************"); // Application.debug("POINTS: " + points.length); // for (int i=0; i < points.length; i++) { // Application.debug(" " + i + ": " + points[i]); // } // Application.debug("SEGMENTS: " + segments.length); // for (int i=0; i < segments.length; i++) { // Application.debug(" " + i + ": " + segments[i]); // } // Application.debug("********************"); // } } // ///////////////////////////// // ggb3D 2009-03-08 - start /** * return number for points * * @return number for points */ public int getPointsLength() { if (getPoints() == null) { return 0; } return getPoints().length; } /** * return the x-coordinate of the i-th vertex * * @param i * number of vertex * @return the x-coordinate */ public double getPointX(int i) { return getPoint(i).inhomX; } /** * return the y-coordinate of the i-th vertex * * @param i * number of vertex * @return the y-coordinate */ public double getPointY(int i) { return getPoint(i).inhomY; } // ggb3D 2009-03-08 - end // ///////////////////////////// /** * Inits the labels of this polygon, its segments and its points. labels[0] * for polygon itself, labels[1..n] for segments, labels[n+1..2n-2] for * points (only used for regular polygon) * * @param labels * labels of points and segments */ public void initLabels(String[] labels) { if (cons.isSuppressLabelsActive()) { return; } initLabelsCalled = true; // Application.debug("INIT LABELS"); // label polygon if (labels == null || labels.length == 0) { // Application.debug("no labels given"); setLabel(null); if (segments != null) { defaultSegmentLabels(); } return; } // label polygon // first label for polygon itself setLabel(labels[0]); // label segments and points if (points != null && segments != null) { // additional labels for the polygon's segments // poly + segments + points - 2 for AlgoPolygonRegular if (labels.length == 1 + segments.length + getPointsLength() - 2) { // Application.debug("labels for segments and points"); int i = 1; for (int k = 0; k < segments.length; k++, i++) { segments[k].setLabel(labels[i]); } for (int k = 2; k < getPointsLength(); k++, i++) { points[k].setLabel(labels[i]); } } // additional labels for the polygon's segments // poly + segments for AlgoPolygon else if (labels.length == 1 + segments.length) { // Application.debug("labels for segments"); int i = 1; for (int k = 0; k < segments.length; k++, i++) { segments[k].setLabel(labels[i]); } } else { // Application.debug("label for polygon (autoset segment // labels)"); defaultSegmentLabels(); } } } /** * Returns whether the method initLabels() was called for this polygon. This * is important to know whether the segments have gotten labels. * * @return true iff the method initLabels() was called for this polygon. */ final public boolean wasInitLabelsCalled() { return initLabelsCalled; } private void defaultSegmentLabels() { // no labels for segments specified // set labels of segments according to point names if (getPointsLength() == 3) { // make sure segment opposite C is called c not a_1 if (getParentAlgorithm() instanceof AlgoPolygonRegularND) { points[2].setLabel(null); } setLabel(segments[0], points[2]); setLabel(segments[1], points[0]); setLabel(segments[2], points[1]); } else { for (int i = 0; i < getPointsLength(); i++) { setLabel(segments[i], points[i]); } } } /** * Sets label of segment to lower case label of point. If the point has no * label, a default label is used for the segment. If the lower case label * of the point is already used, an indexed label is created. */ private static void setLabel(GeoSegmentND s, GeoPointND p) { if (!p.isLabelSet() || p.getLabel(StringTemplate.defaultTemplate) == null) { s.setLabel(null); } else { // use lower case of point label as segment label String lowerCaseLabel = ((GeoElement) p).getFreeLabel( p.getLabel(StringTemplate.get(StringType.GEOGEBRA)) .toLowerCase()); s.setLabel(lowerCaseLabel); } } private ArrayList<GeoSegmentND> segmentsArray; /** * Updates all segments of this polygon for its point array. Note that the * point array may be changed: this method makes sure that segments are * reused if possible. * * @param cons1 * construction in which new segments are born */ public void updateSegments(Construction cons1) { if (points == null) { return; } // make sure the polygon is defined to get correct euclidian visibility setDefined(); boolean euclidianVisible; // check array zand euclidian visibility if (segmentsArray == null) { segmentsArray = new ArrayList<GeoSegmentND>(); } if (segmentsArray.size() < 1) { euclidianVisible = isEuclidianVisible(); } else { euclidianVisible = segmentsArray.get(0).isEuclidianVisible(); } segments = new GeoSegmentND[getPointsLength()]; // set first values for (int i = 0; i < segmentsArray.size() && i < points.length; i++) { GeoPointND startPoint = points[i]; GeoPointND endPoint = points[(i + 1) % getPointsLength()]; GeoSegmentND segment = segmentsArray.get(i); AlgoJoinPointsSegmentInterface algo = (AlgoJoinPointsSegmentInterface) segment .getParentAlgorithm(); algo.modifyInputPoints(startPoint, endPoint); algo.compute(); segments[i] = segment; segment.setEuclidianVisible(euclidianVisible); } // adjust size for (int i = segmentsArray.size(); i < points.length; i++) { GeoPointND startPoint = points[i]; GeoPointND endPoint = points[(i + 1) % getPointsLength()]; GeoSegmentND segment = createSegment(cons1, startPoint, endPoint, euclidianVisible); segment.getParentAlgorithm().setProtectedInput(true); // avoid // remove by // other // algos segmentsArray.add(segment); segments[i] = segment; } // set last segments undefined for (int i = points.length; i < segmentsArray.size(); i++) { segmentsArray.get(i).setUndefined(); } } /** * return a segment joining startPoint and endPoint * * @param cons1 * construction of the new segment * * @param startPoint * the start point * @param endPoint * the end point * @param euclidianVisible * true to make the segment visible * @return the segment */ public GeoSegmentND createSegment(Construction cons1, GeoPointND startPoint, GeoPointND endPoint, boolean euclidianVisible) { AlgoJoinPointsSegment algoSegment = new AlgoJoinPointsSegment(cons1, (GeoPoint) startPoint, (GeoPoint) endPoint, this, false); // cons.removeFromConstructionList(algoSegment); return createSegment(algoSegment.getSegment(), euclidianVisible); } /** * ends the creation of the segment * * @param segment * segment * @param euclidianVisible * true to make the segment visible * @return the segment modified */ protected GeoSegmentND createSegment(GeoSegmentND segment, boolean euclidianVisible) { // refresh color to ensure segments have same color as polygon: segment.setObjColor(getObjectColor()); segment.setLineThickness(getLineThickness()); segment.setLineType(getLineType()); segment.setEuclidianVisible(euclidianVisible); if (condShowObject != null) { try { ((GeoElement) segment) .setShowObjectCondition(getShowObjectCondition()); } catch (Exception e) { // circular definition } } return segment; } /** * The copy of a polygon is a number (!) with its value set to the polygons * current area */ @Override public GeoElement copy() { return new GeoNumeric(cons, getArea()); } @Override public GeoElement copyInternal(Construction cons1) { GeoPolygon ret = newGeoPolygon(cons1); copyInternal(cons1, ret); return ret; } /** * @param cons1 * consctruction * @param ret * poly where to copy */ public void copyInternal(Construction cons1, GeoPolygon ret) { ret.setPoints2D(GeoElement.copyPoints(cons1, getPoints())); ret.set(this, cons1); } /** * Factory method for polygons, overridden in 3D * * @param cons1 * construction * @return new polygon */ protected GeoPolygon newGeoPolygon(Construction cons1) { return new GeoPolygon(cons, null); } private ArrayList<GeoPoint> pointsArray; @Override public final void set(GeoElementND geo) { set(geo, cons); } /** * @param geo * template geo * @param cons1 * construction */ protected void set(GeoElementND geo, Construction cons1) { GeoPolygon poly = (GeoPolygon) geo; area = poly.area; setReverseNormalForDrawing(poly.getReverseNormalForDrawing()); if (!notFixedPointsLength) { // maybe already set by AlgoListElement notFixedPointsLength = poly.notFixedPointsLength; } // fix for Sequence[Polygon[Element[liste1, i], Element[liste1, i + 1], // j], i, 0, 300] if (poly.getPoints() == null) { setUndefined(); return; } // set values updatePoints(poly.getPoints()); setCoordSysAndPoints3D(poly); updateSegments(cons1); defined = poly.defined; if (poly.hasChangeableCoordParentNumbers()) { setChangeableCoordParent(poly.changeableCoordParent); } updateRegionCS(); } /** * set points matching geos list, and segments * * @param geos * input points */ public void setPointsAndSegments(GeoPointND[] geos) { updatePoints(geos); updateSegments(cons); } private void updatePoints(GeoPointND[] geos) { setPointsLength(geos.length, null); for (int i = 0; i < getPoints().length; i++) { ExpressionNode oldDef = getPoint(i).getDefinition(); getPoint(i).set(geos[i].toGeoElement(), false); if (!getPoint(i).isIndependent()) { getPoint(i).setDefinition(oldDef); } } } /** * set points and segments length to arbitrary value (create new points and * segments) * * @param polyLength * length */ public void setPointsAndSegmentsLength(int polyLength) { setPointsLength(polyLength, null); updateSegments(cons); } /** * set points length to arbitrary value (create new points) * * @param polyLength * length * @param template * points to be reused */ protected void setPointsLength(int polyLength, GeoPointND[] template) { if (pointsArray == null) { pointsArray = new ArrayList<GeoPoint>(); } // augment array size if array < polyLength for (int i = pointsArray.size(); i < polyLength; i++) { if (template != null && template.length > i && template[i] instanceof GeoPoint) { pointsArray.add((GeoPoint) template[i]); } else { pointsArray.add(new GeoPoint(cons)); } } // set last points undefined if array > polyLength for (int i = polyLength; i < pointsArray.size(); i++) { pointsArray.get(i).setUndefined(); } // make sure both arrays have same size if (getPoints() == null || getPoints().length != polyLength) { // copy usable array part GeoPoint[] tempPoints = new GeoPoint[polyLength]; for (int i = 0; i < polyLength; i++) { tempPoints[i] = pointsArray.get(i); } setPoints2D(tempPoints); } } /** * Returns the i-th point of this polygon. Note that this array may change * dynamically. * * @param i * number of point * @return the i-th point */ @Override public GeoPoint getPoint(int i) { return (GeoPoint) points[i]; } /** * Returns the points of this polygon as GeoPoints. Note that this array may * change dynamically. * * @return points of this polygon. */ @Override public GeoPointND[] getPoints() { return points; } /** * set the 2D points * * @param points * 2D points */ public void setPoints2D(GeoPoint[] points) { this.points = points; } /** * Returns the points of this polygon as GeoPointNDs. Note that this array * may change dynamically. * * @return points of this polygon */ @Override final public GeoPointND[] getPointsND() { return points; } /** * Returns i-th vertex of this polygon * * @param i * index * @return i-th pointt */ final public GeoPointND getPointND(int i) { return points[i]; } /** * Returns the segments of this polygon. Note that this array may change * dynamically. * * @return segments of this polygon. */ @Override public GeoSegmentND[] getSegments() { return segments; } /** * sets the segments (used by GeoPolyhedron) * * @param segments * the segments */ public void setSegments(GeoSegmentND[] segments) { this.segments = segments; } @Override public boolean isFillable() { return true; } @Override public boolean isInverseFillable() { return isFillable(); } // Michael Borcherds 2008-01-26 BEGIN /** * Sets this polygon's area . This method should only be called by its * parent algorithm of type AlgoPolygon * * @param area * area */ final public void setArea(double area) { this.area = area; defined = !(Double.isNaN(area) || Double.isInfinite(area)); } /** * Returns undirected area * * @return undirected area */ @Override public double getArea() { if (isDefined()) { return Math.abs(area); } return Double.NaN; } @Override public double getMeasure() { return getArea(); } @Override public Path getBoundary() { boolean suppress = this.cons.isSuppressLabelsActive(); kernel.setSilentMode(true); GeoPointND[] pointsForPolyLine = new GeoPointND[getPointsLength() + 1]; System.arraycopy(points, 0, pointsForPolyLine, 0, getPointsLength()); pointsForPolyLine[getPointsLength()] = pointsForPolyLine[0]; GeoPolyLine pl = new GeoPolyLine(this.getConstruction(), pointsForPolyLine); kernel.setSilentMode(false); cons.setSuppressLabelCreation(suppress); return pl; } /** * clockwise=-1 anticlockwise=+1 no area=0 * * @return orientation of area */ public double getDirection() { if (defined) { return MyMath.sgn(area); } return Double.NaN; } /** * Returns directed area * * @return directed area */ public double getAreaWithSign() { if (defined) { return area; } return Double.NaN; } // Michael Borcherds 2008-01-26 END /* * overwrite methods */ @Override public boolean isDefined() { return defined; } /** * Sets the polygon state to "defined" */ public void setDefined() { defined = true; } @Override public void setUndefined() { defined = false; } @Override public final boolean showInAlgebraView() { // return defined; return true; } @Override public boolean hasLineOpacity() { return true; } @Override public void setLineOpacity(int lineOpacity) { setLineOpacity(lineOpacity, true); } /** * Sets the line opacity for this GeoElement. * * @param lineOpacity * opacity value between 0 - 255 * @param updateSegments * true to apply this setting to segments */ public void setLineOpacity(int lineOpacity, boolean updateSegments) { super.setLineOpacity(lineOpacity); if (updateSegments && segments != null) { for (int i = 0; i < segments.length; i++) { segments[i].setLineOpacity(lineOpacity); segments[i].updateVisualStyle(GProperty.LINE_STYLE); } } } /** * Yields true if the area of this polygon is equal to the area of polygon * p. * * @param geo * other geo (type check for polygon included) * * @return whether the two polygons have the same area */ // this method is the original isEqual, modified 2014-01 public boolean hasSameArea(GeoElement geo) { // return false if it's a different type if (geo.isGeoPolygon()) { return Kernel.isEqual(getArea(), ((GeoPolygon) geo).getArea()); } return false; } /** * Yields true if the points of this polygon is equal to the points of * polygon p. */ @Override public boolean isEqual(GeoElementND geo) { // test 3D is geo is 3D if (geo.isGeoElement3D()) { return geo.isEqual(this); } // return false if it's a different type if (geo.isGeoPolygon()) { GeoPolygon g = (GeoPolygon) geo; // return false if the number of points is different int gLength = g.getPointsLength(); if (gLength == this.getPointsLength()) { // search for a first common point GeoPoint firstPoint = this.getPoint(0); boolean fPointFound = false; int iFirstPoint = 0; while ((!fPointFound) && (iFirstPoint < gLength)) { if (firstPoint.isEqual(g.getPoint(iFirstPoint))) { fPointFound = true; } else { iFirstPoint++; } } // next point if (fPointFound) { boolean sPointFound = false; int step = 1; if (this.getPoint(1).isEqual( g.getPoint((iFirstPoint + step) % gLength))) { sPointFound = true; } else { step = -1; int j = iFirstPoint + step; if (j < 0) { j = gLength - 1; } if (this.getPoint(1).isEqual(g.getPoint(j))) { sPointFound = true; } } // other points if (sPointFound) { int i = 2; int j = iFirstPoint + step + step; if (j < 0) { j = j + gLength; } j = j % gLength; boolean pointOK = true; while ((pointOK) && (i < gLength)) { pointOK = (this.getPoint(i).isEqual(g.getPoint(j))); if (pointOK) { j = j + step; if (j < 0) { j = gLength - 1; } j = j % gLength; i++; } } return pointOK; } } } } return false; } /** * @param geo * input * @return true - if input is congruent with this polygon false - otherwise */ @Override public ExtendedBoolean isCongruent(GeoElement geo) { if (geo instanceof GeoPolygon) { GeoPolygon polygon = (GeoPolygon) geo; // Polygons: // two polygon are congruent if the corresponding sides has same // length // and corresponding angles has same size int nrSidesPoly1 = this.getSegments().length; int nrSidesPoly2 = polygon.getSegments().length; // two polygon can be congruent when their number of sides are equal // and have the same area if (nrSidesPoly1 == nrSidesPoly2 && (this).hasSameArea(polygon)) { GeoSegmentND[] segmentsPoly1 = this.getSegments(); GeoSegmentND[] segmentsPoly2 = polygon.getSegments(); AlgoAnglePolygonND algo1 = new AlgoAnglePolygon(cons, this, null); AlgoAnglePolygonND algo2 = new AlgoAnglePolygon(cons, polygon, null); GeoElement[] anglesPoly1 = algo1.getAngles(); GeoElement[] anglesPoly2 = algo2.getAngles(); int nrOfShifts = 0; while (nrOfShifts <= segmentsPoly2.length) { // first two segments from segmentsPoly1 and segmentsPoly2 // are // congruent if (ExpressionNodeEvaluator .evalEquals(kernel, segmentsPoly1[0], segmentsPoly2[0]) .getBoolean() && ExpressionNodeEvaluator.evalEquals(kernel, segmentsPoly1[1], segmentsPoly2[1]) .getBoolean()) { break; } // first two segment from segmentPoly1 are congruent with // the // last two segment from segmentPoly2 if (ExpressionNodeEvaluator .evalEquals(kernel, segmentsPoly1[0], segmentsPoly2[segmentsPoly2.length - 1]) .getBoolean() && ExpressionNodeEvaluator.evalEquals(kernel, segmentsPoly1[1], segmentsPoly2[segmentsPoly2.length - 2]) .getBoolean()) { break; } segmentsPoly2 = shiftSegments(segmentsPoly2); nrOfShifts++; } nrOfShifts = 0; while (nrOfShifts <= anglesPoly2.length) { // we have the external angles if (((GeoAngle) anglesPoly1[0]).getValue() >= Math.PI && ((GeoAngle) anglesPoly2[0]) .getValue() >= Math.PI) { double d1 = 2 * Math.PI - ((GeoAngle) anglesPoly1[0]).getDouble(); double d2 = 2 * Math.PI - ((GeoAngle) anglesPoly2[0]).getDouble(); double d3 = 2 * Math.PI - ((GeoAngle) anglesPoly1[1]).getDouble(); double d4 = 2 * Math.PI - ((GeoAngle) anglesPoly2[1]).getDouble(); // first two angle values are congruent if (Kernel.isEqual(d1, d2) && Kernel.isEqual(d3, d4)) { break; } double d5 = 2 * Math.PI - ((GeoAngle) anglesPoly2[anglesPoly2.length - 1]).getDouble(); double d6 = 2 * Math.PI - ((GeoAngle) anglesPoly2[anglesPoly2.length - 2]).getDouble(); // first two angle values from anglesPoly1 equals to // last // two angle values from anglesPoly2 if (Kernel.isEqual(d1, d5) && Kernel.isEqual(d3, d6)) { break; } anglesPoly2 = shiftAngles(anglesPoly2); nrOfShifts++; } // we have the internal angles of first polygon and external // angles of second polygon else if (((GeoAngle) anglesPoly1[0]).getValue() < Math.PI && ((GeoAngle) anglesPoly2[0]) .getValue() >= Math.PI) { double d1 = ((GeoAngle) anglesPoly1[0]).getDouble(); double d2 = 2 * Math.PI - ((GeoAngle) anglesPoly2[0]).getDouble(); double d3 = ((GeoAngle) anglesPoly1[1]).getDouble(); double d4 = 2 * Math.PI - ((GeoAngle) anglesPoly2[1]).getDouble(); // first two angle values are congruent if (Kernel.isEqual(d1, d2) && Kernel.isEqual(d3, d4)) { break; } double d5 = 2 * Math.PI - ((GeoAngle) anglesPoly2[anglesPoly2.length - 1]).getDouble(); double d6 = 2 * Math.PI - ((GeoAngle) anglesPoly2[anglesPoly2.length - 2]).getDouble(); // first two angle values from anglesPoly1 equals to // last // two angle values from anglesPoly2 if (Kernel.isEqual(d1, d5) && Kernel.isEqual(d3, d6)) { break; } anglesPoly2 = shiftAngles(anglesPoly2); nrOfShifts++; } // we have the external angles of first polygon and internal // angles of second polygon else if (((GeoAngle) anglesPoly1[0]).getValue() >= Math.PI && ((GeoAngle) anglesPoly2[0]) .getValue() < Math.PI) { double d1 = 2 * Math.PI - ((GeoAngle) anglesPoly1[0]).getDouble(); double d2 = ((GeoAngle) anglesPoly2[0]).getDouble(); double d3 = 2 * Math.PI - ((GeoAngle) anglesPoly1[1]).getDouble(); double d4 = ((GeoAngle) anglesPoly2[1]).getDouble(); // first two angle values are congruent if (Kernel.isEqual(d1, d2) && Kernel.isEqual(d3, d4)) { break; } double d5 = ((GeoAngle) anglesPoly2[anglesPoly2.length - 1]).getDouble(); double d6 = ((GeoAngle) anglesPoly2[anglesPoly2.length - 2]).getDouble(); // first two angle values from anglesPoly1 equals to // last // two angle values from anglesPoly2 if (Kernel.isEqual(d1, d5) && Kernel.isEqual(d3, d6)) { break; } anglesPoly2 = shiftAngles(anglesPoly2); nrOfShifts++; } // we have the internal angles of first and second polygon else { // first two angles from both angelPolys are congruent if (ExpressionNodeEvaluator .evalEquals(kernel, anglesPoly1[0], anglesPoly2[0]) .getBoolean() && ExpressionNodeEvaluator.evalEquals(kernel, anglesPoly1[1], anglesPoly2[1]) .getBoolean()) { break; } // first two angles from anglesPoly1 equals to last two // angles from anglesPoly2 if (ExpressionNodeEvaluator .evalEquals(kernel, anglesPoly1[0], anglesPoly2[anglesPoly2.length - 1]) .getBoolean() && ExpressionNodeEvaluator.evalEquals(kernel, anglesPoly1[1], anglesPoly2[anglesPoly2.length - 2]) .getBoolean()) { break; } anglesPoly2 = shiftAngles(anglesPoly2); nrOfShifts++; } } boolean result = checkInBothDirection(segmentsPoly1, segmentsPoly2, anglesPoly1, anglesPoly2); algo1.remove(); algo2.remove(); return ExtendedBoolean.newExtendedBoolean(result); } } // case the geo is a GeoNumeric, e.g. area using formula // we can check whether the areas are equal or not if (geo instanceof GeoNumeric) { if (Kernel.isEqual(this.getArea(), ((GeoNumeric) geo).getValue())) { return ExtendedBoolean.TRUE; } } return ExtendedBoolean.FALSE; } // shift angles to left // e.g. [alpha,beta,gamma,delta] -> [beta,gamma,delta,alpha] private static GeoElement[] shiftAngles(GeoElement[] angles) { GeoElement p = angles[0]; for (int i = 0; i < angles.length - 1; i++) { angles[i] = angles[i + 1]; } angles[angles.length - 1] = p; return angles; } // shift segments to left // e.g. [a,b,c,d] -> [b,c,d,a] private static GeoSegmentND[] shiftSegments(GeoSegmentND[] segments) { GeoSegmentND p = segments[0]; for (int i = 0; i < segments.length - 1; i++) { segments[i] = segments[i + 1]; } segments[segments.length - 1] = p; return segments; } private boolean checkInBothDirection(GeoElementND[] segmentsPoly1, GeoElementND[] segmentsPoly2, GeoElement[] anglesPoly1, GeoElement[] anglesPoly2) { boolean rightDirection = true; boolean leftDirection = true; // check in right direction for (int i = 0; i < segmentsPoly1.length; i++) { if (!(ExpressionNodeEvaluator .evalEquals(kernel, segmentsPoly1[i], segmentsPoly2[i]) .getBoolean())) { rightDirection = false; break; } } // the angles must be checked in same direction if (rightDirection) { // we have the external angles if (((GeoAngle) anglesPoly1[0]).getDouble() >= Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() >= Math.PI) { for (int i = 0; i < anglesPoly1.length; i++) { double d1 = 2 * Math.PI - ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = 2 * Math.PI - ((GeoAngle) anglesPoly2[i]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } // we have the internal angles of first polygon and external // angles of second polygon else if (((GeoAngle) anglesPoly1[0]).getDouble() < Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() >= Math.PI) { for (int i = 0; i < anglesPoly1.length; i++) { double d1 = ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = 2 * Math.PI - ((GeoAngle) anglesPoly2[i]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } // we have the external angles of first polygon and internal // angles of second polygon else if (((GeoAngle) anglesPoly1[0]).getDouble() >= Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() < Math.PI) { for (int i = 0; i < anglesPoly1.length; i++) { double d1 = 2 * Math.PI - ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = ((GeoAngle) anglesPoly2[i]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } // we have the internal angles else { for (int i = 0; i < anglesPoly1.length; i++) { if (!(ExpressionNodeEvaluator .evalEquals(kernel, anglesPoly1[i], anglesPoly2[i]) .getBoolean())) { return false; } } } return true; } // check if segmentsPoly2 is the mirror of segmentsPoly1 for (int i = segmentsPoly1.length - 1; i >= 0; i--) { if (!(ExpressionNodeEvaluator.evalEquals(kernel, segmentsPoly2[segmentsPoly2.length - i - 1], segmentsPoly1[i]).getBoolean())) { leftDirection = false; break; } } // the angles must be checked in same direction if (leftDirection) { if (((GeoAngle) anglesPoly1[0]).getDouble() >= Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() >= Math.PI) { for (int i = anglesPoly2.length - 1; i >= 0; i--) { double d1 = 360 * Math.PI / 180 - ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = 360 * Math.PI / 180 - ((GeoAngle) anglesPoly2[anglesPoly2.length - i - 1]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } // case we have the internal angles else if (((GeoAngle) anglesPoly1[0]).getDouble() < Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() >= Math.PI) { for (int i = anglesPoly2.length - 1; i >= 0; i--) { double d1 = ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = 360 * Math.PI / 180 - ((GeoAngle) anglesPoly2[anglesPoly2.length - i - 1]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } // case we have the external angles else if (((GeoAngle) anglesPoly1[0]).getDouble() >= Math.PI && ((GeoAngle) anglesPoly2[0]).getDouble() < Math.PI) { for (int i = anglesPoly2.length - 1; i >= 0; i--) { double d1 = 360 * Math.PI / 180 - ((GeoAngle) anglesPoly1[i]).getDouble(); double d2 = ((GeoAngle) anglesPoly2[anglesPoly2.length - i - 1]).getDouble(); if (!Kernel.isEqual(d1, d2)) { return false; } } } else { for (int i = segmentsPoly1.length - 1; i >= 0; i--) { if (!(ExpressionNodeEvaluator.evalEquals(kernel, anglesPoly2[anglesPoly2.length - i - 1], anglesPoly1[i]).getBoolean())) { return false; } } } return true; } return false; } @Override public void setEuclidianVisible(boolean visible) { setEuclidianVisible(visible, true); } /** * Switches visibility of this polygon * * @param visible * visibility flag * @param updateSegments * if true, applies also to segments */ public void setEuclidianVisible(boolean visible, boolean updateSegments) { super.setEuclidianVisible(visible); if (updateSegments && segments != null) { for (int i = 0; i < segments.length; i++) { segments[i].setEuclidianVisible(visible); segments[i].updateVisualStyle(GProperty.VISIBLE); } } } @Override public void setObjColor(GColor color) { super.setObjColor(color); if (segments != null && createSegments) { for (int i = 0; i < segments.length; i++) { segments[i].setObjColor(color); segments[i].updateVisualStyle(GProperty.COLOR); } } } @Override public void setLineType(int type) { setLineType(type, true); } /** * set the line type (and eventually the segments) * * @param type * line type * @param updateSegments * true to apply this setting to segments */ public void setLineType(int type, boolean updateSegments) { super.setLineType(type); if (updateSegments) { if (segments != null) { for (int i = 0; i < segments.length; i++) { segments[i].setLineType(type); segments[i].updateVisualStyle(GProperty.LINE_STYLE); } } } } @Override public void setLineTypeHidden(int type) { setLineTypeHidden(type, true); } /** * set the hidden line type (and eventually the segments) * * @param type * line type for hidden lines * @param updateSegments * true to apply this setting to segments */ public void setLineTypeHidden(int type, boolean updateSegments) { super.setLineTypeHidden(type); if (updateSegments) { if (segments != null) { for (int i = 0; i < segments.length; i++) { ((GeoElement) segments[i]).setLineTypeHidden(type); segments[i].updateVisualStyle(GProperty.LINE_STYLE); } } } } @Override public void setLineThickness(int th) { setLineThickness(th, true); } /** * set the line thickness (and eventually the segments) * * @param th * new thickness * @param updateSegments * true to apply this setting to segments as well */ public void setLineThickness(int th, boolean updateSegments) { super.setLineThickness(th); if (updateSegments) { if (segments != null) { for (int i = 0; i < segments.length; i++) { segments[i].setLineThickness(th); segments[i].updateVisualStyle(GProperty.LINE_STYLE); } } } } @Override public void setLineThicknessOrVisibility(int th) { super.setLineThickness(th); if (segments != null) { for (int i = 0; i < segments.length; i++) { ((GeoElement) segments[i]).setLineThicknessOrVisibility(th); segments[i].updateVisualStyle(GProperty.COMBINED); } } } @Override final public String toString(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(label); sbToString.append(" = "); sbToString.append(kernel.format(getArea(), tpl)); return sbToString.toString(); } @Override final public String toStringMinimal(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(regrFormat(getArea())); return sbToString.toString(); } private StringBuilder sbToString = new StringBuilder(50); @Override final public String toValueString(StringTemplate tpl) { return kernel.format(getArea(), tpl); } /** * interface NumberValue */ @Override public MyDouble getNumber() { return new MyDouble(kernel, getArea()); } @Override final public double getDouble() { return getArea(); } @Override protected boolean showInEuclidianView() { return defined; } @Override public boolean isNumberValue() { return true; } @Override public boolean isGeoPolygon() { return true; } /* * Path interface implementation */ @Override public boolean isPath() { return true; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } @Override public double getMaxParameter() { return getPointsLength(); } @Override public double getMinParameter() { return 0; } @Override public boolean isClosedPath() { return true; } @Override public boolean isOnPath(GeoPointND PI, double eps) { GeoPoint P = (GeoPoint) PI; if (P.getPath() == this) { return true; } // check if P is on one of the segments for (int i = 0; i < segments.length; i++) { if (segments[i].isOnPath(P, eps)) { return true; } } return false; } /** * @param coords * coordiantes * @param eps * precision * @return whether given point is on boundary of the polygon within given * precision */ public boolean isOnPath(Coords coords, double eps) { // check if P is on one of the segments for (int i = 0; i < segments.length; i++) { if (segments[i].isOnPath(coords, eps)) { return true; } } return false; } @Override public void pathChanged(GeoPointND PI) { // if kernel doesn't use path/region parameters, do as if point changed // its coords if (!getKernel().usePathAndRegionParameters(PI)) { pointChanged(PI); return; } // parameter is between 0 and segment.length, // i.e. floor(parameter) gives the segment index PathParameter pp = PI.getPathParameter(); pp.t = pp.t % segments.length; if (pp.t < 0) { pp.t += segments.length; } int index = (int) Math.floor(pp.t); GeoSegmentND seg = segments[index]; double segParameter = pp.t - index; // calc point for given parameter PI.setCoords2D(seg.getPointX(segParameter), seg.getPointY(segParameter), 1); } @Override public void pointChanged(GeoPointND PI) { Coords coords = PI.getCoordsInD2(); double qx = coords.getX() / coords.getZ(); double qy = coords.getY() / coords.getZ(); double minDist = Double.POSITIVE_INFINITY; double resx = 0, resy = 0, resz = 0, param = 0; // find closest point on each segment PathParameter pp = PI.getPathParameter(); if (segments != null) { for (int i = 0; i < segments.length; i++) { PI.setCoords2D(qx, qy, 1); PI.updateCoordsFrom2D(false); segments[i].pointChanged(PI); coords = PI.getCoordsInD2(); double x = coords.getX() / coords.getZ() - qx; double y = coords.getY() / coords.getZ() - qy; double dist = x * x + y * y; if (dist < minDist) { minDist = dist; // remember closest point resx = coords.getX(); resy = coords.getY(); resz = coords.getZ(); param = i + pp.t; } } } PI.setCoords2D(resx, resy, resz); PI.updateCoordsFrom2D(false); pp.t = param; } /* * Region interface implementation */ // by default, a polygon is always a region. // A polygon can also be a path (as the boundary of the region) @Override public boolean isRegion() { return true; } /** * @param PI * point * @return true if PI is in this polygon */ @Override public boolean isInRegion(GeoPointND PI) { Coords coords = PI.getCoordsInD2(); return isInRegion(coords.getX() / coords.getZ(), coords.getY() / coords.getZ()); } /** * says if the point (x0,y0) is in the region * * @param x0 * x-coord of the point * @param y0 * y-coord of the point * @return true if the point (x0,y0) is in the region */ @Override public boolean isInRegion(double x0, double y0) { double x1, y1, x2, y2; int numPoints = getPointsLength(); x1 = getPointX(numPoints - 1) - x0; y1 = getPointY(numPoints - 1) - y0; boolean ret = false; for (int i = 0; i < numPoints; i++) { x2 = getPointX(i) - x0; y2 = getPointY(i) - y0; int inter = intersectOx(x1, y1, x2, y2); if (inter == 2) { return true; // point on an edge } ret = ret ^ (inter == 1); x1 = x2; y1 = y2; } return ret; } @Override final 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; } // GeoPoint P = (GeoPoint) PI; RegionParameters rp = P.getRegionParameters(); if (rp.isOnPath()) { pathChanged(P); } else { // Application.debug(rp.getT1()+ "," + rp.getT2()); // pointChangedForRegion(P); double xu = p1.inhomX - p0.inhomX; double yu = p1.inhomY - p0.inhomY; double xv = p2.inhomX - p0.inhomX; double yv = p2.inhomY - p0.inhomY; setRegionChanged(P, p0.inhomX + rp.getT1() * xu + rp.getT2() * xv, p0.inhomY + rp.getT1() * yu + rp.getT2() * yv); if (!isInRegion(P)) { pointChanged(P); rp.setIsOnPath(true); } } } /** * set region coords (x,y) to point PI * * @param PI * point * @param x * x-coord * @param y * y-coord */ public void setRegionChanged(GeoPointND PI, double x, double y) { GeoPoint P = (GeoPoint) PI; P.x = x; P.y = y; P.z = 1; } @Override public void pointChangedForRegion(GeoPointND P) { P.updateCoords2D(); RegionParameters rp = P.getRegionParameters(); // Application.debug("isInRegion : "+isInRegion(P)); if (!isInRegion(P.getX2D(), P.getY2D())) { pointChanged(P); rp.setIsOnPath(true); } else { if (numCS != 3) { // if the coord sys is not defined by 3 // independent points, then the point lies on // the path pointChanged(P); rp.setIsOnPath(true); } else { rp.setIsOnPath(false); double xu = p1.inhomX - p0.inhomX; double yu = p1.inhomY - p0.inhomY; double xv = p2.inhomX - p0.inhomX; double yv = p2.inhomY - p0.inhomY; double x = P.getX2D() - p0.inhomX; double y = P.getY2D() - p0.inhomY; rp.setT1((xv * y - x * yv) / (xv * yu - xu * yv)); rp.setT2((x * yu - xu * y) / (xv * yu - xu * yv)); // Application.debug(rp.getT1()+","+rp.getT2()); P.updateCoordsFrom2D(false, null); } } } /** * @param newp0 * new 1st point for region coords * @param newp1 * new 2nd point for region coords * @param newp2 * new 3rd point for region coords */ final public void updateRegionCS(GeoPoint newp0, GeoPoint newp1, GeoPoint newp2) { this.p0 = newp0; this.p1 = newp1; this.p2 = newp2; numCS = 3; } /** * update the region coord sys with the 3 first points */ final public void updateRegionCSWithFirstPoints() { updateRegionCS(getPoint(0), getPoint(1), getPoint(2)); } /** * update the coord sys used for region parameters */ final public void updateRegionCS() { if (getPoints() == null) { return; } if (p2 != null && !GeoPoint.collinear(p0, p1, p2)) { numCS = 3; return; } p0 = getPoint(0); numCS = 1; // Log.debug(" p0 = " + p0.inhomX + "," + p0.inhomY); int secondPoint = -1; boolean secondPointFound = false; for (secondPoint = 1; secondPoint < getPoints().length && !secondPointFound; secondPoint++) { p1 = getPoint(secondPoint); // Application.debug(" p1 ("+secondPoint+") = // "+p1.inhomX+","+p1.inhomY); if (!Kernel.isEqual(p0.inhomX, p1.inhomX, Kernel.STANDARD_PRECISION)) { secondPointFound = true; } else if (!Kernel.isEqual(p0.inhomY, p1.inhomY, Kernel.STANDARD_PRECISION)) { secondPointFound = true; // Log.debug(" secondPointFound = " + secondPointFound); } } int thirdPoint = -1; if (secondPointFound) { numCS++; secondPoint--; boolean thirdPointFound = false; for (thirdPoint = getPoints().length - 1; thirdPoint > secondPoint && !thirdPointFound; thirdPoint--) { p2 = getPoint(thirdPoint); if (!GeoPoint.collinear(p0, p1, p2)) { thirdPointFound = true; numCS++; } } } // thirdPoint++; // Application.debug(" secondPoint = "+secondPoint+"\n thirdPoint = // "+thirdPoint); // Log.debug("numCS = " + numCS); } // ////////////////////////// // interface GeoSurfaceFinite // ///////////////////////////// private boolean asBoundary = false; @Override public void setRole(boolean isAsBoundary) { this.asBoundary = isAsBoundary; // false means 'as region' } @Override public boolean asBoundary() { return asBoundary; } /** * returns 1 if the segment ((x1,y1),(x2,y2)) intersects y=0 for x>0, 2 if * (0,0) is on the segment and -1 otherwise If the segment only touches the * line for x>0, this touch is counted only if the segment is in y>0. * * Segments lying entirely on y=0 are ignored, unless they go through (0,0). */ private static int intersectOx(double px1, double py1, double px2, double py2) { double x1 = px1, x2 = px2, y1 = py1, y2 = py2; double eps = Kernel.STANDARD_PRECISION; if (Kernel.isZero(y1)) { // first point on (Ox) if (Kernel.isZero(y2)) { // second point on (Ox) if (Kernel.isGreaterEqual(0, x1 * x2)) { return 2; } // ignore the segment on 0x if it is whole on left or right return -1; } // only first point is on (Ox) if (Kernel.isZero(x1)) { return 2; } return y2 > eps && x1 > eps ? 1 : -1; } else if (Kernel.isZero(y2)) { // only second point is on (0x) if (Kernel.isZero(x2)) { return 2; } return y1 > eps && x2 > eps ? 1 : -1; } else if (y1 * y2 > eps) { return -1; } else { if (y1 > y2) { // first point under (Ox) double y = y1; y1 = y2; y2 = y; double x = x1; x1 = x2; x2 = x; } if ((x1 + eps < 0) && (x2 + eps < 0)) { return -1; } else if ((x1 > eps) && (x2 > eps)) { return 1; } else if (x1 * y2 > x2 * y1 + eps) { return 1; } else if (x1 * y2 + eps < x2 * y1) { return -1; } else { return 2; // angle ~ 0 } } } /** * returns all class-specific xml tags for getXML GeoGebra File Format */ @Override protected void getXMLtags(StringBuilder sb) { getLineStyleXML(sb); getXMLvisualTags(sb); getXMLanimationTags(sb); getXMLfixedTag(sb); getXMLisShapeTag(sb); getAuxiliaryXML(sb); getBreakpointXML(sb); getScriptTags(sb); } /** * @return minimum line thickness (normally 1, but 0 for polygons, integrals * etc) */ @Override public int getMinimumLineThickness() { return 0; } private boolean trace; @Override public boolean isTraceable() { return true; } @Override public void setTrace(boolean trace) { this.trace = trace; } @Override public boolean getTrace() { return trace; } // /////////////////// // 3D stuff // /////////////////// /** * Returns the i-th 3D point of this polygon. * * @param i * number of point * @return the i-th point */ public Coords getPoint3D(int i) { return getPoint(i).getInhomCoordsInD3(); } /** * if this is a part of a closed surface * * @return if this is a part of a closed surface */ public boolean isPartOfClosedSurface() { return false; // return (getMetasLength() > 0); } /** * if this is a convex polygon * * @return if this is a convex polygon */ public boolean isConvex() { /* * if (getPointsLength() <= 3){ return true; } */ // remove same successive points ArrayList<Double> xList = new ArrayList<Double>(); ArrayList<Double> yList = new ArrayList<Double>(); double x0 = getPointX(0); double y0 = getPointY(0); xList.add(x0); yList.add(y0); for (int i = 1; i < getPointsLength(); i++) { double x1 = getPointX(i); double y1 = getPointY(i); if (!Kernel.isEqual(x0, x1) || !Kernel.isEqual(y0, y1)) { xList.add(x1); yList.add(y1); x0 = x1; y0 = y1; } } int n = xList.size(); /* * if (n<=3){ return true; } */ // remove last point if equals first points if (Kernel.isEqual(xList.get(0), xList.get(n - 1)) && Kernel.isEqual(yList.get(0), yList.get(n - 1))) { /* * if (n==4){ return true; } */ xList.remove(n - 1); yList.remove(n - 1); n--; } // check orientations boolean answer = true; boolean hasAngle360 = false; double x1 = xList.get(n - 1); double y1 = yList.get(n - 1); double dx1 = x1 - xList.get(n - 2); double dy1 = y1 - yList.get(n - 2); double x2 = xList.get(0); double y2 = yList.get(0); double dx2 = x2 - x1; double dy2 = y2 - y1; // calc first orientation int orientation = Kernel.compare(dy1 * dx2, dx1 * dy2); if (orientation == 0) { if (Kernel.isGreater(0, dx1 * dx2 + dy1 * dy2)) { // U-turn answer = false; } } int i = 1; while (answer && (i < n)) { dx1 = dx2; dy1 = dy2; x1 = x2; y1 = y2; x2 = xList.get(i); y2 = yList.get(i); dx2 = x2 - x1; dy2 = y2 - y1; int orientation2 = Kernel.compare(dy1 * dx2, dx1 * dy2); // Log.debug(""+answer+","+hasAngle360); // Log.debug("i : "+i+" -- orientations : // "+orientation+","+orientation2); if (!hasAngle360 && orientation2 == 0) { // U-turn if (Kernel.isGreater(0, dx1 * dx2 + dy1 * dy2)) { answer = false; } } if (answer) { if (orientation == 0) { // no orientation for now orientation = orientation2; } else { if (orientation2 != 0 && orientation2 != orientation) { answer = false; // stop here } } } i++; } if (answer) { convexOrientation = orientation; } return answer; } /** * orientation (1/-1) when convex */ private int convexOrientation; /** * * @return true if points orientation are not the same as xOy plane (only * used in 2D) */ public boolean isConvexInverseDirection() { // Log.debug(""+convexOrientation); return (convexOrientation > 0); } /* * private class SweepComparator implements Comparator<Integer>{ * * private GeoPolygon p; * * /** constructor * * @param p polygon * * public SweepComparator(GeoPolygon p) { this.p = p; } * * public int compare(Integer i1, Integer i2) { * * // smallest x double x1 = p.getPointX(i1); double x2 = p.getPointX(i2); * if (Kernel.isGreater(x2, x1)){ return -1; } if (Kernel.isGreater(x1, * x2)){ return 1; } * * // then smallest y double y1 = p.getPointY(i1); double y2 = * p.getPointY(i2); if (Kernel.isGreater(y2, y1)){ return -1; } if * (Kernel.isGreater(y1, y2)){ return 1; } * * // then smallest index if (i1 < i2){ return -1; } if (i1 > i2){ return 1; * } * * // same point return 0; } * * * } */ @Override public boolean hasDrawable3D() { return true; } private Coords labelPosition; @Override public Coords getLabelPosition() { double x = 0; double y = 0; double z = 0; for (int i = 0; i < getPointsLength(); i++) { Coords coords = getPoint3D(i); x += coords.getX(); y += coords.getY(); z += coords.getZ(); } if (labelPosition == null) { labelPosition = new Coords(x / getPointsLength(), y / getPointsLength(), z / getPointsLength(), 1); } else { labelPosition.setX(x / getPointsLength()); labelPosition.setY(y / getPointsLength()); labelPosition.setZ(z / getPointsLength()); } return labelPosition; } // ////////////////////////////////////// // GEOCOORDSYS2D INTERFACE // ////////////////////////////////////// @Override public CoordSys getCoordSys() { return CoordSys.Identity3D; } @Override public Coords getPoint(double x2d, double y2d, Coords coords) { return getCoordSys().getPoint(x2d, y2d, coords); } @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; } // //////////////////////////////////////////////////// // PARENT NUMBER (HEIGHT OF A PRISM, ...) // //////////////////////////////////////////////////// private ChangeableCoordParent changeableCoordParent = null; /** * @param ccp * changeable coord parent * */ final public void setChangeableCoordParent(ChangeableCoordParent ccp) { changeableCoordParent = ccp; } @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); } @Override public void rotate(NumberValue r) { for (int i = 0; i < getPointsLength(); i++) { getPoint(i).rotate(r); } updatePathRegion(); } @Override public void rotate(NumberValue r, GeoPointND S) { Coords Scoords = S.getInhomCoords(); for (int i = 0; i < getPointsLength(); i++) { getPoint(i).rotate(r, Scoords); } updatePathRegion(); } @Override public void matrixTransform(double a00, double a01, double a10, double a11) { for (int i = 0; i < getPointsLength(); i++) { getPoint(i).matrixTransform(a00, a01, a10, a11); } calcArea(); updatePathRegion(); } @Override public void translate(Coords v) { for (int i = 0; i < getPointsLength(); i++) { getPoint(i).translate(v); } updatePathRegion(); } @Override public void dilate(NumberValue r, Coords S) { for (int i = 0; i < getPointsLength(); i++) { getPoint(i).dilate(r, S); } calcArea(); updatePathRegion(); } @Override public void mirror(Coords Q) { // important for centroid calculation area *= -1; for (int i = 0; i < getPointsLength(); i++) { getPoint(i).mirror(Q); } updatePathRegion(); } private void updatePathRegion() { updateRegionCS(); this.updateSegments(cons); } @Override public void mirror(GeoLineND g) { // important for centroid calculation area *= -1; for (int i = 0; i < getPointsLength(); i++) { getPoint(i).mirror(g); } updatePathRegion(); } /** * Returns true iff all vertices are labeled * * @return true iff all vertices are labeled */ @Override public boolean isAllVertexLabelsSet() { for (int i = 0; i < getPointsLength(); i++) { if (!getPoint(i).isLabelSet()) { return false; } } return true; } /** * Returns true iff number of vertices is not volatile * * @return true iff number of vertices is not volatile */ @Override public boolean isVertexCountFixed() { // regularPolygon[vertex,vertex,count] if (getParentAlgorithm() instanceof AlgoPolygonRegularND) { return false; } // polygon[list] if (getParentAlgorithm() == null || getParentAlgorithm().getInput().length < 3) { return false; } return true; } @Override public Coords getDirectionInD3() { return Coords.VZ; } @Override public void matrixTransform(double a00, double a01, double a02, double a10, double a11, double a12, double a20, double a21, double a22) { for (int i = 0; i < getPointsLength(); i++) { ((MatrixTransformable) getPointND(i)).matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); } calcArea(); updatePathRegion(); } /** * Update area from vertices */ public void calcArea() { // eg Dilate[Polygon[(0,0),(1,1),(1,0)],4] if (algoParent instanceof AlgoTransformation) { AlgoTransformation algo = (AlgoTransformation) algoParent; double sf = algo.getAreaScaleFactor(); GeoPolygon input = (GeoPolygon) algo.getInput( algoParent.getClassName() == Commands.ApplyMatrix ? 1 : 0); setArea(input.getAreaWithSign() * sf); return; } if (algoParent instanceof PolygonAlgo) { ((PolygonAlgo) algoParent).calcArea(); } // eg // IntersectRegion[Polygon[(1,1),(0,1),(1,0)],Polygon[(1,1),(0,1),(1,0)]] // eg Union[Polygon[(1,1),(0,1),(1,0)],Polygon[(1,1),(0,1),(1,0)]] setArea(AlgoPolygon.calcAreaWithSign(getPoints())); } private double[] tmp3; /** * Updates given point's coordinates to centroid of this * * @param p * centroid */ public void calcCentroid(GeoPointND p) { if (algoParent instanceof PolygonAlgo) { ((PolygonAlgo) algoParent).calcCentroid((GeoPoint) p); return; } // just do long method // could improve by transforming original centroid, but not worth doing // test-case Centroid[Dilate[Polygon[(0,0),(1,1),(1,0)],4]] // test-case Centroid[Polygon[(0,0),(1,1),(1,0)]] if (tmp3 == null) { tmp3 = new double[3]; } AlgoPolygon.calcCentroid(tmp3, area, getPoints()); if (Double.isNaN(tmp3[0])) { p.setUndefined(); } else { p.setCoords(tmp3[0], tmp3[1], tmp3[2]); } } @Override public void toGeoCurveCartesian(GeoCurveCartesianND curve) { if (!isDefined()) { curve.setUndefined(); return; } curve.setFromPolyLine(points, true); } @Override public String getDefaultLabel(char[] chars, boolean isInteger) { if (chars != null) { return super.getDefaultLabel(chars, isInteger); } int counter = 0; String str; String name; if (getMetasLength() == 1) { name = getLoc().getPlainLabel("face"); // Name.face } else { name = getLoc().getPlainLabel("polygon"); // Name.polygon } do { counter++; str = name + kernel.internationalizeDigits(counter + "", StringTemplate.defaultTemplate); } while (!cons.isFreeLabel(str)); return str; } /** * modify input points. Assume that parent algo is an instance of * AlgoPolygon * * @param newPoints * new input points */ public void modifyInputPoints(GeoPointND[] newPoints) { AlgoPolygon algo = (AlgoPolygon) getParentAlgorithm(); algo.modifyInputPoints(newPoints); } @Override public PVariable[] getBotanaVars(GeoElementND geo) { // It's OK to return null here: return null; } @Override public PPolynomial[] getBotanaPolynomials(GeoElementND geo) throws NoSymbolicParametersException { // It's OK to return null here: return null; } @Override public int getMetasLength() { if (metas == null) { return 0; } return metas.size(); } @Override public GeoElement[] getMetas() { GeoElement[] ret = new GeoElement[metas.size()]; metas.toArray(ret); return ret; } private TreeSet<GeoElement> metas; /** * add the polyhedron has meta geo for this (e.g. parent polyhedron, or * linked polyhedron) * * @param polyhedron * polyhedron */ public void addMeta(GeoElement polyhedron) { if (metas == null) { metas = new TreeSet<GeoElement>(); } metas.add(polyhedron); } /** * remove polyhedron as meta for this * * @param polyhedron * polyhedron */ public void removeMeta(GeoElement polyhedron) { if (metas != null) { metas.remove(polyhedron); } } @Override public double distance(final GeoPoint p) { double d = Double.POSITIVE_INFINITY; for (GeoSegmentND seg : getSegments()) { double d1 = seg.distance(p); if (d1 < d) { d = d1; } } return d; } /////////////////////////////////// // REVERSE ORIENTATION FOR DRAWING /////////////////////////////////// private boolean reverseNormalForDrawing = false; /** * set that normal should be reversed for 3D drawing * * @param flag * flag */ public void setReverseNormalForDrawing(boolean flag) { reverseNormalForDrawing = flag; } /** * @return if normal should be reversed for 3D drawing */ public boolean getReverseNormalForDrawing() { return reverseNormalForDrawing; } /** * Sets the point size (and/or visibility) * * @param size * new point size */ public void setPointSizeOrVisibility(int size) { if (size > 0) { setPointSize(size); } else { setPointNotVisibile(); } } private void setPointSize(int size) { for (GeoPointND point : points) { ((GeoElement) point) .setEuclidianVisibleIfNoConditionToShowObject(true); point.setPointSize(size); point.updateRepaint(); } } private void setPointNotVisibile() { for (GeoPointND point : points) { ((GeoElement) point) .setEuclidianVisibleIfNoConditionToShowObject(false); point.updateRepaint(); } } /** * * @param cons1 * construction * @return 2D point if 2D polygon, 3D point if 3D polygon */ public GeoPointND newGeoPoint(Construction cons1) { return new GeoPoint(cons1); } @Override public int getNumPoints() { return points == null ? 0 : points.length; } @Override public ValueType getValueType() { return ValueType.NUMBER; } private PolygonTriangulation pt; /** * * @return polygon triangulation created for this */ public PolygonTriangulation getPolygonTriangulation() { if (pt == null) { pt = new PolygonTriangulation(); pt.setPolygon(this); } return pt; } @Override public void setVisualStyle(final GeoElement geo) { super.setVisualStyle(geo); if (segments == null) { return; } for (GeoSegmentND segment : segments) { segment.setObjColor(geo.getObjectColor()); segment.updateVisualStyle(GProperty.COLOR); } } @Override public boolean isShape() { return kernel.getApplication().has(Feature.BOUNDING_BOXES) && isShape; } /** * @param isShape * - true, if geo was created with shape tool */ @Override public void setIsShape(boolean isShape) { this.isShape = isShape; } }