/* 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 java.util.List; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.MyPoint; import org.geogebra.common.kernel.SegmentType; import org.geogebra.common.kernel.StringTemplate; 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.GeoLocusStroke; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.main.Feature; import org.geogebra.common.util.StringUtil; /** * Creates a PolyLine from a given list of points or point array. * * @author Michael Borcherds */ public class AlgoLocusStroke extends AlgoElement implements AlgoStrokeInterface { protected GeoLocusStroke poly; // output // list of all points (also newly calculated control points of // bezier curve) private ArrayList<MyPoint> pointList = new ArrayList<MyPoint>(); /** * @param cons * the construction * @param points * vertices of the polygon */ public AlgoLocusStroke(Construction cons, GeoPointND[] points) { super(cons); poly = new GeoLocusStroke(this.cons); updatePointArray(points); // poly = new GeoPolygon(cons, points); // updatePointArray already covered compute input = new GeoElement[1]; // for (int i = 0; i < points.length; i++) { // input[i] = (GeoElement) points[i]; // } input[0] = new GeoBoolean(cons, true); // dummy to // force // PolyLine[..., // true] setInputOutput(); // for AlgoElement } @Override public Commands getClassName() { return Commands.PolyLine; } @Override public int getRelatedModeID() { return EuclidianConstants.MODE_POLYLINE; } /** * @return - true, if poly is pen stroke */ public boolean getIsPenStroke() { return true; } // data has to have at least 2 defined points after each other private static boolean canBeBezierCurve(GeoPointND[] data) { boolean firstDefFound = false; for (int i=0;i<data.length;i++) { if (data[i].isDefined()) { if (firstDefFound) { return true; } firstDefFound = true; } else { firstDefFound = false; } } return false; } /** * Update point array of polygon using the given array list * * @param pointList */ public void updatePointArray(GeoPointND[] data) { // check if we have a point list // create new points array int size = data.length; poly.setDefined(true); poly.getPoints().clear(); // to use bezier curve we need at least 2 points // stroke is: (A),(?),(A),(B) -> size 4 if (canBeBezierCurve(data) && poly.getKernel().getApplication() .has(Feature.PEN_SMOOTHING)) { int index = 0; pointList.clear(); if (data[0].isDefined()) { // move at first point pointList.add( new MyPoint(data[0].getInhomX(), data[0].getInhomY(), SegmentType.MOVE_TO)); } // Log.debug("1: (" + data[0].getInhomX() + "," + // data[0].getInhomY() // + ") -> " + // SegmentType.MOVE_TO); while (index <= data.length) { if (!pointList.isEmpty() && pointList.get(pointList.size() - 1).isDefined()) { pointList.add( new MyPoint(Double.NaN, Double.NaN, SegmentType.LINE_TO)); } GeoPointND[] partOfStroke = getPartOfPenStroke(index, data); // if we found single point // just add it to the list without control points if (partOfStroke.length == 1) { pointList.add(new MyPoint(partOfStroke[0].getInhomX(), partOfStroke[0].getInhomY(), SegmentType.MOVE_TO)); } else if (partOfStroke.length > 1) { ArrayList<double[]> controlPoints = getControlPoints( partOfStroke); for (int i = 0; i < partOfStroke.length - 1; i++) { // start point of segment pointList.add(new MyPoint(partOfStroke[i].getInhomX(), partOfStroke[i].getInhomY(), i == 0 ? SegmentType.MOVE_TO : SegmentType.CURVE_TO)); // first control point pointList.add(new MyPoint(controlPoints.get(0)[i], controlPoints.get(1)[i], SegmentType.CONTROL)); // second control point pointList.add(new MyPoint(controlPoints.get(2)[i], controlPoints.get(3)[i], SegmentType.CONTROL)); } // end point of curve pointList.add(new MyPoint( partOfStroke[partOfStroke.length - 1].getInhomX(), partOfStroke[partOfStroke.length - 1].getInhomY(), SegmentType.CURVE_TO)); } if (partOfStroke.length == 3 && Kernel.isZero(partOfStroke[partOfStroke.length-1].distance(partOfStroke[partOfStroke.length-2]))) { index = index + partOfStroke.length; } else { index = index + partOfStroke.length + 1; } } poly.setPoints(pointList); } else { for (int i = 0; i < size; i++) { poly.getPoints().add(new MyPoint(data[i].getInhomX(), data[i].getInhomY(), i == 0 ? SegmentType.MOVE_TO : SegmentType.LINE_TO)); } } } // returns the part of array started at index until first undef point private static GeoPointND[] getPartOfPenStroke(int index, GeoPointND[] data) { int size = 0; for (int i=index;i<data.length;i++) { if (data[i].isDefined()) { size++; } else { break; } } GeoPointND[] partOfStroke; // for simple segment add endpoint once again // trick needed for bezier curve if (size == 2) { partOfStroke = new GeoPointND[size + 1]; for (int i = 0; i < size; i++) { partOfStroke[i] = data[i + index]; } partOfStroke[size] = data[size + index - 1]; } else { partOfStroke = new GeoPointND[size]; for (int i = 0; i < size; i++) { partOfStroke[i] = data[i + index]; } } return partOfStroke; } // calculate control points for bezier curve private static ArrayList<double[]> getControlPoints(GeoPointND[] data) { ArrayList<double[]> values = new ArrayList<double[]>(); if (data.length == 0) { return values; } double[] xCoordsP1 = new double[data.length - 1]; double[] xCoordsP2 = new double[data.length - 1]; double[] yCoordsP1 = new double[data.length - 1]; double[] yCoordsP2 = new double[data.length - 1]; double[] a = new double[data.length - 1]; double[] b = new double[data.length - 1]; double[] c = new double[data.length - 1]; double[] rX = new double[data.length - 1]; double[] rY = new double[data.length - 1]; int n = data.length - 1; /* left most segment */ a[0] = 0; b[0] = 2; c[0] = 1; rX[0] = data[0].getInhomX() + 2 * data[1].getInhomX(); rY[0] = data[0].getInhomY() + 2 * data[1].getInhomY(); /* internal segments */ for (int i = 1; i < n - 1; i++) { a[i] = 1; b[i] = 4; c[i] = 1; rX[i] = 4 * data[i].getInhomX() + 2 * data[i + 1].getInhomX(); rY[i] = 4 * data[i].getInhomY() + 2 * data[i + 1].getInhomY(); } /* right segment */ a[n - 1] = 2; b[n - 1] = 7; c[n - 1] = 0; rX[n - 1] = 8 * data[n - 1].getInhomX() + data[n].getInhomX(); rY[n - 1] = 8 * data[n - 1].getInhomY() + data[n].getInhomY(); /* solves Ax=b with the Thomas algorithm (from Wikipedia) */ for (int i = 1; i < n; i++) { double m = a[i] / b[i - 1]; b[i] = b[i] - m * c[i - 1]; rX[i] = rX[i] - m * rX[i - 1]; rY[i] = rY[i] - m * rY[i - 1]; } xCoordsP1[n - 1] = rX[n - 1] / b[n - 1]; yCoordsP1[n - 1] = rY[n - 1] / b[n - 1]; for (int i = n - 2; i >= 0; --i) { xCoordsP1[i] = (rX[i] - c[i] * xCoordsP1[i + 1]) / b[i]; yCoordsP1[i] = (rY[i] - c[i] * yCoordsP1[i + 1]) / b[i]; } /* we have p1, now compute p2 */ for (int i = 0; i < n - 1; i++) { xCoordsP2[i] = 2 * data[i + 1].getInhomX() - xCoordsP1[i + 1]; yCoordsP2[i] = 2 * data[i + 1].getInhomY() - yCoordsP1[i + 1]; } xCoordsP2[n - 1] = 0.5 * (data[n].getInhomX() + xCoordsP1[n - 1]); yCoordsP2[n - 1] = 0.5 * (data[n].getInhomY() + yCoordsP1[n - 1]); values.add(xCoordsP1); values.add(yCoordsP1); values.add(xCoordsP2); values.add(yCoordsP2); return values; } // for AlgoElement @Override protected void setInputOutput() { // set dependencies for (int i = 0; i < input.length; i++) { input[i].addAlgorithm(this); } // set output setOutputLength(1); setOutput(0, poly); setDependencies(); } @Override public void update() { // compute output from input compute(); getOutput(0).update(); } @Override public void compute() { // poly.getPoints().clear(); // for (int i = 0; i < input.length - 1; i++) { // GeoPoint pt = (GeoPoint) input[i]; // poly.getPoints().add(new MyPoint(pt.getInhomX(), pt.getInhomY(), // i == 0 ? SegmentType.MOVE_TO : SegmentType.LINE_TO)); // } } @Override final public String toString(StringTemplate tpl) { return ""; } public final MyPoint[] getPointsND() { return poly.getPointsND(); } @Override public int getPointsLength() { return poly.getPointLength(); } @Override public GeoPoint getPointCopy(int i) { return new GeoPoint(cons, poly.getPoints().get(i).getInhomX(), poly.getPoints().get(i).getInhomY(), 1); } /** * @return list of points without the control points */ public ArrayList<MyPoint> getPointsWithoutControl() { return poly.getPointsWithoutControl(); } /** * @return full list of definition points */ public ArrayList<MyPoint> getPoints() { return poly.getPoints(); } public void updateFrom(List<MyPoint> data) { if (poly.getPoints() != data) { poly.setDefined(true); poly.getPoints().clear(); for (MyPoint pt : data) { poly.getPoints().add(pt); } } poly.resetPointsWithoutControl(); getOutput(0).update(); } /** * Expressions should be shown as out = expression e.g. * <expression label="u" exp="a + 7 b"/> * * @param tpl * string template * @return expression XML tag */ @Override protected String getExpXML(StringTemplate tpl) { StringBuilder sb = new StringBuilder(); sb.append("<expression"); // add label if (/* output != null && */getOutputLength() == 1) { if (getOutput(0).isLabelSet()) { sb.append(" label=\""); StringUtil.encodeXML(sb, getOutput(0).getLabel(tpl)); sb.append("\""); } } // add expression sb.append(" exp=\"PolyLine["); appendPoints(sb, tpl); sb.append("]\""); // expression sb.append(" />\n"); return sb.toString(); } private void appendPoints(StringBuilder sb, StringTemplate tpl) { ArrayList<MyPoint> pts = this.getPointsWithoutControl(); for (MyPoint m : pts) { sb.append("("); sb.append(kernel.format(m.getX(), tpl)); sb.append(","); sb.append(kernel.format(m.getY(), tpl)); sb.append("), "); } sb.append("true"); } @Override protected boolean hasExpXML(String cmd) { return true; } @Override public String getDefinition(StringTemplate tpl) { String def = "PolyLine"; // #2706 if (input == null) { return null; } sbAE.setLength(0); if (tpl.isPrintLocalizedCommandNames()) { sbAE.append(getLoc().getCommand(def)); } else { sbAE.append(def); } sbAE.append(tpl.leftSquareBracket()); // input legth is 0 for ConstructionStep[] appendPoints(sbAE, tpl); sbAE.append(tpl.rightSquareBracket()); return sbAE.toString(); } }