package net.sourceforge.fidocadj.primitives; import java.io.*; import java.util.*; import net.sourceforge.fidocadj.dialogs.*; import net.sourceforge.fidocadj.export.*; import net.sourceforge.fidocadj.geom.*; import net.sourceforge.fidocadj.globals.*; import net.sourceforge.fidocadj.graphic.*; /** Class to handle the Polygon primitive. <pre> This file is part of FidoCadJ. FidoCadJ 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, either version 3 of the License, or (at your option) any later version. FidoCadJ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FidoCadJ. If not, @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>. Copyright 2007-2014 by Davide Bucci </pre> @author Davide Bucci */ public final class PrimitivePolygon extends GraphicPrimitive { private int nPoints; private boolean isFilled; private int dashStyle; private PolygonInterface p; // If needed, we might increase this stuff. // In other words, we initially create space for storing 5 points and // we increase that if needed. int storageSize=5; // Some private data cached. private int xmin, ymin; private int width, height; // Those are data which are kept for the fast redraw of this primitive. // Basically, they are calculated once and then used as much as possible // without having to calculate everything from scratch. private float w; /** Gets the number of control points used. @return the number of points used by the primitive */ public int getControlPointNumber() { return nPoints+2; } /** Constructor. @param f the name of the font for attached text. @param size the size of the font for attached text. */ public PrimitivePolygon(String f, int size) { super(); isFilled=false; nPoints=0; p = null; initPrimitive(storageSize, f, size); } /** Create a polygon. Add points with the addPoint method. @param f specifies if the polygon should be filled @param layer the layer to be used. @param dashSt the dash style @param font the name of the font for attached text. @param size the size of the font for attached text. */ public PrimitivePolygon(boolean f, int layer, int dashSt, String font, int size) { super(); p = null; initPrimitive(storageSize, font, size); nPoints=0; isFilled=f; dashStyle=dashSt; setLayer(layer); } /** Remove the control point of the polygon closest to the given coordinates, if the distance is less than a certain tolerance @param x the x coordinate of the target @param y the y coordinate of the target @param tolerance the tolerance */ public void removePoint(int x, int y, double tolerance) { // We can not have a polygon with less than three vertices if (nPoints<=3) return; int i; double distance; double min_distance= GeometricDistances.pointToPoint(virtualPoint[0].x, virtualPoint[0].y,x,y); int sel_i=-1; for(i=1;i<nPoints;++i) { distance = GeometricDistances.pointToPoint(virtualPoint[i].x, virtualPoint[i].y,x,y); if (distance<min_distance) { min_distance=distance; sel_i=i; } } // Check if the control node losest to the given coordinates // is closer than the given tolerance if(min_distance<=tolerance){ --nPoints; for(i=0;i<nPoints;++i) { // Shift all the points subsequent to the one which needs // to be erased. if(i>=sel_i) { virtualPoint[i].x=virtualPoint[i+1].x; virtualPoint[i].y=virtualPoint[i+1].y; } changed=true; } } } /** Add a point in the polygon, by splitting the closes side to the point inserted. @param px x coordinates of the point to insert. @param py y coordinates of the point to insert. */ public void addPointClosest(int px, int py) { int[] xp=new int[storageSize]; int[] yp=new int[storageSize]; int k; for(k=0;k<nPoints;++k){ xp[k]=virtualPoint[k].x; yp[k]=virtualPoint[k].y; } // we calculate the distance between the // given point and all the segments composing the polygon and we // take the smallest one. int distance=(int)Math.sqrt((px-xp[0])*(px-xp[0])+ (py-yp[0])*(py-yp[0])); int j; int d; int minv=0; for(int i=0; i<nPoints; ++i) { j=i; if (j==nPoints-1) j=-1; d=GeometricDistances.pointToSegment(xp[i], yp[i], xp[j+1], yp[j+1], px,py); if(d<distance) { distance = d; minv=j+1; } } // Now minv contains the index of the vertex before the one which // should be entered. We begin to enter the new vertex at the end... addPoint(px, py); // ...then we do the swap int dummy; for(int i=nPoints-1; i>minv; --i) { virtualPoint[i].x=virtualPoint[i-1].x; virtualPoint[i].y=virtualPoint[i-1].y; } virtualPoint[minv].x=px; virtualPoint[minv].y=py; changed = true; } /** Add a point at the current polygon @param x the x coordinate of the point. @param y the y coordinate of the point. */ public void addPoint(int x, int y) { if(nPoints+2>=storageSize) { int o_n=storageSize; int i; storageSize += 10; PointG[] nv = new PointG[storageSize]; for(i=0;i<o_n;++i) { nv[i]=virtualPoint[i]; } for(;i<storageSize;++i) { nv[i]=new PointG(); } virtualPoint=nv; } // And now we enter the position of the point we are interested with virtualPoint[nPoints].x=x; virtualPoint[nPoints++].y=y; // We do need to shift the two points describing the position // of the text lines virtualPoint[getNameVirtualPointNumber()].x=x+5; virtualPoint[getNameVirtualPointNumber()].y=y+5; virtualPoint[getValueVirtualPointNumber()].x=x+5; virtualPoint[getValueVirtualPointNumber()].y=y+10; changed = true; } /** Store the polygon, which must be already calculated. @param coordSys the coordinates mapping system. @param g the graphic context. */ public void createPolygon(MapCoordinates coordSys, GraphicsInterface g) { int j; xmin = Integer.MAX_VALUE; ymin = Integer.MAX_VALUE; int xmax = -Integer.MAX_VALUE; int ymax = -Integer.MAX_VALUE; int x, y; p=g.createPolygon(); p.reset(); for(j=0;j<nPoints;++j) { x = coordSys.mapX(virtualPoint[j].x,virtualPoint[j].y); y = coordSys.mapY(virtualPoint[j].x,virtualPoint[j].y); p.addPoint(x,y); if (x<xmin) xmin=x; if (x>xmax) xmax=x; if(y<ymin) ymin=y; if(y>ymax) ymax=y; } width = xmax-xmin; height = ymax-ymin; } /** Draw the graphic primitive on the given graphic context. @param g the graphic context in which the primitive should be drawn. @param coordSys the graphic coordinates system to be applied. @param layerV the layer description. */ public void draw(GraphicsInterface g, MapCoordinates coordSys, Vector layerV) { if(!selectLayer(g,layerV)) return; drawText(g, coordSys, layerV, -1); if(changed) { changed=false; createPolygon(coordSys, g); w = (float)(Globals.lineWidth*coordSys.getXMagnitude()); if (w<D_MIN) w=D_MIN; } if(!g.hitClip(xmin,ymin, width, height)) return; g.applyStroke(w, dashStyle); // Here we implement a small optimization: when the polygon is very // small, it is not filled. if (isFilled && width>=2 && height >=2) g.fillPolygon(p); g.drawPolygon(p); // It seems that under MacOSX, drawing a polygon by cycling with // the lines is much more efficient than the drawPolygon method. // Probably, a further investigation is needed to determine if // this situation is the same with more recent Java runtimes // (mine is 1.5.something on an iMac G5 at 2 GHz and I made // the same comparison with the same results with a MacBook 2GHz). /* for(int i=0; i<nPoints-1; ++i) { g.drawLine(p.xpoints[i], p.ypoints[i], p.xpoints[i+1], p.ypoints[i+1]); } g.drawLine(p.xpoints[nPoints-1], p.ypoints[nPoints-1], p.xpoints[0], p.ypoints[0]); */ } /** Parse a token array and store the graphic data for a given primitive Obviously, that routine should be called *after* having recognized that the called primitive is correct. That routine also sets the current layer. @param tokens the tokens to be processed. tokens[0] should be the command of the actual primitive. @param N the number of tokens present in the array @throws IOException if the arguments are incorrect or the primitive is invalid. */ public void parseTokens(String[] tokens, int N) throws IOException { changed=true; // assert it is the correct primitive if (tokens[0].equals("PP")||tokens[0].equals("PV")) { if (N<6) { IOException E=new IOException("bad arguments on PP/PV"); throw E; } // Load the points in the virtual points associated to the // current primitive. int j=1; int i=0; int x1 = 0; int y1 = 0; while(j<N-1){ if (j+1<N-1 && tokens[j+1].equals("FCJ")) break; x1 = Integer.parseInt(tokens[j++]); y1 = Integer.parseInt(tokens[j++]); ++i; addPoint(x1,y1); } nPoints=i; virtualPoint[getNameVirtualPointNumber()].x=x1+5; virtualPoint[getNameVirtualPointNumber()].y=y1+5; virtualPoint[getValueVirtualPointNumber()].x=x1+5; virtualPoint[getValueVirtualPointNumber()].y=y1+10; if(N>j) { parseLayer(tokens[j++]); if(j<N-1 && tokens[j++].equals("FCJ")) { dashStyle = checkDashStyle(Integer.parseInt(tokens[j++])); } } if (tokens[0].equals("PP")) isFilled=true; else isFilled=false; } else { IOException E=new IOException("PP/PV: Invalid primitive:"+tokens[0]+ " programming error?"); throw E; } } /** Get the control parameters of the given primitive. @return a vector of ParameterDescription containing each control parameter. The first parameters should always be the virtual points. */ public Vector<ParameterDescription> getControls() { Vector<ParameterDescription> v=super.getControls(); ParameterDescription pd = new ParameterDescription(); pd.parameter=Boolean.valueOf(isFilled); pd.description=Globals.messages.getString("ctrl_filled"); v.add(pd); pd = new ParameterDescription(); pd.parameter=new DashInfo(dashStyle); pd.description=Globals.messages.getString("ctrl_dash_style"); pd.isExtension = true; v.add(pd); return v; } /** Set the control parameters of the given primitive. This method is specular to getControls(). @param v a vector of ParameterDescription containing each control parameter. The first parameters should always be the virtual points. @return the next index in v to be scanned (if needed) after the execution of this function. */ public int setControls(Vector<ParameterDescription> v) { int i=super.setControls(v); ParameterDescription pd; pd=(ParameterDescription)v.get(i); ++i; // Check, just for sure... if (pd.parameter instanceof Boolean) isFilled=((Boolean)pd.parameter).booleanValue(); else System.out.println("Warning: unexpected parameter!"+pd); pd=(ParameterDescription)v.get(i++); if (pd.parameter instanceof DashInfo) dashStyle=((DashInfo)pd.parameter).style; else System.out.println("Warning: unexpected parameter!"+pd); // Parameters validation and correction if(dashStyle>=Globals.dashNumber) dashStyle=Globals.dashNumber-1; if(dashStyle<0) dashStyle=0; return i; } /** Gets the distance (in primitive's coordinates space) between a given point and the primitive. When it is reasonable, the behaviour can be binary (polygons, ovals...). In other cases (lines, points), it can be proportional. @param px the x coordinate of the given point. @param py the y coordinate of the given point. @return the distance in logical units. */ public int getDistanceToPoint(int px, int py) { // Here we check if the given point lies inside the text areas if(checkText(px, py)) return 0; int[] xp=new int[storageSize]; int[] yp=new int[storageSize]; int k; for(k=0;k<nPoints;++k){ xp[k]=virtualPoint[k].x; yp[k]=virtualPoint[k].y; } if(isFilled && GeometricDistances.pointInPolygon(xp,yp,nPoints, px,py)) return 1; // If the curve is not filled, we calculate the distance between the // given point and all the segments composing the curve and we // take the smallest one. int distance=(int)Math.sqrt((px-xp[0])*(px-xp[0])+ (py-yp[0])*(py-yp[0])); int j; int d; for(int i=0; i<nPoints; ++i) { j=i; if (j==nPoints-1) j=-1; d=GeometricDistances.pointToSegment(xp[i], yp[i], xp[j+1], yp[j+1], px,py); if(d<distance) distance = d; } return distance; } /** Obtain a string command descripion of the primitive. @param extensions true if FidoCadJ extensions to the old FidoCAD format should be active. @return the FIDOCAD command line. */ public String toString(boolean extensions) { StringBuffer temp=new StringBuffer(25); if(isFilled) temp.append("PP "); else temp.append("PV "); for(int i=0; i<nPoints;++i) { temp.append(virtualPoint[i].x); temp.append(" "); temp.append(virtualPoint[i].y); temp.append(" "); } temp.append(getLayer()); temp.append("\n"); String cmd=temp.toString(); if(extensions && (dashStyle>0 || hasName() || hasValue())) { String text = "0"; if (name.length()!=0 || value.length()!=0) text = "1"; cmd+="FCJ "+dashStyle+" "+text+"\n"; } // The false is needed since saveText should not write the FCJ tag. cmd+=saveText(false); return cmd; } /** Export the primitive on a vector graphic format. @param exp the export interface to employ. @param cs the coordinate mapping to employ. @throws IOException if a problem occurs, such as it is impossible to write on the output file. */ public void export(ExportInterface exp, MapCoordinates cs) throws IOException { exportText(exp, cs, -1); PointDouble[] vertices = new PointDouble[nPoints]; for(int i=0; i<nPoints;++i){ vertices[i]=new PointDouble(); vertices[i].x=cs.mapX(virtualPoint[i].x,virtualPoint[i].y); vertices[i].y=cs.mapY(virtualPoint[i].x,virtualPoint[i].y); } exp.exportPolygon(vertices, nPoints, isFilled, getLayer(), dashStyle, Globals.lineWidth*cs.getXMagnitude()); } /** Get the number of the virtual point associated to the Name property @return the number of the virtual point associated to the Name property */ public int getNameVirtualPointNumber() { return nPoints; } /** Get the number of the virtual point associated to the Value property @return the number of the virtual point associated to the Value property */ public int getValueVirtualPointNumber() { return nPoints+1; } }