001 package cs101.awt.geom;
002 /*
003 * ShapeUtils.java
004 *
005 * Created on March 2, 2003, 2:30 PM
006 */
007 import java.awt.Shape;
008 import java.awt.Point;
009 import java.awt.Rectangle;
010
011 import java.awt.geom.Point2D;
012 import java.awt.geom.Line2D;
013 import java.awt.geom.GeneralPath;
014 import java.awt.geom.PathIterator;
015 import java.awt.geom.AffineTransform;
016 import java.awt.geom.IllegalPathStateException;
017 import java.awt.geom.NoninvertibleTransformException;
018
019 import java.util.ArrayList;
020 import java.util.List;
021
022
023 /**
024 * This utility class holds routines for doing conversions on classes that
025 * implement the <code>Shape</code> interface. Generally the first step is
026 * in each routine is to convert the shape to a <code>GeneralPath</code>, and
027 * return values of type <code>Shape</code> will in fact be instances of
028 * the <code>GeneralPath</code> class unless specified otherwise.
029 *
030 * <a id="knotpts"></a>
031 * <p>Java's <code>GeneralPath</code> class is a series of concatenated line
032 * segments, second order (quadratic) Bezier curves and third order (cubic)
033 * Bezier curves. Bezier Curves consist of two types of points: <b>knot
034 * points</b> which are points that lie on the curve, and <b>control points</b>
035 * which do not lie on the curve but denote vectors that influence the shape
036 * of the curve between knot points.
037 *
038 * <p>None of the routines in this class attempt to convert between lines,
039 * quadratics, and cubics. A shape consisting entirely of lines, will remain
040 * entirely line based, and a purely cubic shape will also remain cubic. Mixed
041 * shapes will remain mixed, but the ratios of the number of segements of any
042 * two types is almost certain to change.
043 *
044 * @author Patrick G. Heck, Gus.heck@olin.edu
045 * @version $Id: ShapeUtils.java,v 1.18 2003/09/23 15:23:40 gus Exp $
046 */
047 public final class ShapeUtils {
048
049 /** Creates a new instance of ShapeUtils */
050 private ShapeUtils() {
051 }
052
053 /**
054 * Compute a velocity vector after a bounce off a given surface. The bounce
055 * is computed by rotating the system until the line is vertical,
056 * inverting the x component to calculate the bounce, and then performing
057 * the inverse transform to get the desired.
058 *
059 * @param surface The line representing the angle of the surface to bounce
060 * off of.
061 * @param velVect A line with starting point 0,0 representing the velocity
062 * vector to be bounced.
063 * @return A line starting at 0,0 representing the new velocity vector.
064 */
065 public static Line2D.Float bounce(Line2D.Float surface, Line2D.Float velVect) {
066 AffineTransform rotateVert, rotateBack;
067 double lineSlant = angleToVert(surface);
068
069 rotateVert = AffineTransform.getRotateInstance(lineSlant);
070
071 try {
072 rotateBack = rotateVert.createInverse();
073 } catch (NoninvertibleTransformException nte) {
074 System.out.println("NoninvertableTransformException! bounce not calculated.");
075 throw new Error("Rotations shoud be invertable!");
076 }
077
078 // this should rotate the line around 0,0 which is the x1,y1 point
079 Shape s = rotateVert.createTransformedShape(velVect);
080 PointIterator iter = new PointIterator(s);
081 velVect = new Line2D.Float(iter.nextPoint(),iter.nextPoint());
082
083 // bouncing off a vertical line is just a matter of inverting the
084 // x component
085 velVect.setLine(0,0,-velVect.getX2(),velVect.getY2());
086
087 s = rotateBack.createTransformedShape(velVect);
088 iter = new PointIterator(s);
089 velVect = new Line2D.Float(iter.nextPoint(),iter.nextPoint());
090
091 return velVect;
092 }
093
094
095 /**
096 * Find the resultant velocity from the collision of a mobile and imobile
097 * shape. The two shapes must have already collided and be in a state of
098 * overlap, and must be shapes consisting of more than one point.
099 *
100 * @param s1 The moving shape (tiny mass)
101 * @param s2 The imobile shape (infinite mass)
102 * @param v1X The x component of the velocity of s1
103 * @param v1Y The y component of the velocity of s1
104 * @return a line starting at the origin and representing the new velocity
105 * vector
106 */
107 public static Line2D.Float doReflect(Shape s1, Shape s2, double v1X, double v1Y) {
108 if (!isOverlapping(s1,s2)) {
109 //System.exit(1);
110 throw new ShapesDontOverlapException("Cannot reflect shapes until"+
111 "they hit each other");
112 }
113 Line2D.Float nearSeg = null;
114
115 Line2D.Float velVect = new Line2D.Float(0,0,
116 new Double(v1X).floatValue(),
117 new Double(v1Y).floatValue());
118 Point2D.Float insidePt;
119 try {
120 insidePt = meanContainedPoint(s2,s1); // points conatained by stationary object
121 } catch (NotEnoughPointsException nepe) {
122 try {
123 insidePt = meanContainedPoint(s1,s2);
124 } catch (NotEnoughPointsException nepe2) {
125 throw new Error("overlapping but neither conatins the other point!");
126 }
127 }
128 try {
129 nearSeg = nearestSegment(s2,insidePt);
130 } catch (NotEnoughPointsException nepe) {
131 System.out.println("Ignoring malformed shape:" + nepe.getMessage());
132 return velVect; // pretend malformed shapes arn't there
133 } catch (NoUniqueLineException nule) {
134 Point2D.Float nearest = getNearestPoint(s1,insidePt);
135 nearSeg = getLineFromNeighbors(s1,nearest);
136 }
137
138 return bounce(nearSeg, velVect);
139
140 }
141
142
143 /**
144 * Find the resultant velocity from the collision of a mobile and imobile
145 * shape. The two shapes must have already collided and be in a state of
146 * overlap, and must be shapes consisting of more than one point.
147 *
148 * @param s1 The moving shape (tiny mass)
149 * @param s2 The imobile shape (infinite mass)
150 * @param velVect1 The velocity vector of s1, (x1,y1 point at 0.0 x2,y2
151 * representing the x and y components of the velocity)
152 * @return a line starting at the origin and representing the new velocity
153 * vector
154 */
155 public static Line2D.Float doReflect(Shape s1, Shape s2, Line2D.Float velVect1) {
156 return doReflect(s1,s2,velVect1.getX2(),velVect1.getY2());
157 }
158
159 /**
160 * Find the new x velocity after reflecting a moving shape from a imobile
161 * shape.
162 *
163 * @param s1 The moving shape (tiny mass)
164 * @param s2 The imobile shape (infinite mass)
165 * @param v1X The x component of the velocity of s1
166 * @param v1Y The y component of the velocity of s1
167 */
168 public static double doXReflect(Shape s1, Shape s2, double v1X, double v1Y) {
169
170 return doReflect(s1,s2,v1X,v1Y).getX2();
171
172 }
173
174 /**
175 * Find the new x velocity after reflecting a moving shape from a imobile
176 * shape.
177 *
178 * @param s1 The moving shape (tiny mass)
179 * @param s2 The imobile shape (infinite mass)
180 * @param v1X The x component of the velocity of s1
181 * @param v1Y The y component of the velocity of s1
182 */
183 public static double doYReflect(Shape s1, Shape s2, double v1X, double v1Y) {
184
185 return doReflect(s1,s2,v1X,v1Y).getY2();
186
187 }
188
189 /**
190 * Find the angle between a given line segment and the vertical axis in
191 * direction that roataes the positive x axis towards positive y axis
192 *
193 * @param aLine A line to find the angle of
194 * @return The angle in radians.
195 */
196 public static double angleToVert(Line2D.Float aLine) {
197
198 Point2D.Float start = new Point2D.Float();
199 Point2D.Float end = new Point2D.Float();
200 Point2D.Float near, far;
201
202 start.setLocation(aLine.getX1(),aLine.getY1());
203 end.setLocation(aLine.getX2(),aLine.getY2());
204
205 double rise = start.y - end.y;
206 double run = start.x - end.x;
207
208 double tanTheta = rise/run;
209
210 return Math.PI/2 - Math.atan(tanTheta);
211
212 }
213
214 /**
215 * Find the line between points on either side of a knot point on a given
216 * shape. If the shape is unclosed, and the point queried is the start
217 * or end of the shape then the line between the querried point and the
218 * nearest point will be returned. The shape must have more than one point.
219 *
220 * @param s The shape to find points from, comprised of than one point.
221 * @param pt The point that is a knot of s for which we want a neighbors
222 * line.
223 */
224
225 public static Line2D.Float getLineFromNeighbors(Shape s, Point2D.Float pt) {
226 PointIterator iter = new PointIterator(s);
227 Point2D.Float preceding = null;
228 Point2D.Float following = null;
229 Point2D.Float prevPt = null;
230 Point2D.Float currPt = iter.nextPoint();
231 Point2D.Float startPt = currPt;
232
233 while (iter.hasNext() && iter.isStart(startPt)) {
234 if (startPt.equals(pt)) {
235 if (iter.hasNext()) {
236 following = iter.nextPoint();
237 currPt = following;
238 } else {
239 throw new IllegalArgumentException("Shape has only one Point!");
240 }
241 while (iter.hasNext() && iter.isStart(startPt) && (currPt != startPt)) {
242 prevPt = currPt;
243 currPt = iter.nextPoint();
244 }
245 if (currPt == startPt) { // closed shape
246 return new Line2D.Float(prevPt,following);
247 }
248
249 // open or discontinuous shape
250 return new Line2D.Float(startPt,following);
251
252 }
253 prevPt = currPt;
254 currPt = iter.nextPoint();
255
256 if (currPt.equals(pt)) {
257 preceding = prevPt;
258 if(iter.hasNext()) {
259 currPt = iter.nextPoint();
260 }
261 return new Line2D.Float(preceding,currPt);
262 }
263
264 }
265
266 throw new IllegalArgumentException("Point was not a knot of the supplied Shape");
267
268 }
269
270 /**
271 * Find the knot point in the given shape that is closest to the specified
272 * point. In the case of equidistant points, the qualifying point closest
273 * to the start of the shape will be returned. The shape passed to this
274 * method must have at least one point.
275 *
276 * @param s The shape to find a point from
277 * @param pt The point to which the points of the shape are compared.
278 * @return A point on the shape that is as close or closer than any other
279 * point on the shape to pt
280 */
281
282 public static Point2D.Float getNearestPoint(Shape s, Point2D.Float pt) {
283 Point2D.Float result = null;
284 Point2D.Float test = null;
285 PointIterator iter = new PointIterator(s);
286
287 // if you pass an empty shape it is your problem not mine doofus :)
288 result = iter.nextPoint();
289 while(iter.hasNext()) {
290 test = iter.nextPoint();
291 if (pt.distance(result) > pt.distance(test)) {
292 result = test;
293 }
294 }
295 return result;
296 }
297
298 /**
299 * Find the line segment between two consecutive knot points of the given
300 * <code>Shape</code> that is closest to a specified point. Note that this
301 * method will return a line even if the segment it represents in the shape
302 * is a cubic or quadratic bezier curve.
303 *
304 * @param aShape The shape from which to find the line segment
305 * @param aPoint The point to which the distance is compared.
306 * @return A unique line segment that is closer to the point than any other.
307 *
308 * @throws NoUniqueLineException If there are two or more lines equidistant
309 * from the point.
310 * @throws NotEnoughPointsException If the aShape contains less than 2 points
311 *
312 */
313 public static Line2D.Float nearestSegment(Shape aShape,
314 Point2D.Float aPoint) throws NoUniqueLineException,
315 NotEnoughPointsException {
316
317 PointIterator iter = new PointIterator(aShape);
318 Point2D.Float last = null;
319 Point2D.Float curr = null;
320 Line2D.Float closest = null;
321 Line2D.Float nextBest = null;
322 Line2D.Float other = null;
323 double closestDist = -1;
324 double nextBestDist = -1;
325
326 if (iter.hasNext()) {
327 last = iter.nextPoint();
328 }
329 if (iter.hasNext()) {
330 curr = iter.nextPoint();
331 }
332
333 if (last == null || curr == null) {
334 throw new NotEnoughPointsException("Not enough points for a line");
335 }
336
337 if (last.equals(curr) && !iter.hasNext()) {
338 throw new NotEnoughPointsException("Only two duplicate points");
339 }
340
341 closest = new Line2D.Float(last,curr);
342 closestDist = closest.ptSegDist(aPoint);
343 while (iter.hasNext()) {
344 last = curr;
345 curr = iter.nextPoint();
346 if (last.equals(curr)) {
347 continue;
348 }
349 other = new Line2D.Float(last,curr);
350 double otherDist = other.ptSegDist(aPoint);
351
352 if (nextBest == null) {
353 nextBest = other;
354 nextBestDist = otherDist;
355 }
356
357 if (closestDist == otherDist) {
358 nextBest = other;
359 nextBestDist = otherDist;
360 } else {
361 if (closestDist > otherDist) {
362 nextBest = closest;
363 nextBestDist = closestDist;
364 closest = other;
365 closestDist = otherDist;
366 }
367 }
368 }
369
370 // The following case would occr with a shape constructed with
371 // moveTo(1,1) lineTo(1,1) closePath() or other "go nowhere" shapes.
372 if (nextBest == null) {
373 throw new NotEnoughPointsException("Only duplicate points found");
374 }
375
376 if (closestDist == nextBestDist) {
377 throw new NoUniqueLineException("More than one Line");
378 }
379
380 return closest;
381
382 }
383
384 /**
385 * Find the mean location of a list of <code>Point2D.Float</code>.
386 *
387 * @param pointList A list containing <em>only</em> Point2D.Float
388 * @return The average point.
389 * @throws ClassCastException If the objects in the supplied list are not
390 * of type Point2D.Float
391 */
392 public static Point2D.Float meanPoint(List pointList) {
393 double sumX = 0;
394 double sumY = 0;
395 float meanX, meanY;
396
397 for (int i=0; i<pointList.size();i++) {
398 sumX += ((Point2D.Float) pointList.get(i)).x;
399 sumY += ((Point2D.Float) pointList.get(i)).y;
400 }
401
402 meanX = new Double(sumX/pointList.size()).floatValue();
403 meanY = new Double(sumY/pointList.size()).floatValue();
404
405 return new Point2D.Float(meanX, meanY);
406
407 }
408
409
410 /**
411 * Calcualte the average of all points contained within one shape and
412 * belonging to another.
413 *
414 * @param container The shape which may contain points
415 * @param containee The shape whose points may be contained
416 * @return The average of the points contained.
417 */
418 public static Point2D.Float meanContainedPoint(Shape container, Shape containee) throws NotEnoughPointsException {
419 ArrayList contained = getContainedPoints(container, containee);
420 if (contained.size() == 0) {
421 throw new NotEnoughPointsException("no points contained by container");
422 }
423 return meanPoint(contained);
424 }
425
426 /**
427 * Get a list of the points belonging to one shape and contained within
428 * another.
429 *
430 * @param container The shape which may contain points
431 * @param containee The shape whose points may be contained
432 * @return The points belonging to containee and within conainer
433 */
434 public static ArrayList getContainedPoints(Shape container, Shape containee) {
435 ArrayList points = new ArrayList();
436 PointIterator iter = new PointIterator(containee);
437 Point2D.Float temp;
438
439 while (iter.hasNext()) {
440 temp = iter.nextPoint();
441 if (container.contains(temp.x,temp.y)) {
442 points.add(temp.clone());
443 }
444 }
445
446 return points;
447 }
448
449 /**
450 * Test to see if two <code>Shape</code> objects overlap.
451 * The algorithim in this test will only detect the type of
452 * overlap where a <a href="knotpts">knot point</a> from one shape is
453 * contained by the other shape (as determined by each shape's
454 * implementation of the <code>contains(double, double)</code> method.
455 *
456 * @param shapeA the first shape
457 * @param shapeB the second shape
458 * @return true if the shapes overlap false otherwise
459 */
460
461 public static boolean isOverlapping(Shape shapeA, Shape shapeB) {
462 PointIterator iter = new PointIterator(shapeA);
463 PointIterator iter2 = new PointIterator(shapeB);
464
465 Point2D.Float temp;
466
467 // Begin CPU time optimization:
468 // ----------------------------
469 // Most comparisons are between distant objects, so itterating
470 // all points is unnecessary if the bounding boxes don't overlap.
471
472 Rectangle rectA = shapeA.getBounds();
473 Rectangle rectB = shapeB.getBounds();
474
475 if(!rectA.intersects(rectB)) {
476 return false;
477 }
478
479 // end CPU time optimization
480
481 while(iter.hasNext()) {
482 temp = iter.nextPoint();
483 if (shapeB.contains(temp.x,temp.y)) {
484 return true;
485 }
486 }
487 while(iter2.hasNext()) {
488 temp = iter2.nextPoint();
489 if (shapeA.contains(temp.x,temp.y)) {
490 return true;
491 }
492 }
493 return false;
494 }
495
496 /**
497 * Convert any AWT shape into a shape with a specified precision.
498 * The specified precision indicates the maximum tolerable distance
499 * between <a href="knotpts">knot points</a>. If the shape is already
500 * precise enough then it is returned unmodified.
501 *
502 * @param aShape the shape to be converted if necessary.
503 * @param precision the maximum tolerable distance between knot points.
504 * @return A more precise version of the shape, or the same shape if
505 * the precision is already satisfied.
506 */
507 public static Shape getPreciseShape(Shape aShape, float precision) {
508 GeneralPath path = new GeneralPath(aShape);
509 GeneralPath newPath = getPrecisePath(path, precision);
510
511 return (path == newPath) ? aShape : newPath;
512 }
513
514
515 /**
516 * Increase the precision of a given <code>GeneralPath</code> object.
517 * The specified precision indicates the maximum tolerable distance
518 * between <a href="knotpts">knot points</a>. If the path is already
519 * precise enough then it is returned unmodified.
520 *
521 * @param aPath The path to convert if necessary.
522 * @param precision the maximum tolerable distance between knot points.
523 * @return A more precise version of the path or the same path if the
524 * precision was alredy satisfied.
525 */
526 public static GeneralPath getPrecisePath(GeneralPath aPath, float precision) {
527 GeneralPath[] paths = new GeneralPath[2];
528
529 paths[0] = aPath;
530 while (paths[0] != paths[1]) {
531 paths[1] = paths[0];
532 paths[0] = getImprovedPath(paths[0],precision);
533 }
534 return paths[0];
535 }
536
537
538 /**
539 * Used iteratively by getPrecisePath to acheive as specified precision.
540 * The path is traversed and for every case in which the distance between
541 * consecutive <a href="knotpts">knot points</a> is greater than the
542 * precision value, a new knot point and associated control points are
543 * inserted. The surroundng knot points have their control points modified
544 * appropriately to maintain an Identical curve. The new knot point is
545 * only guaranteed to lie on the curve and in extreme cases may even be
546 * further from either of the orriginal knot points than the distance
547 * between the original knot points, but this indicates a situation in
548 * which a radical curve has been drawn and further itterations
549 * will eventually reduce the distance.<p>
550 *
551 * Note that because GeneralPath does not overide equals we
552 * return a reference to the SAME object passed in if there are no
553 * improvements to be made. aPath.equals(newPath) is equivalent to
554 * aPath == newPath.
555 *
556 * @param aPath The path that may need to be improved.
557 * @param precision The minimum acceptable distance between knot points.
558 */
559 public static GeneralPath getImprovedPath(GeneralPath aPath, float precision) {
560 GeneralPath newPath = new GeneralPath();
561 PathIterator iter = aPath.getPathIterator(null);
562 Point2D.Float currPt = new Point2D.Float();
563 Point2D.Float conPt1 = new Point2D.Float();
564 Point2D.Float conPt2 = new Point2D.Float();
565 Point2D.Float nextPt = new Point2D.Float();
566 Point2D.Float pathStart = new Point2D.Float();
567 //Point2D.Float floatPt = new Point2D.Float();
568 float[] seg = new float[12];
569 int segType;
570 boolean improved = false;
571
572 // flags to do checking that Sun should have done in GeneralPath.
573 // As of 1.4.1 Sun only guarantees that the path starts with a
574 // segment of type SEG_MOVETO.
575 boolean closed = false;
576
577 while(!iter.isDone()) {
578 segType = iter.currentSegment(seg);
579 iter.next();
580 switch (segType) {
581 case PathIterator.SEG_MOVETO :
582 closed = false;
583 pathStart.setLocation(seg[0], seg[1]);
584 currPt.setLocation(seg[0], seg[1]);
585 nextPt.setLocation(seg[0], seg[1]);
586 newPath.moveTo(nextPt.x,nextPt.y);
587 break;
588 case PathIterator.SEG_CLOSE :
589 if (closed) {
590 throw new IllegalPathStateException("Path closed twice!");
591 }
592 nextPt.x = pathStart.x;
593 nextPt.y = pathStart.y;
594 closed = true;
595
596 if (currPt.distance(nextPt) <= precision) {
597 newPath.closePath();
598 } else {
599 improved = true;
600 seg[0] = nextPt.x;
601 seg[1] = nextPt.y;
602
603 createIntermediateLine(currPt,seg); // seg modified
604 newPath.lineTo(seg[0],seg[1]);
605 newPath.closePath();
606 }
607 break;
608
609 case PathIterator.SEG_LINETO :
610 if (closed) {
611 throw new IllegalPathStateException("Path already closed!");
612 }
613 nextPt.setLocation(seg[0],seg[1]);
614
615 if (currPt.distance(nextPt) <= precision) {
616 newPath.lineTo(nextPt.x,nextPt.y);
617 } else {
618 improved = true;
619
620 createIntermediateLine(currPt,seg); // seg modified
621 newPath.lineTo(seg[0],seg[1]);
622 newPath.lineTo(seg[2],seg[3]);
623 }
624 break;
625 case PathIterator.SEG_QUADTO :
626 if (closed) {
627 throw new IllegalPathStateException("Path already closed!");
628 }
629 conPt1.setLocation(seg[0],seg[1]);
630 nextPt.setLocation(seg[2],seg[3]);
631
632 if (currPt.distance(nextPt) <= precision) {
633 newPath.quadTo(conPt1.x,conPt1.y,nextPt.x,nextPt.y);
634 } else {
635 improved = true;
636
637 createIntermediateQuad(currPt,seg); // seg modified
638 newPath.quadTo(seg[0],seg[1],seg[2],seg[3]);
639 newPath.quadTo(seg[4],seg[5],seg[6],seg[7]);
640 }
641 break;
642 case PathIterator.SEG_CUBICTO :
643 if (closed) {
644 throw new IllegalPathStateException("Path already closed!");
645 }
646 conPt1.setLocation(seg[0], seg[1]);
647 conPt2.setLocation(seg[2], seg[3]);
648 nextPt.setLocation(seg[4], seg[5]);
649
650 if (currPt.distance(nextPt) <= precision) {
651 newPath.curveTo(conPt1.x,conPt1.y,conPt2.x,conPt2.y,nextPt.x,nextPt.y);
652 } else {
653 improved = true;
654
655 createIntermediateCurve(currPt,seg); // seg modified
656 newPath.curveTo(seg[0],seg[1],seg[2],seg[3],seg[4],seg[5]);
657 newPath.curveTo(seg[6],seg[7],seg[8],seg[9],seg[10],seg[11]);
658 // add an intermediate point
659 }
660 break;
661 default:
662 // should never happen unless Sun changes GeneralPath
663 throw new Error("Unknown segment type from Path Iterator");
664 }
665
666 currPt.setLocation(nextPt.x,nextPt.y);
667 }
668
669
670 if (!improved) {
671 return aPath;
672 } else {
673 return newPath;
674 }
675 }
676
677 /**
678 * Mutate the suppled array of float to contain an additional knot.
679 *
680 * @param start the start point of the Line.
681 * @param pts An array of at least size 4 containing the end point x,y
682 * coordinates at position 0 and 1 respectively. This array
683 * format matches that returned by <code>PathIterator</code>
684 */
685 public static void createIntermediateLine(Point2D.Float start, float[] pts) {
686 Point2D.Float newPt = midPoint(start.x,start.y,pts[0],pts[1]);
687 pts[2] = pts[0];
688 pts[3] = pts[1];
689 pts[0] = newPt.x;
690 pts[1] = newPt.y;
691 }
692
693
694 /**
695 * Mutate the suppled array of float to contain an additional knot and
696 * modify the associated control points.
697 *
698 * @param start the start point of the quadratic Bezier spline.
699 * @param pts An array of at least size 8 containing the end point x,y
700 * coordinates at position 2 and 3 respectively. The control
701 * point should be specified at positions 0 and 1. This array
702 * format matches the format returned by
703 * <code>PathIterator</code>.
704 */
705 public static void createIntermediateQuad(Point2D.Float start, float[] pts) {
706 Point2D.Float nearCtl = midPoint(start.x,start.y,pts[0],pts[1]);
707 Point2D.Float farCtl = midPoint(pts[0],pts[1],pts[2],pts[3]);
708
709 Point2D.Float knot = midPoint(nearCtl.x,nearCtl.y,farCtl.x,farCtl.y);
710
711 // move the next point to make space for the new data
712 pts[6] = pts[2];
713 pts[7] = pts[3];
714
715 // record in the new points
716 pts[0] = nearCtl.x;
717 pts[1] = nearCtl.y;
718 pts[2] = knot.x;
719 pts[3] = knot.y;
720 pts[4] = farCtl.x;
721 pts[5] = farCtl.y;
722 }
723
724 /**
725 * Mutate the supplied array of float to contain an additional knot and
726 * modify the associated control points.
727 *
728 * @param start the start point of the quadratic Bezier spline.
729 * @param pts An array of at least size 8 containing the end point x,y
730 * coordinates at position 4 and 5 respectively. The control
731 * point for the start point should be specified at positions
732 * 0 and 1; The control point for the end point should be
733 * specified at positions 2 and 3 This array format matches the
734 * format returned by <code>PathIterator</code>.
735 */
736 public static void createIntermediateCurve(Point2D.Float start, float[] pts) {
737 Point2D.Float stCtl = midPoint(start.x,start.y,pts[0],pts[1]);
738 Point2D.Float endCtl = midPoint(pts[2],pts[3],pts[4],pts[5]);
739 Point2D.Float btwCtls = midPoint(pts[0],pts[1],pts[2],pts[3]);
740
741 Point2D.Float nearCtl = midPoint(stCtl.x,stCtl.y,btwCtls.x,btwCtls.y);
742 Point2D.Float farCtl = midPoint(btwCtls.x,btwCtls.y,endCtl.x,endCtl.y);
743
744 Point2D.Float knot = midPoint(nearCtl.x,nearCtl.y,farCtl.x,farCtl.y);
745
746 // move the next point to make space for the new data
747 pts[10] = pts[4];
748 pts[11] = pts[5];
749
750 // record in the new points
751 pts[0] = stCtl.x; // the modified start control
752 pts[1] = stCtl.y;
753 pts[2] = nearCtl.x; // the start side control for the new knot
754 pts[3] = nearCtl.y;
755 pts[4] = knot.x; // the new knot
756 pts[5] = knot.y;
757 pts[6] = farCtl.x; // the end side control for the new knot
758 pts[7] = farCtl.y;
759 pts[8] = endCtl.x; // the modified end control
760 pts[9] = endCtl.y;
761 }
762
763
764 /**
765 * Find the midpoint of a specifed line segment.
766 *
767 * @param x1 start x coordinate
768 * @param y1 start y coordinate
769 * @param x2 end x coordinate
770 * @param y2 end y coordinate
771 * @return The calculated midpoint
772 */
773 public static Point2D.Float midPoint(float x1, float y1, float x2, float y2) {
774 return new Point2D.Float((x1+x2)/2, (y1+y2)/2);
775 }
776 }
777
778 /*
779 * $Log: ShapeUtils.java,v $
780 * Revision 1.18 2003/09/23 15:23:40 gus
781 * javadoc fixes
782 *
783 * Revision 1.17 2003/06/30 23:12:21 gus
784 * remove a slew of nasty debugging code that was all
785 * commented out anyway. At this point, I certainly
786 * don't remember what most of it did anyway.
787 *
788 * Revision 1.16 2003/06/30 23:06:53 gus
789 * Remove old broken code comment.
790 *
791 * Revision 1.15 2003/06/27 15:32:58 krivard
792 * Bugfix:
793 * Small boxes approaching large boxes from the left were falsely declared not overlapping.
794 * Changed upperleft and lowerleft bounding box check in isOverlapping(Shape,Shape) to Rectangle.intersects().
795 *
796 * Revision 1.14 2003/03/10 16:47:24 gus
797 * Added an optimization to isOverlapping so that points are not itterated unless the
798 * bounding box of the two shapes overlapps. This creates a VERY noticiable
799 * improvement in the breakout problem set.
800 *
801 * Revision 1.13 2003/03/10 15:53:56 gus
802 * Commented out debugging prints
803 * meanContainedPoint now throws NotEnoughPointsException
804 * Fixed nearestSegment
805 * angleToVert now returns PI/2-arcTan(tanTheta)
806 *
807 * Revision 1.12 2003/03/07 19:20:37 gus
808 * Fix npe that occurs when start point is the point around which we are trying to find
809 * a line.
810 *
811 * Revision 1.11 2003/03/06 23:28:30 gus
812 * Some reflection code that might even be useful
813 *
814 * Revision 1.10 2003/03/06 21:19:25 gus
815 * A get nearest point method
816 *
817 * Revision 1.9 2003/03/06 20:52:44 gus
818 * get rid of a class cast exception. CreateTransformedShape always returns
819 * a GeneralPath, so cant' cast to Line2D.Float even if we know it is a line.
820 *
821 * Revision 1.8 2003/03/06 19:54:09 gus
822 * make methods static cause I forgot to
823 *
824 * Revision 1.7 2003/03/06 19:07:07 gus
825 * meant != rather than ==
826 * fixed now.
827 *
828 * Revision 1.6 2003/03/06 18:55:21 gus
829 * We need to advance the Pathe iterator despite what the nutshell book claims
830 *
831 * Revision 1.5 2003/03/06 18:24:39 gus
832 * some partially complete bouncing routines
833 *
834 * Revision 1.4 2003/03/04 23:20:42 gus
835 * bunch more methods added
836 *
837 * Revision 1.3 2003/03/04 16:04:45 gus
838 * Javadoc improvements
839 *
840 * Revision 1.2 2003/03/04 15:32:20 gus
841 * adding log comment
842 *
843 */