001/* 002 * @(#)ArcIterator.java 1.17 05/11/17 003 * 004 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 005 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 006 */ 007 008package armyc2.c2sd.graphics2d; 009 010//import java.util.*; 011 012/** 013 * A utility class to iterate over the path segments of an arc 014 * through the PathIterator interface. 015 * 016 * @version 10 Feb 1997 017 * @author Jim Graham 018 */ 019public class ArcIterator /* implements PathIterator */{ 020 double x, y, w, h, angStRad, increment, cv; 021 AffineTransform affine; 022 int index; 023 int arcSegs; 024 int lineSegs; 025 026 ArcIterator(Arc2D a, AffineTransform at) { 027 this.w = a.getWidth() / 2; 028 this.h = a.getHeight() / 2; 029 this.x = a.getX() + w; 030 this.y = a.getY() + h; 031 this.angStRad = -Math.toRadians(a.getAngleStart()); 032 this.affine = at; 033 double ext = -a.getAngleExtent(); 034 if (ext >= 360.0 || ext <= -360) { 035 arcSegs = 4; 036 this.increment = Math.PI / 2; 037 // btan(Math.PI / 2); 038 this.cv = 0.5522847498307933; 039 if (ext < 0) { 040 increment = -increment; 041 cv = -cv; 042 } 043 } else { 044 arcSegs = (int) Math.ceil(Math.abs(ext) / 90.0); 045 this.increment = Math.toRadians(ext / arcSegs); 046 this.cv = btan(increment); 047 if (cv == 0) { 048 arcSegs = 0; 049 } 050 } 051 switch (a.getArcType()) { 052 case Arc2D.OPEN: 053 lineSegs = 0; 054 break; 055 case Arc2D.CHORD: 056 lineSegs = 1; 057 break; 058 case Arc2D.PIE: 059 lineSegs = 2; 060 break; 061 } 062 if (w < 0 || h < 0) { 063 arcSegs = lineSegs = -1; 064 } 065 } 066 067 /** 068 * Return the winding rule for determining the insideness of the 069 * path. 070 * @see #WIND_EVEN_ODD 071 * @see #WIND_NON_ZERO 072 */ 073 public int getWindingRule() { 074 return PathIterator.WIND_NON_ZERO; 075 } 076 077 /** 078 * Tests if there are more points to read. 079 * @return true if there are more points to read 080 */ 081 public boolean isDone() { 082 return index > arcSegs + lineSegs; 083 } 084 085 /** 086 * Moves the iterator to the next segment of the path forwards 087 * along the primary direction of traversal as long as there are 088 * more points in that direction. 089 */ 090 public void next() { 091 index++; 092 } 093 094 /* 095 * btan computes the length (k) of the control segments at 096 * the beginning and end of a cubic bezier that approximates 097 * a segment of an arc with extent less than or equal to 098 * 90 degrees. This length (k) will be used to generate the 099 * 2 bezier control points for such a segment. 100 * 101 * Assumptions: 102 * a) arc is centered on 0,0 with radius of 1.0 103 * b) arc extent is less than 90 degrees 104 * c) control points should preserve tangent 105 * d) control segments should have equal length 106 * 107 * Initial data: 108 * start angle: ang1 109 * end angle: ang2 = ang1 + extent 110 * start point: P1 = (x1, y1) = (cos(ang1), sin(ang1)) 111 * end point: P4 = (x4, y4) = (cos(ang2), sin(ang2)) 112 * 113 * Control points: 114 * P2 = (x2, y2) 115 * | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1) 116 * | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1) 117 * 118 * P3 = (x3, y3) 119 * | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2) 120 * | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2) 121 * 122 * The formula for this length (k) can be found using the 123 * following derivations: 124 * 125 * Midpoints: 126 * a) bezier (t = 1/2) 127 * bPm = P1 * (1-t)^3 + 128 * 3 * P2 * t * (1-t)^2 + 129 * 3 * P3 * t^2 * (1-t) + 130 * P4 * t^3 = 131 * = (P1 + 3P2 + 3P3 + P4)/8 132 * 133 * b) arc 134 * aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2)) 135 * 136 * Let angb = (ang2 - ang1)/2; angb is half of the angle 137 * between ang1 and ang2. 138 * 139 * Solve the equation bPm == aPm 140 * 141 * a) For xm coord: 142 * x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2) 143 * 144 * cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) + 145 * 3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) = 146 * = 8*cos((ang1 + ang2)/2) 147 * 148 * 4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) = 149 * = 8*cos((ang1 + ang2)/2) 150 * 151 * 8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) + 152 * 6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) = 153 * = 8*cos((ang1 + ang2)/2) 154 * 155 * 4*cos(angb) + 3*k*sin(angb) = 4 156 * 157 * k = 4 / 3 * (1 - cos(angb)) / sin(angb) 158 * 159 * b) For ym coord we derive the same formula. 160 * 161 * Since this formula can generate "NaN" values for small 162 * angles, we will derive a safer form that does not involve 163 * dividing by very small values: 164 * (1 - cos(angb)) / sin(angb) = 165 * = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) = 166 * = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) = 167 * = sin(angb)^2 / sin(angb)*(1 + cos(angb)) = 168 * = sin(angb) / (1 + cos(angb)) 169 * 170 */ 171 private static double btan(double increment) { 172 increment /= 2.0; 173 return 4.0 / 3.0 * Math.sin(increment) / (1.0 + Math.cos(increment)); 174 } 175 176 /** 177 * Returns the coordinates and type of the current path segment in 178 * the iteration. 179 * The return value is the path segment type: 180 * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 181 * A float array of length 6 must be passed in and may be used to 182 * store the coordinates of the point(s). 183 * Each point is stored as a pair of float x,y coordinates. 184 * SEG_MOVETO and SEG_LINETO types will return one point, 185 * SEG_QUADTO will return two points, 186 * SEG_CUBICTO will return 3 points 187 * and SEG_CLOSE will not return any points. 188 * @see #SEG_MOVETO 189 * @see #SEG_LINETO 190 * @see #SEG_QUADTO 191 * @see #SEG_CUBICTO 192 * @see #SEG_CLOSE 193 */ 194 public int currentSegmentFlt(float[] coords) { 195 if (isDone()) { 196 //throw new NoSuchElementException("arc iterator out of bounds"); 197 System.out.println("arc iterator out of bounds"); 198 return -1; 199 } 200 double angle = angStRad; 201 if (index == 0) { 202 coords[0] = (float) (x + Math.cos(angle) * w); 203 coords[1] = (float) (y + Math.sin(angle) * h); 204 return PathIterator.SEG_MOVETO; 205 } 206 if (index > arcSegs) { 207 if (index == arcSegs + lineSegs) { 208 return PathIterator.SEG_CLOSE; 209 } 210 coords[0] = (float) x; 211 coords[1] = (float) y; 212 return PathIterator.SEG_LINETO; 213 } 214 angle += increment * (index - 1); 215 double relx = Math.cos(angle); 216 double rely = Math.sin(angle); 217 coords[0] = (float) (x + (relx - cv * rely) * w); 218 coords[1] = (float) (y + (rely + cv * relx) * h); 219 angle += increment; 220 relx = Math.cos(angle); 221 rely = Math.sin(angle); 222 coords[2] = (float) (x + (relx + cv * rely) * w); 223 coords[3] = (float) (y + (rely - cv * relx) * h); 224 coords[4] = (float) (x + relx * w); 225 coords[5] = (float) (y + rely * h); 226 return PathIterator.SEG_CUBICTO; 227 } 228 229 /** 230 * Returns the coordinates and type of the current path segment in 231 * the iteration. 232 * The return value is the path segment type: 233 * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 234 * A double array of length 6 must be passed in and may be used to 235 * store the coordinates of the point(s). 236 * Each point is stored as a pair of double x,y coordinates. 237 * SEG_MOVETO and SEG_LINETO types will return one point, 238 * SEG_QUADTO will return two points, 239 * SEG_CUBICTO will return 3 points 240 * and SEG_CLOSE will not return any points. 241 * @see #SEG_MOVETO 242 * @see #SEG_LINETO 243 * @see #SEG_QUADTO 244 * @see #SEG_CUBICTO 245 * @see #SEG_CLOSE 246 */ 247 public int currentSegment(double[] coords) { 248 if (isDone()) { 249 //throw new NoSuchElementException("arc iterator out of bounds"); 250 } 251 double angle = angStRad; 252 if (index == 0) { 253 coords[0] = x + Math.cos(angle) * w; 254 coords[1] = y + Math.sin(angle) * h; 255 return PathIterator.SEG_MOVETO; 256 } 257 if (index > arcSegs) { 258 if (index == arcSegs + lineSegs) { 259 return PathIterator.SEG_CLOSE; 260 } 261 coords[0] = x; 262 coords[1] = y; 263 return PathIterator.SEG_LINETO; 264 } 265 angle += increment * (index - 1); 266 double relx = Math.cos(angle); 267 double rely = Math.sin(angle); 268 coords[0] = x + (relx - cv * rely) * w; 269 coords[1] = y + (rely + cv * relx) * h; 270 angle += increment; 271 relx = Math.cos(angle); 272 rely = Math.sin(angle); 273 coords[2] = x + (relx + cv * rely) * w; 274 coords[3] = y + (rely - cv * relx) * h; 275 coords[4] = x + relx * w; 276 coords[5] = y + rely * h; 277 return PathIterator.SEG_CUBICTO; 278 } 279}