001 /*
002 * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.awt.geom;
027
028 import java.util.*;
029
030 /**
031 * A utility class to iterate over the path segments of an arc
032 * through the PathIterator interface.
033 *
034 * @version 10 Feb 1997
035 * @author Jim Graham
036 */
037 class ArcIterator implements PathIterator {
038 double x, y, w, h, angStRad, increment, cv;
039 AffineTransform affine;
040 int index;
041 int arcSegs;
042 int lineSegs;
043
044 ArcIterator(Arc2D a, AffineTransform at) {
045 this .w = a.getWidth() / 2;
046 this .h = a.getHeight() / 2;
047 this .x = a.getX() + w;
048 this .y = a.getY() + h;
049 this .angStRad = -Math.toRadians(a.getAngleStart());
050 this .affine = at;
051 double ext = -a.getAngleExtent();
052 if (ext >= 360.0 || ext <= -360) {
053 arcSegs = 4;
054 this .increment = Math.PI / 2;
055 // btan(Math.PI / 2);
056 this .cv = 0.5522847498307933;
057 if (ext < 0) {
058 increment = -increment;
059 cv = -cv;
060 }
061 } else {
062 arcSegs = (int) Math.ceil(Math.abs(ext) / 90.0);
063 this .increment = Math.toRadians(ext / arcSegs);
064 this .cv = btan(increment);
065 if (cv == 0) {
066 arcSegs = 0;
067 }
068 }
069 switch (a.getArcType()) {
070 case Arc2D.OPEN:
071 lineSegs = 0;
072 break;
073 case Arc2D.CHORD:
074 lineSegs = 1;
075 break;
076 case Arc2D.PIE:
077 lineSegs = 2;
078 break;
079 }
080 if (w < 0 || h < 0) {
081 arcSegs = lineSegs = -1;
082 }
083 }
084
085 /**
086 * Return the winding rule for determining the insideness of the
087 * path.
088 * @see #WIND_EVEN_ODD
089 * @see #WIND_NON_ZERO
090 */
091 public int getWindingRule() {
092 return WIND_NON_ZERO;
093 }
094
095 /**
096 * Tests if there are more points to read.
097 * @return true if there are more points to read
098 */
099 public boolean isDone() {
100 return index > arcSegs + lineSegs;
101 }
102
103 /**
104 * Moves the iterator to the next segment of the path forwards
105 * along the primary direction of traversal as long as there are
106 * more points in that direction.
107 */
108 public void next() {
109 index++;
110 }
111
112 /*
113 * btan computes the length (k) of the control segments at
114 * the beginning and end of a cubic bezier that approximates
115 * a segment of an arc with extent less than or equal to
116 * 90 degrees. This length (k) will be used to generate the
117 * 2 bezier control points for such a segment.
118 *
119 * Assumptions:
120 * a) arc is centered on 0,0 with radius of 1.0
121 * b) arc extent is less than 90 degrees
122 * c) control points should preserve tangent
123 * d) control segments should have equal length
124 *
125 * Initial data:
126 * start angle: ang1
127 * end angle: ang2 = ang1 + extent
128 * start point: P1 = (x1, y1) = (cos(ang1), sin(ang1))
129 * end point: P4 = (x4, y4) = (cos(ang2), sin(ang2))
130 *
131 * Control points:
132 * P2 = (x2, y2)
133 * | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1)
134 * | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1)
135 *
136 * P3 = (x3, y3)
137 * | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2)
138 * | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2)
139 *
140 * The formula for this length (k) can be found using the
141 * following derivations:
142 *
143 * Midpoints:
144 * a) bezier (t = 1/2)
145 * bPm = P1 * (1-t)^3 +
146 * 3 * P2 * t * (1-t)^2 +
147 * 3 * P3 * t^2 * (1-t) +
148 * P4 * t^3 =
149 * = (P1 + 3P2 + 3P3 + P4)/8
150 *
151 * b) arc
152 * aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2))
153 *
154 * Let angb = (ang2 - ang1)/2; angb is half of the angle
155 * between ang1 and ang2.
156 *
157 * Solve the equation bPm == aPm
158 *
159 * a) For xm coord:
160 * x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2)
161 *
162 * cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) +
163 * 3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) =
164 * = 8*cos((ang1 + ang2)/2)
165 *
166 * 4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) =
167 * = 8*cos((ang1 + ang2)/2)
168 *
169 * 8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) +
170 * 6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) =
171 * = 8*cos((ang1 + ang2)/2)
172 *
173 * 4*cos(angb) + 3*k*sin(angb) = 4
174 *
175 * k = 4 / 3 * (1 - cos(angb)) / sin(angb)
176 *
177 * b) For ym coord we derive the same formula.
178 *
179 * Since this formula can generate "NaN" values for small
180 * angles, we will derive a safer form that does not involve
181 * dividing by very small values:
182 * (1 - cos(angb)) / sin(angb) =
183 * = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) =
184 * = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) =
185 * = sin(angb)^2 / sin(angb)*(1 + cos(angb)) =
186 * = sin(angb) / (1 + cos(angb))
187 *
188 */
189 private static double btan(double increment) {
190 increment /= 2.0;
191 return 4.0 / 3.0 * Math.sin(increment)
192 / (1.0 + Math.cos(increment));
193 }
194
195 /**
196 * Returns the coordinates and type of the current path segment in
197 * the iteration.
198 * The return value is the path segment type:
199 * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
200 * A float array of length 6 must be passed in and may be used to
201 * store the coordinates of the point(s).
202 * Each point is stored as a pair of float x,y coordinates.
203 * SEG_MOVETO and SEG_LINETO types will return one point,
204 * SEG_QUADTO will return two points,
205 * SEG_CUBICTO will return 3 points
206 * and SEG_CLOSE will not return any points.
207 * @see #SEG_MOVETO
208 * @see #SEG_LINETO
209 * @see #SEG_QUADTO
210 * @see #SEG_CUBICTO
211 * @see #SEG_CLOSE
212 */
213 public int currentSegment(float[] coords) {
214 if (isDone()) {
215 throw new NoSuchElementException(
216 "arc iterator out of bounds");
217 }
218 double angle = angStRad;
219 if (index == 0) {
220 coords[0] = (float) (x + Math.cos(angle) * w);
221 coords[1] = (float) (y + Math.sin(angle) * h);
222 if (affine != null) {
223 affine.transform(coords, 0, coords, 0, 1);
224 }
225 return SEG_MOVETO;
226 }
227 if (index > arcSegs) {
228 if (index == arcSegs + lineSegs) {
229 return SEG_CLOSE;
230 }
231 coords[0] = (float) x;
232 coords[1] = (float) y;
233 if (affine != null) {
234 affine.transform(coords, 0, coords, 0, 1);
235 }
236 return SEG_LINETO;
237 }
238 angle += increment * (index - 1);
239 double relx = Math.cos(angle);
240 double rely = Math.sin(angle);
241 coords[0] = (float) (x + (relx - cv * rely) * w);
242 coords[1] = (float) (y + (rely + cv * relx) * h);
243 angle += increment;
244 relx = Math.cos(angle);
245 rely = Math.sin(angle);
246 coords[2] = (float) (x + (relx + cv * rely) * w);
247 coords[3] = (float) (y + (rely - cv * relx) * h);
248 coords[4] = (float) (x + relx * w);
249 coords[5] = (float) (y + rely * h);
250 if (affine != null) {
251 affine.transform(coords, 0, coords, 0, 3);
252 }
253 return SEG_CUBICTO;
254 }
255
256 /**
257 * Returns the coordinates and type of the current path segment in
258 * the iteration.
259 * The return value is the path segment type:
260 * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
261 * A double array of length 6 must be passed in and may be used to
262 * store the coordinates of the point(s).
263 * Each point is stored as a pair of double x,y coordinates.
264 * SEG_MOVETO and SEG_LINETO types will return one point,
265 * SEG_QUADTO will return two points,
266 * SEG_CUBICTO will return 3 points
267 * and SEG_CLOSE will not return any points.
268 * @see #SEG_MOVETO
269 * @see #SEG_LINETO
270 * @see #SEG_QUADTO
271 * @see #SEG_CUBICTO
272 * @see #SEG_CLOSE
273 */
274 public int currentSegment(double[] coords) {
275 if (isDone()) {
276 throw new NoSuchElementException(
277 "arc iterator out of bounds");
278 }
279 double angle = angStRad;
280 if (index == 0) {
281 coords[0] = x + Math.cos(angle) * w;
282 coords[1] = y + Math.sin(angle) * h;
283 if (affine != null) {
284 affine.transform(coords, 0, coords, 0, 1);
285 }
286 return SEG_MOVETO;
287 }
288 if (index > arcSegs) {
289 if (index == arcSegs + lineSegs) {
290 return SEG_CLOSE;
291 }
292 coords[0] = x;
293 coords[1] = y;
294 if (affine != null) {
295 affine.transform(coords, 0, coords, 0, 1);
296 }
297 return SEG_LINETO;
298 }
299 angle += increment * (index - 1);
300 double relx = Math.cos(angle);
301 double rely = Math.sin(angle);
302 coords[0] = x + (relx - cv * rely) * w;
303 coords[1] = y + (rely + cv * relx) * h;
304 angle += increment;
305 relx = Math.cos(angle);
306 rely = Math.sin(angle);
307 coords[2] = x + (relx + cv * rely) * w;
308 coords[3] = y + (rely - cv * relx) * h;
309 coords[4] = x + relx * w;
310 coords[5] = y + rely * h;
311 if (affine != null) {
312 affine.transform(coords, 0, coords, 0, 3);
313 }
314 return SEG_CUBICTO;
315 }
316 }
|