/*
 * $RCSfile: GeneralizedStrip.java,v $
 *
 * Copyright 1998-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 * $Revision: 1.6 $
 * $Date: 2008/02/28 20:17:21 $
 * $State: Exp $
 */

package javax.media.j3d;
import java.util.* ;


/**
 * This class provides static methods to support topological
 * transformations on generalized strips.  This is used by the
 * GeometryDecompressor.  These methods only need to look at the
 * vertex replacement flags to determine how the vertices in the strip
 * are connected.  The connections are rearranged in different ways to
 * transform generalized strips to GeometryArray representations.
 *
 * @see GeneralizedStripFlags
 * @see GeneralizedVertexList
 * @see GeometryDecompressor
 */
class GeneralizedStrip {
    private static final boolean debug = false ;

    // Private convenience copies of various constants.
    private static final int CW  =
	GeneralizedStripFlags.FRONTFACE_CW ;
    private static final int CCW =
	GeneralizedStripFlags.FRONTFACE_CCW ;
    private static final int RESTART_CW   =
	GeneralizedStripFlags.RESTART_CW ;
    private static final int RESTART_CCW  =
	GeneralizedStripFlags.RESTART_CCW ;
    private static final int REPLACE_MIDDLE =
	GeneralizedStripFlags.REPLACE_MIDDLE ;
    private static final int REPLACE_OLDEST =
	GeneralizedStripFlags.REPLACE_OLDEST ;

    /**
     * The IntList is like an ArrayList, but avoids the Integer
     * object wrapper and accessor overhead for simple lists of ints.
     */
    static class IntList {
	/**
	 * The array of ints.
	 */
	int ints[] ;

	/**
	 * The number of ints in this instance.
	 */
	int count ;

	/**
	 * Construct a new empty IntList of the given initial size.
	 * @param initialSize initial size of the backing array
	 */
	IntList(int initialSize) {
	    ints = new int[initialSize] ;
	    count = 0 ;
	}

	/**
	 * Constructs an IntList with the given contents.
	 * @param ints the array of ints to use as the contents
	 */
	IntList(int ints[]) {
	    this.ints = ints ;
	    this.count = ints.length ;
	}

	/**
	 * Add a new int to the end of this list.
	 * @param i the int to be appended to this list
	 */
	void add(int i) {
	    if (count == ints.length) {
		int newints[] = new int[2*count] ;
		System.arraycopy(ints, 0, newints, 0, count) ;
		ints = newints ;
		if (debug)
		    System.err.println
			("GeneralizedStrip.IntList: reallocated " +
			 (2*count) + " ints") ;
	    }
	    ints[count++] = i ;
	}

	/**
	 * Trim the backing array to the current count and return the
	 * resulting backing array.
	 */
	int[] trim() {
	    if (count != ints.length) {
		int newints[] = new int[count] ;
		System.arraycopy(ints, 0, newints, 0, count) ;
		ints = newints ;
	    }
	    return ints ;
	}

	/**
	 * Fill the list with consecutive integers starting from 0.
	 */
	void fillAscending() {
	    for (int i = 0 ; i < ints.length ; i++)
		ints[i] = i ;

	    count = ints.length ;
	}

	public String toString() {
	    String s = new String("[") ;
	    for (int i = 0 ; i < count-1 ; i++)
		s = s + Integer.toString(ints[i]) + ", " ;
	    return s + Integer.toString(ints[count-1]) + "]" ;
	}
    }

    /**
     * The StripArray class is used as the output of some conversion methods
     * in the GeneralizedStrip class.
     */
    static class StripArray {
	/**
	 * A list of indices into the vertices of the original generalized
	 * strip.  It specifies the order in which vertices in the original
	 * strip should be followed to build GeometryArray objects.  
	 */
	IntList vertices ;

	/**
	 * A list of strip counts.
	 */
	IntList stripCounts ;

	/**
	 * Creates a StripArray with the specified vertices and stripCounts.
	 * @param vertices IntList containing vertex indicies.
	 * @param stripCounts IntList containing strip lengths.
	 */
	StripArray(IntList vertices, IntList stripCounts) {
	    this.vertices = vertices ;
	    this.stripCounts = stripCounts ;
	}
    }

