/* 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.algos; import java.util.ArrayList; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumberValue; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.kernelND.GeoDirectionND; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoSegmentND; /** * Creates a regular Polygon for two points and the number of vertices. * * @author Markus Hohenwarter */ public abstract class AlgoPolygonRegularND extends AlgoElement implements PolygonAlgo { /** first input point */ protected final GeoPointND A; // input /** second input point */ protected final GeoPointND B; protected GeoNumberValue num; // input protected int numOld = 2; /** output handler */ protected OutputHandler<GeoPolygon> outputPolygon; protected OutputHandler<GeoElement> outputPoints; protected OutputHandler<GeoElement> outputSegments; protected GeoPointND centerPoint; protected MyDouble rotAngle; protected boolean labelPointsAndSegments; /** whether new segment labels should be visible */ boolean showNewSegmentsLabels; /** whether new point labels should be visible */ boolean showNewPointsLabels; private boolean labelsNeedIniting; private double alpha; private int n; /** * Creates a new regular polygon algorithm * * @param c * construction * @param labels * labels[0] for polygon, then labels for segments and then for * points * @param A1 * first input point * @param B1 * second input point * @param num * number of vertices */ public AlgoPolygonRegularND(Construction c, String[] labels, GeoPointND A1, GeoPointND B1, GeoNumberValue num, GeoDirectionND direction) { super(c); labelsNeedIniting = true; this.A = A1; this.B = B1; this.num = num; setDirection(direction); // labels given by user or loaded from file int labelsLength = labels == null ? 0 : labels.length; // set labels for segments only when points have labels labelPointsAndSegments = A.isLabelSet() || B.isLabelSet() || labelsLength > 1; showNewSegmentsLabels = false; showNewPointsLabels = false; // temp center point of regular polygon centerPoint = (GeoPointND) newGeoPoint(c); rotAngle = new MyDouble(kernel); outputPolygon = new OutputHandler<GeoPolygon>( new elementFactory<GeoPolygon>() { @Override public GeoPolygon newElement() { GeoPolygon p = newGeoPolygon(cons); p.setParentAlgorithm(AlgoPolygonRegularND.this); return p; } }); outputSegments = new OutputHandler<GeoElement>( new elementFactory<GeoElement>() { @Override public GeoElement newElement() { GeoElement segment = (GeoElement) outputPolygon .getElement(0).createSegment(cons, A, B, true); segment.setAuxiliaryObject(true); boolean segmentsVisible = false; int size = outputSegments.size(); if (size > 0) { // check if at least one segment is // visible for (int i = 0; i < size && !segmentsVisible; i++) { segmentsVisible = segmentsVisible || outputSegments.getElement(i) .isEuclidianVisible(); } } else { // no segment yet segmentsVisible = true; } segment.setEuclidianVisible(segmentsVisible); segment.setLabelVisible(showNewSegmentsLabels); segment.setViewFlags(((GeoElement) A).getViewSet()); segment.setVisibleInView3D((GeoElement) A); segment.setVisibleInViewForPlane((GeoElement) A); return segment; } }); if (!labelPointsAndSegments) { outputSegments.removeFromHandler(); // no segments has output } outputPoints = new OutputHandler<GeoElement>( new elementFactory<GeoElement>() { @Override public GeoElement newElement() { GeoElement newPoint = newGeoPoint(cons); newPoint.setParentAlgorithm(AlgoPolygonRegularND.this); newPoint.setAuxiliaryObject(true); ((GeoPointND) newPoint).setPointSize(A.getPointSize()); newPoint.setEuclidianVisible(A.isEuclidianVisible() || B.isEuclidianVisible()); newPoint.setAuxiliaryObject(true); newPoint.setLabelVisible(showNewPointsLabels); newPoint.setViewFlags(((GeoElement) A).getViewSet()); newPoint.setVisibleInView3D((GeoElement) A); newPoint.setVisibleInViewForPlane((GeoElement) A); GeoBoolean conditionToShow = ((GeoElement) A) .getShowObjectCondition(); if (conditionToShow == null) { conditionToShow = ((GeoElement) B) .getShowObjectCondition(); } if (conditionToShow != null) { try { newPoint.setShowObjectCondition( conditionToShow); } catch (Exception e) { // circular exception -- do nothing } } return newPoint; } }); if (!labelPointsAndSegments) { outputPoints.removeFromHandler(); // no segments has output } // create polygon outputPolygon.adjustOutputSize(1); // create 2 first segments outputSegments.augmentOutputSize(2, false); outputSegments.getElement(0).setAuxiliaryObject(false); ((GeoSegmentND) outputSegments.getElement(1)).modifyInputPoints(B, A); // for AlgoElement setInputOutput(); GeoPolygon poly = getPoly(); // set that the poly output can have different points length poly.setNotFixedPointsLength(true); // compute poly if (labelsLength > 1) { compute((labelsLength + 1) / 2);// create maybe undefined outputs poly.setLabel(labels[0]); int d = 1; for (int i = 0; i < outputSegments.size(); i++) { outputSegments.getElement(i).setLabel(labels[d + i]); } d += outputSegments.size(); for (int i = 0; i < outputPoints.size(); i++) { outputPoints.getElement(i).setLabel(labels[d + i]); } } else if (labelsLength == 1) { poly.setLabel(labels[0]); } else { poly.setLabel(null); } labelsNeedIniting = false; update(); /* * if (labelPointsAndSegments) { //poly.initLabels(labels); } else if * (labelsLength == 1) { poly.setLabel(labels[0]); } else { * poly.setLabel(null); } * * * labelsNeedIniting = false; */ // make sure that we set all point and segment labels when needed // updateSegmentsAndPointsLabels(points.length); } /** * * @param cons * @return new GeoPolygon 2D/3D */ protected abstract GeoPolygon newGeoPolygon(Construction cons); /** * * @param cons * @return new GeoPoint 2D/3D */ protected abstract GeoElement newGeoPoint(Construction cons); /** * set the direction (only for 3D) * * @param direction * direction */ protected abstract void setDirection(GeoDirectionND direction); @Override public Commands getClassName() { return Commands.Polygon; } @Override public int getRelatedModeID() { return EuclidianConstants.MODE_REGULAR_POLYGON; } /** * * @return resulting polygon */ public final GeoPolygon getPoly() { return outputPolygon.getElement(0); } /** * Computes points of regular polygon */ @Override public final void compute() { // check points and number double nd = num.getDouble(); if (Double.isNaN(nd)) { nd = 2; } compute((int) Math.round(nd)); } /** * set the center point coords * * @param n * @param beta */ protected abstract void setCenterPoint(int n, double beta); protected void rotatePoints(int n, double alpha) { // now we have the center point of the polygon and // the center angle alpha between two neighbouring points // let's create the points by rotating A around the center point for (int k = 0; k < n - 2; k++) { // rotate point around center point outputPoints.getElement(k).set(A); rotAngle.set((k + 2) * alpha); rotate((GeoPointND) outputPoints.getElement(k)); } } /** * rotate the point regarding current parameters * * @param point * point */ protected abstract void rotate(GeoPointND point); /** * * @param n * @return true if undefined */ protected boolean checkUnDefined(int n) { if (n < 3 || !A.isDefined() || !B.isDefined()) { getPoly().setUndefined(); numOld = n; return true; } return false; } /** * @param nd * number of vertices */ public final void compute(int nd) { GeoPolygon poly = getPoly(); // get integer number of vertices n this.n = Math.max(2, nd); // if number of points changed, we need to update the // points array and the output array updateOutput(n); // check if regular polygon is defined if (checkUnDefined(n)) { return; } this.alpha = Kernel.PI_2 / n; // center angle ACB double beta = (Math.PI - alpha) / 2; // base angle CBA = BAC setCenterPoint(n, beta); rotatePoints(n, alpha); GeoPointND[] points = new GeoPointND[n]; points[0] = A; points[1] = B; for (int i = 2; i < n; i++) { points[i] = (GeoPointND) outputPoints.getElement(i - 2); } // update new segments for (int i = numOld - 1; i < n; i++) { // Log.debug(i+": "+points[i]+" , "+points[(i+1)%n]); ((GeoSegmentND) outputSegments.getElement(i)) .modifyInputPoints(points[i], points[(i + 1) % n]); } // update polygon poly.setPoints(points, null, false); // don't create segments GeoSegmentND[] segments = new GeoSegmentND[n]; for (int i = 0; i < n; i++) { segments[i] = (GeoSegmentND) outputSegments.getElement(i); } poly.setSegments(segments); // compute area of poly calcArea(); // update region coordinate system poly.updateRegionCSWithFirstPoints(); numOld = n; } /** * * @return current points length */ public int getCurrentPointsLength() { return numOld; } /** * Ensures that the pointList holds n points. * * @param n */ private void updateOutput(int n) { int nOld = outputPoints.size() + 2; // App.error("nOld="+nOld+", n="+n); if (nOld == n) { return; } // update points and segments if (n > nOld) { showNewPointsLabels = labelPointsAndSegments && (A.isEuclidianVisible() && A.isLabelVisible() || B.isEuclidianVisible() && B.isLabelVisible()); outputPoints.augmentOutputSize(n - nOld, false); if (labelPointsAndSegments && !labelsNeedIniting) { outputPoints.updateLabels(); } showNewSegmentsLabels = false; for (int i = 0; i < outputSegments.size(); i++) { showNewSegmentsLabels = showNewSegmentsLabels || outputSegments.getElement(i).isLabelVisible(); } outputSegments.augmentOutputSize(n - nOld, false); if (labelPointsAndSegments && !labelsNeedIniting) { outputSegments.updateLabels(); } } else { for (int i = n; i < nOld; i++) { outputPoints.getElement(i - 2).setUndefined(); outputSegments.getElement(i).setUndefined(); } // update last segment if (n > 2) { ((GeoSegmentND) outputSegments.getElement(n - 1)) .modifyInputPoints( (GeoPointND) outputPoints.getElement(n - 3), A); } else { ((GeoSegmentND) outputSegments.getElement(n - 1)) .modifyInputPoints(B, A); } } } private void removePoint(GeoElement oldPoint) { // remove dependent algorithms (e.g. segments) from update sets of // objects further up (e.g. polygon) the tree ArrayList<AlgoElement> algoList = oldPoint.getAlgorithmList(); for (int k = 0; k < algoList.size(); k++) { AlgoElement algo = algoList.get(k); for (int j = 0; j < input.length; j++) { input[j].removeFromUpdateSets(algo); } } // remove old point oldPoint.setParentAlgorithm(null); // remove dependent segment algorithm that are part of this polygon // to make sure we don't remove the polygon as well GeoPolygon poly = getPoly(); for (int k = 0; k < algoList.size(); k++) { AlgoElement algo = algoList.get(k); // make sure we don't remove the polygon as well if (algo instanceof AlgoJoinPointsSegmentInterface && ((AlgoJoinPointsSegmentInterface) algo) .getPoly() == poly) { continue; } algo.remove(); } algoList.clear(); // remove point oldPoint.doRemove(); } /** * Calls doRemove() for all output objects of this algorithm except for * keepGeo. */ @Override public void removeOutputExcept(GeoElement keepGeo) { for (int i = 0; i < super.getOutputLength(); i++) { GeoElement geo = super.getOutput(i); if (geo != keepGeo) { if (geo.isGeoPoint()) { removePoint(geo); } else { geo.doRemove(); } } } } @Override public void calcArea() { // more accurate method for 2D if (A instanceof GeoPoint && B instanceof GeoPoint && centerPoint instanceof GeoPoint) { // area = 1/2 | det(P[i], P[i+1]) | double area = GeoPoint.det((GeoPoint) A, (GeoPoint) B); area += GeoPoint.det((GeoPoint) B, (GeoPoint) this.centerPoint); area += GeoPoint.det((GeoPoint) this.centerPoint, (GeoPoint) A); area = area * this.n / 2; getPoly().setArea(area); return; } // TODO: more accurate method should be possible for 3D too double radius = A.distance(centerPoint); // 1/2 a b sin(C) getPoly().setArea(n * radius * radius * Math.sin(alpha) / 2.0); } }