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 */