    /**
     * Interprets the vertex flags associated with a class implementing
     * GeneralizedStripFlags, constructing and returning a 2-element array of
     * StripArray objects.  The first StripArray will contain triangle strips
     * and the second will contain triangle fans.
     *
     * @param vertices an object implementing GeneralizedStripFlags
     * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or
     * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding
     * @return a 2-element array containing strips in 0 and fans in 1
     */
    static StripArray[] toStripsAndFans(GeneralizedStripFlags vertices,
					int frontFace) {

	int size = vertices.getFlagCount() ;

	// Initialize IntLists to worst-case sizes.
	IntList stripVerts  = new IntList(size*3) ;
	IntList fanVerts    = new IntList(size*3) ;
	IntList stripCounts = new IntList(size) ;
	IntList fanCounts   = new IntList(size) ;

	toStripsAndFans(vertices, frontFace,
			stripVerts, stripCounts, fanVerts, fanCounts) ;

	// Construct the StripArray output.
	StripArray sa[] = new StripArray[2] ;

	if (stripCounts.count > 0)
	    sa[0] = new StripArray(stripVerts, stripCounts) ;

	if (fanCounts.count > 0)
	    sa[1] = new StripArray(fanVerts, fanCounts) ;

	return sa ;
    }

    private static void toStripsAndFans(GeneralizedStripFlags vertices,
					int frontFace,
					IntList stripVerts,
					IntList stripCounts,
					IntList fanVerts,
					IntList fanCounts) {
	int newFlag, curFlag, winding ;
	int v, size, stripStart, stripLength ;
	boolean transition = false ;

	stripStart = 0 ;
	stripLength = 3 ;
	curFlag = vertices.getFlag(0) ;
	winding = (curFlag == RESTART_CW ? CW : CCW) ;
	size = vertices.getFlagCount() ;

	// Vertex replace flags for the first 3 vertices are irrelevant since
	// they can only define a single triangle.  The first meaningful
	// replace flag starts at the 4th vertex.
	v = 3 ;
	if (v < size)
	    curFlag = vertices.getFlag(v) ;

	while (v < size) {
	    newFlag = vertices.getFlag(v) ;

	    if ((newFlag == curFlag) &&
		(newFlag != RESTART_CW) && (newFlag != RESTART_CCW)) {
		// The last flag was the same as this one, and it wasn't a
		// restart: proceed to the next vertex.
		stripLength++ ;
		v++ ;

	    } else {
		// Either this vertex flag changed from the last one, or
		// the flag explicitly specifies a restart: process the
		// last strip and start up a new one.
		if (curFlag == REPLACE_MIDDLE)
		    addFan(fanVerts, fanCounts, stripStart, stripLength,
			   frontFace, winding, transition) ;
		else
		    addStrip(stripVerts, stripCounts, stripStart, stripLength,
			     frontFace, winding) ;

		// Restart: skip to the 4th vertex of the new strip.
		if ((newFlag == RESTART_CW) || (newFlag == RESTART_CCW)) {
		    winding = (newFlag == RESTART_CW ? CW : CCW) ;
		    stripStart = v ;
		    stripLength = 3 ;
		    v += 3 ;
		    transition = false ;
		    if (v < size)
			curFlag = vertices.getFlag(v) ;
		}
		// Strip/fan transition:  decrement start of strip.
		else {
		    if (newFlag == REPLACE_OLDEST) {
			// Flip winding order when transitioning from fans
			// to strips.
			winding = (winding == CW ? CCW : CW) ;
			stripStart = v-2 ;
			stripLength = 3 ;
		    } else {
			// Flip winding order when transitioning from
			// strips to fans only if the preceding strip has
			// an even number of vertices.
			if ((stripLength & 0x01) == 0)
			    winding = (winding == CW ? CCW : CW) ;
			stripStart = v-3 ;
			stripLength = 4 ;
		    }
		    v++ ;
		    transition = true ;
		    curFlag = newFlag ;
		}
	    }
	}
	    
	// Finish off the last strip or fan.
	// If v > size then the strip is degenerate.
	if (v == size)
	    if (curFlag == REPLACE_MIDDLE)
		addFan(fanVerts, fanCounts, stripStart, stripLength,
		       frontFace, winding, transition) ;
	    else
		addStrip(stripVerts, stripCounts, stripStart, stripLength,
			 frontFace, winding) ;
	else
	    throw new IllegalArgumentException
		(J3dI18N.getString("GeneralizedStrip0")) ;

	if (debug) {
	    System.err.println("GeneralizedStrip.toStripsAndFans") ;
	    if (v > size)
		System.err.println(" ended with a degenerate triangle:" +
				   " number of vertices: " + (v-size)) ;
	    
	    System.err.println("\n number of strips: " + stripCounts.count) ;
	    if (stripCounts.count > 0) {
		System.err.println(" number of vertices: " + stripVerts.count) ;
		System.err.println(" vertices/strip: " +
				   (float)stripVerts.count/stripCounts.count) ;
		System.err.println(" strip counts: " + stripCounts.toString()) ;
		// System.err.println(" indices: " + stripVerts.toString()) ;
	    }

	    System.err.println("\n number of fans: " + fanCounts.count) ;
	    if (fanCounts.count > 0) {
		System.err.println(" number of vertices: " + fanVerts.count) ;
		System.err.println(" vertices/strip: " +
				   (float)fanVerts.count/fanCounts.count) ;
		System.err.println(" fan counts: " + fanCounts.toString()) ;
		// System.err.println(" indices: " + fanVerts.toString()) ;
	    }
	    System.err.println("\n total vertices: " +
			       (stripVerts.count + fanVerts.count) + 
			       "\n original number of vertices: " + size +
			       "\n") ;
	}
    }

