001/*
002 * @(#)FlatteningPathIterator.java      1.17 05/11/17
003 *
004 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
005 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
006 */
007
008package armyc2.c2sd.graphics2d;
009
010//import java.util.*;
011
012/**
013 * The <code>FlatteningPathIterator</code> class returns a flattened view of
014 * another {@link PathIterator} object.  Other {@link java.awt.Shape Shape} 
015 * classes can use this class to provide flattening behavior for their paths
016 * without having to perform the interpolation calculations themselves.
017 *
018 * @version 1.6 06/29/98
019 * @author Jim Graham
020 */
021public class FlatteningPathIterator /*implements PathIterator */{
022    static final int GROW_SIZE = 24;    // Multiple of cubic & quad curve size
023
024    ArcIterator src;                    // The source iterator
025
026    double squareflat;                  // Square of the flatness parameter
027                                        // for testing against squared lengths
028
029    int limit;                          // Maximum number of recursion levels
030
031    double hold[] = new double[14];     // The cache of interpolated coords
032                                        // Note that this must be long enough
033                                        // to store a full cubic segment and
034                                        // a relative cubic segment to avoid
035                                        // aliasing when copying the coords
036                                        // of a curve to the end of the array.
037                                        // This is also serendipitously equal
038                                        // to the size of a full quad segment
039                                        // and 2 relative quad segments.
040
041    double curx, cury;                  // The ending x,y of the last segment
042
043    double movx, movy;                  // The x,y of the last move segment
044
045    int holdType;                       // The type of the curve being held
046                                        // for interpolation
047
048    int holdEnd;                        // The index of the last curve segment
049                                        // being held for interpolation
050
051    int holdIndex;                      // The index of the curve segment
052                                        // that was last interpolated.  This
053                                        // is the curve segment ready to be
054                                        // returned in the next call to
055                                        // currentSegment().
056
057    int levels[];                       // The recursion level at which
058                                        // each curve being held in storage
059                                        // was generated.
060
061    int levelIndex;                     // The index of the entry in the
062                                        // levels array of the curve segment
063                                        // at the holdIndex
064
065    boolean done;                       // True when iteration is done
066
067    /**
068     * Constructs a new <code>FlatteningPathIterator</code> object that 
069     * flattens a path as it iterates over it.  The iterator does not 
070     * subdivide any curve read from the source iterator to more than 
071     * 10 levels of subdivision which yields a maximum of 1024 line 
072     * segments per curve.
073     * @param src the original unflattened path being iterated over
074     * @param flatness the maximum allowable distance between the
075     * control points and the flattened curve
076     */
077    public FlatteningPathIterator(ArcIterator src, double flatness) {
078        this(src, flatness, 10);
079    }
080
081    /**
082     * Constructs a new <code>FlatteningPathIterator</code> object 
083     * that flattens a path as it iterates over it.
084     * The <code>limit</code> parameter allows you to control the
085     * maximum number of recursive subdivisions that the iterator
086     * can make before it assumes that the curve is flat enough
087     * without measuring against the <code>flatness</code> parameter.
088     * The flattened iteration therefore never generates more than
089     * a maximum of <code>(2^limit)</code> line segments per curve.
090     * @param src the original unflattened path being iterated over
091     * @param flatness the maximum allowable distance between the
092     * control points and the flattened curve
093     * @param limit the maximum number of recursive subdivisions
094     * allowed for any curved segment
095     * @exception <code>IllegalArgumentException</code> if 
096     *          <code>flatness</code> or <code>limit</code>
097     *          is less than zero
098     */
099    public FlatteningPathIterator(ArcIterator src, double flatness,
100                                  int limit) {
101        if (flatness < 0.0) {
102            throw new IllegalArgumentException("flatness must be >= 0");
103        }
104        if (limit < 0) {
105            throw new IllegalArgumentException("limit must be >= 0");
106        }
107        this.src = src;
108        this.squareflat = flatness * flatness;
109        this.limit = limit;
110        this.levels = new int[limit + 1];
111        // prime the first path segment
112        next2(false);
113    }
114
115    /**
116     * Returns the flatness of this iterator.
117     * @return the flatness of this <code>FlatteningPathIterator</code>.
118     */
119    public double getFlatness() {
120        return Math.sqrt(squareflat);
121    }
122
123    /**
124     * Returns the recursion limit of this iterator.
125     * @return the recursion limit of this 
126     * <code>FlatteningPathIterator</code>.
127     */
128    public int getRecursionLimit() {
129        return limit;
130    }
131
132    /**
133     * Returns the winding rule for determining the interior of the
134     * path.
135     * @return the winding rule of the original unflattened path being
136     * iterated over.
137     * @see PathIterator#WIND_EVEN_ODD
138     * @see PathIterator#WIND_NON_ZERO
139     */
140    public int getWindingRule() {
141        return src.getWindingRule();
142    }
143
144    /**
145     * Tests if the iteration is complete.
146     * @return <code>true</code> if all the segments have
147     * been read; <code>false</code> otherwise.
148     */
149    public boolean isDone() {
150        return done;
151    }
152
153    /*
154     * Ensures that the hold array can hold up to (want) more values.
155     * It is currently holding (hold.length - holdIndex) values.
156     */
157    void ensureHoldCapacity(int want) {
158        if (holdIndex - want < 0) {
159            int have = hold.length - holdIndex;
160            int newsize = hold.length + GROW_SIZE;
161            double newhold[] = new double[newsize];
162            System.arraycopy(hold, holdIndex,
163                             newhold, holdIndex + GROW_SIZE,
164                             have);
165            hold = newhold;
166            holdIndex += GROW_SIZE;
167            holdEnd += GROW_SIZE;
168        }
169    }
170
171    /**
172     * Moves the iterator to the next segment of the path forwards
173     * along the primary direction of traversal as long as there are
174     * more points in that direction.
175     */
176    public void next() {
177        next2(true);
178    }
179
180    private void next2(boolean doNext) {
181        int level;
182
183        if (holdIndex >= holdEnd) {
184            if (doNext) {
185                src.next();
186            }
187            if (src.isDone()) {
188                done = true;
189                return;
190            }
191            holdType = src.currentSegment(hold);
192            levelIndex = 0;
193            levels[0] = 0;
194        }
195
196        switch (holdType) {
197        case PathIterator.SEG_MOVETO:
198        case PathIterator.SEG_LINETO:
199            curx = hold[0];
200            cury = hold[1];
201            if (holdType == PathIterator.SEG_MOVETO) {
202                movx = curx;
203                movy = cury;
204            }
205            holdIndex = 0;
206            holdEnd = 0;
207            break;
208        case PathIterator.SEG_CLOSE:
209            curx = movx;
210            cury = movy;
211            holdIndex = 0;
212            holdEnd = 0;
213            break;
214        case PathIterator.SEG_QUADTO:
215            if (holdIndex >= holdEnd) {
216                // Move the coordinates to the end of the array.
217                holdIndex = hold.length - 6;
218                holdEnd = hold.length - 2;
219                hold[holdIndex + 0] = curx;
220                hold[holdIndex + 1] = cury;
221                hold[holdIndex + 2] = hold[0];
222                hold[holdIndex + 3] = hold[1];
223                hold[holdIndex + 4] = curx = hold[2];
224                hold[holdIndex + 5] = cury = hold[3];
225            }
226
227            level = levels[levelIndex];
228            while (level < limit) {
229                if (QuadCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) {
230                    break;
231                }
232
233                ensureHoldCapacity(4);
234                QuadCurve2D.subdivide(hold, holdIndex,
235                                      hold, holdIndex - 4,
236                                      hold, holdIndex);
237                holdIndex -= 4;
238
239                // Now that we have subdivided, we have constructed
240                // two curves of one depth lower than the original
241                // curve.  One of those curves is in the place of
242                // the former curve and one of them is in the next
243                // set of held coordinate slots.  We now set both
244                // curves level values to the next higher level.
245                level++;
246                levels[levelIndex] = level;
247                levelIndex++;
248                levels[levelIndex] = level;
249            }
250
251            // This curve segment is flat enough, or it is too deep
252            // in recursion levels to try to flatten any more.  The
253            // two coordinates at holdIndex+4 and holdIndex+5 now
254            // contain the endpoint of the curve which can be the
255            // endpoint of an approximating line segment.
256            holdIndex += 4;
257            levelIndex--;
258            break;
259        case PathIterator.SEG_CUBICTO:
260            if (holdIndex >= holdEnd) {
261                // Move the coordinates to the end of the array.
262                holdIndex = hold.length - 8;
263                holdEnd = hold.length - 2;
264                hold[holdIndex + 0] = curx;
265                hold[holdIndex + 1] = cury;
266                hold[holdIndex + 2] = hold[0];
267                hold[holdIndex + 3] = hold[1];
268                hold[holdIndex + 4] = hold[2];
269                hold[holdIndex + 5] = hold[3];
270                hold[holdIndex + 6] = curx = hold[4];
271                hold[holdIndex + 7] = cury = hold[5];
272            }
273
274            level = levels[levelIndex];     
275            while (level < limit) {
276                if (CubicCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) {
277                    break;
278                }
279                                
280                ensureHoldCapacity(6);
281                CubicCurve2D.subdivide(hold, holdIndex,
282                                       hold, holdIndex - 6,
283                                       hold, holdIndex);
284                holdIndex -= 6;
285
286                // Now that we have subdivided, we have constructed
287                // two curves of one depth lower than the original
288                // curve.  One of those curves is in the place of
289                // the former curve and one of them is in the next
290                // set of held coordinate slots.  We now set both
291                // curves level values to the next higher level.
292                level++;
293                levels[levelIndex] = level;
294                levelIndex++;
295                levels[levelIndex] = level;
296            }
297
298            // This curve segment is flat enough, or it is too deep
299            // in recursion levels to try to flatten any more.  The
300            // two coordinates at holdIndex+6 and holdIndex+7 now
301            // contain the endpoint of the curve which can be the
302            // endpoint of an approximating line segment.
303            holdIndex += 6;
304            levelIndex--;
305            break;
306        }
307    }
308
309    /**
310     * Returns the coordinates and type of the current path segment in
311     * the iteration.
312     * The return value is the path segment type:
313     * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE.
314     * A float array of length 6 must be passed in and can be used to
315     * store the coordinates of the point(s).
316     * Each point is stored as a pair of float x,y coordinates.
317     * SEG_MOVETO and SEG_LINETO types return one point,
318     * and SEG_CLOSE does not return any points.
319     * @param coords an array that holds the data returned from
320     * this method
321     * @return the path segment type of the current path segment.
322     * @exception <code>NoSuchElementException</code> if there
323     *          are no more elements in the flattening path to be
324     *          returned.
325     * @see PathIterator#SEG_MOVETO
326     * @see PathIterator#SEG_LINETO
327     * @see PathIterator#SEG_CLOSE
328     */
329    public int currentSegmentFlt(float[] coords) {
330        if (isDone()) {
331            //throw new NoSuchElementException("flattening iterator out of bounds");
332            System.out.println("flattening iterator out of bounds");
333            return -1;
334        }
335        int type = holdType;
336        if (type != PathIterator.SEG_CLOSE) {
337            coords[0] = (float) hold[holdIndex + 0];
338            coords[1] = (float) hold[holdIndex + 1];
339            if (type != PathIterator.SEG_MOVETO) {
340                type = PathIterator.SEG_LINETO;
341            }
342        }
343        return type;
344    }
345
346    /**
347     * Returns the coordinates and type of the current path segment in
348     * the iteration.
349     * The return value is the path segment type:
350     * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE.
351     * A double array of length 6 must be passed in and can be used to
352     * store the coordinates of the point(s).
353     * Each point is stored as a pair of double x,y coordinates.
354     * SEG_MOVETO and SEG_LINETO types return one point,
355     * and SEG_CLOSE does not return any points.
356     * @param coords an array that holds the data returned from
357     * this method
358     * @return the path segment type of the current path segment.
359     * @exception <code>NoSuchElementException</code> if there
360     *          are no more elements in the flattening path to be
361     *          returned.
362     * @see PathIterator#SEG_MOVETO
363     * @see PathIterator#SEG_LINETO
364     * @see PathIterator#SEG_CLOSE
365     */
366    public int currentSegment(double[] coords) {
367        if (isDone()) {
368            //throw new NoSuchElementException("flattening iterator out of bounds");
369            System.out.println("flattening iterator out of bounds");
370            return -1;
371        }
372        int type = holdType;
373        if (type != PathIterator.SEG_CLOSE) {
374            coords[0] = hold[holdIndex + 0];
375            coords[1] = hold[holdIndex + 1];
376            if (type != PathIterator.SEG_MOVETO) {
377                type = PathIterator.SEG_LINETO;
378            }
379        }
380        return type;
381    }
382}