/* 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.euclidian.draw; import java.util.ArrayList; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.awt.GBasicStroke; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GEllipse2DDouble; import org.geogebra.common.awt.GGeneralPath; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.awt.GShape; import org.geogebra.common.euclidian.BoundingBox; import org.geogebra.common.euclidian.Drawable; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.euclidian.GeneralPathClipped; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoTurtle; import org.geogebra.common.kernel.kernelND.GeoPointND; /** * * @author G.Sturr adapted from DrawPolyLine */ public class DrawTurtle extends Drawable { /** turtle */ protected GeoTurtle turtle; private boolean isVisible, labelVisible; /** list of paths */ protected ArrayList<PartialPath> pathList; private GRectangle boundRect; private double turnAngle = 0.0; private GRectangle turtleImageBounds = AwtFactory.getPrototype() .newRectangle(); private double imageSize = 10; private double[] currentCoords = new double[2]; private GAffineTransform at = AwtFactory.getPrototype() .newAffineTransform(); /** * @param view * view * @param turtle * turtle */ public DrawTurtle(EuclidianView view, GeoTurtle turtle) { this.view = view; this.turtle = turtle; geo = turtle; turtleImageBounds.setFrame(0, 0, 0, 0); update(); turtle.setCoords(turtle.inhomX, turtle.inhomY); } private static class PartialPath { public GColor color; public int thickness; public GeneralPathClipped path1; private GBasicStroke stroke; public PartialPath(GColor c, int th, GeneralPathClipped p) { color = c; thickness = th; path1 = p; stroke = AwtFactory.getPrototype().newBasicStroke(thickness); } public void draw(GGraphics2D g2) { g2.setColor(color); g2.setStroke(stroke); g2.draw(path1); } } private class DrawState implements GeoTurtle.DrawState { private boolean penDown = true; private GColor penColor = GColor.BLACK; private int penThickness = 1; private int nlines = 0; double turnAngle1 = 0d; private GeneralPathClipped currentPath; // private GeoPointND currentPosition = turtle.getStartPoint(); double coords[] = new double[2]; public DrawState() { currentPath = new GeneralPathClipped(getView()); penDown = false; move(turtle.getStartPoint()); penDown = true; nlines = 0; } @Override public void setPen(boolean down) { penDown = down; } @Override public void move(GeoPointND newPosition) { newPosition.getInhomCoords(coords); getView().toScreenCoords(coords); if (penDown) { currentPath.lineTo(coords[0], coords[1]); nlines += 1; } else { currentPath.moveTo(coords[0], coords[1]); } } @Override public void partialMove(GeoPointND newPosition, double progress) { double[] newCoords = new double[2]; newPosition.getInhomCoords(newCoords); getView().toScreenCoords(newCoords); coords[0] = coords[0] * (1d - progress) + newCoords[0] * progress; coords[1] = coords[1] * (1d - progress) + newCoords[1] * progress; if (penDown) { currentPath.lineTo(coords[0], coords[1]); nlines += 1; } else { currentPath.moveTo(coords[0], coords[1]); } } @Override public void turn(double angle) { turnAngle1 += angle; } @Override public void partialTurn(double angle, double progress) { turnAngle1 += angle * progress; } @Override public void setColor(GColor color) { if (penColor != color) { finishPartialPath(); penColor = color; } } @Override public void setThickness(int thickness) { if (penThickness != thickness) { finishPartialPath(); penThickness = thickness; } } public void finishPartialPath() { if (nlines > 0) { pathList.add( new PartialPath(penColor, penThickness, currentPath)); } currentPath = new GeneralPathClipped(getView()); currentPath.moveTo(coords[0], coords[1]); } } @Override final public void update() { isVisible = geo.isEuclidianVisible(); if (isVisible) { labelVisible = geo.isLabelVisible(); updateStrokes(turtle); if (pathList == null) { pathList = new ArrayList<PartialPath>(); } else { pathList.clear(); } DrawState ds = new DrawState(); int ncommands = turtle.getTurtleCommandList().size(); if (turtle.getSpeed() != 0d) { ncommands = turtle.getNumberOfCompletedCommands(); } // Partially process the turtle command list. // The turtle command list is converted to a list of partial paths // which know how to draw themselves on a Graphic2D. // Iteration stops when ncommands is reached, the current // in-progress limit. for (GeoTurtle.TurtleCommand cmd : turtle.getTurtleCommandList()) { if (ncommands-- > 0) { cmd.draw(ds); } else { cmd.partialDraw(ds, turtle.getCurrentCommandProgress()); break; } } ds.finishPartialPath(); currentCoords[0] = ds.coords[0]; currentCoords[1] = ds.coords[1]; turnAngle = ds.turnAngle1; } turtleImageBounds.setFrame(currentCoords[0] - imageSize / 2, currentCoords[1] - imageSize / 2, imageSize, imageSize); // turtle path on screen? isVisible = false; isVisible = getBounds() != null && getBounds().intersects(0, 0, view.getWidth(), view.getHeight()); if (isVisible) { at.setTransform(1, 0, 0, 1, 1, 0); at.translate(currentCoords[0], currentCoords[1]); at.rotate(-turnAngle); if (geo.getFillImage() == null) { updateTurtleShape(); } } } @Override final public void draw(GGraphics2D g2) { if (isVisible) { // TODO: handle variable line thickness g2.setStroke(objStroke); for (PartialPath path : pathList) { path.draw(g2); } if (geo.doHighlighting()) { g2.setPaint(turtle.getSelColor()); g2.setStroke(selStroke); for (PartialPath path : pathList) { g2.draw(path.path1); } } if (labelVisible) { g2.setPaint(turtle.getLabelColor()); g2.setFont(view.getFontPoint()); drawLabel(g2); } // draw turtle if (turtle.getFillImage() != null) { int imgWidth = turtle.getFillImage().getWidth(); int imgHeight = turtle.getFillImage().getHeight(); g2.saveTransform(); g2.transform(at); // temp - until x,y parameters won't be used in drawImage on // desktop for SVG images if (turtle.getFillImage().isSVG() && !turtle.kernel.getApplication().isHTML5Applet()) { g2.translate(-imgWidth / 2.0, -imgHeight / 2.0); } g2.drawImage(turtle.getFillImage(), -imgWidth / 2, -imgHeight / 2); g2.restoreTransform(); } else { // draw rotated turtle drawTurtleShape(g2); } } } @Override final public boolean hit(int x, int y, int hitThreshold) { if (isVisible) { for (PartialPath path : pathList) { if (path.path1.intersects(x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold)) { return true; } } } return false; } @Override final public boolean isInside(GRectangle rect) { return pathList != null && rect.contains(getBounds()); } @Override public boolean intersectsRectangle(GRectangle rect) { if (isVisible) { for (PartialPath p : pathList) { if (p.path1.intersects(rect)) { return true; } } } return false; } @Override public GeoElement getGeoElement() { return geo; } @Override public void setGeoElement(GeoElement geo) { this.geo = geo; } /** * Returns the bounding box of this Drawable in screen coordinates. */ @Override final public GRectangle getBounds() { if (!geo.isDefined() || !geo.isEuclidianVisible()) { return null; } boundRect = turtleImageBounds; for (PartialPath path : pathList) { boundRect = boundRect.union(path.path1.getBounds()); } return boundRect; } // =================================================== // Turtle Shapes // // TODO: handle images when Common supports loading internal images // =================================================== private GEllipse2DDouble ellipse = AwtFactory.getPrototype() .newEllipse2DDouble(); private GBasicStroke stroke1 = AwtFactory.getPrototype().newBasicStroke(1f); private GBasicStroke stroke2 = AwtFactory.getPrototype().newBasicStroke(2f); private GGeneralPath gPath = AwtFactory.getPrototype().newGeneralPath(); private GShape legs; private GShape head; private GShape body; private GShape dot; private void drawTurtleShape(GGraphics2D g2) { gPath.reset(); // back legs g2.setStroke(stroke2); g2.setColor(GColor.BLACK); g2.draw(legs); // front legs g2.setStroke(stroke1); // head g2.setColor(GColor.GRAY); g2.fill(head); g2.setColor(GColor.BLACK); g2.draw(head); // body g2.setColor(GColor.GREEN); g2.fill(body); g2.setColor(GColor.BLACK); g2.draw(body); // pen color dot g2.setColor(turtle.getPenColor()); g2.fill(dot); // g2.setColor(Color.black); // g2.draw(ellipse); } private void updateTurtleShape() { int r = 8; // turtle radius double x, y; gPath.reset(); // back legs x = (1.3 * r * Math.cos(Math.PI / 6)); y = (1.3 * r * Math.sin(Math.PI / 6)); gPath.moveTo(0, 0); gPath.lineTo(-x, y); gPath.moveTo(0, 0); gPath.lineTo(-x, -y); // front legs x = (1.2 * r * Math.cos(Math.PI / 4)); y = (1.2 * r * Math.sin(Math.PI / 4)); gPath.moveTo(0, 0); gPath.lineTo(x, y); gPath.moveTo(0, 0); gPath.lineTo(x, -y); legs = gPath.createTransformedShape(at); // head ellipse.setFrame(r - 3, -3, 6, 6); head = at.createTransformedShape(ellipse); // body ellipse.setFrame(-r, -r, 2 * r, 1.8 * r); body = at.createTransformedShape(ellipse); // pen color dot ellipse.setFrame(-3, -3, 6, 6); dot = at.createTransformedShape(ellipse); // g2.setColor(Color.black); // g2.draw(ellipse); } @Override public BoundingBox getBoundingBox() { // TODO Auto-generated method stub return null; } @Override public void updateBoundingBox() { // TODO Auto-generated method stub } /** * Draw turtle shapes. * * @param g2 **/ /* * private void drawTurtleShape(geogebra.common.awt.GGraphics2D g2, int * shapeNumber, GColor penColor) { * * int r = 8; // turtle radius float x, y; gPath.reset(); * * switch (shapeNumber) { * * case 0: // no turtle is drawn break; * * case 1: // ellipse body with legs and head * * // back legs g2.setStroke(stroke2); x = (1.3 * r * Math.cos(Math.PI / * 6)); y = (1.3 * r * Math.sin(Math.PI / 6)); gPath.moveTo(0, 0); * gPath.lineTo(-x, y); gPath.moveTo(0, 0); gPath.lineTo(-x, -y); * g2.setColor(GColor.black); g2.draw(gPath); * * // front legs g2.setStroke(stroke2); x = (1.2 * r * Math.cos(Math.PI / * 4)); y = (1.2 * r * Math.sin(Math.PI / 4)); gPath.moveTo(0, 0); * gPath.lineTo(x, y); gPath.moveTo(0, 0); gPath.lineTo(x, -y); * g2.setColor(GColor.black); gPath.createTransformedShape(at); * g2.draw(gPath); * * g2.setStroke(stroke1); * * // head ellipse.setFrame(r - 3, -3, 6, 6); g2.setColor(GColor.gray); * g2.fill(ellipse); g2.setColor(GColor.black); g2.draw(ellipse); * * // body ellipse.setFrame(-r, -r, 2 * r, 1.8 * r); * g2.setColor(GColor.green); g2.fill(ellipse); g2.setColor(GColor.black); * g2.draw(ellipse); * * // pen color dot ellipse.setFrame(-3, -3, 6, 6); * g2.setColor(turtle.getPenColor()); g2.fill(ellipse); * //g2.setColor(Color.black); //g2.draw(ellipse); * * break; * * case 2: // triangle shape * * g2.setStroke(stroke1); * * // body ellipse.setFrame(-r, -r, 2 * r, 2 * r); * g2.setColor(GColor.green); g2.fill(ellipse); g2.setColor(GColor.black); * g2.draw(ellipse); * * // triangle x = (r * Math.cos(2*Math.PI / 3)); y = (r * * Math.sin(2*Math.PI / 3)); gPath.moveTo(r, 0); gPath.lineTo(x, y); * gPath.lineTo(x, -y); gPath.lineTo(r, 0); g2.setColor(penColor); * g2.fill(gPath); * * break; * * case 3: * * GImage img = turtle.getTurtleImageList().get(0); GAffineTransform tr = * g2.getTransform(); g2.setTransform(at); g2.drawImage(img, -8, -8); * g2.setTransform(tr); * * } } */ }