    //
    // Java 3D specifies that the vertices of front-facing polygons
    // have counter-clockwise (CCW) winding order when projected to
    // the view surface. Polygons with clockwise (CW) vertex winding
    // will be culled as back-facing by default.
    // 
    // Generalized triangle strips can flip the orientation of their
    // triangles with the RESTART_CW and RESTART_CCW vertex flags.
    // Strips flagged with an orientation opposite to what has been
    // specified as front-facing must have their windings reversed in
    // order to have the correct face orientation when represented as
    // GeometryArray objects.
    //
    private static void addStrip(IntList stripVerts,
				 IntList stripCounts,
				 int start, int length,
				 int frontFace, int winding) {
	int vindex = start ;

	if (winding == frontFace) {
	    // Maintain original order.
	    stripCounts.add(length) ;
	    while (vindex < start + length) {
		stripVerts.add(vindex++) ;
	    }
	} else if ((length & 0x1) == 1) {
	    // Reverse winding order if number of vertices is odd.
	    stripCounts.add(length) ;
	    vindex += length-1 ;
	    while (vindex >= start) {
		stripVerts.add(vindex--) ;
	    }
	} else if (length == 4) {
	    // Swap middle vertices.
	    stripCounts.add(4) ;
	    stripVerts.add(vindex) ;
	    stripVerts.add(vindex+2) ;
	    stripVerts.add(vindex+1) ;
	    stripVerts.add(vindex+3) ;
	} else {
	    // Make the 1st triangle a singleton with reverse winding.
	    stripCounts.add(3) ;
	    stripVerts.add(vindex) ;
	    stripVerts.add(vindex+2) ;
	    stripVerts.add(vindex+1) ;
	    if (length > 3) {
		// Copy the rest of the vertices in original order.
		vindex++ ;
		stripCounts.add(length-1) ;
		while (vindex < start + length) {
		    stripVerts.add(vindex++) ;
		}
	    }
	}
    }
	
    private static void addFan(IntList fanVerts,
			       IntList fanCounts,
			       int start, int length,
			       int frontFace, int winding,
			       boolean transition) {
	int vindex = start ;
	fanVerts.add(vindex++) ;

	if (winding == frontFace) {
	    if (transition) {
		// Skip 1st triangle if this is the result of a transition.
		fanCounts.add(length-1) ;
		vindex++ ;
	    } else {
		fanCounts.add(length) ;
		fanVerts.add(vindex++) ;
	    }
	    while (vindex < start + length) {
		fanVerts.add(vindex++) ;
	    }
	} else {
	    // Reverse winding order.
	    vindex += length-2 ;
	    while (vindex > start+1) {
		fanVerts.add(vindex--) ;
	    }
	    if (transition) {
		// Skip 1st triangle if this is the result of a transition.
		fanCounts.add(length-1) ;
	    } else {
		fanCounts.add(length) ;
		fanVerts.add(vindex) ;
	    }
	}
    }

