001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Denis M. Kishenko
019: * @version $Revision$
020: */package java.awt.geom;
021:
022: import java.util.NoSuchElementException;
023:
024: import org.apache.harmony.awt.internal.nls.Messages;
025:
026: public abstract class Arc2D extends RectangularShape {
027:
028: public final static int OPEN = 0;
029: public final static int CHORD = 1;
030: public final static int PIE = 2;
031:
032: public static class Float extends Arc2D {
033:
034: public float x;
035: public float y;
036: public float width;
037: public float height;
038: public float start;
039: public float extent;
040:
041: public Float() {
042: super (OPEN);
043: }
044:
045: public Float(int type) {
046: super (type);
047: }
048:
049: public Float(float x, float y, float width, float height,
050: float start, float extent, int type) {
051: super (type);
052: this .x = x;
053: this .y = y;
054: this .width = width;
055: this .height = height;
056: this .start = start;
057: this .extent = extent;
058: }
059:
060: public Float(Rectangle2D bounds, float start, float extent,
061: int type) {
062: super (type);
063: this .x = (float) bounds.getX();
064: this .y = (float) bounds.getY();
065: this .width = (float) bounds.getWidth();
066: this .height = (float) bounds.getHeight();
067: this .start = start;
068: this .extent = extent;
069: }
070:
071: @Override
072: public double getX() {
073: return x;
074: }
075:
076: @Override
077: public double getY() {
078: return y;
079: }
080:
081: @Override
082: public double getWidth() {
083: return width;
084: }
085:
086: @Override
087: public double getHeight() {
088: return height;
089: }
090:
091: @Override
092: public double getAngleStart() {
093: return start;
094: }
095:
096: @Override
097: public double getAngleExtent() {
098: return extent;
099: }
100:
101: @Override
102: public boolean isEmpty() {
103: return width <= 0.0f || height <= 0.0f;
104: }
105:
106: @Override
107: public void setArc(double x, double y, double width,
108: double height, double start, double extent, int type) {
109: this .setArcType(type);
110: this .x = (float) x;
111: this .y = (float) y;
112: this .width = (float) width;
113: this .height = (float) height;
114: this .start = (float) start;
115: this .extent = (float) extent;
116: }
117:
118: @Override
119: public void setAngleStart(double start) {
120: this .start = (float) start;
121: }
122:
123: @Override
124: public void setAngleExtent(double extent) {
125: this .extent = (float) extent;
126: }
127:
128: @Override
129: protected Rectangle2D makeBounds(double x, double y,
130: double width, double height) {
131: return new Rectangle2D.Float((float) x, (float) y,
132: (float) width, (float) height);
133: }
134:
135: }
136:
137: public static class Double extends Arc2D {
138:
139: public double x;
140: public double y;
141: public double width;
142: public double height;
143: public double start;
144: public double extent;
145:
146: public Double() {
147: super (OPEN);
148: }
149:
150: public Double(int type) {
151: super (type);
152: }
153:
154: public Double(double x, double y, double width, double height,
155: double start, double extent, int type) {
156: super (type);
157: this .x = x;
158: this .y = y;
159: this .width = width;
160: this .height = height;
161: this .start = start;
162: this .extent = extent;
163: }
164:
165: public Double(Rectangle2D bounds, double start, double extent,
166: int type) {
167: super (type);
168: this .x = bounds.getX();
169: this .y = bounds.getY();
170: this .width = bounds.getWidth();
171: this .height = bounds.getHeight();
172: this .start = start;
173: this .extent = extent;
174: }
175:
176: @Override
177: public double getX() {
178: return x;
179: }
180:
181: @Override
182: public double getY() {
183: return y;
184: }
185:
186: @Override
187: public double getWidth() {
188: return width;
189: }
190:
191: @Override
192: public double getHeight() {
193: return height;
194: }
195:
196: @Override
197: public double getAngleStart() {
198: return start;
199: }
200:
201: @Override
202: public double getAngleExtent() {
203: return extent;
204: }
205:
206: @Override
207: public boolean isEmpty() {
208: return width <= 0.0 || height <= 0.0;
209: }
210:
211: @Override
212: public void setArc(double x, double y, double width,
213: double height, double start, double extent, int type) {
214: this .setArcType(type);
215: this .x = x;
216: this .y = y;
217: this .width = width;
218: this .height = height;
219: this .start = start;
220: this .extent = extent;
221: }
222:
223: @Override
224: public void setAngleStart(double start) {
225: this .start = start;
226: }
227:
228: @Override
229: public void setAngleExtent(double extent) {
230: this .extent = extent;
231: }
232:
233: @Override
234: protected Rectangle2D makeBounds(double x, double y,
235: double width, double height) {
236: return new Rectangle2D.Double(x, y, width, height);
237: }
238:
239: }
240:
241: /*
242: * Arc2D path iterator
243: */
244: class Iterator implements PathIterator {
245:
246: /**
247: * The x coordinate of left-upper corner of the arc rectangle bounds
248: */
249: double x;
250:
251: /**
252: * The y coordinate of left-upper corner of the arc rectangle bounds
253: */
254: double y;
255:
256: /**
257: * The width of the arc rectangle bounds
258: */
259: double width;
260:
261: /**
262: * The height of the arc rectangle bounds
263: */
264: double height;
265:
266: /**
267: * The start angle of the arc in degrees
268: */
269: double angle;
270:
271: /**
272: * The angle extent in degrees
273: */
274: double extent;
275:
276: /**
277: * The closure type of the arc
278: */
279: int type;
280:
281: /**
282: * The path iterator transformation
283: */
284: AffineTransform t;
285:
286: /**
287: * The current segmenet index
288: */
289: int index;
290:
291: /**
292: * The number of arc segments the source arc subdivided to be approximated by Bezier curves.
293: * Depends on extent value.
294: */
295: int arcCount;
296:
297: /**
298: * The number of line segments. Depends on closure type.
299: */
300: int lineCount;
301:
302: /**
303: * The step to calculate next arc subdivision point
304: */
305: double step;
306:
307: /**
308: * The tempopary value of cosinus of the current angle
309: */
310: double cos;
311:
312: /**
313: * The tempopary value of sinus of the current angle
314: */
315: double sin;
316:
317: /**
318: * The coefficient to calculate control points of Bezier curves
319: */
320: double k;
321:
322: /**
323: * The tempopary value of x coordinate of the Bezier curve control vector
324: */
325: double kx;
326:
327: /**
328: * The tempopary value of y coordinate of the Bezier curve control vector
329: */
330: double ky;
331:
332: /**
333: * The x coordinate of the first path point (MOVE_TO)
334: */
335: double mx;
336:
337: /**
338: * The y coordinate of the first path point (MOVE_TO)
339: */
340: double my;
341:
342: /**
343: * Constructs a new Arc2D.Iterator for given line and transformation
344: * @param a - the source Arc2D object
345: * @param at - the AffineTransform object to apply rectangle path
346: */
347: Iterator(Arc2D a, AffineTransform t) {
348: if (width < 0 || height < 0) {
349: arcCount = 0;
350: lineCount = 0;
351: index = 1;
352: return;
353: }
354:
355: this .width = a.getWidth() / 2.0;
356: this .height = a.getHeight() / 2.0;
357: this .x = a.getX() + width;
358: this .y = a.getY() + height;
359: this .angle = -Math.toRadians(a.getAngleStart());
360: this .extent = -a.getAngleExtent();
361: this .type = a.getArcType();
362: this .t = t;
363:
364: if (Math.abs(extent) >= 360.0) {
365: arcCount = 4;
366: k = 4.0 / 3.0 * (Math.sqrt(2.0) - 1.0);
367: step = Math.PI / 2.0;
368: if (extent < 0.0) {
369: step = -step;
370: k = -k;
371: }
372: } else {
373: arcCount = (int) Math.rint(Math.abs(extent) / 90.0);
374: step = Math.toRadians(extent / arcCount);
375: k = 4.0 / 3.0 * (1.0 - Math.cos(step / 2.0))
376: / Math.sin(step / 2.0);
377: }
378:
379: lineCount = 0;
380: if (type == Arc2D.CHORD) {
381: lineCount++;
382: } else if (type == Arc2D.PIE) {
383: lineCount += 2;
384: }
385: }
386:
387: public int getWindingRule() {
388: return WIND_NON_ZERO;
389: }
390:
391: public boolean isDone() {
392: return index > arcCount + lineCount;
393: }
394:
395: public void next() {
396: index++;
397: }
398:
399: public int currentSegment(double[] coords) {
400: if (isDone()) {
401: // awt.4B=Iterator out of bounds
402: throw new NoSuchElementException(Messages
403: .getString("awt.4B")); //$NON-NLS-1$
404: }
405: int type;
406: int count;
407: if (index == 0) {
408: type = SEG_MOVETO;
409: count = 1;
410: cos = Math.cos(angle);
411: sin = Math.sin(angle);
412: kx = k * width * sin;
413: ky = k * height * cos;
414: coords[0] = mx = x + cos * width;
415: coords[1] = my = y + sin * height;
416: } else if (index <= arcCount) {
417: type = SEG_CUBICTO;
418: count = 3;
419: coords[0] = mx - kx;
420: coords[1] = my + ky;
421: angle += step;
422: cos = Math.cos(angle);
423: sin = Math.sin(angle);
424: kx = k * width * sin;
425: ky = k * height * cos;
426: coords[4] = mx = x + cos * width;
427: coords[5] = my = y + sin * height;
428: coords[2] = mx + kx;
429: coords[3] = my - ky;
430: } else if (index == arcCount + lineCount) {
431: type = SEG_CLOSE;
432: count = 0;
433: } else {
434: type = SEG_LINETO;
435: count = 1;
436: coords[0] = x;
437: coords[1] = y;
438: }
439: if (t != null) {
440: t.transform(coords, 0, coords, 0, count);
441: }
442: return type;
443: }
444:
445: public int currentSegment(float[] coords) {
446: if (isDone()) {
447: // awt.4B=Iterator out of bounds
448: throw new NoSuchElementException(Messages
449: .getString("awt.4B")); //$NON-NLS-1$
450: }
451: int type;
452: int count;
453: if (index == 0) {
454: type = SEG_MOVETO;
455: count = 1;
456: cos = Math.cos(angle);
457: sin = Math.sin(angle);
458: kx = k * width * sin;
459: ky = k * height * cos;
460: coords[0] = (float) (mx = x + cos * width);
461: coords[1] = (float) (my = y + sin * height);
462: } else if (index <= arcCount) {
463: type = SEG_CUBICTO;
464: count = 3;
465: coords[0] = (float) (mx - kx);
466: coords[1] = (float) (my + ky);
467: angle += step;
468: cos = Math.cos(angle);
469: sin = Math.sin(angle);
470: kx = k * width * sin;
471: ky = k * height * cos;
472: coords[4] = (float) (mx = x + cos * width);
473: coords[5] = (float) (my = y + sin * height);
474: coords[2] = (float) (mx + kx);
475: coords[3] = (float) (my - ky);
476: } else if (index == arcCount + lineCount) {
477: type = SEG_CLOSE;
478: count = 0;
479: } else {
480: type = SEG_LINETO;
481: count = 1;
482: coords[0] = (float) x;
483: coords[1] = (float) y;
484: }
485: if (t != null) {
486: t.transform(coords, 0, coords, 0, count);
487: }
488: return type;
489: }
490:
491: }
492:
493: /**
494: * The closure type of the arc
495: */
496: private int type;
497:
498: protected Arc2D(int type) {
499: setArcType(type);
500: }
501:
502: protected abstract Rectangle2D makeBounds(double x, double y,
503: double width, double height);
504:
505: public abstract double getAngleStart();
506:
507: public abstract double getAngleExtent();
508:
509: public abstract void setAngleStart(double start);
510:
511: public abstract void setAngleExtent(double extent);
512:
513: public abstract void setArc(double x, double y, double width,
514: double height, double start, double extent, int type);
515:
516: public int getArcType() {
517: return type;
518: }
519:
520: public void setArcType(int type) {
521: if (type != OPEN && type != CHORD && type != PIE) {
522: // awt.205=Invalid type of Arc: {0}
523: throw new IllegalArgumentException(Messages.getString(
524: "awt.205", type)); //$NON-NLS-1$
525: }
526: this .type = type;
527: }
528:
529: public Point2D getStartPoint() {
530: double a = Math.toRadians(getAngleStart());
531: return new Point2D.Double(getX() + (1.0 + Math.cos(a))
532: * getWidth() / 2.0, getY() + (1.0 - Math.sin(a))
533: * getHeight() / 2.0);
534: }
535:
536: public Point2D getEndPoint() {
537: double a = Math.toRadians(getAngleStart() + getAngleExtent());
538: return new Point2D.Double(getX() + (1.0 + Math.cos(a))
539: * getWidth() / 2.0, getY() + (1.0 - Math.sin(a))
540: * getHeight() / 2.0);
541: }
542:
543: public Rectangle2D getBounds2D() {
544: if (isEmpty()) {
545: return makeBounds(getX(), getY(), getWidth(), getHeight());
546: }
547: double rx1 = getX();
548: double ry1 = getY();
549: double rx2 = rx1 + getWidth();
550: double ry2 = ry1 + getHeight();
551:
552: Point2D p1 = getStartPoint();
553: Point2D p2 = getEndPoint();
554:
555: double bx1 = containsAngle(180.0) ? rx1 : Math.min(p1.getX(),
556: p2.getX());
557: double by1 = containsAngle(90.0) ? ry1 : Math.min(p1.getY(), p2
558: .getY());
559: double bx2 = containsAngle(0.0) ? rx2 : Math.max(p1.getX(), p2
560: .getX());
561: double by2 = containsAngle(270.0) ? ry2 : Math.max(p1.getY(),
562: p2.getY());
563:
564: if (type == PIE) {
565: double cx = getCenterX();
566: double cy = getCenterY();
567: bx1 = Math.min(bx1, cx);
568: by1 = Math.min(by1, cy);
569: bx2 = Math.max(bx2, cx);
570: by2 = Math.max(by2, cy);
571: }
572: return makeBounds(bx1, by1, bx2 - bx1, by2 - by1);
573: }
574:
575: @Override
576: public void setFrame(double x, double y, double width, double height) {
577: setArc(x, y, width, height, getAngleStart(), getAngleExtent(),
578: type);
579: }
580:
581: public void setArc(Point2D point, Dimension2D size, double start,
582: double extent, int type) {
583: setArc(point.getX(), point.getY(), size.getWidth(), size
584: .getHeight(), start, extent, type);
585: }
586:
587: public void setArc(Rectangle2D rect, double start, double extent,
588: int type) {
589: setArc(rect.getX(), rect.getY(), rect.getWidth(), rect
590: .getHeight(), start, extent, type);
591: }
592:
593: public void setArc(Arc2D arc) {
594: setArc(arc.getX(), arc.getY(), arc.getWidth(), arc.getHeight(),
595: arc.getAngleStart(), arc.getAngleExtent(), arc
596: .getArcType());
597: }
598:
599: public void setArcByCenter(double x, double y, double radius,
600: double start, double extent, int type) {
601: setArc(x - radius, y - radius, radius * 2.0, radius * 2.0,
602: start, extent, type);
603: }
604:
605: public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3,
606: double radius) {
607: // Used simple geometric calculations of arc center, radius and angles by tangents
608: double a1 = -Math.atan2(p1.getY() - p2.getY(), p1.getX()
609: - p2.getX());
610: double a2 = -Math.atan2(p3.getY() - p2.getY(), p3.getX()
611: - p2.getX());
612: double am = (a1 + a2) / 2.0;
613: double ah = a1 - am;
614: double d = radius / Math.abs(Math.sin(ah));
615: double x = p2.getX() + d * Math.cos(am);
616: double y = p2.getY() - d * Math.sin(am);
617: ah = ah >= 0.0 ? Math.PI * 1.5 - ah : Math.PI * 0.5 - ah;
618: a1 = getNormAngle(Math.toDegrees(am - ah));
619: a2 = getNormAngle(Math.toDegrees(am + ah));
620: double delta = a2 - a1;
621: if (delta <= 0.0) {
622: delta += 360.0;
623: }
624: setArcByCenter(x, y, radius, a1, delta, type);
625: }
626:
627: public void setAngleStart(Point2D point) {
628: double angle = Math.atan2(point.getY() - getCenterY(), point
629: .getX()
630: - getCenterX());
631: setAngleStart(getNormAngle(-Math.toDegrees(angle)));
632: }
633:
634: public void setAngles(double x1, double y1, double x2, double y2) {
635: double cx = getCenterX();
636: double cy = getCenterY();
637: double a1 = getNormAngle(-Math.toDegrees(Math.atan2(y1 - cy, x1
638: - cx)));
639: double a2 = getNormAngle(-Math.toDegrees(Math.atan2(y2 - cy, x2
640: - cx)));
641: a2 -= a1;
642: if (a2 <= 0.0) {
643: a2 += 360.0;
644: }
645: setAngleStart(a1);
646: setAngleExtent(a2);
647: }
648:
649: public void setAngles(Point2D p1, Point2D p2) {
650: setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY());
651: }
652:
653: /**
654: * Normalizes angle
655: * @param angle - the source angle in degrees
656: * @return a normalized angle
657: */
658: double getNormAngle(double angle) {
659: double n = Math.floor(angle / 360.0);
660: return angle - n * 360.0;
661: }
662:
663: public boolean containsAngle(double angle) {
664: double extent = getAngleExtent();
665: if (extent >= 360.0) {
666: return true;
667: }
668: angle = getNormAngle(angle);
669: double a1 = getNormAngle(getAngleStart());
670: double a2 = a1 + extent;
671: if (a2 > 360.0) {
672: return angle >= a1 || angle <= a2 - 360.0;
673: }
674: if (a2 < 0.0) {
675: return angle >= a2 + 360.0 || angle <= a1;
676: }
677: return extent > 0.0 ? a1 <= angle && angle <= a2 : a2 <= angle
678: && angle <= a1;
679: }
680:
681: public boolean contains(double px, double py) {
682: // Normalize point
683: double nx = (px - getX()) / getWidth() - 0.5;
684: double ny = (py - getY()) / getHeight() - 0.5;
685:
686: if ((nx * nx + ny * ny) > 0.25) {
687: return false;
688: }
689:
690: double extent = getAngleExtent();
691: double absExtent = Math.abs(extent);
692: if (absExtent >= 360.0) {
693: return true;
694: }
695:
696: boolean containsAngle = containsAngle(Math.toDegrees(-Math
697: .atan2(ny, nx)));
698: if (type == PIE) {
699: return containsAngle;
700: }
701: if (absExtent <= 180.0 && !containsAngle) {
702: return false;
703: }
704:
705: Line2D l = new Line2D.Double(getStartPoint(), getEndPoint());
706: int ccw1 = l.relativeCCW(px, py);
707: int ccw2 = l.relativeCCW(getCenterX(), getCenterY());
708: return ccw1 == 0 || ccw2 == 0
709: || ((ccw1 + ccw2) == 0 ^ absExtent > 180.0);
710: }
711:
712: public boolean contains(double rx, double ry, double rw, double rh) {
713:
714: if (!(contains(rx, ry) && contains(rx + rw, ry)
715: && contains(rx + rw, ry + rh) && contains(rx, ry + rh))) {
716: return false;
717: }
718:
719: double absExtent = Math.abs(getAngleExtent());
720: if (type != PIE || absExtent <= 180.0 || absExtent >= 360.0) {
721: return true;
722: }
723:
724: Rectangle2D r = new Rectangle2D.Double(rx, ry, rw, rh);
725:
726: double cx = getCenterX();
727: double cy = getCenterY();
728: if (r.contains(cx, cy)) {
729: return false;
730: }
731:
732: Point2D p1 = getStartPoint();
733: Point2D p2 = getEndPoint();
734:
735: return !r.intersectsLine(cx, cy, p1.getX(), p1.getY())
736: && !r.intersectsLine(cx, cy, p2.getX(), p2.getY());
737: }
738:
739: @Override
740: public boolean contains(Rectangle2D rect) {
741: return contains(rect.getX(), rect.getY(), rect.getWidth(), rect
742: .getHeight());
743: }
744:
745: public boolean intersects(double rx, double ry, double rw, double rh) {
746:
747: if (isEmpty() || rw <= 0.0 || rh <= 0.0) {
748: return false;
749: }
750:
751: // Check: Does arc contain rectangle's points
752: if (contains(rx, ry) || contains(rx + rw, ry)
753: || contains(rx, ry + rh) || contains(rx + rw, ry + rh)) {
754: return true;
755: }
756:
757: double cx = getCenterX();
758: double cy = getCenterY();
759: Point2D p1 = getStartPoint();
760: Point2D p2 = getEndPoint();
761: Rectangle2D r = new Rectangle2D.Double(rx, ry, rw, rh);
762:
763: // Check: Does rectangle contain arc's points
764: if (r.contains(p1) || r.contains(p2)
765: || (type == PIE && r.contains(cx, cy))) {
766: return true;
767: }
768:
769: if (type == PIE) {
770: if (r.intersectsLine(p1.getX(), p1.getY(), cx, cy)
771: || r.intersectsLine(p2.getX(), p2.getY(), cx, cy)) {
772: return true;
773: }
774: } else {
775: if (r.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2
776: .getY())) {
777: return true;
778: }
779: }
780:
781: // Nearest rectangle point
782: double nx = cx < rx ? rx : (cx > rx + rw ? rx + rw : cx);
783: double ny = cy < ry ? ry : (cy > ry + rh ? ry + rh : cy);
784: return contains(nx, ny);
785: }
786:
787: public PathIterator getPathIterator(AffineTransform at) {
788: return new Iterator(this, at);
789: }
790:
791: }
|