1. /*
  2. * @(#)ArcIterator.java 1.16 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.geom;
  8. import java.util.*;
  9. /**
  10. * A utility class to iterate over the path segments of an arc
  11. * through the PathIterator interface.
  12. *
  13. * @version 10 Feb 1997
  14. * @author Jim Graham
  15. */
  16. class ArcIterator implements PathIterator {
  17. double x, y, w, h, angStRad, increment, cv;
  18. AffineTransform affine;
  19. int index;
  20. int arcSegs;
  21. int lineSegs;
  22. ArcIterator(Arc2D a, AffineTransform at) {
  23. this.w = a.getWidth() / 2;
  24. this.h = a.getHeight() / 2;
  25. this.x = a.getX() + w;
  26. this.y = a.getY() + h;
  27. this.angStRad = -Math.toRadians(a.getAngleStart());
  28. this.affine = at;
  29. double ext = -a.getAngleExtent();
  30. if (ext >= 360.0 || ext <= -360) {
  31. arcSegs = 4;
  32. this.increment = Math.PI / 2;
  33. // btan(Math.PI / 2);
  34. this.cv = 0.5522847498307933;
  35. if (ext < 0) {
  36. increment = -increment;
  37. cv = -cv;
  38. }
  39. } else {
  40. arcSegs = (int) Math.ceil(Math.abs(ext) / 90.0);
  41. this.increment = Math.toRadians(ext / arcSegs);
  42. this.cv = btan(increment);
  43. if (cv == 0) {
  44. arcSegs = 0;
  45. }
  46. }
  47. switch (a.getArcType()) {
  48. case Arc2D.OPEN:
  49. lineSegs = 0;
  50. break;
  51. case Arc2D.CHORD:
  52. lineSegs = 1;
  53. break;
  54. case Arc2D.PIE:
  55. lineSegs = 2;
  56. break;
  57. }
  58. if (w < 0 || h < 0) {
  59. arcSegs = lineSegs = -1;
  60. }
  61. }
  62. /**
  63. * Return the winding rule for determining the insideness of the
  64. * path.
  65. * @see #WIND_EVEN_ODD
  66. * @see #WIND_NON_ZERO
  67. */
  68. public int getWindingRule() {
  69. return WIND_NON_ZERO;
  70. }
  71. /**
  72. * Tests if there are more points to read.
  73. * @return true if there are more points to read
  74. */
  75. public boolean isDone() {
  76. return index > arcSegs + lineSegs;
  77. }
  78. /**
  79. * Moves the iterator to the next segment of the path forwards
  80. * along the primary direction of traversal as long as there are
  81. * more points in that direction.
  82. */
  83. public void next() {
  84. index++;
  85. }
  86. /*
  87. * btan computes the length (k) of the control segments at
  88. * the beginning and end of a cubic bezier that approximates
  89. * a segment of an arc with extent less than or equal to
  90. * 90 degrees. This length (k) will be used to generate the
  91. * 2 bezier control points for such a segment.
  92. *
  93. * Assumptions:
  94. * a) arc is centered on 0,0 with radius of 1.0
  95. * b) arc extent is less than 90 degrees
  96. * c) control points should preserve tangent
  97. * d) control segments should have equal length
  98. *
  99. * Initial data:
  100. * start angle: ang1
  101. * end angle: ang2 = ang1 + extent
  102. * start point: P1 = (x1, y1) = (cos(ang1), sin(ang1))
  103. * end point: P4 = (x4, y4) = (cos(ang2), sin(ang2))
  104. *
  105. * Control points:
  106. * P2 = (x2, y2)
  107. * | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1)
  108. * | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1)
  109. *
  110. * P3 = (x3, y3)
  111. * | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2)
  112. * | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2)
  113. *
  114. * The formula for this length (k) can be found using the
  115. * following derivations:
  116. *
  117. * Midpoints:
  118. * a) bezier (t = 1/2)
  119. * bPm = P1 * (1-t)^3 +
  120. * 3 * P2 * t * (1-t)^2 +
  121. * 3 * P3 * t^2 * (1-t) +
  122. * P4 * t^3 =
  123. * = (P1 + 3P2 + 3P3 + P4)/8
  124. *
  125. * b) arc
  126. * aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2))
  127. *
  128. * Let angb = (ang2 - ang1)/2; angb is half of the angle
  129. * between ang1 and ang2.
  130. *
  131. * Solve the equation bPm == aPm
  132. *
  133. * a) For xm coord:
  134. * x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2)
  135. *
  136. * cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) +
  137. * 3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) =
  138. * = 8*cos((ang1 + ang2)/2)
  139. *
  140. * 4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) =
  141. * = 8*cos((ang1 + ang2)/2)
  142. *
  143. * 8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) +
  144. * 6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) =
  145. * = 8*cos((ang1 + ang2)/2)
  146. *
  147. * 4*cos(angb) + 3*k*sin(angb) = 4
  148. *
  149. * k = 4 / 3 * (1 - cos(angb)) / sin(angb)
  150. *
  151. * b) For ym coord we derive the same formula.
  152. *
  153. * Since this formula can generate "NaN" values for small
  154. * angles, we will derive a safer form that does not involve
  155. * dividing by very small values:
  156. * (1 - cos(angb)) / sin(angb) =
  157. * = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) =
  158. * = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) =
  159. * = sin(angb)^2 / sin(angb)*(1 + cos(angb)) =
  160. * = sin(angb) / (1 + cos(angb))
  161. *
  162. */
  163. private static double btan(double increment) {
  164. increment /= 2.0;
  165. return 4.0 / 3.0 * Math.sin(increment) / (1.0 + Math.cos(increment));
  166. }
  167. /**
  168. * Returns the coordinates and type of the current path segment in
  169. * the iteration.
  170. * The return value is the path segment type:
  171. * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
  172. * A float array of length 6 must be passed in and may be used to
  173. * store the coordinates of the point(s).
  174. * Each point is stored as a pair of float x,y coordinates.
  175. * SEG_MOVETO and SEG_LINETO types will return one point,
  176. * SEG_QUADTO will return two points,
  177. * SEG_CUBICTO will return 3 points
  178. * and SEG_CLOSE will not return any points.
  179. * @see #SEG_MOVETO
  180. * @see #SEG_LINETO
  181. * @see #SEG_QUADTO
  182. * @see #SEG_CUBICTO
  183. * @see #SEG_CLOSE
  184. */
  185. public int currentSegment(float[] coords) {
  186. if (isDone()) {
  187. throw new NoSuchElementException("arc iterator out of bounds");
  188. }
  189. double angle = angStRad;
  190. if (index == 0) {
  191. coords[0] = (float) (x + Math.cos(angle) * w);
  192. coords[1] = (float) (y + Math.sin(angle) * h);
  193. if (affine != null) {
  194. affine.transform(coords, 0, coords, 0, 1);
  195. }
  196. return SEG_MOVETO;
  197. }
  198. if (index > arcSegs) {
  199. if (index == arcSegs + lineSegs) {
  200. return SEG_CLOSE;
  201. }
  202. coords[0] = (float) x;
  203. coords[1] = (float) y;
  204. if (affine != null) {
  205. affine.transform(coords, 0, coords, 0, 1);
  206. }
  207. return SEG_LINETO;
  208. }
  209. angle += increment * (index - 1);
  210. double relx = Math.cos(angle);
  211. double rely = Math.sin(angle);
  212. coords[0] = (float) (x + (relx - cv * rely) * w);
  213. coords[1] = (float) (y + (rely + cv * relx) * h);
  214. angle += increment;
  215. relx = Math.cos(angle);
  216. rely = Math.sin(angle);
  217. coords[2] = (float) (x + (relx + cv * rely) * w);
  218. coords[3] = (float) (y + (rely - cv * relx) * h);
  219. coords[4] = (float) (x + relx * w);
  220. coords[5] = (float) (y + rely * h);
  221. if (affine != null) {
  222. affine.transform(coords, 0, coords, 0, 3);
  223. }
  224. return SEG_CUBICTO;
  225. }
  226. /**
  227. * Returns the coordinates and type of the current path segment in
  228. * the iteration.
  229. * The return value is the path segment type:
  230. * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE.
  231. * A double array of length 6 must be passed in and may be used to
  232. * store the coordinates of the point(s).
  233. * Each point is stored as a pair of double x,y coordinates.
  234. * SEG_MOVETO and SEG_LINETO types will return one point,
  235. * SEG_QUADTO will return two points,
  236. * SEG_CUBICTO will return 3 points
  237. * and SEG_CLOSE will not return any points.
  238. * @see #SEG_MOVETO
  239. * @see #SEG_LINETO
  240. * @see #SEG_QUADTO
  241. * @see #SEG_CUBICTO
  242. * @see #SEG_CLOSE
  243. */
  244. public int currentSegment(double[] coords) {
  245. if (isDone()) {
  246. throw new NoSuchElementException("arc iterator out of bounds");
  247. }
  248. double angle = angStRad;
  249. if (index == 0) {
  250. coords[0] = x + Math.cos(angle) * w;
  251. coords[1] = y + Math.sin(angle) * h;
  252. if (affine != null) {
  253. affine.transform(coords, 0, coords, 0, 1);
  254. }
  255. return SEG_MOVETO;
  256. }
  257. if (index > arcSegs) {
  258. if (index == arcSegs + lineSegs) {
  259. return SEG_CLOSE;
  260. }
  261. coords[0] = x;
  262. coords[1] = y;
  263. if (affine != null) {
  264. affine.transform(coords, 0, coords, 0, 1);
  265. }
  266. return SEG_LINETO;
  267. }
  268. angle += increment * (index - 1);
  269. double relx = Math.cos(angle);
  270. double rely = Math.sin(angle);
  271. coords[0] = x + (relx - cv * rely) * w;
  272. coords[1] = y + (rely + cv * relx) * h;
  273. angle += increment;
  274. relx = Math.cos(angle);
  275. rely = Math.sin(angle);
  276. coords[2] = x + (relx + cv * rely) * w;
  277. coords[3] = y + (rely - cv * relx) * h;
  278. coords[4] = x + relx * w;
  279. coords[5] = y + rely * h;
  280. if (affine != null) {
  281. affine.transform(coords, 0, coords, 0, 3);
  282. }
  283. return SEG_CUBICTO;
  284. }
  285. }