    /**
     * Interprets the vertex flags associated with a class implementing
     * GeneralizedStripFlags, constructing and returning a StripArray containing
     * exclusively strips.
     *
     * @param vertices an object implementing GeneralizedStripFlags
     * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or
     * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding
     * @return a StripArray containing the converted strips
     */
    static StripArray toTriangleStrips(GeneralizedStripFlags vertices,
				       int frontFace) {

	int size = vertices.getFlagCount() ;

	// initialize lists to worst-case sizes.
	IntList stripVerts  = new IntList(size*3) ;
	IntList fanVerts    = new IntList(size*3) ;
	IntList stripCounts = new IntList(size) ;
	IntList fanCounts   = new IntList(size) ;

	toStripsAndFans(vertices, frontFace,
			stripVerts, stripCounts, fanVerts, fanCounts) ;

	if (fanCounts.count == 0)
	    if (stripCounts.count > 0)
		return new StripArray(stripVerts, stripCounts) ;
	    else
		return null ;

	// convert each fan to one or more strips
	int i, v = 0 ;
	for (i = 0 ; i < fanCounts.count ; i++) {
	    fanToStrips(v, fanCounts.ints[i], fanVerts.ints,
			stripVerts, stripCounts, false) ;
	    v += fanCounts.ints[i] ;
	}

	// create the StripArray output
	StripArray sa = new StripArray(stripVerts, stripCounts) ;

	if (debug) {
	    System.err.println("GeneralizedStrip.toTriangleStrips" + 
			       "\n number of strips: " +
			       sa.stripCounts.count) ;
	    if (sa.stripCounts.count > 0) {
		System.err.println(" number of vertices: " +
				   sa.vertices.count + 
				   "\n vertices/strip: " +
				   ((float)sa.vertices.count /
				    (float)sa.stripCounts.count)) ;
		System.err.print(" strip counts: [") ;
		for (i = 0 ; i < sa.stripCounts.count-1 ; i++)
		    System.err.print(sa.stripCounts.ints[i] + ", ") ;
		System.err.println(sa.stripCounts.ints[i] + "]") ;
	    }
	    System.err.println() ;
	}
	return sa ;
    }

    private static void fanToStrips(int v, int length, int fans[],
				    IntList stripVerts,
				    IntList stripCounts,
				    boolean convexPlanar) {
	if (convexPlanar) {
	    // Construct a strip by criss-crossing across the interior.
	    stripCounts.add(length) ;
	    stripVerts.add(fans[v]) ;
	    
	    int j = v + 1 ;
	    int k = v + (length - 1) ;
	    while (j <= k) {
		stripVerts.add(fans[j++]) ;
		if (j > k) break ;
		stripVerts.add(fans[k--]) ;
	    }
	} else {
	    // Traverse non-convex or non-planar fan, biting off 3-triangle
	    // strips or less.  First 5 vertices produce 1 strip of 3
	    // triangles, and every 4 vertices after that produce another
	    // strip of 3 triangles.  Each remaining strip adds 2 vertices.
	    int fanStart = v ;
	    v++ ;
	    while (v+4 <= fanStart + length) {
		stripVerts.add(fans[v]) ;
		stripVerts.add(fans[v+1]) ;
		stripVerts.add(fans[fanStart]) ;
		stripVerts.add(fans[v+2]) ;
		stripVerts.add(fans[v+3]) ;
		stripCounts.add(5) ;
		v += 3 ;
	    }

	    // Finish off the fan.
	    if (v+1 < fanStart + length) {
		stripVerts.add(fans[v]) ;
		stripVerts.add(fans[v+1]) ;
		stripVerts.add(fans[fanStart]) ;
		v++ ;

		if (v+1 < fanStart + length) {
		    stripVerts.add(fans[v+1]) ;
		    stripCounts.add(4) ;
		}
		else
		    stripCounts.add(3) ;
	    }
	}
    }

