package org.geogebra.common.euclidian; import java.util.ArrayList; import java.util.Iterator; import org.apache.commons.math3.linear.Array2DRowRealMatrix; import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.linear.RealVector; import org.apache.commons.math3.linear.SingularValueDecomposition; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GGeneralPath; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPoint; import org.geogebra.common.euclidian.event.AbstractEvent; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.MyPoint; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.algos.AlgoAttachCopyToView; import org.geogebra.common.kernel.algos.AlgoCircleThreePoints; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.AlgoFocus; import org.geogebra.common.kernel.algos.AlgoFunctionFreehand; import org.geogebra.common.kernel.algos.AlgoJoinPointsSegment; import org.geogebra.common.kernel.algos.AlgoLocusStroke; import org.geogebra.common.kernel.algos.AlgoPolygon; import org.geogebra.common.kernel.algos.AlgoStrokeInterface; import org.geogebra.common.kernel.arithmetic.Equation; import org.geogebra.common.kernel.arithmetic.ExpressionNode; import org.geogebra.common.kernel.arithmetic.FunctionVariable; import org.geogebra.common.kernel.geos.GeoConic; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolyLine; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.geos.GeoSegment; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoSegmentND; import org.geogebra.common.kernel.statistics.AlgoFitImplicit; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.plugin.EuclidianStyleConstants; import org.geogebra.common.plugin.Operation; import org.geogebra.common.util.GTimer; import org.geogebra.common.util.GTimerListener; import org.geogebra.common.util.debug.Log; /** * Handles pen and freehand tool * */ public class EuclidianPen implements GTimerListener { /** * app */ protected App app; /** * view */ protected EuclidianView view; /** * minimum determinant for circles decrease to allow less "round" circles */ public double CIRCLE_MIN_DET = 0.95; /** * increase to allow uglier circles */ public double CIRCLE_MAX_SCORE = 0.10; /** * maximum deviation between the segment lengths increase to allow less * beautiful rectangles */ public double RECTANGLE_LINEAR_TOLERANCE = 0.20; /** * maximum deviation between the segment lengths increase to allow less * beautiful polygons */ public double POLYGON_LINEAR_TOLERANCE = 0.20; /** * maximum deviation between the angles of a rectangle increase to allow * less beautiful rectangles */ public double RECTANGLE_ANGLE_TOLERANCE = 15 * Math.PI / 180; /** * maximum determinant for lines (e.g. sides of a polygon) decrease to allow * lines that are not so straight */ public double LINE_MAX_DET = 0.015; /** Polyline that conects stylebar to pen settings */ public final GeoPolyLine DEFAULT_PEN_LINE; private AlgoElement lastAlgo = null; /** points created by pen */ protected ArrayList<GPoint> penPoints = new ArrayList<GPoint>(); private ArrayList<GPoint> temp = null; private int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; private double score = 0; // segment private int brk[]; private int recognizer_queue_length = 0; private static final int MAX_POLYGON_SIDES = 4; private static final double SLANT_TOLERANCE = 5 * Math.PI / 180; private Inertia a = null; private Inertia b = null; private Inertia c = null; private Inertia d = null; private RecoSegment reco_queue_a = new RecoSegment(); private RecoSegment reco_queue_b = new RecoSegment(); private RecoSegment reco_queue_c = new RecoSegment(); private RecoSegment reco_queue_d = new RecoSegment(); private RecoSegment reco_queue_e = new RecoSegment(); private final static int PEN_SIZE_FACTOR = 2; private static final double CONIC_AXIS_ERROR_RATIO = 10; private boolean startNewStroke = false; private int penSize; private int lineOpacity; private int lineThickness; private GColor lineDrawingColor; private int lineDrawingStyle; // true if we need repaint private boolean needsRepaint; /** * start point of the gesture */ protected GeoPoint initialPoint = null; /** * delete initialPoint if no shape is found */ protected boolean deleteInitialPoint = false; private boolean absoluteScreenPosition; private GTimer timer = null; private int eraserSize; private int penLineStyle; private GColor penColor; // being used for Freehand Function tool private boolean freehand = false; // being used for Freehand Shape tool (not done yet) // private boolean recognizeShapes = false; /************************************************ * Construct EuclidianPen * * @param app * application * @param view * view */ public EuclidianPen(App app, EuclidianView view) { this.view = view; this.app = app; timer = app.newTimer(this, 1500); setDefaults(); DEFAULT_PEN_LINE = new GeoPolyLine(app.getKernel().getConstruction()) { @Override public void setObjColor(GColor color) { super.setObjColor(color); setPenColor(color); } @Override public void setLineThickness(int th) { super.setLineThickness(th); setPenSize(th); } @Override public void setLineType(int i) { super.setLineType(i); setPenLineStyle(i); } @Override public void setLineOpacity(int lineOpacity) { super.setLineOpacity(lineOpacity); setPenOpacity(lineOpacity); } }; DEFAULT_PEN_LINE.setLineThickness(penSize); DEFAULT_PEN_LINE.setLineOpacity(lineOpacity); DEFAULT_PEN_LINE.setObjColor(penColor); } // =========================================== // Getters/Setters // =========================================== /** * Set default pen color, line style, thickness, eraser size */ public void setDefaults() { penSize = 3; eraserSize = EuclidianConstants.DEFAULT_ERASER_SIZE; penLineStyle = EuclidianStyleConstants.LINE_TYPE_FULL; penColor = GColor.BLACK; lineOpacity = 10; setAbsoluteScreenPosition(false); } /** * @return pen size */ public int getPenSize() { return penSize; } /** * @param lineOpacity * Opacity */ public void setPenOpacity(int lineOpacity) { if (this.lineOpacity != lineOpacity) { startNewStroke = true; } this.lineOpacity = lineOpacity; setPenColor(penColor.deriveWithAlpha(lineOpacity)); } /** * @return pen size + 1 */ public int getLineThickness() { return lineThickness + 1; } /** * @param penSize * pen size */ public void setPenSize(int penSize) { if (this.penSize != penSize) { startNewStroke = true; } this.penSize = penSize; lineThickness = penSize; } /** * @return pen line style */ public int getPenLineStyle() { return penLineStyle; } /** * @param penLineStyle * pen line style */ public void setPenLineStyle(int penLineStyle) { if (this.penLineStyle != penLineStyle) { startNewStroke = true; } this.penLineStyle = penLineStyle; lineDrawingStyle = penLineStyle; } /** * @return pen color */ public GColor getPenColor() { return penColor; } /** * @return true if we need to repaint the preview line */ public boolean needsRepaint() { return needsRepaint; } /** * use one point as first point of the created shape * * @param point * start point * @param deletePoint * delete the point if no shape is found */ public void setInitialPoint(GeoPoint point, boolean deletePoint) { this.initialPoint = point; this.deleteInitialPoint = deletePoint; } /** * * @param e * event * @return Is this MouseEvent an erasing Event. */ public boolean isErasingEvent(AbstractEvent e) { return app.isRightClick(e) && !freehand; } /** * Update the info about last geo so that we can continue a polyline * * @param penGeo * last object created with pen */ public void setPenGeo(GeoElement penGeo) { if (penGeo == null) { lastAlgo = null; } else if (penGeo.getParentAlgorithm() instanceof AlgoStrokeInterface) { lastAlgo = penGeo.getParentAlgorithm(); } } /** * Make sure we start using a new polyline */ public void resetPenOffsets() { lastAlgo = null; } // =========================================== // Mouse Event Handlers // =========================================== /** * Mouse dragged while in pen mode, decide whether erasing or new points. * * @param e * mouse event */ public void handleMouseDraggedForPenMode(AbstractEvent e) { view.setCursor(EuclidianCursor.TRANSPARENT); if (isErasingEvent(e)) { view.getEuclidianController().getDeleteMode() .handleMouseDraggedForDelete(e, eraserSize, true); app.getKernel().notifyRepaint(); } else { // drawing in progress, so we need repaint needsRepaint = true; addPointPenMode(e, null); } } /** * @param e * event * @param hits * hits */ public void handleMousePressedForPenMode(AbstractEvent e, Hits hits) { if (!isErasingEvent(e)) { timer.stop(); penPoints.clear(); addPointPenMode(e, hits); // we need single point only for pen tool // prevent creating points with freehand tool if (!freehand) { // will create the single point for pen tool addPointsToPolyLine(penPoints); } } } /** * Method to repaint the whole preview line * * @param g2D * graphics for pen */ public void doRepaintPreviewLine(GGraphics2D g2D) { if (penPoints.size() < 2) { return; } GGeneralPath gp = AwtFactory.getPrototype().newGeneralPath(); g2D.setStroke(EuclidianStatic.getStroke(getLineThickness(), lineDrawingStyle)); g2D.setColor(lineDrawingColor); gp.moveTo(penPoints.get(0).x, penPoints.get(0).y); for (int i = 1; i < penPoints.size() - 1; i++) { gp.lineTo(penPoints.get(i).x, penPoints.get(i).y); } g2D.draw(gp); } /** * Method to repaint the whole preview line from (x, y) with a given width. * * @param g2D * graphics for pen * @param color * of the pen preview * @param thickness * of the pen preview * @param x * Start x coordinate * @param y * Start y coordinate * @param width * of the preview */ public void drawStylePreview(GGraphics2D g2D, GColor color, int thickness, int x, int y, int width) { GGeneralPath gp = AwtFactory.getPrototype().newGeneralPath(); g2D.setStroke(EuclidianStatic.getStroke(thickness, EuclidianStyleConstants.LINE_TYPE_FULL)); g2D.setColor(color); gp.reset(); gp.moveTo(x, y); gp.lineTo(x + width, y); g2D.draw(gp); } /** * add the saved points to the last stroke or create a new one * * @param e * event * @param h * hits */ public void addPointPenMode(AbstractEvent e, Hits h) { // if a PolyLine is selected, we can append to it. ArrayList<GeoElement> selGeos = app.getSelectionManager() .getSelectedGeos(); if (selGeos.size() == 1 && selGeos.get(0) instanceof GeoPolyLine) { lastAlgo = selGeos.get(0).getParentAlgorithm(); } view.setCursor(EuclidianCursor.TRANSPARENT); // if (g2D == null) g2D = penImage.createGraphics(); GPoint newPoint = new GPoint(e.getX(), e.getY()); if (minX > e.getX()) { minX = e.getX(); } if (maxX < e.getX()) { maxX = e.getX(); } if (penPoints.size() == 0) { if (initialPoint != null) { // also add the coordinates of the initialPoint to the penPoints Coords coords = initialPoint.getCoords(); // calculate the screen coordinates int locationX = (int) (view.getXZero() + (coords.getX() / view.getInvXscale())); int locationY = (int) (view.getYZero() - (coords.getY() / view.getInvYscale())); GPoint p = new GPoint(locationX, locationY); penPoints.add(p); needsRepaint = true; view.repaintView(); // draw a line between the initalPoint and the first point // drawPenPreviewLine(g2D, newPoint, p); } penPoints.add(newPoint); } else { GPoint lastPoint = penPoints.get(penPoints.size() - 1); // drawPenPreviewLine(g2D, newPoint, lastPoint); if (lastPoint.distance(newPoint) > 3) { needsRepaint = true; penPoints.add(newPoint); view.repaintView(); } } } // private void drawPenPreviewLine(GGraphics2D g2D, GPoint point1, // GPoint point2) { // GLine2D line = AwtFactory.getPrototype().newLine2D(); // line.setLine(point1.getX(), point1.getY(), point2.getX(), // point2.getY()); // g2D.setStroke(EuclidianStatic.getStroke(getLineThickness(), // lineDrawingStyle)); // g2D.setColor(lineDrawingColor); // g2D.fill(line); // g2D.draw(line); // } /** * Clean up the pen mode stuff, add points. * * @param right * true for right click * @param x * x-coord * @param y * y-coord * * @return true if a GeoElement was created * */ public boolean handleMouseReleasedForPenMode(boolean right, int x, int y) { if (right && !freehand) { return false; } if (freehand) { boolean shapeCreated = mouseReleasedFreehand(x, y); penPoints.clear(); app.refreshViews(); // clear trace minX = Integer.MAX_VALUE; maxX = Integer.MIN_VALUE; return shapeCreated; } timer.start(); app.setDefaultCursor(); // if (!erasing && recognizeShapes) { // checkShapes(e); // } // if (lastPenImage != null) penImage = lastPenImage.getImage(); // //app.getExternalImage(lastPenImage); // Application.debug(penPoints.size()+""); addPointsToPolyLine(penPoints); penPoints.clear(); // drawing done, so no need for repaint needsRepaint = false; return true; } /** * start timer to check if polyline is same stroke */ public void startTimer() { timer.start(); } /** * @param x * x-coord of new point * @param y * y-coord of new point * @return geo that fits current points + new point */ protected GeoElement checkShapes(int x, int y) { initShapeRecognition(x, y); // AbstractApplication.debug(penPoints); // if recognize_shape option is checked GeoElement geo; if ((geo = tryPolygonOrLine()) != null || (geo = tryCircle()) != null) { if (geo.isGeoConic()) { geo.setIsShape(true); } return geo; } resetInitialPoint(); return makeAConic(); // might return null } /** * @param x * initial x * @param y * initial y */ protected void initShapeRecognition(int x, int y) { penPoints.add(new GPoint(x, y)); } private GeoElement tryCircle() { GeoConic circle = getCircleThreePoints(); if (circle != null) { circle.setIsShape(true); if (app.isWhiteboardActive()) { return circle; } // midpoint GeoPoint m = new GeoPoint(app.getKernel().getConstruction(), null, circle.getMidpoint().getX(), circle.getMidpoint().getY(), 1.0); m.setEuclidianVisible(false); // point on the circle GeoPoint p = circle.getPointsOnConic(1).get(0); p.setLabel(null); p.setEuclidianVisible(false); // delete the circle that was created in makeACircle circle.remove(); // create a new circle with midpoint and point return app.getKernel().getAlgoDispatcher().Circle(null, m, p); } return null; } /** * @return circle through three points */ protected GeoConic getCircleThreePoints() { Inertia s = new Inertia(); this.calc_inertia(0, penPoints.size() - 1, s); if (EuclidianPen.I_det(s) > CIRCLE_MIN_DET) { score = this.score_circle(0, penPoints.size() - 1, s); if (score < CIRCLE_MAX_SCORE) { return this.makeACircle(EuclidianPen.center_x(s), EuclidianPen.center_y(s), EuclidianPen.I_rad(s)); } } return null; } /** * Reset the first point */ protected void resetInitialPoint() { if (this.deleteInitialPoint && this.initialPoint != null) { this.initialPoint.remove(); } this.initialPoint = null; } /** * @return {@link GeoElement} if polygon or line could be created, * {@code null} otherwise */ private GeoElement tryPolygonOrLine() { int n = getPolygonal(); if (n <= 0) { return null; } else if (n == 1) { return tryLine(); } return tryPolygon(n); } /** * @param n * number of vertices * @return polygon */ protected GeoElement tryPolygon(int n) { int j; RecoSegment temp1; optimize_polygonal(n); while (n + recognizer_queue_length > MAX_POLYGON_SIDES) { j = 1; temp1 = reco_queue_b; while (j < recognizer_queue_length && temp1.startpt != 0) { j++; if (j == 2) { temp1 = reco_queue_c; } if (j == 3) { temp1 = reco_queue_d; } if (j == 4) { temp1 = reco_queue_e; } } recognizer_queue_length = recognizer_queue_length - j; int te1 = 0; int te2 = j; RecoSegment t1; RecoSegment t2; for (int k = 0; k < recognizer_queue_length; ++k) { t1 = getRecoSegment(te1); t2 = getRecoSegment(te2); t1.startpt = t2.startpt; t1.endpt = t2.endpt; t1.xcenter = t2.xcenter; t1.ycenter = t2.ycenter; t1.angle = t2.angle; t1.radius = t2.radius; t1.x1 = t2.x1; t1.x2 = t2.x2; t1.y1 = t2.y2; t1.y2 = t2.y2; t1.reversed = t2.reversed; te1++; te2++; } } RecoSegment rs; Inertia ss = null; int temp_reco = recognizer_queue_length; recognizer_queue_length = recognizer_queue_length + n; for (j = 0; j < n; ++j) { rs = getRecoSegment(temp_reco + j); if (j == 0) { ss = a; } else if (j == 1) { ss = b; } else if (j == 2) { ss = c; } else if (j == 3) { ss = d; } rs.startpt = brk[j]; rs.endpt = brk[j + 1]; get_segment_geometry(ss, rs); } GeoElement geo; if ((geo = try_rectangle()) != null || (geo = try_closed_polygon(3)) != null || (geo = try_closed_polygon(4)) != null) { recognizer_queue_length = 0; return geo; } return null; } /** * @return {@link GeoElement} if polygon or line could be created, * {@code null} otherwise */ private GeoElement tryLine() { RecoSegment rs = getRecoSegment(0); rs.startpt = brk[0]; rs.endpt = brk[1]; get_segment_geometry(a, rs); if (Math.abs(rs.angle) < SLANT_TOLERANCE) { rs.angle = 0; rs.y1 = rs.y2 = rs.ycenter; } if (Math.abs(rs.angle) > Math.PI / 2 - SLANT_TOLERANCE) { rs.angle = (rs.angle > 0) ? (Math.PI / 2) : (-Math.PI / 2); rs.x1 = rs.x2 = rs.xcenter; } double x_first = view.toRealWorldCoordX(rs.x1); double y_first = view.toRealWorldCoordY(rs.y1); double x_last = view.toRealWorldCoordX(rs.x2); double y_last = view.toRealWorldCoordY(rs.y2); GeoPoint p; if (this.initialPoint != null) { p = initialPoint; } else { p = new GeoPoint(app.getKernel().getConstruction(), null, x_first, y_first, 1.0); } GeoPoint q = new GeoPoint(app.getKernel().getConstruction(), null, x_last, y_last, 1.0); return getJoinPointsSegment(p, q); } /** * @return */ protected int getPolygonal() { brk = new int[5]; a = new Inertia(); b = new Inertia(); c = new Inertia(); d = new Inertia(); // AbstractApplication.debug(penPoints); return this.findPolygonal(0, penPoints.size() - 1, MAX_POLYGON_SIDES, 0, 0); } /** * @param points * - list of points without control points * @param i * - index * @return copy of i-th no control point of poly */ public GeoPoint getNoControlPointCopy(ArrayList<MyPoint> points, int i) { return new GeoPoint( app.getKernel().getConstruction(), points.get(i).getInhomX(), points.get(i).getInhomY(), 1); } private void addPointsToPolyLine(ArrayList<GPoint> penPoints2) { Construction cons = app.getKernel().getConstruction(); // GeoList newPts;// = new GeoList(cons); GeoPoint[] newPts;// = new GeoList(cons); int offset; if (startNewStroke) { lastAlgo = null; startNewStroke = false; } if (lastAlgo == null) { // lastPolyLine = new GeoPolyLine(cons, "hello"); newPts = new GeoPoint[penPoints2.size()]; // newPts = new GeoList(cons); offset = 0; } else { // newPts = lastPolyLine.getPointsList(); // force a gap // newPts.add(new GeoPoint2(cons, Double.NaN, Double.NaN, 1)); AlgoStrokeInterface algo = getAlgoStrokeInterface(lastAlgo); int ptsLength; if (app.has(Feature.PEN_SMOOTHING) && algo instanceof AlgoLocusStroke) { ArrayList<MyPoint> pointsNoControl = ((AlgoLocusStroke) algo) .getPointsWithoutControl(); ptsLength = pointsNoControl.size(); newPts = new GeoPoint[penPoints2.size() + 1 + ptsLength]; for (int i = 0; i < ptsLength; i++) { newPts[i] = getNoControlPointCopy(pointsNoControl, i); } } else { ptsLength = algo.getPointsLength(); newPts = new GeoPoint[penPoints2.size() + 1 + ptsLength]; for (int i = 0; i < ptsLength; i++) { newPts[i] = algo.getPointCopy(i); } } newPts[ptsLength] = new GeoPoint(cons, Double.NaN, Double.NaN, 1); offset = ptsLength + 1; } Iterator<GPoint> it = penPoints2.iterator(); while (it.hasNext()) { GPoint p = it.next(); // newPts.add(new GeoPoint2(cons, view.toRealWorldCoordX(p.getX()), // view.toRealWorldCoordY(p.getY()), 1)); newPts[offset++] = new GeoPoint(cons, view.toRealWorldCoordX(p.getX()), view.toRealWorldCoordY(p.getY()), 1); } AlgoElement algo; // don't set label Kernel kernelA = app.getKernel(); AlgoElement newPolyLine = kernelA.getAlgoDispatcher() .getStrokeAlgo(newPts); if (!absoluteScreenPosition) { // set label newPolyLine.getOutput(0).setLabel(null); algo = newPolyLine; } else { EuclidianViewInterfaceCommon ev = app.getActiveEuclidianView(); GeoPoint corner1 = new GeoPoint(kernelA.getConstruction()); GeoPoint corner3 = new GeoPoint(kernelA.getConstruction()); GeoPoint screenCorner1 = new GeoPoint(kernelA.getConstruction()); GeoPoint screenCorner3 = new GeoPoint(kernelA.getConstruction()); int viewNo = 1; if (ev != null) { corner1.setCoords(ev.getXmin(), ev.getYmin(), 1); corner3.setCoords(ev.getXmax(), ev.getYmax(), 1); screenCorner1.setCoords(0, ev.getHeight(), 1); screenCorner3.setCoords(ev.getWidth(), 0, 1); viewNo = ev.getEuclidianViewNo(); } GeoNumeric evNo = new GeoNumeric(kernelA.getConstruction(), viewNo); cons.removeFromConstructionList(newPolyLine); algo = new AlgoAttachCopyToView(cons, null, newPolyLine.getOutput(0), evNo, corner1, corner3, screenCorner1, screenCorner3); } newPolyLine.getOutput(0).setTooltipMode(GeoElement.TOOLTIP_OFF); if (lastAlgo == null) { // lastPolyLine = new AlgoStrokeInterface(cons, null, newPts); } else { try { cons.replace(lastAlgo.getOutput(0), algo.getOutput(0)); // String label = lastPolyLine.getPoly().getLabelSimple(); // lastPolyLine.getPoly().remove(); // lastPolyLine.remove(); // newPolyLine.getPoly().setLabel(label); } catch (Exception e) { e.printStackTrace(); } // lastPolyLine.setPointsList(newPts); } lastAlgo = algo; GeoElement poly = algo.getOutput(0); poly.setLineThickness(penSize * PEN_SIZE_FACTOR); poly.setLineType(penLineStyle); poly.setLineOpacity(lineOpacity); poly.setObjColor(penColor); app.getSelectionManager().clearSelectedGeos(false); app.getSelectionManager().addSelectedGeo(poly); // app.getKernel().getAlgebraProcessor().processAlgebraCommandNoExceptionsOrErrors("AttachCopyToView["+poly.getLabelSimple()+",1]", // false); poly.setSelected(false); poly.updateRepaint(); // app.storeUndoInfo() will be called from wrapMouseReleasedND } private static AlgoStrokeInterface getAlgoStrokeInterface(AlgoElement al) { if (al instanceof AlgoStrokeInterface) { return (AlgoStrokeInterface) al; } return (AlgoStrokeInterface) al.getInput()[0].getParentAlgorithm(); } // Return true if a shape was created, false otherwise private boolean mouseReleasedFreehand(int x, int y) { int n = maxX - minX + 1; double[] freehand1 = new double[n]; GeoElement shape = checkShapes(x, y); if (shape != null && shape.isGeoLine()) { // lines take priority over functions penPoints.clear(); return true; } // now check if it can be a function (increasing or decreasing x) double monotonicTest = 0; for (int i = 0; i < penPoints.size() - 1; i++) { GPoint p1 = penPoints.get(i); GPoint p2 = penPoints.get(i + 1); if (Math.signum(p2.x - p1.x) != 1) { monotonicTest++; } } Log.debug("mono" + monotonicTest + " " + monotonicTest / penPoints.size()); monotonicTest = monotonicTest / penPoints.size(); // allow 10% error boolean monotonic = monotonicTest > 0.9 || monotonicTest < 0.1; if (!monotonic) { // may or may not have recognized a shape eg circle in checkShapes() // earlier penPoints.clear(); return shape != null; } // now definitely a function if (shape != null) { for (GeoElement geo : shape.getParentAlgorithm().getInput()) { geo.remove(); } shape.remove(); } for (int i = 0; i < n; i++) { freehand1[i] = Double.NaN; } for (int i = 0; i < penPoints.size(); i++) { GPoint p = penPoints.get(i); int index = p.x - minX; if (index >= 0 && index < freehand1.length && Double.isNaN(freehand1[index])) { freehand1[index] = view.toRealWorldCoordY(p.y); } } // fill in any gaps (eg from fast mouse movement) double val = freehand1[0]; int valIndex = 0; double nextVal = Double.NaN; int nextValIndex = -1; for (int i = 0; i < n; i++) { if (Double.isNaN(freehand1[i])) { if (i > nextValIndex) { nextValIndex = i; while (nextValIndex < n && Double.isNaN(freehand1[nextValIndex])) { nextValIndex++; } } if (nextValIndex >= n) { freehand1[i] = val; } else { nextVal = freehand1[nextValIndex]; freehand1[i] = (val * (nextValIndex - i) + nextVal * (i - valIndex)) / (nextValIndex - valIndex); } } else { val = freehand1[i]; valIndex = i; } } Construction cons = app.getKernel().getConstruction(); GeoList list = new GeoList(cons); // checkDecimalFraction() -> shorter XML list.add(new GeoNumeric(cons, Kernel.checkDecimalFraction(view.toRealWorldCoordX(minX)))); list.add(new GeoNumeric(cons, Kernel.checkDecimalFraction(view.toRealWorldCoordX(maxX)))); for (int i = 0; i < n; i++) { list.add(new GeoNumeric(cons, Kernel.checkDecimalFraction(freehand1[i]))); } // create the freehand function AlgoFunctionFreehand algo = new AlgoFunctionFreehand(cons, null, list); GeoElement fun = algo.getOutput(0); // fun.setLineThickness(penSize * PEN_SIZE_FACTOR); fun.setLineType(penLineStyle); fun.setObjColor(penColor); minX = Integer.MAX_VALUE; maxX = Integer.MIN_VALUE; return true; } /** * @param b * whether we are using freehand shape (ie shape recognition) */ public void setFreehand(boolean b) { freehand = b; } /* * ported from xournal by Neel Shah */ private int findPolygonal(int start, int end, int n, int offset1, int offset2) { Inertia s = new Inertia(); Inertia s1 = new Inertia(); Inertia s2 = new Inertia(); int k, i1 = 0, i2 = 0, n1 = 0, n2; double det1, det2; int nsides = n; // AbstractApplication.debug(start); // AbstractApplication.debug(end); if (end == start) { return 0; // no way } if (nsides <= 0) { return 0; } if (end - start < 5) { nsides = 1; // too small for a polygon } // look for a linear piece that's big enough for (k = 0; k < nsides; ++k) { i1 = start + (k * (end - start)) / nsides; // AbstractApplication.debug(i1); i2 = start + ((k + 1) * (end - start)) / nsides; // AbstractApplication.debug(i2); calc_inertia(i1, i2, s); if (EuclidianPen.I_det(s) < LINE_MAX_DET) { break; } } if (k == nsides) { return 0; } while (true) { if (i1 > start) { s1.copyValuesFrom(s); this.incr_inertia(i1 - 1, s1, 1); det1 = EuclidianPen.I_det(s1); } else { det1 = 1; } if (i2 < end) { s2.copyValuesFrom(s); this.incr_inertia(i2, s2, 1); det2 = EuclidianPen.I_det(s2); } else { det2 = 1; } if (det1 < det2 && det1 < LINE_MAX_DET) { i1--; s.copyValuesFrom(s1); } else if (det2 < det1 && det2 < LINE_MAX_DET) { i2++; s.copyValuesFrom(s2); } else { break; } } if (i1 > start) { n1 = this.findPolygonal(start, i1, (i2 == end) ? (nsides - 1) : (nsides - 2), offset1, offset2); if (n1 == 0) { return 0; } } else { n1 = 0; } brk[n1 + offset1] = i1; brk[n1 + 1 + offset1] = i2; if (offset2 + n1 == 0) { a.copyValuesFrom(s); } else if (offset2 + n1 == 1) { b.copyValuesFrom(s); } else if (offset2 + n1 == 2) { c.copyValuesFrom(s); } else if (offset2 + n1 == 3) { d.copyValuesFrom(s); } if (i2 < end) { n2 = this.findPolygonal(i2, end, nsides - n1 - 1, offset1 + n1 + 1, offset2 + n1 + 1); if (n2 == 0.) { return 0; } } else { n2 = 0; } return n1 + n2 + 1; } private void calc_inertia(int start, int end, Inertia s) { int i; int coeff = 1; int temp1[] = new int[4]; double dm = 0; s.mass = 0.; s.sx = 0.; s.sxx = 0.; s.sxy = 0.; s.sy = 0.; s.syy = 0.; temp1[0] = penPoints.get(start).x; temp1[1] = penPoints.get(start).y; temp1[2] = penPoints.get(start + 1).x; temp1[3] = penPoints.get(start + 1).y; dm = coeff * Math.hypot(temp1[2] - temp1[0], temp1[3] - temp1[1]); s.mass = s.mass + dm; s.sx = s.sx + (dm * temp1[0]); s.sxx = s.sxx + (dm * temp1[0] * temp1[0]); s.sxy = s.sxy + (dm * temp1[0] * temp1[1]); s.sy = s.sy + (dm * temp1[1]); s.syy = s.syy + (dm * temp1[1] * temp1[1]); for (i = start + 1; i < end; ++i) { temp1[0] = penPoints.get(i).x; temp1[1] = penPoints.get(i).y; temp1[2] = penPoints.get(i + 1).x; temp1[3] = penPoints.get(i + 1).y; dm = coeff * Math.hypot(temp1[2] - temp1[0], temp1[3] - temp1[1]); s.mass = s.mass + dm; s.sx = s.sx + (dm * temp1[0]); s.sxx = s.sxx + (dm * temp1[0] * temp1[0]); s.sxy = s.sxy + (dm * temp1[0] * temp1[1]); s.sy = s.sy + (dm * temp1[1]); s.syy = s.syy + (dm * temp1[1] * temp1[1]); } } private final static double I_det(Inertia s) { double ixx = I_xx(s); double iyy = I_yy(s); double ixy = I_xy(s); if (s.mass <= 0.) { return 0.; } if (ixx + iyy <= 0.) { return 0.; } return 4 * (ixx * iyy - ixy * ixy) / (ixx + iyy) / (ixx + iyy); } private static double I_xx(Inertia s) { if (s.mass <= 0.) { return 0.; } return (s.sxx - s.sx * s.sx / s.mass) / s.mass; } private static double I_xy(Inertia s) { if (s.mass <= 0.) { return 0.; } return (s.sxy - s.sx * s.sy / s.mass) / s.mass; } private static double I_yy(Inertia s) { if (s.mass <= 0.) { return 0.; } return (s.syy - s.sy * s.sy / s.mass) / s.mass; } private double score_circle(int start, int end, Inertia s) { double sum, x0, y0, r0, dm, deltar; int i; if (s.mass == 0.) { return 0; } sum = 0.; x0 = EuclidianPen.center_x(s); y0 = EuclidianPen.center_y(s); r0 = EuclidianPen.I_rad(s); for (i = start; i < end; ++i) { dm = Math.hypot(penPoints.get(i + 1).x - penPoints.get(i).x, penPoints.get(i + 1).y - penPoints.get(i).y); deltar = Math.hypot(penPoints.get(i).x - x0, penPoints.get(i).y - y0) - r0; sum = sum + (dm * Math.abs(deltar)); } return sum / (s.mass * r0); } private static double center_x(Inertia s) { return s.sx / s.mass; } private static double center_y(Inertia s) { return s.sy / s.mass; } private static double I_rad(Inertia s) { double ixx = EuclidianPen.I_xx(s); double iyy = EuclidianPen.I_yy(s); if (ixx + iyy <= 0.) { return 0.; } return Math.sqrt(ixx + iyy); } private GeoConic makeACircle(double x, double y, double r) { temp = new ArrayList<GPoint>(); int npts, i = 0; npts = (int) (2 * r); if (npts < 12) { npts = 12; } GPoint p; for (i = 0; i <= npts; i++) { p = new GPoint(); p.x = (int) (x + r * Math.cos((2 * i * Math.PI) / npts)); p.y = (int) (y + r * Math.sin((2 * i * Math.PI) / npts)); temp.add(p); } int size = temp.size(); double x1 = view.toRealWorldCoordX(temp.get(0).x); double y1 = view.toRealWorldCoordY(temp.get(0).y); double x2 = view.toRealWorldCoordX(temp.get(size / 3).x); double y2 = view.toRealWorldCoordY(temp.get(size / 3).y); double x3 = view.toRealWorldCoordX(temp.get(2 * size / 3).x); double y3 = view.toRealWorldCoordY(temp.get(2 * size / 3).y); if (x2 == x1) { x1 = view.toRealWorldCoordX(temp.get(size / 4).x); y1 = view.toRealWorldCoordY(temp.get(size / 4).y); } if (x2 == x3) { x3 = view.toRealWorldCoordX(temp.get(11 * size / 12).x); y3 = view.toRealWorldCoordY(temp.get(11 * size / 12).y); } GeoPoint p1 = new GeoPoint(app.getKernel().getConstruction(), x1, y1, 1.0); GeoPoint q = new GeoPoint(app.getKernel().getConstruction(), x2, y2, 1.0); GeoPoint z = new GeoPoint(app.getKernel().getConstruction(), x3, y3, 1.0); AlgoCircleThreePoints algo = new AlgoCircleThreePoints( app.getKernel().getConstruction(), null, p1, q, z); GeoConic circle = (GeoConic) algo.getCircle(); Equation equ = getEquationOfConic(circle.getMatrix()); equ.initEquation(); GeoElement[] geos = view.getKernel().getAlgebraProcessor() .processConic(equ, equ.wrap()); geos[0].setEuclidianVisible(true); circle.remove(); algo.remove(); circle = (GeoConic) geos[0]; // circle.setLineThickness(penSize * PEN_SIZE_FACTOR); // circle.setLineType(penLineStyle); // circle.setObjColor(penColor); circle.updateRepaint(); return circle; } private Equation getEquationOfConic(double[] coeffs) { FunctionVariable xx = new FunctionVariable(view.getKernel(), "x"); FunctionVariable yy = new FunctionVariable(view.getKernel(), "y"); // x^2 ExpressionNode xSqr = new ExpressionNode(view.getKernel(), xx, Operation.MULTIPLY, xx); // x*x ExpressionNode xy = new ExpressionNode(view.getKernel(), xx, Operation.MULTIPLY, yy); // y^2 ExpressionNode ySqr = new ExpressionNode(view.getKernel(), yy, Operation.MULTIPLY, yy); ExpressionNode term1 = new ExpressionNode(view.getKernel(), new ExpressionNode(view.getKernel(), coeffs[0]), Operation.MULTIPLY, xSqr); ExpressionNode term2 = new ExpressionNode(view.getKernel(), new ExpressionNode(view.getKernel(), coeffs[3] * 2), Operation.MULTIPLY, xy); ExpressionNode term3 = new ExpressionNode(view.getKernel(), new ExpressionNode(view.getKernel(), coeffs[1]), Operation.MULTIPLY, ySqr); ExpressionNode term4 = new ExpressionNode(view.getKernel(), new ExpressionNode(view.getKernel(), coeffs[4] * 2), Operation.MULTIPLY, xx); ExpressionNode term5 = new ExpressionNode(view.getKernel(), new ExpressionNode(view.getKernel(), coeffs[5] * 2), Operation.MULTIPLY, yy); ExpressionNode term12 = new ExpressionNode(view.getKernel(), term1, Operation.PLUS, term2); ExpressionNode term34 = new ExpressionNode(view.getKernel(), term3, Operation.PLUS, term4); ExpressionNode term1234 = new ExpressionNode(view.getKernel(), term12, Operation.PLUS, term34); ExpressionNode lhs = new ExpressionNode(view.getKernel(), term1234, Operation.PLUS, term5); ExpressionNode rhs = new ExpressionNode(view.getKernel(), -coeffs[2]); Equation equ = new Equation(view.getKernel(), lhs, rhs); return equ; } /** * creates a conic form the points in penPoints, if there are enough points * and a conic exists that fits good enough * * @return the conic that fits best to the given points; null in case that * there are too few points or the thresholds cannot be fulfilled */ private GeoConic makeAConic() { // disable ellipse for whiteboard if (app.isWhiteboardActive()) { return null; } double px, py; // adapted from FitImplicit // order 2 ie conic int order = 2; // sample 10 points from what we're given int datasize = 10; if (this.penPoints.size() < datasize) { return null; } int step = this.penPoints.size() / datasize; Array2DRowRealMatrix M = new Array2DRowRealMatrix(datasize, order * (order + 1)); double[] coeffs = new double[6]; try { int r = 0; for (int j = 0; j < datasize; j++) { GPoint point = penPoints.get(r); r += step; px = view.toRealWorldCoordX(point.getX()); py = view.toRealWorldCoordY(point.getY()); // uncomment for debugging (to see which points were sampled) // new GeoPoint(app.getKernel().getConstruction(), null, px, py, // 1); int c1 = 0; // create powers eg x^2y^0, x^1y^1, x^0*y^2, x, y, 1 for (int i = 0; i <= order; i++) { for (int xpower = 0; xpower <= i; xpower++) { int ypower = i - xpower; double val = AlgoFitImplicit.power(px, xpower) * AlgoFitImplicit.power(py, ypower); // Log.debug(val + "x^"+xpower+" * y^"+ypower); M.setEntry(j, c1++, val); } } } SingularValueDecomposition svd = new SingularValueDecomposition( M); RealMatrix V = svd.getV(); RealVector coeffsRV = V.getColumnVector(5); // create powers eg x^2y^0, x^1y^1, x^0*y^2, x, y, 1 for (int i = 0; i < 6; i++) { coeffs[5 - i] = coeffsRV.getEntry(i); // Log.debug("coeff of " + i + " = "+ coeffs[i]); } // double eccentricity = conic.eccentricity; // GeoVec2D midpoint = conic.b; // Log.debug("size of M = "+M.getColumnDimension()+" // "+M.getRowDimension()); // Log.debug("size of V = "+V.getColumnDimension()+" // "+V.getRowDimension()); } catch (Throwable t) { t.printStackTrace(); return null; } GeoConic conic = new GeoConic(this.app.getKernel().getConstruction(), coeffs); GeoPoint point = new GeoPoint(this.app.getKernel().getConstruction(), 0, 0, 1); double error = 0; for (GPoint p : penPoints) { point.setCoords(view.toRealWorldCoordX(p.x), view.toRealWorldCoordY(p.y), 1); error += conic.distance(point); } error /= penPoints.size(); if (conic.isDefined() && conic.getHalfAxis(0) / error > CONIC_AXIS_ERROR_RATIO && conic.getHalfAxis(1) / error > CONIC_AXIS_ERROR_RATIO) { AlgoFocus algo = new AlgoFocus(app.getKernel().getConstruction(), new String[] { null, null }, conic); GeoPointND[] focus = algo.getFocus(); int type = conic.getType(); GeoPoint pointOnConic = this.app.getKernel().getAlgoDispatcher() .Point(null, conic, null); conic.remove(); GeoPoint f0 = new GeoPoint(app.getKernel().getConstruction(), null, focus[0].getInhomX(), focus[0].getInhomY(), 1); f0.setEuclidianVisible(false); GeoPoint f1 = new GeoPoint(app.getKernel().getConstruction(), null, focus[1].getInhomX(), focus[1].getInhomY(), 1); f1.setEuclidianVisible(false); GeoPoint additionalPoint = new GeoPoint( app.getKernel().getConstruction(), null, pointOnConic.getInhomX(), pointOnConic.getInhomY(), 1); additionalPoint.setEuclidianVisible(false); conic = (GeoConic) this.app.getKernel().getAlgoDispatcher() .EllipseHyperbola(null, f0, f1, additionalPoint, type); } else { conic.remove(); conic = null; } if (conic != null) { conic.setIsShape(true); conic.setLabelVisible(false); } return conic; } private void optimize_polygonal(int nsides) { double cost, newcost; boolean improved; Inertia temp1 = new Inertia(); Inertia temp2 = new Inertia(); for (int i = 1; i < nsides; ++i) { copyInertiaToTemp(temp1, temp2, i); cost = getCost(temp1, temp2); improved = false; while (brk[i] > brk[i - 1] + 1) { incr_inertia(brk[i] - 1, temp1, -1); incr_inertia(brk[i] - 1, temp2, 1); newcost = getCost(temp1, temp2); if (newcost >= cost) { break; } improved = true; cost = newcost; brk[i]--; copyInertiaFromTemp(temp1, temp2, i); } if (improved) { continue; } copyInertiaToTemp(temp1, temp2, i); while (brk[i] < brk[i + 1] - 1) { incr_inertia(brk[i], temp1, 1); incr_inertia(brk[i], temp2, -1); newcost = getCost(temp1, temp2); if (newcost >= cost) { break; } cost = newcost; brk[i]++; copyInertiaFromTemp(temp1, temp2, i); } } } private void copyInertiaToTemp(Inertia temp1, Inertia temp2, int i) { if (i - 1 == 0) { temp1.copyValuesFrom(a); temp2.copyValuesFrom(b); } else if (i - 1 == 1) { temp1.copyValuesFrom(b); temp2.copyValuesFrom(c); } else if (i - 1 == 2) { temp1.copyValuesFrom(c); temp2.copyValuesFrom(d); } } private void copyInertiaFromTemp(Inertia temp1, Inertia temp2, int i) { if (i - 1 == 0) { a.copyValuesFrom(temp1); b.copyValuesFrom(temp2); } else if (i - 1 == 1) { b.copyValuesFrom(temp1); c.copyValuesFrom(temp2); } else if (i - 1 == 2) { c.copyValuesFrom(temp1); d.copyValuesFrom(temp2); } } private static double getCost(Inertia temp1, Inertia temp2) { return (EuclidianPen.I_det(temp1) * EuclidianPen.I_det(temp1)) + (EuclidianPen.I_det(temp2) * EuclidianPen.I_det(temp2)); } private void incr_inertia(int start, Inertia s, int coeff) { // defensive code // https://play.google.com/apps/publish/?dev_acc=05873811091523087820#ErrorClusterDetailsPlace:p=org.geogebra.android&et=CRASH&lr=LAST_30_DAYS&ecn=java.lang.ArrayIndexOutOfBoundsException&tf=SourceFile&tc=org.geogebra.a.c.v&tm=a&nid&an&c&s=new_status_desc if (start + 1 >= penPoints.size()) { // Log.error("problem in incr_inertia "+ start + " " + s + " " + // coeff); Log.debug("problem in EuclidianPen.incr_inertia " + start + " " + s + " " + coeff); return; } double pt1_x = penPoints.get(start).x; double pt1_y = penPoints.get(start).y; double pt2_x = penPoints.get(start + 1).x; double pt2_y = penPoints.get(start + 1).y; double dm = 0; dm = coeff * Math.hypot(pt2_x - pt1_x, pt2_y - pt1_y); s.mass = s.mass + dm; s.sx = s.sx + (dm * pt1_x); s.sy = s.sy + (dm * pt1_y); s.sxx = s.sxx + (dm * pt1_x * pt1_x); s.syy = s.syy + (dm * pt1_y * pt1_y); s.sxy = s.sxy + (dm * pt1_x * pt1_y); } private void get_segment_geometry(Inertia s, RecoSegment r) { double a1, b1, c1, lmin, lmax, l; int i; int start = r.startpt; r.xcenter = EuclidianPen.center_x(s); r.ycenter = EuclidianPen.center_y(s); a1 = EuclidianPen.I_xx(s); b1 = EuclidianPen.I_xy(s); c1 = EuclidianPen.I_yy(s); r.angle = Math.atan2(2 * b1, a1 - c1) / 2; r.radius = Math.sqrt(3 * (a1 + c1)); lmin = lmax = 0; for (i = start; i <= r.endpt; ++i) { l = (penPoints.get(start).x - r.xcenter) * Math.cos(r.angle) + (penPoints.get(start).y - r.ycenter) * Math.sin(r.angle); if (l < lmin) { lmin = l; } if (l > lmax) { lmax = l; } start++; } r.x1 = r.xcenter + lmin * Math.cos(r.angle); r.y1 = r.ycenter + lmin * Math.sin(r.angle); r.x2 = r.xcenter + lmax * Math.cos(r.angle); r.y2 = r.ycenter + lmax * Math.sin(r.angle); } private GeoElement try_rectangle() { int nsides = 4; if (recognizer_queue_length < nsides) { return null; } int i; double dist, avg_angle = 0; RecoSegment rs = getRecoSegment(recognizer_queue_length - nsides); RecoSegment r1; RecoSegment r2; // AbstractApplication.debug(rs.startpt); if (rs.startpt != 0) { return null; } for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + ((i + 1) % nsides)); // AbstractApplication.debug(Math.abs(Math.abs(r1.angle-r2.angle)-Math.PI/2) // > RECTANGLE_ANGLE_TOLERANCE); if (Math.abs(Math.abs(r1.angle - r2.angle) - Math.PI / 2) > RECTANGLE_ANGLE_TOLERANCE) { return null; } avg_angle = avg_angle + r1.angle; if (r2.angle > r1.angle) { avg_angle = avg_angle + ((i + 1) * Math.PI / 2); } else { avg_angle = avg_angle - ((i + 1) * Math.PI / 2); } r1.reversed = ((r1.x2 - r1.x1) * (r2.xcenter - r1.xcenter) + (r1.y2 - r1.y1) * (r2.ycenter - r1.ycenter)) < 0; } for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + ((i + 1) % nsides)); dist = Math.hypot( (r1.reversed ? r1.x1 : r1.x2) - (r2.reversed ? r2.x2 : r2.x1), (r1.reversed ? r1.y1 : r1.y2) - (r2.reversed ? r2.y2 : r2.y1)); if (dist > RECTANGLE_LINEAR_TOLERANCE * (r1.radius + r2.radius)) { return null; } } avg_angle = avg_angle / nsides; if (Math.abs(avg_angle) < SLANT_TOLERANCE) { avg_angle = 0; } if (Math.abs(avg_angle) > Math.PI / 2 - SLANT_TOLERANCE) { avg_angle = Math.PI / 2; } for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r1.angle = avg_angle + i * Math.PI / 2; } double pt[] = new double[2]; double points[] = new double[10]; for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + ((i + 1) % nsides)); EuclidianPen.calc_edge_isect(r1, r2, pt); points[2 * i + 2] = pt[0]; points[2 * i + 3] = pt[1]; } points[0] = points[2 * nsides]; points[1] = points[2 * nsides + 1]; // in case a initialPoint is defined, move the polygon so that its first // point matches the initialPoint double offsetInitialPointX = 0; double offsetInitialPointY = 0; // in case the initialPoint cannot be used and can be deleted safely, // delete it if (initialPoint != null && !initialPoint.isIndependent() && deleteInitialPoint) { this.initialPoint.remove(); this.initialPoint = null; } Construction cons = app.getKernel().getConstruction(); GeoPointND[] pts = new GeoPointND[nsides]; double x_first; double y_first; for (i = 0; i < nsides; ++i) { x_first = view.toRealWorldCoordX(points[2 * i]) + offsetInitialPointX; y_first = view.toRealWorldCoordY(points[2 * i + 1]) + offsetInitialPointY; if (i == 0 && this.initialPoint != null && this.initialPoint.isIndependent()) { offsetInitialPointX = this.initialPoint.x - x_first; offsetInitialPointY = this.initialPoint.y - y_first; pts[0] = this.initialPoint; } else { // null -> created labeled point pts[i] = new GeoPoint(cons, null, x_first, y_first, 1.0); } } Log.debug("Rectangle Recognized"); return createPolygonFromPoints(pts); } /** * * @param points * {@link GeoPointND} * @return {@link GeoElement Polygon} created of given points */ private GeoElement createPolygonFromPoints(GeoPointND[] points) { AlgoPolygon algo = new AlgoPolygon(app.getKernel().getConstruction(), null, points); GeoElement poly = algo.getOutput(0); // poly.setLineThickness(penSize * PEN_SIZE_FACTOR); // poly.setLineType(penLineStyle); // poly.setObjColor(penColor); if (view.getEuclidianController() .getPreviousMode() != EuclidianConstants.MODE_POLYGON) { poly.setIsShape(true); poly.setAlphaValue(0); poly.setBackgroundColor(GColor.WHITE); poly.setObjColor(GColor.BLACK); poly.updateRepaint(); for (GeoPointND point : points) { point.setEuclidianVisible(false); } if (poly instanceof GeoPolygon) { for (GeoSegmentND geoSeg : ((GeoPolygon) poly).getSegments()) { ((GeoSegment) geoSeg).setSelectionAllowed(false); ((GeoSegment) geoSeg).setLabelVisible(false); } } } return poly; } private GeoElement getJoinPointsSegment(GeoPoint first, GeoPoint last) { Construction cons = app.getKernel().getConstruction(); AlgoJoinPointsSegment algo = new AlgoJoinPointsSegment(cons, null, first, last); first.setEuclidianVisible(false); last.setEuclidianVisible(false); GeoElement line = algo.getOutput(0); line.updateRepaint(); line.setIsShape(true); line.setLabelVisible(false); return line; } private GeoElement try_closed_polygon(int nsides) { if (recognizer_queue_length < nsides) { return null; } RecoSegment rs = getRecoSegment(recognizer_queue_length - nsides); if (rs.startpt != 0) { return null; } RecoSegment r1; RecoSegment r2; int i; double dist; double pt[] = new double[2]; for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + (i + 1) % nsides); EuclidianPen.calc_edge_isect(r1, r2, pt); r1.reversed = (Math.hypot(pt[0] - r1.x1, pt[1] - r1.y1)) < (Math .hypot(pt[0] - r1.x2, pt[1] - r1.y2)); } for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + (i + 1) % nsides); EuclidianPen.calc_edge_isect(r1, r2, pt); dist = Math.hypot((r1.reversed ? r1.x1 : r1.x2) - pt[0], (r1.reversed ? r1.y1 : r1.y2) - pt[1]) + Math.hypot((r2.reversed ? r2.x2 : r2.x1) - pt[0], (r2.reversed ? r2.y2 : r2.y1) - pt[1]); if (dist > POLYGON_LINEAR_TOLERANCE * (r1.radius + r2.radius)) { return null; } } double points[] = new double[nsides * 2 + 2]; for (i = 0; i < nsides; ++i) { r1 = getRecoSegment(recognizer_queue_length - nsides + i); r2 = getRecoSegment( recognizer_queue_length - nsides + (i + 1) % nsides); EuclidianPen.calc_edge_isect(r1, r2, pt); points[2 * i + 2] = pt[0]; points[2 * i + 3] = pt[1]; } points[0] = points[2 * nsides]; points[1] = points[2 * nsides + 1]; GeoPointND[] pts = new GeoPointND[nsides]; for (i = 0; i < nsides; ++i) { if (i == 0 && this.initialPoint != null) { pts[0] = initialPoint; initialPoint = null; } else { // null -> created labeled point pts[i] = new GeoPoint(app.getKernel().getConstruction(), null, view.toRealWorldCoordX(points[2 * i]), view.toRealWorldCoordY(points[2 * i + 1]), 1.0); } } if (nsides == 3) { Log.debug("Triangle Recognized"); } else { Log.debug("Quadrilateral Recognized"); } return createPolygonFromPoints(pts); } private RecoSegment getRecoSegment(int n) { switch (n) { case 0: return reco_queue_a; case 1: return reco_queue_b; case 2: return reco_queue_c; case 3: return reco_queue_d; case 4: return reco_queue_e; } return null; } private static void calc_edge_isect(RecoSegment r1, RecoSegment r2, double pt[]) { double t = (r2.xcenter - r1.xcenter) * Math.sin(r2.angle) - (r2.ycenter - r1.ycenter) * Math.cos(r2.angle); t = t / Math.sin(r2.angle - r1.angle); pt[0] = r1.xcenter + t * Math.cos(r1.angle); pt[1] = r1.ycenter + t * Math.sin(r1.angle); } /** * @param color * pen color */ public void setPenColor(GColor color) { if (!this.penColor.equals(color)) { startNewStroke = true; } this.penColor = color; lineDrawingColor = color; } /** * @param b * true for absolute position (AttachCopyToView) */ public void setAbsoluteScreenPosition(boolean b) { absoluteScreenPosition = b; } private static class RecoSegment { protected RecoSegment() { } int startpt = 0, endpt = 0; double xcenter = 0, ycenter = 0, angle = 0, radius = 0; double x1 = 0, y1 = 0, x2 = 0, y2 = 0; boolean reversed; } private static class Inertia { protected Inertia() { } double mass = 0; double sx = 0; double sxx = 0; double sy = 0; double sxy = 0; double syy = 0; protected void copyValuesFrom(Inertia inertia) { mass = inertia.mass; sx = inertia.sx; sxx = inertia.sxx; sy = inertia.sy; sxy = inertia.sxy; syy = inertia.syy; } } /** * used for subclasses to return the last shape that was created * * NOT USED IN THIS CLASS * * @return null */ public GeoElement getCreatedShape() { return null; } public void remove(GeoElement geo) { if (geo.getParentAlgorithm() == this.lastAlgo) { lastAlgo = null; } } @Override public void onRun() { startNewStroke = true; } }