/* 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.GArea; import org.geogebra.common.awt.GGeneralPath; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPathIterator; import org.geogebra.common.awt.GPoint2D; 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.DrawableND; import org.geogebra.common.euclidian.EuclidianBoundingBoxHandler; import org.geogebra.common.euclidian.EuclidianStatic; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.euclidian.GeneralPathClipped; import org.geogebra.common.euclidian.Previewable; import org.geogebra.common.euclidian.event.AbstractEvent; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.ConstructionDefaults; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Matrix.Coords; import org.geogebra.common.kernel.discrete.PolygonTriangulation; import org.geogebra.common.kernel.discrete.PolygonTriangulation.Convexity; import org.geogebra.common.kernel.discrete.PolygonTriangulation.TriangleFan; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElement.HitType; import org.geogebra.common.kernel.geos.GeoLine; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.geos.GeoPolygon; import org.geogebra.common.kernel.geos.GeoVec3D; import org.geogebra.common.kernel.kernelND.GeoPointND; import org.geogebra.common.kernel.kernelND.GeoSegmentND; import org.geogebra.common.main.Feature; import org.geogebra.common.util.MyMath; import org.geogebra.common.util.debug.Log; /** * * @author Markus Hohenwarter */ public class DrawPolygon extends Drawable implements Previewable { private GeoPolygon poly; private boolean isVisible, labelVisible; private GeneralPathClipped gp, gpTriangularize; private double[] coords = new double[2]; private ArrayList<GeoPointND> points; private Coords[] extraCoords; private BoundingBox boundingBox; private double fixCornerX = Double.NaN, fixCornerY = Double.NaN; private double proportion = Double.NaN; private double oldWidth = Double.NaN, oldHeight = Double.NaN; private boolean isSquare = false; private GGeneralPath prewPolygon = AwtFactory.getPrototype() .newGeneralPath(); /** * Creates new DrawPolygon * * @param view * Euclidian view to be used * @param poly * Polygon to be drawn */ public DrawPolygon(EuclidianView view, GeoPolygon poly) { this.view = view; this.poly = poly; geo = poly; extraCoords = new Coords[8]; for (int i = 0; i < 8; i++) { extraCoords[i] = new Coords(0, 0); } update(); } /** * Creates a new DrawPolygon for preview. * * @param view * Euclidian view to be used * @param points * vertices */ public DrawPolygon(EuclidianView view, ArrayList<GeoPointND> points) { this.view = view; this.points = points; geo = view.getKernel().getConstruction().getConstructionDefaults() .getDefaultGeo(ConstructionDefaults.DEFAULT_POLYGON); updatePreview(); } @Override final public void update() { isVisible = geo.isEuclidianVisible(); if (isVisible) { labelVisible = geo.isLabelVisible(); updateStrokes(poly); // build general path for this polygon isVisible = addPointsToPath(poly.getPointsLength()); if (!isVisible) { return; } gp.closePath(); fillShape = false; if (!getView().getApplication().has(Feature.POLYGON_TRIANGULATION) || isAllPointsOnScreen()) { if (geo.isInverseFill()) { createShape(); fillShape = true; } } else { triangularize(); fillShape = true; } // polygon on screen? if (!gp.intersects(0, 0, view.getWidth(), view.getHeight()) && !geo.isInverseFill()) { isVisible = false; // don't return here to make sure that getBounds() works for // offscreen points too } // draw trace if (poly.getTrace()) { isTracing = true; GGraphics2D g2 = view.getBackgroundGraphics(); if (g2 != null) { fill(g2, gp); } } else { if (isTracing) { isTracing = false; // view.updateBackground(); } } } if (geo.isShape()) { if (getBounds() != null) { getBoundingBox().setRectangle(getBounds()); if (Kernel.isEqual(getBoundingBox().getRectangle().getHeight(), getBoundingBox().getRectangle().getWidth(), 2)) { setIsSquare(true); } } } } private void createShape() { setShape(AwtFactory.getPrototype().newArea(view.getBoundingPath())); getShape().subtract(AwtFactory.getPrototype().newArea(gp)); } private Coords getCoords(int i) { if (poly != null) { return view.getCoordsForView(poly.getPoint3D(i)); } return view.getCoordsForView(points.get(i).getInhomCoordsInD3()); } // return false if a point doesn't lie on the plane private boolean addPointsToPath(int length) { if (gp == null) { gp = new GeneralPathClipped(view); } else { gp.reset(); } if (length <= 0) { return false; } // first point Coords v = getCoords(0); if (!Kernel.isZero(v.getZ())) { return false; } coords[0] = v.getX(); coords[1] = v.getY(); view.toScreenCoords(coords); gp.moveTo(coords[0], coords[1]); // for centroid calculation (needed for label pos) double xsum = coords[0]; double ysum = coords[1]; for (int i = 1; i < length; i++) { v = getCoords(i); if (!Kernel.isZero(v.getZ())) { return false; } coords[0] = v.getX(); coords[1] = v.getY(); view.toScreenCoords(coords); if (labelVisible) { xsum += coords[0]; ysum += coords[1]; } gp.lineTo(coords[0], coords[1]); } if (labelVisible) { labelDesc = geo.getLabelDescription(); xLabel = (int) (xsum / length); yLabel = (int) (ysum / length); addLabelOffset(); } return true; } private boolean fillShape = false; @Override final public void draw(GGraphics2D g2) { if (isVisible) { fill(g2, (fillShape ? getShape() : gp)); // fill // using // default/hatching/image // as // appropriate if (geo.doHighlighting()) { g2.setPaint(poly.getSelColor()); g2.setStroke(selStroke); g2.draw(gp); } // polygons (e.g. in GeoLists) that don't have labeled segments // should also draw their border else if (!poly.wasInitLabelsCalled() && poly.getLineThickness() > 0) { g2.setPaint(getObjectColor()); g2.setStroke(objStroke); g2.draw(gp); } if (labelVisible) { g2.setPaint(poly.getLabelColor()); g2.setFont(view.getFontPoint()); drawLabel(g2); } } } @Override final public void updatePreview() { int size = points.size(); isVisible = size > 0; if (isVisible) { addPointsToPath(size); } } private GPoint2D endPoint = AwtFactory.getPrototype().newPoint2D(); @Override final public void updateMousePos(double mouseRWx, double mouseRWy) { double xRW = mouseRWx; double yRW = mouseRWy; if (isVisible) { // double xRW = view.toRealWorldCoordX(mx); // double yRW = view.toRealWorldCoordY(my); // Application.debug(xRW+" "+yRW); int mx = view.toScreenCoordX(xRW); int my = view.toScreenCoordY(yRW); // round angle to nearest 15 degrees if alt pressed if (view.getEuclidianController().isAltDown()) { GeoPoint p = (GeoPoint) points.get(points.size() - 1); double px = p.inhomX; double py = p.inhomY; if (points.size() > 1) { Construction cons = view.getKernel().getConstruction(); GeoPoint intersection = new GeoPoint(cons); GeoLine l = new GeoLine(cons); GeoLine l2 = new GeoLine(cons); GeoPoint p2 = (GeoPoint) points.get(0); double px2 = p2.inhomX; double py2 = p2.inhomY; double nearestX = Double.MAX_VALUE; double nearestY = Double.MAX_VALUE; double dist = Double.MAX_VALUE; for (double angle = 0; angle < 180; angle += 15) { if (angle == 90) { l.setCoords(1, 0, -px); } else { double gradient = Math.tan(angle * Math.PI / 180.0); l.setCoords(gradient, -1.0, py - gradient * px); } for (double ang2 = 0; ang2 < 180; ang2 += 15) { if (Kernel.isEqual(ang2, angle)) { continue; } else if (Kernel.isEqual(ang2, 90)) { l2.setCoords(1.0, 0, -px2); } else { double gradient2 = Math .tan(ang2 * Math.PI / 180.0); l2.setCoords(gradient2, -1.0, py2 - gradient2 * px2); } // calculate intersection GeoVec3D.cross(l, l2, intersection); double x1 = intersection.x / intersection.z; double y1 = intersection.y / intersection.z; double d = MyMath.length(x1 - xRW, y1 - yRW); // Log.debug("angle = "+angle+"\nang2 = // "+ang2+"\n("+x1+","+y1+")");// // "+xRW+","+yRW); // Application.debug(x1+","+y1); if (d < dist) { nearestX = x1; nearestY = y1; dist = d; } } } xRW = nearestX; yRW = nearestY; } else { double angle = Math.atan2(yRW - py, xRW - px) * 180 / Math.PI; double radius = Math.sqrt( (py - yRW) * (py - yRW) + (px - xRW) * (px - xRW)); // round angle to nearest 15 degrees angle = Math.round(angle / 15) * 15; xRW = px + radius * Math.cos(angle * Math.PI / 180); yRW = py + radius * Math.sin(angle * Math.PI / 180); } mx = view.toScreenCoordX(xRW); my = view.toScreenCoordY(yRW); endPoint.setX(xRW); endPoint.setY(yRW); view.getEuclidianController().setLineEndPoint(endPoint); gp.lineTo(mx, my); } else { view.getEuclidianController().setLineEndPoint(null); } gp.lineTo(view.toScreenCoordX(xRW), view.toScreenCoordY(yRW)); } } @Override final public void drawPreview(GGraphics2D g2) { if (isVisible) { fill(g2, (geo.isInverseFill() ? getShape() : gp)); g2.setPaint(getObjectColor()); updateStrokes(geo); g2.setStroke(objStroke); g2.draw(gp); } } @Override public void disposePreview() { // do nothing } /** * * @return true if it has to check it's on filling */ protected boolean checkIsOnFilling() { return geo.isFilled(); } @Override final public boolean hit(int x, int y, int hitThreshold) { GShape t = geo.isInverseFill() ? getShape() : gp; // needed for MOW-114 GeoSegmentND[] segmentsOfPoly = poly.getSegments(); boolean wasSegmentHit = false; // check if one of sides was hit for (GeoSegmentND geoSegmentND : segmentsOfPoly) { DrawableND d = view.getDrawableFor(geoSegmentND); if (d != null && d instanceof DrawSegment && ((DrawSegment) d).hit(x, y, hitThreshold)) { wasSegmentHit = true; break; } } // no filling if (!checkIsOnFilling()) { // draggable only from sides of poly // or from sides of boundingBox if (wasSegmentHit || (getBoundingBox() != null && getBoundingBox().getRectangle() != null && getBoundingBox() == view.getBoundingBox() && getBoundingBox().getRectangle().intersects( x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold)) && getBoundingBox().hitSideOfBoundingBox(x, y, hitThreshold)) { poly.setLastHitType(HitType.ON_BOUNDARY); return true; } poly.setLastHitType(HitType.NONE); return false; } // also check for boundingBox is has filling return (t != null && (t.contains(x, y) || t.intersects(x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold)))|| (getBoundingBox() != null && getBoundingBox().getRectangle() != null && getBoundingBox().getRectangle().intersects( x - hitThreshold, y - hitThreshold, 2 * hitThreshold, 2 * hitThreshold) && getBoundingBox().hitSideOfBoundingBox(x, y, hitThreshold)); } @Override final public boolean isInside(GRectangle rect) { return gp != null && gp.getBounds() != null && rect.contains(gp.getBounds()); } @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; } return gp.getBounds(); } @Override public GArea getShape() { if (geo.isInverseFill() || super.getShape() != null) { return super.getShape(); } setShape(AwtFactory.getPrototype().newArea(gp)); return super.getShape(); } /** * update bounding box construction */ @Override public void updateBoundingBox() { if (getBoundingBox().getRectangle() == null) { if (geo.isShape() && getBounds() != null) { boundingBox.setRectangle(getBounds()); } } } @Override public BoundingBox getBoundingBox() { if (boundingBox == null) { boundingBox = new BoundingBox(); } return boundingBox; } /** * @return fixed x coord of corner */ public double getFixCornerX() { return fixCornerX; } /** * @param fixCornerX * - x coord of fixed corner */ public void setFixCornerX(double fixCornerX) { this.fixCornerX = fixCornerX; } /** * @return fixed y coord of corner */ public double getFixCornerY() { return fixCornerY; } /** * @param fixCornerY * - y coord of fixed corner */ public void setFixCornerY(double fixCornerY) { this.fixCornerY = fixCornerY; } /** * @param oldWidth * - old width of bounding box */ public void setOldWidth(double oldWidth) { this.oldWidth = oldWidth; } /** * @param oldHeight * - old height of bounding box */ public void setOldHeight(double oldHeight) { this.oldHeight = oldHeight; } /** * @return true if is square */ public boolean isSquare() { return isSquare; } /** * @param isSquare * - if it is square */ public void setIsSquare(boolean isSquare) { this.isSquare = isSquare; } /** * method to update points of poly after mouse release * * @param event * - mouse event */ @Override public void updateGeo(AbstractEvent event) { updateRealPointsOfPolygon(); poly.updateCascade(true); poly.getParentAlgorithm().update(); for (GeoSegmentND geoSeg : poly.getSegments()) { geoSeg.getParentAlgorithm().update(); } for (GeoPointND geoPoint : poly.getPoints()) { geoPoint.update(); } poly.setEuclidianVisible(true); poly.updateRepaint(); this.update(); view.setShapePolygon(null); view.setShapeRectangle(null); setFixCornerX(Double.NaN); setFixCornerY(Double.NaN); setOldWidth(Double.NaN); setOldHeight(Double.NaN); view.repaintView(); } private static boolean isCornerHandler( EuclidianBoundingBoxHandler handler) { return handler == EuclidianBoundingBoxHandler.BOTTOM_LEFT || handler == EuclidianBoundingBoxHandler.BOTTOM_RIGHT || handler == EuclidianBoundingBoxHandler.TOP_LEFT || handler == EuclidianBoundingBoxHandler.TOP_RIGHT; } @Override public void updateByBoundingBoxResize(AbstractEvent e, EuclidianBoundingBoxHandler handler) { poly.setEuclidianVisible(false); poly.updateRepaint(); if (isCornerHandler(handler)) { updateFreePolygonCorner(handler, e); } else { updateFreePolygonSide(handler, e); } view.setShapePolygon(prewPolygon); view.setShapeFillCol(poly.getFillColor()); view.setShapeObjCol(poly.getObjectColor()); view.setShapeStroke(EuclidianStatic .getStroke(poly.getLineThickness() / 2.0, poly.getLineType())); view.getEuclidianController().setDynamicStylebarVisible(false); view.repaintView(); } private void updateRealPointsOfPolygon() { double[] coordArr = new double[6]; GPathIterator it = prewPolygon.getPathIterator(null); int i = poly.getPoints().length; while (!it.isDone() && i > 0) { i--; it.currentSegment(coordArr); poly.getPoint(i).setCoords(view.toRealWorldCoordX(coordArr[0]), view.toRealWorldCoordY(coordArr[1]), 1); it.next(); } } private void fixCornerCoords(EuclidianBoundingBoxHandler hitHandler) { if (Double.isNaN(fixCornerX)) { switch (hitHandler) { case BOTTOM_LEFT: case TOP_LEFT: case LEFT: fixCornerX = getBoundingBox().getRectangle().getMaxX(); break; case TOP_RIGHT: case BOTTOM_RIGHT: case RIGHT: fixCornerX = getBoundingBox().getRectangle().getMinX(); break; default: break; } } if (Double.isNaN(fixCornerY)) { switch (hitHandler) { case TOP_LEFT: case TOP_RIGHT: case TOP: fixCornerY = getBoundingBox().getRectangle().getMaxY(); break; case BOTTOM_LEFT: case BOTTOM_RIGHT: case BOTTOM: fixCornerY = getBoundingBox().getRectangle().getMinY(); break; default: break; } } if (Double.isNaN(proportion)) { proportion = getBoundingBox().getRectangle().getWidth() / getBoundingBox().getRectangle().getHeight(); } if (Double.isNaN(oldWidth)) { oldWidth = getBoundingBox().getRectangle().getWidth(); } if (Double.isNaN(oldHeight)) { oldHeight = getBoundingBox().getRectangle().getHeight(); } } /** * update the coords of free polygon by dragging corner handler * * @param hitHandler * - handler was hit * @param event * - mouse event */ protected void updateFreePolygonCorner( EuclidianBoundingBoxHandler hitHandler, AbstractEvent event) { double pointsX[] = new double[poly.getPointsLength()]; double pointsY[] = new double[poly.getPointsLength()]; if (prewPolygon == null) { prewPolygon = AwtFactory.getPrototype().newGeneralPath(); } fixCornerCoords(hitHandler); int newWidth = (int) (event.getX() - fixCornerX); int height = (int) (event.getY() - fixCornerY); int newHeight = (int) (newWidth * oldHeight / oldWidth); double ratioWidth = newWidth / oldWidth; double ratioHeight = newHeight / oldHeight; double[] currCoords = new double[6]; GPathIterator it = gp.getPathIterator(null); int i = poly.getPointsLength(); while (!it.isDone() && i > 0) { i--; it.currentSegment(currCoords); // bottom or top right corner was moved if (height >= 0) { pointsX[i] = fixCornerX + (Math.abs(currCoords[0] - fixCornerX)) * ratioWidth; if (newWidth >= 0) { pointsY[i] = fixCornerY + (Math.abs(currCoords[1] - fixCornerY)) * ratioHeight; } else { pointsY[i] = fixCornerY - (Math.abs(currCoords[1] - fixCornerY)) * ratioHeight; } } // bottom or top left corner was moved else { pointsX[i] = fixCornerX + (Math.abs(currCoords[0] - fixCornerX)) * ratioWidth; if (newWidth >= 0) { pointsY[i] = fixCornerY - (Math.abs(currCoords[1] - fixCornerY)) * ratioHeight; } else { pointsY[i] = fixCornerY + (Math.abs(currCoords[1] - fixCornerY)) * ratioHeight; } } it.next(); } prewPolygon.reset(); prewPolygon.moveTo(pointsX[0], pointsY[0]); for (int index = 1; index < pointsX.length; index++) { prewPolygon.lineTo(pointsX[index], pointsY[index]); } prewPolygon.closePath(); getBoundingBox().setRectangle(prewPolygon.getBounds()); } /** * update the coords of free polygon by dragging side handler * * @param hitHandler * - handler was hit * @param event * - mouse event */ protected void updateFreePolygonSide(EuclidianBoundingBoxHandler hitHandler, AbstractEvent event) { double pointsX[] = new double[poly.getPointsLength()]; double pointsY[] = new double[poly.getPointsLength()]; if (prewPolygon == null) { prewPolygon = AwtFactory.getPrototype().newGeneralPath(); } fixCornerCoords(hitHandler); if (!Double.isNaN(fixCornerX)) { int width = (int) (event.getX() - fixCornerX); double[] currCoords = new double[6]; GPathIterator it = gp.getPathIterator(null); int i = poly.getPointsLength(); while (!it.isDone() && i > 0) { i--; it.currentSegment(currCoords); // left or right side was moved pointsX[i] = fixCornerX + (Math.abs(currCoords[0] - fixCornerX)) * width / oldWidth; pointsY[i] = currCoords[1]; it.next(); } } if (!Double.isNaN(fixCornerY)) { int height = (int) (event.getY() - fixCornerY); double[] currCoords = new double[6]; GPathIterator it = gp.getPathIterator(null); int i = poly.getPointsLength(); while (!it.isDone() && i > 0) { i--; it.currentSegment(currCoords); // bottom or top side was moved pointsX[i] = currCoords[0]; pointsY[i] = fixCornerY + (Math.abs(currCoords[1] - fixCornerY)) * height / oldHeight; it.next(); } } prewPolygon.reset(); prewPolygon.moveTo(pointsX[0], pointsY[0]); for (int index = 1; index < pointsX.length; index++) { prewPolygon.lineTo(pointsX[index], pointsY[index]); } prewPolygon.closePath(); getBoundingBox().setRectangle(prewPolygon.getBounds()); } private final void calculateCorners() { calculateViewCorners(); calculateBounds(); } private final void calculateBounds() { double xmin = Double.MAX_VALUE; double ymin = Double.MAX_VALUE; double xmax = -Double.MAX_VALUE; double ymax = -Double.MAX_VALUE; for (int i = 0; i < poly.getPointsLength(); i++) { double x = poly.getPointX(i); double y = poly.getPointY(i); if (x < xmin) { xmin = x; } if (x > xmax) { xmax = x; } if (y < ymin) { ymin = y; } if (y > ymax) { ymax = y; } } xmin = xmin < extraCoords[0].getX() ? xmin : extraCoords[0].getX(); xmax = xmax > extraCoords[2].getX() ? xmax : extraCoords[2].getX(); ymin = ymin < extraCoords[0].getY() ? ymin : extraCoords[0].getY(); ymax = ymax > extraCoords[2].getY() ? ymax : extraCoords[2].getY(); extraCoords[4].setX(xmin); extraCoords[4].setY(ymin); extraCoords[5].setX(xmax); extraCoords[5].setY(ymin); extraCoords[6].setX(xmax); extraCoords[6].setY(ymax); extraCoords[7].setX(xmin); extraCoords[7].setY(ymax); } private void calculateViewCorners() { extraCoords[0].setX(view.getXmin()); extraCoords[0].setY(view.getYmin()); extraCoords[1].setX(view.getXmax()); extraCoords[1].setY(view.getYmin()); extraCoords[2].setX(view.getXmax()); extraCoords[2].setY(view.getYmax()); extraCoords[3].setX(view.getXmin()); extraCoords[3].setY(view.getYmax()); } private void drawPolygonConvex(Coords[] vertices, int length, boolean reverse) { Log.debug("[POLY] drawPolygonConvex: " + reverse); Coords coordsApex = vertices[0]; coords[0] = coordsApex.getX(); coords[1] = coordsApex.getY(); view.toScreenCoords(coords); double startX = coords[0]; double startY = coords[1]; gpTriangularize.moveTo(coords[0], coords[1]); for (int i = length - 1; i < 0; i--) { Coords coord = vertices[i]; coords[0] = coord.getX(); coords[1] = coord.getY(); view.toScreenCoords(coords); gpTriangularize.lineTo(coords[0], coords[1]); } // we have to move back manually to apex since we may have new fan to // draw gpTriangularize.moveTo(startX, startY); } private void triangularize() { PolygonTriangulation pt = poly.getPolygonTriangulation(); pt.clear(); calculateCorners(); pt.setCorners(extraCoords); try { // simplify the polygon and check if there are at least 3 points // left if (pt.updatePoints() > 2) { // check if the polygon is convex int length = poly.getPointsLength(); Coords[] vertices = new Coords[length + PolygonTriangulation.EXTRA_POINTS]; int j = 0; for (int i = 0; i < poly.getPointsLength(); i++) { vertices[i] = poly.getPointND(i).getCoords(); if (!Kernel.isZero(vertices[i].getZ())) { return; } j++; } vertices[j] = poly.getPointND(0).getCoords(); j++; for (int i = 0; i < PolygonTriangulation.CORNERS; i++) { vertices[j] = extraCoords[i]; j++; } vertices[j] = extraCoords[0]; j++; for (int i = 0; i < PolygonTriangulation.CORNERS; i++) { vertices[j] = extraCoords[4 + i]; j++; } vertices[j] = extraCoords[4]; j++; vertices[j] = extraCoords[0]; Convexity convexity = pt.checkIsConvex(); if (convexity != Convexity.NOT) { boolean reverse = poly.getReverseNormalForDrawing() ^ (convexity == Convexity.CLOCKWISE); drawPolygonConvex(vertices, poly.getPointsLength(), reverse); } else { // set intersections (if needed) and divide the polygon into // non self-intersecting polygons pt.setIntersections(); // convert the set of polygons to triangle fans pt.triangulate(); // compute 3D coords for intersections pt.setCompleteVertices(vertices, poly.getCoordSys(), poly.getPointsLength()); Coords[] verticesWithIntersections = pt.getCompleteVertices( vertices, poly.getPointsLength()); // draw the triangle fans // needs specific path for fans if (gpTriangularize == null) { gpTriangularize = new GeneralPathClipped(view); } else { gpTriangularize.reset(); } for (TriangleFan triFan : pt.getTriangleFans()) { // we need here verticesWithIntersections, for // self-intersecting polygons drawTriangleFan(verticesWithIntersections, triFan); } // create the shape if (geo.isInverseFill()) { setShape(AwtFactory.getPrototype() .newArea(view.getBoundingPath())); getShape().subtract(AwtFactory.getPrototype() .newArea(gpTriangularize)); } else { setShape(AwtFactory.getPrototype() .newArea(gpTriangularize)); } } } } catch (Exception e) { Log.debug(e.getMessage()); e.printStackTrace(); } } private double[][] fanCoords; private static final int FAN_DELTA = 10; private boolean isOutView(double[] c) { return c[0] < -FAN_DELTA || c[1] < -FAN_DELTA || c[0] > view.getWidth() + FAN_DELTA || c[1] > view.getHeight() + FAN_DELTA; } private void drawTriangleFan(Coords[] v, TriangleFan triFan) { Log.debug("[POLY] drawTriangleFan"); int size = triFan.size(); if (fanCoords == null || fanCoords.length < size) { fanCoords = new double[size][]; } // apex coords to screen, check it's inside Coords coordsApex = v[triFan.getApexPoint()]; coords[0] = coordsApex.getX(); coords[1] = coordsApex.getY(); view.toScreenCoords(coords); if (isOutView(coords)) { return; } // fan coords to screen, check it's inside for (int i = 0; i < size; i++) { Coords coord = v[triFan.getVertexIndex(i)]; if (fanCoords[i] == null) { fanCoords[i] = new double[2]; } fanCoords[i][0] = coord.getX(); fanCoords[i][1] = coord.getY(); view.toScreenCoords(fanCoords[i]); if (isOutView(fanCoords[i])) { return; } } // all vertices inside : draw // start gpTriangularize.moveTo(coords[0], coords[1]); // fan for (int i = 0; i < size; i++) { gpTriangularize.lineTo(fanCoords[i][0], fanCoords[i][1]); } // we have to move back manually to apex since we may have new fan to // draw gpTriangularize.lineTo(coords[0], coords[1]); } private boolean isAllPointsOnScreen() { if (poly.getPoints() == null) { return false; } for (GeoPointND p : poly.getPoints()) { double x = view.toScreenCoordXd(p.getInhomX()); double y = view.toScreenCoordYd(p.getInhomY()); if (x < 0 || x > view.getWidth() || y < 0 || y > view.getHeight()) { return false; } } return true; } }