    /**
     * Interprets the vertex flags associated with a class implementing
     * GeneralizedStripFlags, constructing and returning an array of vertex
     * references representing the original generalized strip as individual
     * triangles.  Each sequence of three consecutive vertex references in the
     * output defines a single triangle.
     *
     * @param vertices an object implementing GeneralizedStripFlags
     * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or
     * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding
     * @return an array of indices into the original vertex array
     */
    static int[] toTriangles(GeneralizedStripFlags vertices, int frontFace) {

	int vertexCount = 0 ;
	StripArray sa[] = toStripsAndFans(vertices, frontFace) ;

	if (sa[0] != null)
	    vertexCount  = 3 * getTriangleCount(sa[0].stripCounts) ;
	if (sa[1] != null)
	    vertexCount += 3 * getTriangleCount(sa[1].stripCounts) ;

	if (debug)
	    System.err.println("GeneralizedStrip.toTriangles\n" +
			       " number of triangles: " + vertexCount/3 + "\n" +
			       " number of vertices: " + vertexCount + "\n") ;
	int t = 0 ;
	int triangles[] = new int[vertexCount] ;

	if (sa[0] != null)
	    t = stripsToTriangles(t, triangles,
				  0, sa[0].vertices.ints,
				  0, sa[0].stripCounts.ints,
				  sa[0].stripCounts.count) ;
	if (sa[1] != null)
	    t = fansToTriangles(t, triangles,
				0, sa[1].vertices.ints,
				0, sa[1].stripCounts.ints,
				sa[1].stripCounts.count) ;
	return triangles ;
    }

