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