/* 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 org.geogebra.common.kernel.Construction; 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.StringTemplate; import org.geogebra.common.kernel.Matrix.Coords; 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.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.plugin.GeoClass; import org.geogebra.common.util.debug.Log; /** * PolyLine (open Polygon) through given points * * @author Michael Borcherds, adapted from GeoPolygon */ public class GeoPolyLine extends GeoElement implements GeoNumberValue, Traceable, Transformable, Mirrorable, MatrixTransformable, PointRotateable, Translateable, Dilateable, GeoPoly { /** maximum number of points when created by tool */ public static final int POLYLINE_MAX_POINTS = 500; /** array of vertices */ protected GeoPointND[] points; /** length of the line */ protected double length; /** whether this is defined */ protected boolean defined = false; /** * common constructor for 2D. * * @param cons * the construction * @param label * label * @param points * vertices */ public GeoPolyLine(Construction cons, String label, GeoPointND[] points) { this(cons, points); setLabel(label); } /** * @param cons * construction */ public GeoPolyLine(Construction cons) { super(cons); this.points = new GeoPointND[] {}; // 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 } /** * @param cons * construction * @param label * label */ public GeoPolyLine(Construction cons, String label) { this(cons); setLabel(label); } /** * @param cons * construction * @param points * vertices */ public GeoPolyLine(Construction cons, GeoPointND[] points) { super(cons); this.points = points; // 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 } @Override public GeoClass getGeoClassType() { return GeoClass.POLYLINE; } /** * return number for points * * @return number for points */ @Override public int getNumPoints() { return points.length; } /** * 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, getLength()); } @Override public GeoElement copyInternal(Construction cons1) { GeoPolyLine ret = new GeoPolyLine(cons1); ret.points = GeoElement.copyPoints(cons1, points); ret.set(this); return ret; } @Override public void set(GeoElementND geo) { GeoPolyLine poly = (GeoPolyLine) geo; length = poly.length; defined = poly.defined; // make sure both arrays have same size if (points.length != poly.points.length) { GeoPointND[] tempPoints = new GeoPointND[poly.points.length]; for (int i = 0; i < tempPoints.length; i++) { tempPoints[i] = i < points.length ? points[i] : new GeoPoint(cons); } points = tempPoints; } for (int i = 0; i < points.length; i++) { ((GeoPoint) points[i]).set(poly.points[i]); } } @Override public boolean isFillable() { return false; } /* * overwrite methods */ @Override public boolean isDefined() { return defined; } /** * Make the polyline defined */ public void setDefined() { defined = true; } @Override public void setUndefined() { defined = false; } @Override public final boolean showInAlgebraView() { // return defined; return true; } // Michael Borcherds 2008-04-30 /** * Yields true if the points of this polyline is equal to the points of * polyline p. */ @Override final public boolean isEqual(GeoElementND geo) { boolean pointsOK = false; if (geo.isGeoPolyLine()) { GeoPolyLine g = (GeoPolyLine) geo; int numPoints = this.getNumPoints(); if (numPoints == g.getNumPoints()) { pointsOK = true; for (int i = 0; i < numPoints && pointsOK; i++) { pointsOK = this.getPointND(i).isEqual(g.getPointND(i)) || this .getPointND(i) .isEqual(g.getPointND(numPoints - 1 - i)); } } } return pointsOK; } @Override public String toString(StringTemplate tpl) { sbToString.setLength(0); sbToString.append(label); sbToString.append(" = "); sbToString.append(kernel.format(getLength(), tpl)); return sbToString.toString(); } private StringBuilder sbToString = new StringBuilder(50); @Override final public String toValueString(StringTemplate tpl) { return kernel.format(getLength(), tpl); } /** * interface NumberValue */ @Override public MyDouble getNumber() { return new MyDouble(kernel, getLength()); } /** * @return length of the polyline */ final public double getLength() { return length; } @Override final public double getDouble() { return getLength(); } @Override protected boolean showInEuclidianView() { return defined; } @Override public boolean isNumberValue() { return true; } @Override public boolean isGeoPolygon() { return false; } @Override public boolean isGeoPolyLine() { return true; } /* * Path interface implementation */ @Override public boolean isPath() { return true; } @Override public PathMover createPathMover() { return new PathMoverGeneric(this); } @Override public double getMaxParameter() { return points.length - 1; } @Override public double getMinParameter() { return 0; } @Override public boolean isClosedPath() { if (!isDefined()) { return false; } return points[0] == points[points.length - 1]; } // dummy segment to use in calculations private GeoSegment seg = new GeoSegment(cons); @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 < points.length - 1; i++) { setSegmentPoints((GeoPoint) points[i], (GeoPoint) points[i + 1]); if (seg.isOnPath(P, 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; } GeoPoint P = (GeoPoint) PI; // parameter is between 0 and points.length - 1, // i.e. floor(parameter) gives the point index int index; PathParameter pp = P.getPathParameter(); if (pp.t == points.length - 1) { // at very end of path index = points.length - 2; } else { pp.t = pp.t % (points.length - 1); if (pp.t < 0) { pp.t += (points.length - 1); } index = (int) Math.floor(pp.t); } setSegmentPoints((GeoPoint) points[index], (GeoPoint) points[index + 1]); double segParameter = pp.t - index; // calc point for given parameter P.x = seg.getPointX(segParameter); P.y = seg.getPointY(segParameter); P.z = 1.0; } @Override public void pointChanged(GeoPointND PI) { if (PI.isGeoElement3D()) { Log.warn("3D point on 2D line"); return; } GeoPoint P = (GeoPoint) PI; double qx = P.x / P.z; double qy = P.y / P.z; double minDist = Double.POSITIVE_INFINITY; double resx = 0, resy = 0, resz = 0, param = 0; // find closest point on each segment PathParameter pp = P.getPathParameter(); for (int i = 0; i < points.length - 1; i++) { P.x = qx; P.y = qy; P.z = 1; setSegmentPoints((GeoPoint) points[i], (GeoPoint) points[i + 1]); seg.pointChanged(P); double x = P.x / P.z - qx; double y = P.y / P.z - qy; double dist = x * x + y * y; if (dist < minDist) { minDist = dist; // remember closest point resx = P.x; resy = P.y; resz = P.z; param = i + pp.t; } } P.x = resx; P.y = resy; P.z = resz; pp.t = param; } private void setSegmentPoints(GeoPoint geoPoint, GeoPoint geoPoint2) { seg.setStartPoint(geoPoint); seg.setEndPoint(geoPoint2); GeoVec3D.lineThroughPoints(geoPoint, geoPoint2, seg); seg.calcLength(); } /** * 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); getAuxiliaryXML(sb); getBreakpointXML(sb); getScriptTags(sb); } @Override public final GeoPointND[] getPoints() { return points; } @Override public GeoPointND[] getPointsND() { return points; } /** * Returns i-th vertex * * @param i * index * @return i-th vertex */ public GeoPointND getPointND(int i) { return points[i]; } /** * Recompute length of this polyline */ public void calcLength() { if (points == null || points.length == 0) { setUndefined(); length = Double.NaN; return; } length = 0; for (int i = 0; i < points.length - 1; i++) { if (!((GeoPoint) points[i]).isDefined() || !((GeoPoint) points[i + 1]).isDefined()) { // (?,?) makes a hole in the polyline continue; } setSegmentPoints((GeoPoint) points[i], (GeoPoint) points[i + 1]); length += seg.getLength(); } setDefined(); } /** * Set vertices of the polyline * * @param points * new vertices */ public void setPoints(GeoPointND[] points) { this.points = points; } @Override public void rotate(NumberValue r) { for (int i = 0; i < points.length; i++) { ((GeoPoint) points[i]).rotate(r); } } @Override public void rotate(NumberValue r, GeoPointND S) { Coords sCoords = S.getInhomCoords(); for (int i = 0; i < points.length; i++) { ((GeoPoint) points[i]).rotate(r, sCoords); } } @Override public void matrixTransform(double a00, double a01, double a10, double a11) { for (int i = 0; i < points.length; i++) { ((GeoPoint) points[i]).matrixTransform(a00, a01, a10, a11); } calcLength(); } @Override public void translate(Coords v) { for (int i = 0; i < points.length; i++) { points[i].translate(v); } } @Override public void dilate(NumberValue r, Coords S) { for (int i = 0; i < points.length; i++) { points[i].dilate(r, S); } calcLength(); } @Override public void mirror(Coords Q) { for (int i = 0; i < points.length; i++) { points[i].mirror(Q); } } @Override public void mirror(GeoLineND g) { for (int i = 0; i < points.length; i++) { points[i].mirror(g); } } @Override public boolean isAllVertexLabelsSet() { for (int i = 0; i < points.length; i++) { if (!points[i].isLabelSet()) { return false; } } return true; } @Override public boolean isVertexCountFixed() { if (getParentAlgorithm().getInput().length < 3) { return false; } return true; } 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 boolean hasLineOpacity() { return true; } @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 < points.length; i++) { ((GeoPoint) points[i]).matrixTransform(a00, a01, a02, a10, a11, a12, a20, a21, a22); } calcLength(); } @Override public GeoPoint getPoint(int i) { return (GeoPoint) points[i]; } @Override public void toGeoCurveCartesian(GeoCurveCartesianND curve) { if (!isDefined()) { curve.setUndefined(); return; } curve.setFromPolyLine(points, false); } @Override public Path getBoundary() { return this; } @Override final public HitType getLastHitType() { return HitType.ON_BOUNDARY; } @Override public ValueType getValueType() { return ValueType.NUMBER; } @Override public boolean hasDrawable3D() { return true; } }