    private static int stripsToTriangles(int tstart, int tbuff[],
					 int vstart, int vertices[],
					 int stripStart, int stripCounts[],
					 int stripCount) {
	int t = tstart ;
	int v = vstart ;
	for (int i = 0 ; i < stripCount ; i++) {
	    for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) {
		if ((j & 0x01) == 0) {
		    // even-numbered triangles
		    tbuff[t*3 +0] = vertices[v+0] ;
		    tbuff[t*3 +1] = vertices[v+1] ;
		    tbuff[t*3 +2] = vertices[v+2] ;
		} else {
		    // odd-numbered triangles
		    tbuff[t*3 +0] = vertices[v+1] ;
		    tbuff[t*3 +1] = vertices[v+0] ;
		    tbuff[t*3 +2] = vertices[v+2] ;
		}
		t++ ; v++ ;
	    }
	    v += 2 ;
	}
	return t ;
    }

    private static int fansToTriangles(int tstart, int tbuff[],
				       int vstart, int vertices[],
				       int stripStart, int stripCounts[],
				       int stripCount) {
	int t = tstart ;
	int v = vstart ;
	for (int i = 0 ; i < stripCount ; i++) {
	    for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) {
		tbuff[t*3 +0] = vertices[v] ;
		tbuff[t*3 +1] = vertices[v+j+1] ;
		tbuff[t*3 +2] = vertices[v+j+2] ;
		t++ ;
	    }
	    v += stripCounts[i+stripStart] ;
	}
	return t ;
    }

    /**
     * Interprets the vertex flags associated with a class implementing
     * GeneralizedStripFlags, constructing and returning a 2-element array of
     * StripArray objects.  The first StripArray will contain triangle strips
     * and the second will contain individual triangles in the vertices
     * field.  Short strips will be converted to individual triangles.
     *
     * @param vertices an object implementing GeneralizedStripFlags
     * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or
     * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding
     * @param shortStripSize strips this size or less will be converted to
     * individual triangles if there are more than maxShortStrips of them
     * @param maxShortStrips maximum number of short strips allowed before
     * creating individual triangles
     * @return a 2-element array containing strips in 0 and triangles in 1
     */
    static StripArray[] toStripsAndTriangles(GeneralizedStripFlags vertices,
					     int frontFace, int shortStripSize,
					     int maxShortStrips) {
	int longStripCount = 0 ;
	int longStripVertexCount = 0 ;
	int shortStripCount = 0 ;
	int triangleCount = 0 ;

	StripArray sa[] = new StripArray[2] ;
	StripArray ts = toTriangleStrips(vertices, frontFace) ;

	for (int i = 0 ; i < ts.stripCounts.count ; i++)
	    if (ts.stripCounts.ints[i] <= shortStripSize) {
		shortStripCount++ ;
		triangleCount += ts.stripCounts.ints[i] - 2 ;
	    } else {
		longStripCount++ ;
		longStripVertexCount += ts.stripCounts.ints[i] ;
	    }

	if (debug)
	    System.err.print("GeneralizedStrip.toStripsAndTriangles\n" +
			     " short strip size: " + shortStripSize +
			     " short strips tolerated: " + maxShortStrips +
			     " number of short strips: " + shortStripCount +
			     "\n\n") ;

	if (shortStripCount <= maxShortStrips) {
	    sa[0] = ts ;
	    sa[1] = null ;
	} else {
	    int si = 0 ; int newStripVerts[] = new int[longStripVertexCount] ;
	    int ci = 0 ; int newStripCounts[] = new int[longStripCount] ;
	    int ti = 0 ; int triangles[] = new int[3*triangleCount] ;
	    int vi = 0 ;

	    for (int i = 0 ; i < ts.stripCounts.count ; i++) {
		if (ts.stripCounts.ints[i] <= shortStripSize) {
		    ti = stripsToTriangles(ti, triangles,
					   vi, ts.vertices.ints,
					    i, ts.stripCounts.ints, 1) ;
		    vi += ts.stripCounts.ints[i] ;
		} else {
		    newStripCounts[ci++] = ts.stripCounts.ints[i] ;
		    for (int j = 0 ; j < ts.stripCounts.ints[i] ; j++)
			newStripVerts[si++] = ts.vertices.ints[vi++] ;
		}
	    }

	    if (longStripCount > 0)
		sa[0] = new StripArray(new IntList(newStripVerts),
				       new IntList(newStripCounts)) ;
	    else
		sa[0] = null ;

	    sa[1] = new StripArray(new IntList(triangles), null) ;

	    if (debug) {
		System.err.println(" triangles separated: " + triangleCount) ;
		if (longStripCount > 0) {
		    System.err.println
			(" new vertices/strip: " +
			 ((float)longStripVertexCount/(float)longStripCount)) ;

		    System.err.print(" long strip counts: [") ;
		    for (int i = 0 ; i < longStripCount-1 ; i++)
			System.err.print(newStripCounts[i++] + ", ") ;
		    
		    System.err.println
			(newStripCounts[longStripCount-1] + "]\n") ;
		}
	    }
	}
	return sa ;
    }

    /**
     * Interprets the vertex flags associated with a class implementing
     * GeneralizedStripFlags, constructing and returning a StripArray.
     *
     * RESTART_CW and RESTART_CCW are treated as equivalent, as are
     * REPLACE_MIDDLE and REPLACE_OLDEST.
     * 
     * @param vertices an object implementing GeneralizedStripFlags
     * @return a StripArray representing an array of line strips
     */
     static StripArray toLineStrips(GeneralizedStripFlags vertices) {
	int v, size, stripStart, stripLength, flag ;

	stripStart = 0 ;
	stripLength = 2 ;
	size = vertices.getFlagCount() ;

	// Initialize IntLists to worst-case sizes.
	IntList stripVerts  = new IntList(size*2) ;
	IntList stripCounts = new IntList(size) ;

	// Vertex replace flags for the first two vertices are irrelevant.
	v = 2 ;
	while (v < size) {
	    flag = vertices.getFlag(v) ;

	    if ((flag != RESTART_CW) && (flag != RESTART_CCW)) {
		// proceed to the next vertex.
		stripLength++ ;
		v++ ;

	    } else {
		// Record the last strip.
		stripCounts.add(stripLength) ;
		for (int i = stripStart ; i < stripStart+stripLength ; i++)
		    stripVerts.add(i) ;

		// Start a new strip and skip to its 3rd vertex.
		stripStart = v ;
		stripLength = 2 ;
		v += 2 ;
	    }
	}
	    
	// Finish off the last strip.
	// If v > size then the strip is degenerate.
	if (v == size) {
	    stripCounts.add(stripLength) ;
	    for (int i = stripStart ; i < stripStart+stripLength ; i++)
		stripVerts.add(i) ;
	} else
	    throw new IllegalArgumentException
		(J3dI18N.getString("GeneralizedStrip0")) ;

	if (debug) {
	    System.err.println("GeneralizedStrip.toLineStrips\n") ;
	    if (v > size)
		System.err.println(" ended with a degenerate line") ;

	    System.err.println(" number of strips: " + stripCounts.count) ;
	    if (stripCounts.count > 0) {
		System.err.println(" number of vertices: " + stripVerts.count) ;
		System.err.println(" vertices/strip: " +
				   (float)stripVerts.count/stripCounts.count) ;
		System.err.println(" strip counts: " + stripCounts.toString()) ;
		// System.err.println(" indices: " + stripVerts.toString()) ;
	    }
	    System.err.println() ;
	}

	if (stripCounts.count > 0)
	    return new StripArray(stripVerts, stripCounts) ;
	else
	    return null ;
    }

    /**
     * Counts the number of lines defined by arrays of line strips.
     * 
     * @param stripCounts array of strip counts, as used by the
     * GeometryStripArray object
     * @return number of lines in the strips
     */
    static int getLineCount(int stripCounts[]) {
	int count = 0 ;
	for (int i = 0 ; i < stripCounts.length ; i++)
	    count += (stripCounts[i] - 1) ;
	return count ;
    }

    /**
     * Counts the number of triangles defined by arrays of
     * triangle strips or fans.
     *
     * @param stripCounts array of strip counts, as used by the
     * GeometryStripArray object
     * @return number of triangles in the strips or fans
     */
    static int getTriangleCount(int stripCounts[]) {
	int count = 0 ;
	for (int i = 0 ; i < stripCounts.length ; i++)
	    count += (stripCounts[i] - 2) ;
	return count ;
    }

    /**
     * Counts the number of triangles defined by arrays of
     * triangle strips or fans.
     *
     * @param stripCounts IntList of strip counts
     * @return number of triangles in the strips or fans
     */
    static int getTriangleCount(IntList stripCounts) {
	int count = 0 ;
	for (int i = 0 ; i < stripCounts.count ; i++)
	    count += (stripCounts.ints[i] - 2) ;
	return count ;
    }

    /**
     * Breaks up triangle strips into separate triangles.
     * 
     * @param stripCounts array of strip counts, as used by the
     * GeometryStripArray object
     * @return array of ints which index into the original vertex array; each
     * set of three consecutive vertex indices defines a single triangle
     */
    static int[] stripsToTriangles(int stripCounts[]) {
	int triangleCount = getTriangleCount(stripCounts) ;
	int tbuff[] = new int[3*triangleCount] ;
	IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ;

	vertices.fillAscending() ;
	stripsToTriangles(0, tbuff,
			  0, vertices.ints,
			  0, stripCounts,
			  stripCounts.length) ;
	return tbuff ;
    }

    /**
     * Breaks up triangle fans into separate triangles.
     * 
     * @param stripCounts array of strip counts, as used by the
     * GeometryStripArray object
     * @return array of ints which index into the original vertex array; each
     * set of three consecutive vertex indices defines a single triangle
     */
    static int[] fansToTriangles(int stripCounts[]) {
	int triangleCount = getTriangleCount(stripCounts) ;
	int tbuff[] = new int[3*triangleCount] ;
	IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ;

	vertices.fillAscending() ;
	fansToTriangles(0, tbuff,
			0, vertices.ints,
			0, stripCounts,
			stripCounts.length) ;
	return tbuff ;
    }

    /**
     * Takes a fan and converts it to one or more strips.
     * 
     * @param v index into the fans array of the first vertex in the fan
     * @param length number of vertices in the fan
     * @param fans array of vertex indices representing one or more fans
     * @param convexPlanar if true indicates that the fan is convex and
     * planar; such fans will always be converted into a single strip
     * @return a StripArray containing the converted strips
     */
    static StripArray fanToStrips(int v, int length, int fans[],
				  boolean convexPlanar) {

	// Initialize IntLists to worst-case sizes.
	IntList stripVerts  = new IntList(length*3) ;
	IntList stripCounts = new IntList(length) ;

	fanToStrips(v, length, fans, stripVerts, stripCounts, convexPlanar) ;
	return new StripArray(stripVerts, stripCounts) ;
    }
}
