All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.util.GeometryBuilder Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.util;

import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.geom.*;

import javax.media.opengl.GL;
import javax.media.opengl.glu.*;
import java.nio.*;
import java.util.*;

/**
 * @author dcollins
 * @version $Id: GeometryBuilder.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class GeometryBuilder
{
    public static final int OUTSIDE = 0;
    public static final int INSIDE = 1;

    public static final int COUNTER_CLOCKWISE = 0;
    public static final int CLOCKWISE = 1;

    public static final int TOP = 1;
    public static final int BOTTOM = 2;
    public static final int LEFT = 4;
    public static final int RIGHT = 8;

    /**
     * Bit code indicating that the leader's location is inside the rectangle. Used by {@link
     * #computeLeaderLocationCode(float, float, float, float, float, float)}.
     */
    protected static final int LEADER_LOCATION_INSIDE = 0;
    /**
     * Bit code indicating that the leader's location is above the rectangle. Used by {@link
     * #computeLeaderLocationCode(float, float, float, float, float, float)}.
     */
    protected static final int LEADER_LOCATION_TOP = 1;
    /**
     * Bit code indicating that the leader's location is below the rectangle. Used by {@link
     * #computeLeaderLocationCode(float, float, float, float, float, float)}.
     */
    protected static final int LEADER_LOCATION_BOTTOM = 2;
    /**
     * Bit code indicating that the leader's location is to the right of the rectangle. Used by {@link
     * #computeLeaderLocationCode(float, float, float, float, float, float)}.
     */
    protected static final int LEADER_LOCATION_RIGHT = 4;
    /**
     * Bit code indicating that the leader's location is to the left of the rectangle. Used by {@link
     * #computeLeaderLocationCode(float, float, float, float, float, float)}.
     */
    protected static final int LEADER_LOCATION_LEFT = 8;

    private int orientation = OUTSIDE;

    public GeometryBuilder()
    {
    }

    public int getOrientation()
    {
        return this.orientation;
    }

    public void setOrientation(int orientation)
    {
        this.orientation = orientation;
    }

    //**************************************************************//
    //********************  Sphere  ********************************//
    //**************************************************************//

    public IndexedTriangleArray tessellateSphere(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int[] indexArray = new int[ICOSAHEDRON_INDEX_COUNT];
        float[] vertexArray = new float[3 * ICOSAHEDRON_VERTEX_COUNT];
        System.arraycopy(icosahedronIndexArray, 0, indexArray, 0, ICOSAHEDRON_INDEX_COUNT);
        System.arraycopy(icosahedronVertexArray, 0, vertexArray, 0, 3 * ICOSAHEDRON_VERTEX_COUNT);

        // The static icosahedron tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < ICOSAHEDRON_INDEX_COUNT; index += 3)
            {
                int tmp = indexArray[index];
                indexArray[index] = indexArray[index + 2];
                indexArray[index + 2] = tmp;
            }
        }

        // Start with a triangular tessellated icosahedron.
        IndexedTriangleArray ita = new IndexedTriangleArray(
            ICOSAHEDRON_INDEX_COUNT, indexArray, ICOSAHEDRON_VERTEX_COUNT, vertexArray);

        // Subdivide the icosahedron a specified number of times. The subdivison step computes midpoints between
        // adjacent vertices. These midpoints are not on the sphere, but must be moved onto the sphere. We normalize
        // each midpoint vertex to acheive this.
        for (int i = 0; i < subdivisions; i++)
        {
            this.subdivideIndexedTriangleArray(ita);

            vertexArray = ita.getVertices();
            for (int vertex = 0; vertex < ita.vertexCount; vertex++)
            {
                norm3AndSet(vertexArray, 3 * vertex);
            }
        }

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexArray = ita.getVertices();
            for (int vertex = 0; vertex < ita.vertexCount; vertex++)
            {
                mul3AndSet(vertexArray, 3 * vertex, radius);
            }
        }

        return ita;
    }

    public IndexedTriangleBuffer tessellateSphereBuffer(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(ICOSAHEDRON_INDEX_COUNT);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * ICOSAHEDRON_VERTEX_COUNT);
        indexBuffer.put(icosahedronIndexArray, 0, ICOSAHEDRON_INDEX_COUNT);
        vertexBuffer.put(icosahedronVertexArray, 0, 3 * ICOSAHEDRON_VERTEX_COUNT);

        // The static icosahedron tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < ICOSAHEDRON_INDEX_COUNT; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated icosahedron.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            ICOSAHEDRON_INDEX_COUNT, indexBuffer, ICOSAHEDRON_VERTEX_COUNT, vertexBuffer);

        // Subdivide the icosahedron a specified number of times. The subdivison step computes midpoints between
        // adjacent vertices. These midpoints are not on the sphere, but must be moved onto the sphere. We normalize
        // each midpoint vertex to achieve this.
        for (int i = 0; i < subdivisions; i++)
        {
            this.subdivideIndexedTriangleBuffer(itb);

            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.getVertexCount(); vertex++)
            {
                norm3AndSet(vertexBuffer, 3 * vertex);
            }
        }

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    public IndexedTriangleBuffer tessellateEllipsoidBuffer(float a, float b, float c, int subdivisions)
    {
        IndexedTriangleBuffer itb = tessellateSphereBuffer(a, subdivisions);

        // normalize 2nd and 3rd radii in terms of the first one
        float bScale = b / a;
        float cScale = c / a;

        // scale Y and Z components of each vertex by appropriate scaling factor
        FloatBuffer vertexBuffer = itb.getVertices();
        for (int vertex = 0; vertex < itb.getVertexCount(); vertex++)
        {
            // offset = 0 for x coord, 1 for y coord, etc.
            mulAndSet(vertexBuffer, 3 * vertex, bScale, 2);
            mulAndSet(vertexBuffer, 3 * vertex, cScale, 1);
        }

        itb.vertices.rewind();

        return itb;
    }

    // Icosahedron tessellation taken from the
    // OpenGL Programming Guide, Chapter 2, Example 2-13: Drawing an Icosahedron.

    private static final int ICOSAHEDRON_INDEX_COUNT = 60;
    private static final int ICOSAHEDRON_VERTEX_COUNT = 12;
    private static final float X = 0.525731112119133606f;
    private static final float Z = 0.850650808352039932f;

    private static float[] icosahedronVertexArray =
        {
            -X, 0, Z,
            X, 0, Z,
            -X, 0, -Z,
            X, 0, -Z,
            0, Z, X,
            0, Z, -X,
            0, -Z, X,
            0, -Z, -X,
            Z, X, 0,
            -Z, X, 0,
            Z, -X, 0,
            -Z, -X, 0
        };

    private static int[] icosahedronIndexArray =
        {
            1, 4, 0,
            4, 9, 0,
            4, 5, 9,
            8, 5, 4,
            1, 8, 4,
            1, 10, 8,
            10, 3, 8,
            8, 3, 5,
            3, 2, 5,
            3, 7, 2,
            3, 10, 7,
            10, 6, 7,
            6, 11, 7,
            6, 0, 11,
            6, 1, 0,
            10, 1, 6,
            11, 0, 9,
            2, 11, 9,
            5, 2, 9,
            11, 2, 7
        };

    //**************************************************************//
    //***********************  Box  ********************************//
    //**************************************************************//

    // create the entire box
    public IndexedTriangleBuffer tessellateBoxBuffer(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(BOX_INDEX_COUNT);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * BOX_VERTEX_COUNT);
        indexBuffer.put(boxIndexArray, 0, BOX_INDEX_COUNT);
        vertexBuffer.put(boxVertexArray, 0, 3 * BOX_VERTEX_COUNT);

        // The static box tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < BOX_INDEX_COUNT; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a tessellated box.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            BOX_INDEX_COUNT, indexBuffer, BOX_VERTEX_COUNT, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    // create only one face of the box
    public IndexedTriangleBuffer tessellateBoxBuffer(int face, float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (face < 0 || face >= 6)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "face < 0 or face >= 6");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(BOX_INDEX_COUNT / 6);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * BOX_VERTEX_COUNT / 6);

        // fill subset of index buffer
        int[] subArray = new int[BOX_INDEX_COUNT / 6];
        for (int i = 0; i < BOX_INDEX_COUNT / 6; i++)
        {
            subArray[i] = boxFacesIndexArray[face * BOX_INDEX_COUNT / 6 + i];
        }
        indexBuffer.put(subArray, 0, BOX_INDEX_COUNT / 6);

        float[] vertexSubset = new float[3 * BOX_VERTEX_COUNT / 6];
        for (int i = 0; i < 3 * BOX_VERTEX_COUNT / 6; i++)
        {
            vertexSubset[i] = boxVertexArray[face * 3 * BOX_VERTEX_COUNT / 6 + i];
        }
        vertexBuffer.put(vertexSubset, 0, 3 * BOX_VERTEX_COUNT / 6);

        // The static box tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < BOX_INDEX_COUNT / 6; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a tessellated box.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            BOX_INDEX_COUNT / 6, indexBuffer, BOX_VERTEX_COUNT / 6, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    private static final int BOX_INDEX_COUNT = 36;
    private static final int BOX_VERTEX_COUNT = 24;
    private static final float B = 1.0f;

    private static float[] boxVertexArray =
        {    // right
            B, -B, B,          // 0
            B, B, B,           // 1
            B, -B, -B,         // 2
            B, B, -B,          // 3

            // front
            -B, B, B,          // 4
            B, B, B,           // 5
            -B, -B, B,         // 6
            B, -B, B,          // 7

            // left
            -B, B, B,          // 8
            -B, -B, B,         // 9
            -B, B, -B,         // 10
            -B, -B, -B,        // 11

            // back
            B, B, -B,          // 12
            -B, B, -B,         // 13
            B, -B, -B,         // 14
            -B, -B, -B,        // 15

            // top
            B, B, B,           // 16
            -B, B, B,          // 17
            B, B, -B,          // 18
            -B, B, -B,         // 19

            // bottom
            -B, -B, B,          // 20
            B, -B, B,           // 21
            -B, -B, -B,         // 22
            B, -B, -B           // 23
        };

    private static int[] boxIndexArray =
        {
            2, 3, 1,             // right
            2, 1, 0,
            4, 6, 7,             // front
            4, 7, 5,
            8, 10, 11,           // left
            8, 11, 9,
            12, 14, 15,          // back
            12, 15, 13,
            16, 18, 19,          // top
            16, 19, 17,
            20, 22, 23,          // bottom
            20, 23, 21,
        };

    private static int[] boxFacesIndexArray =
        {
            2, 3, 1,            // right
            2, 1, 0,
            0, 2, 3,            // front
            0, 3, 1,
            0, 2, 3,            // left
            0, 3, 1,
            0, 2, 3,            // back
            0, 3, 1,
            0, 2, 3,            // top
            0, 3, 1,
            0, 2, 3,            // bottom
            0, 3, 1,
        };

    //**************************************************************//
    //***********************  Pyramid  ****************************//
    //**************************************************************//

    public IndexedTriangleBuffer tessellatePyramidBuffer(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(PYRAMID_INDEX_COUNT);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * PYRAMID_VERTEX_COUNT);
        indexBuffer.put(pyramidIndexArray, 0, PYRAMID_INDEX_COUNT);
        vertexBuffer.put(pyramidVertexArray, 0, 3 * PYRAMID_VERTEX_COUNT);

        // The static box tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < PYRAMID_INDEX_COUNT; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a tessellated pyramid.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            PYRAMID_INDEX_COUNT, indexBuffer, PYRAMID_VERTEX_COUNT, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    public IndexedTriangleBuffer tessellatePyramidBuffer(int face, float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // default values for pyramid side
        int faceIndexCount = 3;
        int faceVertexCount = 3;
        int faceIndicesOffset = face * faceIndexCount;
        int faceVerticesOffset = face * 3 * faceVertexCount;

        if (face == 4)   // the pyramid base
        {
            faceIndicesOffset = 4 * faceIndexCount;
            faceVerticesOffset = 4 * 3 * faceVertexCount;
            faceIndexCount = 6;
            faceVertexCount = 4;
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(faceIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * faceVertexCount);

        // fill subset of index buffer
        int[] subArray = new int[faceIndexCount];
        for (int i = 0; i < faceIndexCount; i++)
        {
            subArray[i] = pyramidFacesIndexArray[faceIndicesOffset + i];
        }
        indexBuffer.put(subArray, 0, faceIndexCount);

        float[] vertexSubset = new float[3 * faceVertexCount];
        for (int i = 0; i < 3 * faceVertexCount; i++)
        {
            vertexSubset[i] = pyramidVertexArray[faceVerticesOffset + i];
        }
        vertexBuffer.put(vertexSubset, 0, 3 * faceVertexCount);

        // The static box tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (int index = 0; index < faceIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a tessellated pyramid.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            faceIndexCount, indexBuffer, faceVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    private static final int PYRAMID_INDEX_COUNT = 18;
    private static final int PYRAMID_VERTEX_COUNT = 16;
    private static final float P = 1.0f;

    private static float[] pyramidVertexArray =
        {    // right
            0, 0, P,           // 0   (point)
            P, -P, -P,         // 1
            P, P, -P,          // 2

            // front
            0, 0, P,           // 3   (point)
            -P, -P, -P,         // 4
            P, -P, -P,          // 5

            // left
            0, 0, P,           // 6   (point)
            -P, P, -P,         // 7
            -P, -P, -P,        // 8

            // back
            0, 0, P,           // 9   (point)
            P, P, -P,         // 10
            -P, P, -P,        // 11

            // bottom (base) face
            P, P, -P,          // 12
            -P, P, -P,         // 13
            P, -P, -P,         // 14
            -P, -P, -P         // 15
        };

    private static int[] pyramidIndexArray =
        {
            0, 1, 2,              // right
            3, 4, 5,              // front
            6, 7, 8,              // left
            9, 10, 11,            // back
            12, 14, 15,           // base
            12, 15, 13,
        };

    private static int[] pyramidFacesIndexArray =
        {
            0, 1, 2,              // right
            0, 1, 2,              // front
            0, 1, 2,              // left
            0, 1, 2,              // back
            0, 2, 3,              // base
            0, 3, 1,
        };

    //**************************************************************//
    //********************      Unit Cylinder    *******************//
    //**************************************************************//

    public IndexedTriangleBuffer tessellateCylinderBuffer(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, index;
        float x, y, z, a;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        int cylinderIndexCount = 12 * slices;
        int cylinderVertexCount = 4 * slices + 4;

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(cylinderIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * cylinderVertexCount);

        // VERTICES

        // top and bottom center points
        vertexBuffer.put(0, 0f);
        vertexBuffer.put(1, 0f);
        vertexBuffer.put(2, 1.0f);

        vertexBuffer.put(3 * (slices + 1), 0f);
        vertexBuffer.put(3 * (slices + 1) + 1, 0f);
        vertexBuffer.put(3 * (slices + 1) + 2, -1.0f);

        // rim points
        for (i = 0; i < slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            z = 1.0f;

            index = 3 * i + 3;

            // cylinder top
            vertexBuffer.put(index, x * radius);
            vertexBuffer.put(index + 1, y * radius);
            vertexBuffer.put(index + 2, z);

            index += 3;     // add 3 for the second center point

            // cylinder bottom
            vertexBuffer.put(index + 3 * slices, x * radius);
            vertexBuffer.put(index + 3 * slices + 1, y * radius);
            vertexBuffer.put(index + 3 * slices + 2, -z);

            index += 6 * slices + 3 * i;

            // core upper rim
            vertexBuffer.put(index, x * radius);
            vertexBuffer.put(index + 1, y * radius);
            vertexBuffer.put(index + 2, z);

            // core lower rim
            vertexBuffer.put(index + 3, x * radius);
            vertexBuffer.put(index + 4, y * radius);
            vertexBuffer.put(index + 5, -z);
        }

        // extra vertices for seamless texture mapping

        int wrapIndex = 3 * (4 * slices + 2);
        x = (float) Math.sin(0);
        y = (float) Math.cos(0);
        z = 1.0f;

        vertexBuffer.put(wrapIndex, x * radius);
        vertexBuffer.put(wrapIndex + 1, y * radius);
        vertexBuffer.put(wrapIndex + 2, z);

        vertexBuffer.put(wrapIndex + 3, x * radius);
        vertexBuffer.put(wrapIndex + 4, y * radius);
        vertexBuffer.put(wrapIndex + 5, -z);

        // INDICES

        int coreIndex = (2 * slices) + 2;
        int centerPoint = 0;

        for (i = 0; i < slices; i++)
        {
            // cylinder top
            index = 3 * i;

            indexBuffer.put(index, 0);                  // center point
            indexBuffer.put(index + 1, (i < slices - 1) ? i + 2 : 1);
            indexBuffer.put(index + 2, i + 1);

            // cylinder bottom
            index = 3 * (slices + i);

            indexBuffer.put(index, (slices + 1));             // center point
            indexBuffer.put(index + 1, (i < slices - 1) ? (slices + 1) + i + 2 : (slices + 1) + 1);
            indexBuffer.put(index + 2, (slices + 1) + i + 1);

            // cylinder core
            index = 6 * (slices + i);

            indexBuffer.put(index, coreIndex);
            indexBuffer.put(index + 1, coreIndex + 1);
            indexBuffer.put(index + 2, coreIndex + 2);

            indexBuffer.put(index + 3, coreIndex + 2);
            indexBuffer.put(index + 4, coreIndex + 1);
            indexBuffer.put(index + 5, coreIndex + 3);

            coreIndex += 2;
        }

        // The static cylinder tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < cylinderIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated cylinder.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            cylinderIndexCount, indexBuffer, cylinderVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    public IndexedTriangleBuffer tessellateCylinderBuffer(int face, float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, index;
        float x, y, z, a;

        // face 0 = top
        // face 1 = bottom
        // face 2 = round cylinder core

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        int cylinderIndexCount = 3 * slices;
        int cylinderVertexCount = slices + 1;

        if (face == 2)      // cylinder core
        {
            cylinderIndexCount = 6 * slices;
            cylinderVertexCount = 2 * slices + 2;
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(cylinderIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * cylinderVertexCount);

        // VERTICES

        if (face == 0 || face == 1)     // top or bottom cylinder face
        {
            int isTop = 1;
            if (face == 1)
                isTop = -1;

            // top center point
            vertexBuffer.put(0, 0f);
            vertexBuffer.put(1, 0f);
            vertexBuffer.put(2, isTop * 1.0f);

            // rim points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 3 * i + 3;

                // cylinder top
                vertexBuffer.put(index, x * radius);
                vertexBuffer.put(index + 1, y * radius);
                vertexBuffer.put(index + 2, isTop * z);
            }
        }
        else if (face == 2)     // cylinder core
        {
            // rim points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 6 * i;

                // core upper rim
                vertexBuffer.put(index, x * radius);
                vertexBuffer.put(index + 1, y * radius);
                vertexBuffer.put(index + 2, z);

                // core lower rim
                vertexBuffer.put(index + 3, x * radius);
                vertexBuffer.put(index + 4, y * radius);
                vertexBuffer.put(index + 5, -z);
            }

            // extra vertices for seamless texture mapping

            int wrapIndex = 3 * (2 * slices);
            x = (float) Math.sin(0);
            y = (float) Math.cos(0);
            z = 1.0f;

            vertexBuffer.put(wrapIndex, x * radius);
            vertexBuffer.put(wrapIndex + 1, y * radius);
            vertexBuffer.put(wrapIndex + 2, z);

            vertexBuffer.put(wrapIndex + 3, x * radius);
            vertexBuffer.put(wrapIndex + 4, y * radius);
            vertexBuffer.put(wrapIndex + 5, -z);
        }

        // INDICES

        int centerPoint = 0;

        if (face == 0 || face == 1)      // top or bottom cylinder face
        {
            for (i = 0; i < slices; i++)
            {
                index = 3 * i;

                indexBuffer.put(index, 0);                  // center point
                indexBuffer.put(index + 1, (i < slices - 1) ? i + 2 : 1);
                indexBuffer.put(index + 2, i + 1);
            }
        }
        else if (face == 2)             // cylinder core
        {
            int coreIndex = 0;

            for (i = 0; i < slices; i++)
            {
                index = 6 * i;

                indexBuffer.put(index, coreIndex);
                indexBuffer.put(index + 1, coreIndex + 1);
                indexBuffer.put(index + 2, coreIndex + 2);

                indexBuffer.put(index + 3, coreIndex + 2);
                indexBuffer.put(index + 4, coreIndex + 1);
                indexBuffer.put(index + 5, coreIndex + 3);

                coreIndex += 2;
            }
        }

        // The static cylinder tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < cylinderIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated cylinder.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            cylinderIndexCount, indexBuffer, cylinderVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    //**************************************************************//
    //********************          Wedge        *******************//
    //**************************************************************//

    public IndexedTriangleBuffer tessellateWedgeBuffer(float radius, int subdivisions, Angle angle)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (angle.getRadians() < 0 || angle.getRadians() > 2 * Math.PI)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "angle < 0 or angle > 2 PI");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, index;
        float x, y, z, a;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = (float) angle.getRadians() / slices;

        int wedgeIndexCount = 12 * slices + 12;
        int wedgeVertexCount = 4 * (slices + 1) + 2 + 8;

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(wedgeIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * wedgeVertexCount);

        // VERTICES

        // top and bottom center points
        vertexBuffer.put(0, 0f);
        vertexBuffer.put(1, 0f);
        vertexBuffer.put(2, 1.0f);

        vertexBuffer.put(3 * (slices + 2), 0f);
        vertexBuffer.put(3 * (slices + 2) + 1, 0f);
        vertexBuffer.put(3 * (slices + 2) + 2, -1.0f);

        // rim points
        for (i = 0; i <= slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            z = 1.0f;

            index = 3 * i + 3;

            // wedge top
            vertexBuffer.put(index, x * radius);
            vertexBuffer.put(index + 1, y * radius);
            vertexBuffer.put(index + 2, z);

            index += 3;     // add 3 for the second center point

            // wedge bottom
            vertexBuffer.put(index + 3 * (slices + 1), x * radius);
            vertexBuffer.put(index + 3 * (slices + 1) + 1, y * radius);
            vertexBuffer.put(index + 3 * (slices + 1) + 2, -z);

            index = 3 * (2 * slices + 4 + 2 * i);

            // core upper rim
            vertexBuffer.put(index, x * radius);
            vertexBuffer.put(index + 1, y * radius);
            vertexBuffer.put(index + 2, z);

            // core lower rim
            vertexBuffer.put(index + 3, x * radius);
            vertexBuffer.put(index + 4, y * radius);
            vertexBuffer.put(index + 5, -z);
        }

        // wedge sides
        for (i = 0; i < 2; i++)
        {
            x = (float) Math.sin(i * angle.getRadians());
            y = (float) Math.cos(i * angle.getRadians());
            z = 1.0f;

            index = 3 * (4 * (slices + 1 + i) + 2);

            // inner points
            vertexBuffer.put(index, 0);
            vertexBuffer.put(index + 1, 0);
            vertexBuffer.put(index + 2, z);

            vertexBuffer.put(index + 3, 0);
            vertexBuffer.put(index + 4, 0);
            vertexBuffer.put(index + 5, -z);

            // outer points
            vertexBuffer.put(index + 6, x * radius);
            vertexBuffer.put(index + 7, y * radius);
            vertexBuffer.put(index + 8, z);

            vertexBuffer.put(index + 9, x * radius);
            vertexBuffer.put(index + 10, y * radius);
            vertexBuffer.put(index + 11, -z);
        }

        // INDICES

        int coreIndex = 2 * (slices + 1) + 2;

        for (i = 0; i < slices; i++)
        {
            // wedge top
            index = 3 * i;

            indexBuffer.put(index, 0);                          // center point
            indexBuffer.put(index + 1, i + 2);
            indexBuffer.put(index + 2, i + 1);

            // wedge bottom
            index = 3 * (slices + i);

            indexBuffer.put(index, (slices + 2));               // center point
            indexBuffer.put(index + 1, (slices + 2) + i + 2);
            indexBuffer.put(index + 2, (slices + 2) + i + 1);

            // wedge core
            index = 6 * (slices + i);

            indexBuffer.put(index + 0, coreIndex + 0);
            indexBuffer.put(index + 1, coreIndex + 1);
            indexBuffer.put(index + 2, coreIndex + 2);

            indexBuffer.put(index + 3, coreIndex + 2);
            indexBuffer.put(index + 4, coreIndex + 1);
            indexBuffer.put(index + 5, coreIndex + 3);

            coreIndex += 2;
        }

        // wedge sides
        for (i = 0; i < 2; i++)
        {
            index = 3 * (4 * slices) + 6 * i;
            coreIndex = 4 * (slices + 1) + 2 + i * 4;

            indexBuffer.put(index + 0, coreIndex + 0);
            indexBuffer.put(index + 1, coreIndex + 2);
            indexBuffer.put(index + 2, coreIndex + 1);

            indexBuffer.put(index + 3, coreIndex + 1);
            indexBuffer.put(index + 4, coreIndex + 2);
            indexBuffer.put(index + 5, coreIndex + 3);
        }

        // The static wedge tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < wedgeIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated wedge.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            wedgeIndexCount, indexBuffer, wedgeVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    public IndexedTriangleBuffer tessellateWedgeBuffer(int face, float radius, int subdivisions, Angle angle)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (angle.getRadians() < 0 || angle.getRadians() > 2 * Math.PI)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "angle < 0 or angle > 2 PI");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, index;
        float x, y, z, a;

        // face 0 = top
        // face 1 = bottom
        // face 2 = round core wall
        // face 3 = first wedge side
        // face 4 = second wedge side

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = (float) angle.getRadians() / slices;

        int wedgeIndexCount = 6;
        int wedgeVertexCount = 4;

        if (face == 0 || face == 1)
        {
            wedgeIndexCount = 3 * slices;
            wedgeVertexCount = slices + 2;
        }
        else if (face == 2)
        {
            wedgeIndexCount = 6 * slices;
            wedgeVertexCount = 2 * slices + 2;
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(wedgeIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * wedgeVertexCount);

        // VERTICES

        if (face == 0 || face == 1)      // wedge top or bottom
        {

            int isTop = 1;
            if (face == 1)
                isTop = -1;

            // center point
            vertexBuffer.put(0, 0f);
            vertexBuffer.put(1, 0f);
            vertexBuffer.put(2, isTop * 1.0f);

            // rim points
            for (i = 0; i <= slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 3 * i + 3;

                // wedge top
                vertexBuffer.put(index, x * radius);
                vertexBuffer.put(index + 1, y * radius);
                vertexBuffer.put(index + 2, isTop * z);
            }
        }
        else if (face == 2)              // round core wall
        {
            // rim points
            for (i = 0; i <= slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 3 * (2 * i);

                // core upper rim
                vertexBuffer.put(index, x * radius);
                vertexBuffer.put(index + 1, y * radius);
                vertexBuffer.put(index + 2, z);

                // core lower rim
                vertexBuffer.put(index + 3, x * radius);
                vertexBuffer.put(index + 4, y * radius);
                vertexBuffer.put(index + 5, -z);
            }
        }
        else if (face == 3 || face == 4)
        {
            // wedge side
            i = face - 3;

            x = (float) Math.sin(i * angle.getRadians());
            y = (float) Math.cos(i * angle.getRadians());
            z = 1.0f;

            index = 0;

            // inner points
            vertexBuffer.put(index, 0);
            vertexBuffer.put(index + 1, 0);
            vertexBuffer.put(index + 2, z);

            vertexBuffer.put(index + 3, 0);
            vertexBuffer.put(index + 4, 0);
            vertexBuffer.put(index + 5, -z);

            // outer points
            vertexBuffer.put(index + 6, x * radius);
            vertexBuffer.put(index + 7, y * radius);
            vertexBuffer.put(index + 8, z);

            vertexBuffer.put(index + 9, x * radius);
            vertexBuffer.put(index + 10, y * radius);
            vertexBuffer.put(index + 11, -z);
        }

        // INDICES

        if (face == 0 || face == 1)      // top or bottom
        {
            for (i = 0; i < slices; i++)
            {
                // wedge top
                index = 3 * i;

                indexBuffer.put(index, 0);                          // center point
                indexBuffer.put(index + 1, i + 2);
                indexBuffer.put(index + 2, i + 1);
            }
        }
        else if (face == 2)
        {
            int coreIndex = 0;

            for (i = 0; i < slices; i++)
            {
                // wedge core
                index = 6 * i;

                indexBuffer.put(index + 0, coreIndex + 0);
                indexBuffer.put(index + 1, coreIndex + 1);
                indexBuffer.put(index + 2, coreIndex + 2);

                indexBuffer.put(index + 3, coreIndex + 2);
                indexBuffer.put(index + 4, coreIndex + 1);
                indexBuffer.put(index + 5, coreIndex + 3);

                coreIndex += 2;
            }
        }
        else if (face == 3 || face == 4)
        {
            // wedge side
            indexBuffer.put(0, 0);
            indexBuffer.put(1, 2);
            indexBuffer.put(2, 1);

            indexBuffer.put(3, 1);
            indexBuffer.put(4, 2);
            indexBuffer.put(5, 3);
        }

        // The static wedge tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < wedgeIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated wedge.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            wedgeIndexCount, indexBuffer, wedgeVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    //**************************************************************//
    //*********************         Cone         *******************//
    //**************************************************************//

    public IndexedTriangleBuffer tessellateConeBuffer(float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, index;
        float x, y, z, a;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        int coneIndexCount = 12 * slices;
        int coneVertexCount = 4 * slices + 4;

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(coneIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * coneVertexCount);

        // VERTICES

        // bottom center point
        vertexBuffer.put(0, 0f);
        vertexBuffer.put(1, 0f);
        vertexBuffer.put(2, -1.0f);

        // rim points
        for (i = 0; i < slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            z = 1.0f;

            index = 3 * i + 3;

            // cone bottom
            vertexBuffer.put(index, x * radius);
            vertexBuffer.put(index + 1, y * radius);
            vertexBuffer.put(index + 2, -z);

            index += 3 * slices + 3 * i;

            // core upper rim - all points are at same location
            vertexBuffer.put(index, 0);
            vertexBuffer.put(index + 1, 0);
            vertexBuffer.put(index + 2, z);

            // core lower rim
            vertexBuffer.put(index + 3, x * radius);
            vertexBuffer.put(index + 4, y * radius);
            vertexBuffer.put(index + 5, -z);
        }

        // extra vertices for seamless texture mapping

        int wrapIndex = 3 * (3 * slices + 1);
        x = (float) Math.sin(0);
        y = (float) Math.cos(0);
        z = 1.0f;

        vertexBuffer.put(wrapIndex, 0);
        vertexBuffer.put(wrapIndex + 1, 0);
        vertexBuffer.put(wrapIndex + 2, z);

        vertexBuffer.put(wrapIndex + 3, x * radius);
        vertexBuffer.put(wrapIndex + 4, y * radius);
        vertexBuffer.put(wrapIndex + 5, -z);

        // INDICES

        int coreIndex = slices + 1;
        int centerPoint = 0;

        for (i = 0; i < slices; i++)
        {
            index = 3 * i;

            // cone bottom
            indexBuffer.put(index, 0);             // center point
            indexBuffer.put(index + 1, (i < slices - 1) ? i + 2 : 1);
            indexBuffer.put(index + 2, i + 1);

            // cone core
            index += 3 * (slices + i);

            indexBuffer.put(index, coreIndex);
            indexBuffer.put(index + 1, coreIndex + 1);
            indexBuffer.put(index + 2, coreIndex + 2);

            indexBuffer.put(index + 3, coreIndex + 2);
            indexBuffer.put(index + 4, coreIndex + 1);
            indexBuffer.put(index + 5, coreIndex + 3);

            coreIndex += 2;
        }

        // The static cone tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < coneIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated cone.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            coneIndexCount, indexBuffer, coneVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    public IndexedTriangleBuffer tessellateConeBuffer(int face, float radius, int subdivisions)
    {
        if (radius < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "radius < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // face 0 = base
        // face 1 = core

        int i, index;
        float x, y, z, a;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        int coneIndexCount = 3 * slices;
        int coneVertexCount = slices + 1;

        if (face == 1)   // cone core
        {
            coneIndexCount = 6 * slices;
            coneVertexCount = 2 * slices + 2;
        }

        IntBuffer indexBuffer = Buffers.newDirectIntBuffer(coneIndexCount);
        FloatBuffer vertexBuffer = Buffers.newDirectFloatBuffer(3 * coneVertexCount);

        // VERTICES

        if (face == 0)     // cone base
        {
            // base center point
            vertexBuffer.put(0, 0f);
            vertexBuffer.put(1, 0f);
            vertexBuffer.put(2, -1.0f);

            // base rim points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 3 * i + 3;

                vertexBuffer.put(index, x * radius);
                vertexBuffer.put(index + 1, y * radius);
                vertexBuffer.put(index + 2, -z);
            }
        }
        else if (face == 1)     // cone core
        {
            // rim points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                z = 1.0f;

                index = 6 * i;

                // core upper rim
                vertexBuffer.put(index, 0);
                vertexBuffer.put(index + 1, 0);
                vertexBuffer.put(index + 2, z);

                // core lower rim
                vertexBuffer.put(index + 3, x * radius);
                vertexBuffer.put(index + 4, y * radius);
                vertexBuffer.put(index + 5, -z);
            }

            // extra vertices for seamless texture mapping

            int wrapIndex = 3 * (2 * slices);
            x = (float) Math.sin(0);
            y = (float) Math.cos(0);
            z = 1.0f;

            vertexBuffer.put(wrapIndex, 0);
            vertexBuffer.put(wrapIndex + 1, 0);
            vertexBuffer.put(wrapIndex + 2, z);

            vertexBuffer.put(wrapIndex + 3, x * radius);
            vertexBuffer.put(wrapIndex + 4, y * radius);
            vertexBuffer.put(wrapIndex + 5, -z);
        }

        // INDICES

        int centerPoint = 0;

        if (face == 0)      // cone base
        {
            for (i = 0; i < slices; i++)
            {
                index = 3 * i;

                indexBuffer.put(index, 0);                  // center point
                indexBuffer.put(index + 1, (i < slices - 1) ? i + 2 : 1);
                indexBuffer.put(index + 2, i + 1);
            }
        }
        else if (face == 1)     // cone core
        {
            int coreIndex = 0;

            for (i = 0; i < slices; i++)
            {
                index = 6 * i;

                indexBuffer.put(index, coreIndex);
                indexBuffer.put(index + 1, coreIndex + 1);
                indexBuffer.put(index + 2, coreIndex + 2);

                indexBuffer.put(index + 3, coreIndex + 2);
                indexBuffer.put(index + 4, coreIndex + 1);
                indexBuffer.put(index + 5, coreIndex + 3);

                coreIndex += 2;
            }
        }

        // The static cone tessellation is assumed to be viewed from the outside. If the orientation is set to
        // inside, then we must reverse the winding order for each triangle's indices.
        if (this.orientation == INSIDE)
        {
            for (index = 0; index < coneIndexCount; index += 3)
            {
                int tmp = indexBuffer.get(index);
                indexBuffer.put(index, indexBuffer.get(index + 2));
                indexBuffer.put(index + 2, tmp);
            }
        }

        // Start with a triangular tessellated cone.
        IndexedTriangleBuffer itb = new IndexedTriangleBuffer(
            coneIndexCount, indexBuffer, coneVertexCount, vertexBuffer);

        // Scale each vertex by the specified radius.
        if (radius != 1)
        {
            vertexBuffer = itb.getVertices();
            for (int vertex = 0; vertex < itb.vertexCount; vertex++)
            {
                mul3AndSet(vertexBuffer, 3 * vertex, radius);
            }
        }

        itb.vertices.rewind();
        itb.indices.rewind();

        return itb;
    }

    //**************************************************************//
    //********************       Cylinder        *******************//
    //**************************************************************//

    public int getCylinderVertexCount(int slices, int stacks)
    {
        return slices * (stacks + 1);
    }

    public int getCylinderIndexCount(int slices, int stacks)
    {
        return stacks * 2 * (slices + 1) + 2 * (stacks - 1);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public int getCylinderOutlineIndexCount(int slices, int stacks)
    {
        return slices * 4;
    }

    public int getCylinderDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public int getCylinderOutlineDrawMode()
    {
        return GL.GL_LINES;
    }

    public void makeCylinderVertices(float radius, float height, int slices, int stacks, float[] dest)
    {
        int numPoints = this.getCylinderVertexCount(slices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y, z;
        float a;
        float dz, da;
        int i, j;
        int index;

        if (stacks != 0.0f)
            dz = height / (float) stacks;
        else
            dz = 0.0f;
        da = 2.0f * (float) Math.PI / (float) slices;

        for (i = 0; i < slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            z = 0.0f;
            for (j = 0; j <= stacks; j++)
            {
                index = j + i * (stacks + 1);
                index = 3 * index;
                dest[index] = x * radius;
                dest[index + 1] = y * radius;
                dest[index + 2] = z;
                z += dz;
            }
        }
    }

    public void makeCylinderNormals(int slices, int stacks, float[] dest)
    {
        int numPoints = this.getCylinderVertexCount(slices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a;
        float da;
        float nsign;
        int i, j;
        int index;
        float[] norm;

        da = 2.0f * (float) Math.PI / (float) slices;
        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];

        for (i = 0; i < slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            norm[0] = x * nsign;
            norm[1] = y * nsign;
            norm[2] = 0.0f;
            this.norm3AndSet(norm, 0);

            for (j = 0; j <= stacks; j++)
            {
                index = j + i * (stacks + 1);
                index = 3 * index;
                System.arraycopy(norm, 0, dest, index, 3);
            }
        }
    }

    public void makeCylinderIndices(int slices, int stacks, int[] dest)
    {
        int numIndices = this.getCylinderIndexCount(slices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, j;
        int vertex, index;

        index = 0;
        for (j = 0; j < stacks; j++)
        {
            if (j != 0)
            {
                if (this.orientation == INSIDE)
                    vertex = j + 1;
                else // (this.orientation == OUTSIDE)
                    vertex = j;
                dest[index++] = vertex;
                dest[index++] = vertex;
            }
            for (i = 0; i <= slices; i++)
            {
                if (i == slices)
                    vertex = j;
                else
                    vertex = j + i * (stacks + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex + 1;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + 1;
                }
            }
        }
    }

    public void makeCylinderOutlineIndices(int slices, int stacks, int[] dest)
    {
        int numIndices = this.getCylinderOutlineIndexCount(slices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i;
        int vertex, index;

        index = 0;
        // Bottom ring
        for (i = 0; i < slices; i++)
        {
            vertex = i * (stacks + 1);
            dest[index++] = vertex;
            dest[index++] = (i != slices - 1) ? vertex + stacks + 1 : 0;
        }
        // Top ring
        for (i = 0; i < slices; i++)
        {
            vertex = i * (stacks + 1) + stacks;
            dest[index++] = vertex;
            dest[index++] = (i != slices - 1) ? vertex + stacks + 1 : stacks;
        }
//        // Vertical edges
//        for (i = 0; i < slices; i++)
//        {
//            vertex = i * (stacks + 1);
//            dest[index++] = vertex;
//            dest[index++] = vertex + stacks;
//        }
    }

    //**************************************************************//
    //********************  Partial Cylinder    ********************//
    //**************************************************************//

    public int getPartialCylinderVertexCount(int slices, int stacks)
    {
        return (slices + 1) * (stacks + 1);
    }

    public int getPartialCylinderIndexCount(int slices, int stacks)
    {
        return stacks * 2 * (slices + 1) + 2 * (stacks - 1);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public int getPartialCylinderOutlineIndexCount(int slices, int stacks)
    {
        return slices * 4;
    }

    public int getPartialCylinderDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public int getPartialCylinderOutlineDrawMode()
    {
        return GL.GL_LINES;
    }

    public void makePartialCylinderVertices(float radius, float height, int slices, int stacks,
        float start, float sweep, float[] dest)
    {
        int numPoints = this.getPartialCylinderVertexCount(slices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y, z;
        float a;
        float dz, da;
        int i, j;
        int index;

        if (stacks != 0.0f)
            dz = height / (float) stacks;
        else
            dz = 0.0f;
        da = sweep / (float) slices;

        for (i = 0; i <= slices; i++)
        {
            a = i * da + start;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            z = 0.0f;
            for (j = 0; j <= stacks; j++)
            {
                index = j + i * (stacks + 1);
                index = 3 * index;
                dest[index] = x * radius;
                dest[index + 1] = y * radius;
                dest[index + 2] = z;
                z += dz;
            }
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void makePartialCylinderNormals(float radius, float height, int slices, int stacks,
        float start, float sweep, float[] dest)
    {
        int numPoints = this.getPartialCylinderVertexCount(slices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a;
        float da;
        float nsign;
        int i, j;
        int index;
        float[] norm;

        da = sweep / (float) slices;
        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];

        for (i = 0; i <= slices; i++)
        {
            a = i * da + start;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            norm[0] = x * nsign;
            norm[1] = y * nsign;
            norm[2] = 0.0f;
            this.norm3AndSet(norm, 0);

            for (j = 0; j <= stacks; j++)
            {
                index = j + i * (stacks + 1);
                index = 3 * index;
                System.arraycopy(norm, 0, dest, index, 3);
            }
        }
    }

    public void makePartialCylinderIndices(int slices, int stacks, int[] dest)
    {
        int numIndices = this.getPartialCylinderIndexCount(slices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, j;
        int vertex, index;

        index = 0;
        for (j = 0; j < stacks; j++)
        {
            if (j != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = j + slices * (stacks + 1);
                    dest[index++] = vertex - 1;
                    vertex = j + 1;
                    dest[index++] = vertex;
                }
                else //(this.orientation == OUTSIDE)
                {
                    vertex = j + slices * (stacks + 1);
                    dest[index++] = vertex;
                    vertex = j;
                    dest[index++] = vertex;
                }
            }
            for (i = 0; i <= slices; i++)
            {
                vertex = j + i * (stacks + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex + 1;
                    dest[index++] = vertex;
                }
                else //(this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + 1;
                }
            }
        }
    }

    public void makePartialCylinderOutlineIndices(int slices, int stacks, int[] dest)
    {
        int numIndices = this.getPartialCylinderOutlineIndexCount(slices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i;
        int vertex, index;

        index = 0;
        // Bottom ring
        for (i = 0; i < slices; i++)
        {
            vertex = i * (stacks + 1);
            dest[index++] = vertex;
            dest[index++] = vertex + stacks + 1;
        }
        // Top ring
        for (i = 0; i < slices; i++)
        {
            vertex = i * (stacks + 1) + stacks;
            dest[index++] = vertex;
            dest[index++] = vertex + stacks + 1;
        }
    }

    //**************************************************************//
    //********************  Disk                ********************//
    //**************************************************************//

    public int getDiskVertexCount(int slices, int loops)
    {
        return slices * (loops + 1);
    }

    public int getDiskIndexCount(int slices, int loops)
    {
        return loops * 2 * (slices + 1) + 2 * (loops - 1);
    }

    public int getDiskDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public void makeDiskVertices(float innerRadius, float outerRadius, int slices, int loops, float[] dest)
    {
        int numPoints = this.getDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a, r;
        float da, dr;
        int s, l;
        int index;

        da = 2.0f * (float) Math.PI / (float) slices;
        dr = (outerRadius - innerRadius) / (float) loops;

        for (s = 0; s < slices; s++)
        {
            a = s * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            for (l = 0; l <= loops; l++)
            {
                index = l + s * (loops + 1);
                index = 3 * index;
                r = innerRadius + l * dr;
                dest[index] = r * x;
                dest[index + 1] = r * y;
                dest[index + 2] = 0.0f;
            }
        }
    }

    public void makeDiskNormals(int slices, int loops, float[] dest)
    {
        int numPoints = this.getDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int index;
        float nsign;
        float[] normal;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        normal = new float[3];
        normal[0] = 0.0f;
        normal[1] = 0.0f;
        //noinspection PointlessArithmeticExpression
        normal[2] = 1.0f * nsign;

        for (s = 0; s < slices; s++)
        {
            for (l = 0; l <= loops; l++)
            {
                index = l + s * (loops + 1);
                index = 3 * index;
                System.arraycopy(normal, 0, dest, index, 3);
            }
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void makeDiskVertexNormals(float innerRadius, float outerRadius, int slices, int loops,
        float[] srcVerts, float[] dest)
    {
        int numPoints = this.getDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (srcVerts == null)
        {
            String message = "nullValue.SourceVertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int index;
        float nsign;
        float[] norm, zero, tmp;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        zero = new float[3];
        tmp = new float[3];

        for (l = 0; l <= loops; l++)
        {
            // Normal vectors for first and last loops require a special case.
            if (l == 0 || l == loops)
            {
                // Closed disk: all slices share a common center point.
                if (l == 0 && innerRadius == 0.0f)
                {
                    // Compute common center point normal.
                    int nextSlice;
                    int adjacentLoop;
                    System.arraycopy(zero, 0, norm, 0, 3);
                    for (s = 0; s < slices; s++)
                    {
                        index = l + s * (loops + 1);
                        nextSlice = l + (s + 1) * (loops + 1);
                        if (s == slices - 1)
                            nextSlice = l;
                        adjacentLoop = index + 1;
                        this.facenorm(srcVerts, index, nextSlice + 1, adjacentLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                    this.mul3AndSet(norm, 0, nsign);
                    this.norm3AndSet(norm, 0);
                    // Copy common normal to the first point of each slice.
                    for (s = 0; s < slices; s++)
                    {
                        index = l + s * (loops + 1);
                        System.arraycopy(norm, 0, dest, 3 * index, 3);
                    }
                }
                // Open disk: each slice has a unique starting point.
                else
                {
                    for (s = 0; s < slices; s++)
                    {
                        int prevSlice, nextSlice;
                        int adjacentLoop;
                        index = l + s * (loops + 1);
                        prevSlice = l + (s - 1) * (loops + 1);
                        nextSlice = l + (s + 1) * (loops + 1);

                        if (s == 0)
                            prevSlice = l + (slices - 1) * (loops + 1);
                        else if (s == slices - 1)
                            nextSlice = l;

                        if (l == 0)
                            adjacentLoop = index + 1;
                        else
                            adjacentLoop = index - 1;

                        System.arraycopy(zero, 0, norm, 0, 3);

                        // Add clockwise adjacent face.
                        if (l == 0)
                            this.facenorm(srcVerts, index, nextSlice, adjacentLoop, tmp);
                        else
                            this.facenorm(srcVerts, index, adjacentLoop, nextSlice, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        // Add counter-clockwise adjacent face.
                        if (l == 0)
                            this.facenorm(srcVerts, index, adjacentLoop, prevSlice, tmp);
                        else
                            this.facenorm(srcVerts, index, prevSlice, adjacentLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);

                        // Normalize and place in output.
                        this.mul3AndSet(norm, 0, nsign);
                        this.norm3AndSet(norm, 0);
                        System.arraycopy(norm, 0, dest, 3 * index, 3);
                    }
                }
            }
            // Normal vectors for internal loops.
            else
            {
                for (s = 0; s < slices; s++)
                {
                    int prevSlice, nextSlice;
                    int prevLoop, nextLoop;
                    index = l + s * (loops + 1);
                    prevSlice = l + (s - 1) * (loops + 1);
                    nextSlice = l + (s + 1) * (loops + 1);

                    if (s == 0)
                        prevSlice = l + (slices - 1) * (loops + 1);
                    else if (s == slices - 1)
                        nextSlice = l;

                    prevLoop = index - 1;
                    nextLoop = index + 1;

                    System.arraycopy(zero, 0, norm, 0, 3);

                    // Add lower-left adjacent face.
                    this.facenorm(srcVerts, index, prevSlice, prevSlice - 1, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, prevSlice - 1, prevLoop, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add lower-right adjacent face.
                    this.facenorm(srcVerts, index, prevLoop, nextSlice - 1, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, nextSlice - 1, nextSlice, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add upper-right adjacent face.
                    this.facenorm(srcVerts, index, nextSlice, nextSlice + 1, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, nextSlice + 1, nextLoop, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add upper-left adjacent face.
                    this.facenorm(srcVerts, index, nextLoop, prevSlice + 1, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, prevSlice + 1, prevSlice, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);

                    // Normalize and place in output.
                    this.mul3AndSet(norm, 0, nsign);
                    this.norm3AndSet(norm, 0);
                    System.arraycopy(norm, 0, dest, 3 * index, 3);
                }
            }
        }
    }

    public void makeDiskIndices(int slices, int loops, int[] dest)
    {
        int numIndices = this.getDiskIndexCount(slices, loops);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int vertex, index;

        index = 0;
        for (l = 0; l < loops; l++)
        {
            if (l != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = l;
                    dest[index++] = vertex;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = l - 1;
                    dest[index++] = vertex;
                    vertex = l + 1;
                    dest[index++] = vertex;
                }
            }
            for (s = 0; s <= slices; s++)
            {
                if (s == slices)
                    vertex = l;
                else
                    vertex = l + s * (loops + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + 1;
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex + 1;
                    dest[index++] = vertex;
                }
            }
        }
    }

    //**************************************************************//
    //********************  Partial Disk        ********************//
    //**************************************************************//

    public int getPartialDiskVertexCount(int slices, int loops)
    {
        return (slices + 1) * (loops + 1);
    }

    public int getPartialDiskIndexCount(int slices, int loops)
    {
        return loops * 2 * (slices + 1) + 2 * (loops - 1);
    }

    public int getPartialDiskDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public void makePartialDiskVertices(float innerRadius, float outerRadius, int slices, int loops,
        float start, float sweep, float[] dest)
    {
        int numPoints = this.getPartialDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a, r;
        float da, dr;
        int s, l;
        int index;

        da = sweep / (float) slices;
        dr = (outerRadius - innerRadius) / (float) loops;

        for (s = 0; s <= slices; s++)
        {
            a = s * da + start;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);
            for (l = 0; l <= loops; l++)
            {
                index = l + s * (loops + 1);
                index = 3 * index;
                r = innerRadius + l * dr;
                dest[index] = r * x;
                dest[index + 1] = r * y;
                dest[index + 2] = 0.0f;
            }
        }
    }

    public void makePartialDiskNormals(int slices, int loops, float[] dest)
    {
        int numPoints = this.getPartialDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int index;
        float nsign;
        float[] normal;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        normal = new float[3];
        normal[0] = 0.0f;
        normal[1] = 0.0f;
        //noinspection PointlessArithmeticExpression
        normal[2] = 1.0f * nsign;

        for (s = 0; s <= slices; s++)
        {
            for (l = 0; l <= loops; l++)
            {
                index = l + s * (loops + 1);
                index = 3 * index;
                System.arraycopy(normal, 0, dest, index, 3);
            }
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void makePartialDiskVertexNormals(float innerRadius, float outerRadius, int slices, int loops,
        float start, float sweep, float[] srcVerts, float[] dest)
    {
        int numPoints = this.getPartialDiskVertexCount(slices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (srcVerts == null)
        {
            String message = "nullValue.SourceVertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int index;
        float nsign;
        float[] norm, zero, tmp;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        zero = new float[3];
        tmp = new float[3];

        for (l = 0; l <= loops; l++)
        {
            // Normal vectors for first and last loops require a special case.
            if (l == 0 || l == loops)
            {
                // Closed disk: all slices share a common center point.
                if (l == 0 && innerRadius == 0.0f)
                {
                    // Compute common center point normal.
                    int nextSlice;
                    int adjacentLoop;
                    System.arraycopy(zero, 0, norm, 0, 3);
                    for (s = 0; s < slices; s++)
                    {
                        index = l + s * (loops + 1);
                        nextSlice = l + (s + 1) * (loops + 1);
                        adjacentLoop = index + 1;
                        this.facenorm(srcVerts, index, nextSlice + 1, adjacentLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                    this.mul3AndSet(norm, 0, nsign);
                    this.norm3AndSet(norm, 0);
                    // Copy common normal to the first point of each slice.
                    for (s = 0; s <= slices; s++)
                    {
                        index = l + s * (loops + 1);
                        System.arraycopy(norm, 0, dest, 3 * index, 3);
                    }
                }
                // Open disk: each slice has a unique starting point.
                else
                {
                    for (s = 0; s <= slices; s++)
                    {
                        int prevSlice, nextSlice;
                        int adjacentLoop;
                        index = l + s * (loops + 1);

                        if (l == 0)
                            adjacentLoop = index + 1;
                        else
                            adjacentLoop = index - 1;

                        System.arraycopy(zero, 0, norm, 0, 3);

                        if (s > 0)
                        {
                            prevSlice = l + (s - 1) * (loops + 1);
                            // Add counter-clockwise adjacent face.
                            if (l == 0)
                                this.facenorm(srcVerts, index, adjacentLoop, prevSlice, tmp);
                            else
                                this.facenorm(srcVerts, index, prevSlice, adjacentLoop, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                        }
                        if (s < slices)
                        {
                            nextSlice = l + (s + 1) * (loops + 1);
                            // Add clockwise adjacent face.
                            if (l == 0)
                                this.facenorm(srcVerts, index, nextSlice, adjacentLoop, tmp);
                            else
                                this.facenorm(srcVerts, index, adjacentLoop, nextSlice, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                        }

                        // Normalize and place in output.
                        this.mul3AndSet(norm, 0, nsign);
                        this.norm3AndSet(norm, 0);
                        System.arraycopy(norm, 0, dest, 3 * index, 3);
                    }
                }
            }
            // Normal vectors for internal loops.
            else
            {
                for (s = 0; s <= slices; s++)
                {
                    int prevSlice, nextSlice;
                    int prevLoop, nextLoop;
                    index = l + s * (loops + 1);
                    prevLoop = index - 1;
                    nextLoop = index + 1;

                    System.arraycopy(zero, 0, norm, 0, 3);
                    if (s > 0)
                    {
                        prevSlice = l + (s - 1) * (loops + 1);
                        // Add lower-left adjacent face.
                        this.facenorm(srcVerts, index, prevSlice, prevSlice - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, prevSlice - 1, prevLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        // Add upper-left adjacent face.
                        this.facenorm(srcVerts, index, nextLoop, prevSlice + 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, prevSlice + 1, prevSlice, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                    if (s < slices)
                    {
                        nextSlice = l + (s + 1) * (loops + 1);
                        // Add lower-right adjacent face.
                        this.facenorm(srcVerts, index, prevLoop, nextSlice - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, nextSlice - 1, nextSlice, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        // Add upper-right adjacent face.
                        this.facenorm(srcVerts, index, nextSlice, nextSlice + 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, nextSlice + 1, nextLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }

                    // Normalize and place in output.
                    this.mul3AndSet(norm, 0, nsign);
                    this.norm3AndSet(norm, 0);
                    System.arraycopy(norm, 0, dest, 3 * index, 3);
                }
            }
        }
    }

    public void makePartialDiskIndices(int slices, int loops, int[] dest)
    {
        int numIndices = this.getPartialDiskIndexCount(slices, loops);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "slices=" + slices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int s, l;
        int vertex, index;

        index = 0;
        for (l = 0; l < loops; l++)
        {
            if (l != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = l + slices * (loops + 1);
                    dest[index++] = vertex;
                    vertex = l;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = (l - 1) + slices * (loops + 1);
                    dest[index++] = vertex;
                    vertex = l;
                    dest[index++] = vertex + 1;
                }
            }
            for (s = 0; s <= slices; s++)
            {
                vertex = l + s * (loops + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + 1;
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex + 1;
                    dest[index++] = vertex;
                }
            }
        }
    }

    //**************************************************************//
    //********************  Radial Wall         ********************//
    //**************************************************************//

    public int getRadialWallVertexCount(int pillars, int stacks)
    {
        return (pillars + 1) * (stacks + 1);
    }

    public int getRadialWallIndexCount(int pillars, int stacks)
    {
        return stacks * 2 * (pillars + 1) + 2 * (stacks - 1);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public int getRadialWallOutlineIndexCount(int pillars, int stacks)
    {
        return pillars * 4;
    }

    public int getRadialWallDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public int getRadialWallOutlineDrawMode()
    {
        return GL.GL_LINES;
    }

    public void makeRadialWallVertices(float innerRadius, float outerRadius, float height, float angle,
        int pillars, int stacks, float[] dest)
    {
        int numPoints = this.getRadialWallVertexCount(pillars, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "pillars=" + pillars
                + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y, z;
        float a, r;
        float dz, dr;
        int s, p;
        int index;

        a = angle;
        x = (float) Math.sin(a);
        y = (float) Math.cos(a);
        z = 0.0f;

        if (stacks != 0.0f)
            dz = height / (float) stacks;
        else
            dz = 0.0f;
        dr = (outerRadius - innerRadius) / (float) pillars;

        for (s = 0; s <= stacks; s++)
        {
            for (p = 0; p <= pillars; p++)
            {
                index = p + s * (pillars + 1);
                index = 3 * index;
                r = innerRadius + p * dr;
                dest[index] = r * x;
                dest[index + 1] = r * y;
                dest[index + 2] = z;
            }
            z += dz;
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void makeRadialWallNormals(float innerRadius, float outerRadius, float height, float angle,
        int pillars, int stacks, float[] dest)
    {
        int numPoints = this.getRadialWallVertexCount(pillars, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "pillars=" + pillars
                + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a;
        int s, p;
        int index;
        float nsign;
        float[] norm;

        a = angle;
        x = (float) Math.cos(a);
        y = (float) -Math.sin(a);

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        norm[0] = x * nsign;
        norm[1] = y * nsign;
        norm[2] = 0.0f;
        this.norm3AndSet(norm, 0);

        for (s = 0; s <= stacks; s++)
        {
            for (p = 0; p <= pillars; p++)
            {
                index = p + s * (pillars + 1);
                index = 3 * index;
                System.arraycopy(norm, 0, dest, index, 3);
            }
        }
    }

    public void makeRadialWallIndices(int pillars, int stacks, int[] dest)
    {
        int numIndices = this.getRadialWallIndexCount(pillars, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "pillars=" + pillars
                + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int p, s;
        int vertex, index;

        index = 0;
        for (s = 0; s < stacks; s++)
        {
            if (s != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = pillars + s * (pillars + 1);
                    dest[index++] = vertex;
                    vertex = s * (pillars + 1);
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = pillars + (s - 1) * (pillars + 1);
                    dest[index++] = vertex;
                    vertex = (s + 1) * (pillars + 1);
                    dest[index++] = vertex;
                }
            }
            for (p = 0; p <= pillars; p++)
            {
                vertex = p + s * (pillars + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + (pillars + 1);
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex + (pillars + 1);
                    dest[index++] = vertex;
                }
            }
        }
    }

    public void makeRadialWallOutlineIndices(int pillars, int stacks, int[] dest)
    {
        int numIndices = this.getRadialWallOutlineIndexCount(pillars, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "pillars=" + pillars
                + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int vertex;
        int index = 0;
        // Bottom
        for (int i = 0; i < pillars; i++)
        {
            vertex = i;
            dest[index++] = vertex;
            dest[index++] = vertex + 1;
        }
        // Top
        for (int i = 0; i < pillars; i++)
        {
            vertex = i + stacks * (pillars + 1);
            dest[index++] = vertex;
            dest[index++] = vertex + 1;
        }
    }

    //**************************************************************//
    //********************  Long Cylinder       ********************//
    //**************************************************************//

    public int getLongCylinderVertexCount(int arcSlices, int lengthSlices, int stacks)
    {
        int slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        return slices * (stacks + 1);
    }

    public int getLongCylinderIndexCount(int arcSlices, int lengthSlices, int stacks)
    {
        int slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        return stacks * 2 * (slices + 1) + 2 * (stacks - 1);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public int getLongCylinderOutlineIndexCount(int arcSlices, int lengthSlices, int stacks)
    {
        return (arcSlices + lengthSlices) * 2 * 4;
    }

    public int getLongCylinderDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public int getLongCylinderOutlineDrawMode()
    {
        return GL.GL_LINES;
    }

    public void makeLongCylinderVertices(float radius, float length, float height,
        int arcSlices, int lengthSlices, int stacks, float[] dest)
    {
        int numPoints = this.getLongCylinderVertexCount(arcSlices, lengthSlices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y, z;
        float a;
        float dy, dz, da;
        int i, j;
        int index;

        da = (float) Math.PI / (float) arcSlices;
        dy = length / (float) lengthSlices;
        if (stacks != 0.0f)
            dz = height / (float) stacks;
        else
            dz = 0.0f;
        z = 0.0f;
        index = 0;

        for (j = 0; j <= stacks; j++)
        {
            // Top arc
            for (i = 0; i <= arcSlices; i++)
            {
                a = i * da + (3.0f * (float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * radius;
                dest[index++] = y * radius + length;
                dest[index++] = z;
            }
            // Right side.
            for (i = lengthSlices - 1; i >= 1; i--)
            {
                dest[index++] = radius;
                dest[index++] = i * dy;
                dest[index++] = z;
            }
            // Bottom arc
            for (i = 0; i <= arcSlices; i++)
            {
                a = i * da + ((float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * radius;
                dest[index++] = y * radius;
                dest[index++] = z;
            }
            // Left side.
            for (i = 1; i < lengthSlices; i++)
            {
                dest[index++] = -radius;
                dest[index++] = i * dy;
                dest[index++] = z;
            }
            z += dz;
        }
    }

    public void makeLongCylinderNormals(int arcSlices, int lengthSlices, int stacks, float[] dest)
    {
        int numPoints = this.getLongCylinderVertexCount(arcSlices, lengthSlices, stacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a, da;
        float nsign;
        int i, j;
        int index;

        da = (float) Math.PI / (float) arcSlices;
        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        index = 0;

        for (j = 0; j <= stacks; j++)
        {
            // Top arc
            for (i = 0; i <= arcSlices; i++)
            {
                a = i * da + (3.0f * (float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * nsign;
                dest[index++] = y * nsign;
                dest[index++] = 0.0f;
            }
            // Right side.
            for (i = lengthSlices - 1; i >= 1; i--)
            {
                //noinspection PointlessArithmeticExpression
                dest[index++] = 1.0f * nsign;
                dest[index++] = 0.0f;
                dest[index++] = 0.0f;
            }
            // Bottom arc
            for (i = 0; i <= arcSlices; i++)
            {
                a = i * da + ((float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * nsign;
                dest[index++] = y * nsign;
                dest[index++] = 0.0f;
            }
            // Left side.
            for (i = 1; i < lengthSlices; i++)
            {
                dest[index++] = -1.0f * nsign;
                dest[index++] = 0.0f;
                dest[index++] = 0.0f;
            }
        }
    }

    public void makeLongCylinderIndices(int arcSlices, int lengthSlices, int stacks, int[] dest)
    {
        int numIndices = this.getLongCylinderIndexCount(arcSlices, lengthSlices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int slices;
        int i, j;
        int vertex, index;

        slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        index = 0;

        for (j = 0; j < stacks; j++)
        {
            if (j != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = (j - 1) * slices;
                    dest[index++] = vertex;
                    vertex = j * slices;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = (j - 1) * slices;
                    dest[index++] = vertex + slices;
                    vertex = (j - 1) * slices;
                    dest[index++] = vertex;
                }
            }
            for (i = 0; i <= slices; i++)
            {
                if (i == slices)
                    vertex = j * slices;
                else
                    vertex = i + j * slices;
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex + slices;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + slices;
                }
            }
        }
    }

    public void makeLongCylinderOutlineIndices(int arcSlices, int lengthSlices, int stacks, int[] dest)
    {
        int numIndices = this.getLongCylinderOutlineIndexCount(arcSlices, lengthSlices, stacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " stacks=" + stacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        int i;
        int vertex, index;

        index = 0;
        // Bottom ring
        for (i = 0; i < slices; i++)
        {
            vertex = i;
            dest[index++] = vertex;
            dest[index++] = (i != slices - 1) ? vertex + 1 : 0;
        }
        // Top ring
        for (i = 0; i < slices; i++)
        {
            vertex = i + slices * stacks;
            dest[index++] = vertex;
            dest[index++] = (i != slices - 1) ? vertex + 1 : slices * stacks;
        }
    }

    //**************************************************************//
    //********************  Long Disk           ********************//
    //**************************************************************//

    public int getLongDiskVertexCount(int arcSlices, int lengthSlices, int loops)
    {
        int slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        return slices * (loops + 1);
    }

    public int getLongDiskIndexCount(int arcSlices, int lengthSlices, int loops)
    {
        int slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        return loops * 2 * (slices + 1) + 2 * (loops - 1);
    }

    public int getLongDiskDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public void makeLongDiskVertices(float innerRadius, float outerRadius, float length,
        int arcSlices, int lengthSlices, int loops, float[] dest)
    {
        int numPoints = this.getLongDiskVertexCount(arcSlices, lengthSlices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y;
        float a, r;
        float dy, da, dr;
        int s, l;
        int index;

        dy = length / (float) lengthSlices;
        da = (float) Math.PI / (float) arcSlices;
        dr = (outerRadius - innerRadius) / (float) loops;
        index = 0;

        for (l = 0; l <= loops; l++)
        {
            r = innerRadius + l * dr;
            // Top arc.
            for (s = 0; s <= arcSlices; s++)
            {
                a = s * da + (3.0f * (float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * r;
                dest[index++] = y * r + length;
                dest[index++] = 0.0f;
            }
            // Right side.
            for (s = lengthSlices - 1; s >= 1; s--)
            {
                dest[index++] = r;
                dest[index++] = s * dy;
                dest[index++] = 0.0f;
            }
            // Bottom arc.
            for (s = 0; s <= arcSlices; s++)
            {
                a = s * da + ((float) Math.PI / 2.0f);
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);
                dest[index++] = x * r;
                dest[index++] = y * r;
                dest[index++] = 0.0f;
            }
            // Left side.
            for (s = 1; s < lengthSlices; s++)
            {
                dest[index++] = -r;
                dest[index++] = s * dy;
                dest[index++] = 0.0f;
            }
        }
    }

    public void makeLongDiskNormals(int arcSlices, int lengthSlices, int loops, float[] dest)
    {
        int numPoints = this.getLongDiskVertexCount(arcSlices, lengthSlices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int slices;
        int s, l;
        int index;
        float nsign;
        float[] normal;

        slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        normal = new float[3];
        normal[0] = 0.0f;
        normal[1] = 0.0f;
        //noinspection PointlessArithmeticExpression
        normal[2] = 1.0f * nsign;

        for (l = 0; l <= loops; l++)
        {
            for (s = 0; s < slices; s++)
            {
                index = s + l * slices;
                index = 3 * index;
                System.arraycopy(normal, 0, dest, index, 3);
            }
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void makeLongDiskVertexNormals(float innerRadius, float outerRadius, float length,
        int arcSlices, int lengthSlices, int loops,
        float[] srcVerts, float[] dest)
    {
        int numPoints = this.getLongDiskVertexCount(arcSlices, lengthSlices, loops);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (srcVerts == null)
        {
            String message = "nullValue.SourceVertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int slices;
        int s, l;
        int index;
        float nsign;
        float[] norm, zero, tmp;

        slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        zero = new float[3];
        tmp = new float[3];

        for (l = 0; l <= loops; l++)
        {
            // Normal vectors for first and last loops require a special case.
            if (l == 0 || l == loops)
            {
                // Closed disk: slices are collapsed.
                if (l == 0 && innerRadius == 0.0f)
                {
                    // Top arc.
                    {
                        // Compute common normal.
                        System.arraycopy(zero, 0, norm, 0, 3);
                        for (s = 0; s <= arcSlices; s++)
                        {
                            index = s;
                            this.facenorm(srcVerts, index, index + slices + 1, index + slices, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                        }
                        index = arcSlices;
                        this.facenorm(srcVerts, index, index + 1, index + slices, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        index = 0;
                        this.facenorm(srcVerts, index, index + slices, index + slices - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.mul3AndSet(norm, 0, nsign);
                        this.norm3AndSet(norm, 0);
                        // Copy common normal to the first point of each slice.
                        for (s = 0; s <= arcSlices; s++)
                        {
                            index = s;
                            System.arraycopy(norm, 0, dest, 3 * index, 3);
                        }
                    }
                    // Right and left sides.
                    {
                        int leftSideIndex;
                        for (s = 1; s < lengthSlices; s++)
                        {
                            // Compute common normal.
                            index = s + arcSlices;
                            leftSideIndex = slices - s;
                            System.arraycopy(zero, 0, norm, 0, 3);
                            this.facenorm(srcVerts, index, index + slices, index - 1, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                            this.facenorm(srcVerts, index, index + 1, index + slices, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                            if (s == 1)
                                this.facenorm(srcVerts, leftSideIndex, leftSideIndex - slices + 1,
                                    leftSideIndex + slices, tmp);
                            else
                                this.facenorm(srcVerts, leftSideIndex, leftSideIndex + 1, leftSideIndex + slices, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                            this.facenorm(srcVerts, leftSideIndex, leftSideIndex + slices, leftSideIndex - 1, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                            this.mul3AndSet(norm, 0, nsign);
                            this.norm3AndSet(norm, 0);
                            // Copy common normal to the first point of each slice.
                            System.arraycopy(norm, 0, dest, 3 * index, 3);
                            System.arraycopy(norm, 0, dest, 3 * leftSideIndex, 3);
                        }
                    }
                    // Bottom arc.
                    {
                        // Compute common normal.
                        System.arraycopy(zero, 0, norm, 0, 3);
                        for (s = 0; s <= arcSlices; s++)
                        {
                            index = s + arcSlices + lengthSlices;
                            this.facenorm(srcVerts, index, index + slices + 1, index + slices, tmp);
                            this.add3AndSet(norm, 0, tmp, 0);
                        }
                        index = arcSlices + lengthSlices;
                        this.facenorm(srcVerts, index, index + slices, index - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        index = (2 * arcSlices) + lengthSlices;
                        this.facenorm(srcVerts, index, index + 1, index + slices, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.mul3AndSet(norm, 0, nsign);
                        this.norm3AndSet(norm, 0);
                        // Copy common normal to the first point of each slice.
                        for (s = 0; s <= arcSlices; s++)
                        {
                            index = s + arcSlices + lengthSlices;
                            System.arraycopy(norm, 0, dest, 3 * index, 3);
                        }
                    }
                }
                // Open disk: each slice has a unique starting point.
                else
                {
                    for (s = 0; s < slices; s++)
                    {
                        int prevSlice, nextSlice;
                        int adjacentLoop;
                        index = s + l * slices;
                        prevSlice = index - 1;
                        nextSlice = index + 1;

                        if (s == 0)
                            prevSlice = l * slices;
                        else if (s == slices - 1)
                            nextSlice = l;

                        if (l == 0)
                            adjacentLoop = index + slices;
                        else
                            adjacentLoop = index - slices;

                        System.arraycopy(zero, 0, norm, 0, 3);

                        // Add clockwise adjacent face.
                        if (l == 0)
                            this.facenorm(srcVerts, index, nextSlice, adjacentLoop, tmp);
                        else
                            this.facenorm(srcVerts, index, adjacentLoop, nextSlice, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        // Add counter-clockwise adjacent face.
                        if (l == 0)
                            this.facenorm(srcVerts, index, adjacentLoop, prevSlice, tmp);
                        else
                            this.facenorm(srcVerts, index, prevSlice, adjacentLoop, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);

                        // Normalize and place in output.
                        this.mul3AndSet(norm, 0, nsign);
                        this.norm3AndSet(norm, 0);
                        System.arraycopy(norm, 0, dest, 3 * index, 3);
                    }
                }
            }
            // Normal vectors for internal loops.
            else
            {
                for (s = 0; s < slices; s++)
                {
                    int prevSlice, nextSlice;
                    int prevLoop, nextLoop;
                    index = s + l * slices;
                    prevSlice = index - 1;
                    nextSlice = index + 1;

                    if (s == 0)
                        prevSlice = (slices - 1) + l * slices;
                    else if (s == slices - 1)
                        nextSlice = l * slices;

                    prevLoop = index - slices;
                    nextLoop = index + slices;

                    System.arraycopy(zero, 0, norm, 0, 3);

                    // Add lower-left adjacent face.
                    this.facenorm(srcVerts, index, prevSlice, prevSlice - slices, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, prevSlice - slices, prevLoop, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add lower-right adjacent face.
                    this.facenorm(srcVerts, index, prevLoop, nextSlice - slices, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, nextSlice - slices, nextSlice, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add upper-right adjacent face.
                    this.facenorm(srcVerts, index, nextSlice, nextSlice + slices, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, nextSlice + slices, nextLoop, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    // Add upper-left adjacent face.
                    this.facenorm(srcVerts, index, nextLoop, prevSlice + slices, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);
                    this.facenorm(srcVerts, index, prevSlice + slices, prevSlice, tmp);
                    this.add3AndSet(norm, 0, tmp, 0);

                    // Normalize and place in output.
                    this.mul3AndSet(norm, 0, nsign);
                    this.norm3AndSet(norm, 0);
                    System.arraycopy(norm, 0, dest, 3 * index, 3);
                }
            }
        }
    }

    public void makeLongDiskIndices(int arcSlices, int lengthSlices, int loops, int[] dest)
    {
        int numIndices = this.getLongDiskIndexCount(arcSlices, lengthSlices, loops);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "arcSlices=" + arcSlices
                + " lengthSlices=" + lengthSlices + " loops=" + loops);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numIndices)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int slices;
        int s, l;
        int vertex, index;

        slices = 2 * (arcSlices + 1) + 2 * (lengthSlices - 1);
        index = 0;

        for (l = 0; l < loops; l++)
        {
            if (l != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = (l - 1) * slices;
                    dest[index++] = vertex + slices;
                    vertex = (l - 1) * slices;
                    dest[index++] = vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = (l - 1) * slices;
                    dest[index++] = vertex;
                    vertex = l * slices;
                    dest[index++] = vertex;
                }
            }
            for (s = 0; s <= slices; s++)
            {
                if (s == slices)
                    vertex = l * slices;
                else
                    vertex = s + l * slices;
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertex;
                    dest[index++] = vertex + slices;
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertex + slices;
                    dest[index++] = vertex;
                }
            }
        }
    }

    //**************************************************************//
    //********************  Polygon                 ****************//
    //**************************************************************//

    public int computePolygonWindingOrder2(int pos, int count, Vec4[] points)
    {
        float area;
        int order;

        area = this.computePolygonArea2(pos, count, points);
        if (area < 0.0f)
            order = CLOCKWISE;
        else
            order = COUNTER_CLOCKWISE;

        return order;
    }

    public float computePolygonArea2(int pos, int count, Vec4[] points)
    {
        if (pos < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "pos=" + pos);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (count < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "count=" + count);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (points == null)
        {
            String message = "nullValue.PointsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (points.length < (pos + count))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "points.length < " + (pos + count));
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float area;
        int i;
        int coord, nextCoord;

        area = 0.0f;
        for (i = 0; i < count; i++)
        {
            coord = pos + i;
            nextCoord = (i == count - 1) ? (pos) : (pos + i + 1);
            area += points[coord].x * points[nextCoord].y;
            area -= points[nextCoord].x * points[coord].y;
        }
        area /= 2.0f;

        return area;
    }

    public IndexedTriangleArray tessellatePolygon(int pos, int count, float[] vertices, Vec4 normal)
    {
        if (count < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "count=" + count);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.length < (pos + count))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        TessellatorCallback cb;
        GLUTessellatorSupport glts;
        double[] dvertices = new double[3 * count];
        int i;
        int srcIndex, destIndex;

        if (normal == null)
            normal = Vec4.UNIT_Z;

        cb = new TessellatorCallback(this, count, vertices);
        glts = new GLUTessellatorSupport();
        glts.beginTessellation(cb, normal);
        try
        {
            GLU.gluTessBeginPolygon(glts.getGLUtessellator(), null);
            GLU.gluTessBeginContour(glts.getGLUtessellator());
            for (i = 0; i < count; i++)
            {
                srcIndex = 3 * (pos + i);
                destIndex = 3 * i;
                dvertices[destIndex] = vertices[srcIndex];
                dvertices[destIndex + 1] = vertices[srcIndex + 1];
                dvertices[destIndex + 2] = vertices[srcIndex + 2];
                GLU.gluTessVertex(glts.getGLUtessellator(), dvertices, destIndex, pos + i);
            }
            GLU.gluTessEndContour(glts.getGLUtessellator());
            GLU.gluTessEndPolygon(glts.getGLUtessellator());
        }
        finally
        {
            glts.endTessellation();
        }

        return new IndexedTriangleArray(
            cb.getIndexCount(), cb.getIndices(),
            cb.getVertexCount(), cb.getVertices());
    }

    public IndexedTriangleArray tessellatePolygon2(int pos, int count, float[] vertices)
    {
        if (count < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "count=" + count);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.length < (pos + count))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.tessellatePolygon(pos, count, vertices, Vec4.UNIT_Z);
    }

    private static class TessellatorCallback extends GLUtessellatorCallbackAdapter
    {
        private GeometryBuilder gb;
        private int type;
        private int indexCount;
        private int primIndexCount;
        private int vertexCount;
        private int[] indices;
        private int[] primIndices;
        private float[] vertices;

        private TessellatorCallback(GeometryBuilder gb, int vertexCount, float[] vertices)
        {
            this.gb = gb;
            this.indexCount = 0;
            this.primIndexCount = 0;
            this.vertexCount = vertexCount;

            int initialCapacity = this.gb.nextPowerOfTwo(3 * vertexCount);
            this.indices = new int[initialCapacity];
            this.primIndices = new int[initialCapacity];
            this.vertices = this.gb.copyOf(vertices, initialCapacity);
        }

        public int getIndexCount()
        {
            return this.indexCount;
        }

        public int[] getIndices()
        {
            return this.indices;
        }

        public int getVertexCount()
        {
            return this.vertexCount;
        }

        public float[] getVertices()
        {
            return this.vertices;
        }

        protected void addTriangle(int i1, int i2, int i3)
        {
            // Triangle indices will be specified in counter-clockwise order. To reverse the ordering, we
            // swap the indices.

            int minCapacity, oldCapacity, newCapacity;

            minCapacity = this.indexCount + 3;
            oldCapacity = this.indices.length;
            while (minCapacity > oldCapacity)
            {
                newCapacity = 2 * oldCapacity;
                this.indices = this.gb.copyOf(this.indices, newCapacity);
                oldCapacity = minCapacity;
            }

            if (this.gb.orientation == GeometryBuilder.INSIDE)
            {
                this.indices[this.indexCount++] = this.primIndices[i1];
                this.indices[this.indexCount++] = this.primIndices[i3];
                this.indices[this.indexCount++] = this.primIndices[i2];
            }
            else // (this.gb.orientation == GeometryBuilder.OUTSIDE)
            {
                this.indices[this.indexCount++] = this.primIndices[i1];
                this.indices[this.indexCount++] = this.primIndices[i2];
                this.indices[this.indexCount++] = this.primIndices[i3];
            }
        }

        public void begin(int type)
        {
            this.type = type;
            this.primIndexCount = 0;
        }

        public void vertex(Object vertexData)
        {
            int minCapacity, oldCapacity, newCapacity;

            oldCapacity = this.primIndices.length;
            minCapacity = this.primIndexCount + 1;
            while (minCapacity > oldCapacity)
            {
                newCapacity = 2 * oldCapacity;
                this.primIndices = this.gb.copyOf(this.primIndices, newCapacity);
                oldCapacity = newCapacity;
            }

            int index = (Integer) vertexData;
            this.primIndices[this.primIndexCount++] = index;
        }

        public void end()
        {
            int i;

            if (this.type == GL.GL_TRIANGLES)
            {
                for (i = 2; i < this.primIndexCount; i++)
                {
                    if (((i + 1) % 3) == 0)
                        this.addTriangle(i - 2, i - 1, i);
                }
            }
            else if (this.type == GL.GL_TRIANGLE_STRIP)
            {
                for (i = 2; i < this.primIndexCount; i++)
                {
                    if ((i % 2) == 0)
                        this.addTriangle(i - 2, i - 1, i);
                    else
                        this.addTriangle(i - 1, i - 2, i);
                }
            }
            else if (this.type == GL.GL_TRIANGLE_FAN)
            {
                for (i = 2; i < this.primIndexCount; i++)
                {
                    this.addTriangle(0, i - 1, i);
                }
            }
        }

        public void combine(double[] coords, Object[] data, float[] weight, Object[] outData)
        {
            outData[0] = data[0];
        }
    }

    //**************************************************************//
    //********************  Indexed Triangle Buffer  ***************//
    //**************************************************************//

    public int getIndexedTriangleBufferDrawMode()
    {
        return GL.GL_TRIANGLES;
    }

    public static class IndexedTriangleBuffer
    {
        private IntBuffer indices;
        private FloatBuffer vertices;
        private int indexCount;
        private int vertexCount;

        public IndexedTriangleBuffer(int indexCount, IntBuffer indices, int vertexCount, FloatBuffer vertices)
        {
            this.indices = indices;
            this.vertices = vertices;
            this.indexCount = indexCount;
            this.vertexCount = vertexCount;
        }

        public int getIndexCount()
        {
            return this.indexCount;
        }

        public IntBuffer getIndices()
        {
            return this.indices;
        }

        public int getVertexCount()
        {
            return this.vertexCount;
        }

        public FloatBuffer getVertices()
        {
            return this.vertices;
        }
    }

    public void subdivideIndexedTriangleBuffer(IndexedTriangleBuffer itb)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int indexCount;
        int a, b, c;
        int ab, bc, ca;
        int i, j;
        HashMap edgeMap;
        Edge e;
        Integer split;

        indexCount = itb.getIndexCount();
        edgeMap = new HashMap();

        // Iterate over each triangle, and split the edge of each triangle. Each edge is split exactly once. The
        // index of the new vertex created by a split is stored in edgeMap.
        for (i = 0; i < indexCount; i += 3)
        {
            for (j = 0; j < 3; j++)
            {
                a = itb.indices.get(i + j);
                b = itb.indices.get((j < 2) ? (i + j + 1) : i);
                e = new Edge(a, b);
                split = edgeMap.get(e);
                if (split == null)
                {
                    split = this.splitVertex(itb, a, b);
                    edgeMap.put(e, split);
                }
            }
        }

        // Iterate over each triangle, and create indices for four new triangles, replacing indices of the original
        // triangle.
        for (i = 0; i < indexCount; i += 3)
        {
            a = itb.indices.get(i);
            b = itb.indices.get(i + 1);
            c = itb.indices.get(i + 2);
            ab = edgeMap.get(new Edge(a, b));
            bc = edgeMap.get(new Edge(b, c));
            ca = edgeMap.get(new Edge(c, a));
            this.indexSplitTriangle(itb, i, a, b, c, ab, bc, ca);
        }
    }

    public void makeIndexedTriangleBufferNormals(IndexedTriangleBuffer itb, FloatBuffer dest)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 3 * itb.vertexCount;

        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.capacity();
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeIndexedTriangleBufferNormals(0, itb.getIndexCount(), itb.getIndices(),
            0, itb.getVertexCount(), itb.getVertices(), dest);
    }

    public void makeIndexedTriangleBufferNormals(int indexPos, int indexCount, IntBuffer indices,
        int vertexPos, int vertexCount, FloatBuffer vertices,
        FloatBuffer dest)
    {
        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.capacity() < (indexPos + indexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + dest.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, v;
        int index;
        float nsign;
        float[] norm;
        int[] faceIndices;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        faceIndices = new int[3];

        // Compute the normal for each face, contributing that normal to each vertex of the face.
        for (i = 0; i < indexCount; i += 3)
        {
            faceIndices[0] = indices.get(indexPos + i);
            faceIndices[1] = indices.get(indexPos + i + 1);
            faceIndices[2] = indices.get(indexPos + i + 2);
            // Compute the normal for this face.
            this.facenorm(vertices, faceIndices[0], faceIndices[1], faceIndices[2], norm);
            // Add this face normal to the normal at each vertex.
            for (v = 0; v < 3; v++)
            {
                index = 3 * faceIndices[v];
                this.add3AndSet(dest, index, norm, 0);
            }
        }

        // Scale and normalize each vertex normal.
        for (v = 0; v < vertexCount; v++)
        {
            index = 3 * (vertexPos + v);
            this.mul3AndSet(dest, index, nsign);
            this.norm3AndSet(dest, index);
        }

        dest.rewind();
    }

    private int splitVertex(IndexedTriangleBuffer itb, int a, int b)
    {
        int minCapacity, oldCapacity, newCapacity;

        oldCapacity = itb.vertices.capacity();
        minCapacity = 3 * (itb.getVertexCount() + 1);
        while (minCapacity > oldCapacity)
        {
            newCapacity = 2 * oldCapacity;
            itb.vertices = this.copyOf(itb.vertices, newCapacity);
            oldCapacity = newCapacity;
        }

        int s = itb.getVertexCount();
        int is = 3 * s;
        int ia = 3 * a;
        int ib = 3 * b;
        itb.vertices.put(is, (itb.vertices.get(ia) + itb.vertices.get(ib)) / 2.0f);
        itb.vertices.put(is + 1, (itb.vertices.get(ia + 1) + itb.vertices.get(ib + 1)) / 2.0f);
        itb.vertices.put(is + 2, (itb.vertices.get(ia + 2) + itb.vertices.get(ib + 2)) / 2.0f);
        itb.vertexCount++;

        return s;
    }

    public void makeEllipsoidNormals(IndexedTriangleBuffer itb, FloatBuffer dest)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 3 * itb.vertexCount;

        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.capacity();
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeEllipsoidNormals(0, itb.getIndexCount(), itb.getIndices(),
            0, itb.getVertexCount(), itb.getVertices(), dest);
    }

    public void makeEllipsoidNormals(int indexPos, int indexCount, IntBuffer indices,
        int vertexPos, int vertexCount, FloatBuffer vertices,
        FloatBuffer dest)
    {
        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.capacity() < (indexPos + indexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + dest.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, v;
        int index;
        float nsign;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;

        // for a sphere, normals are just the normalized vectors of the vertex positions

        // first copy all the vertices to the normals buffer
        for (i = 0; i < 3 * vertexCount; i++)
        {
            dest.put(i, vertices.get(i));
        }

        // Scale and normalize each vertex normal.
        for (v = 0; v < vertexCount; v++)
        {
            index = 3 * (vertexPos + v);
            this.mul3AndSet(dest, index, nsign);
            this.norm3AndSet(dest, index);
        }

        dest.rewind();
    }

    public void makeCylinderNormals(IndexedTriangleBuffer itb, FloatBuffer dest)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 3 * itb.vertexCount;

        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.capacity();
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeCylinderNormals(0, itb.getIndexCount(), itb.getIndices(),
            0, itb.getVertexCount(), itb.getVertices(), dest);
    }

    public void makeCylinderNormals(int indexPos, int indexCount, IntBuffer indices,
        int vertexPos, int vertexCount, FloatBuffer vertices,
        FloatBuffer dest)
    {
        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.capacity() < (indexPos + indexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.capacity() < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + dest.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, v;
        int index;
        float nsign;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;

        // for a cylinder, normals are just the normalized vectors of the (x, y) coords of the vertex positions

        // first copy all the vertices to the normals buffer
        for (i = 0; i < 3 * vertexCount; i++)
        {
            if (i % 3 == 2)    // set z coord to zero
                dest.put(i, 0);
            else
                dest.put(i, -vertices.get(i));
        }

        // Scale and normalize each vertex normal.
        for (v = 0; v < vertexCount; v++)
        {
            index = 3 * (vertexPos + v);
            this.mul3AndSet(dest, index, nsign);
            this.norm3AndSet(dest, index);
        }

        dest.rewind();
    }

    private void indexSplitTriangle(IndexedTriangleBuffer itb, int original, int a, int b, int c, int ab, int bc,
        int ca)
    {
        int minCapacity, oldCapacity, newCapacity;

        // One of the new triangles will overwrite the original triangles, so we only need enough space to index
        // three new triangles.
        oldCapacity = itb.indices.capacity();
        minCapacity = itb.getIndexCount() + 9;
        while (minCapacity > oldCapacity)
        {
            newCapacity = 2 * oldCapacity;
            itb.indices = this.copyOf(itb.indices, newCapacity);
            oldCapacity = newCapacity;
        }

        // Lower-left triangle.
        // This triangle replaces the original.
        itb.indices.put(original, a);
        itb.indices.put(original + 1, ab);
        itb.indices.put(original + 2, ca);

        // Center triangle.
        itb.indices.put(itb.indexCount++, ab);
        itb.indices.put(itb.indexCount++, bc);
        itb.indices.put(itb.indexCount++, ca);

        // Lower-right triangle.
        itb.indices.put(itb.indexCount++, ab);
        itb.indices.put(itb.indexCount++, b);
        itb.indices.put(itb.indexCount++, bc);

        // Upper triangle.
        itb.indices.put(itb.indexCount++, ca);
        itb.indices.put(itb.indexCount++, bc);
        itb.indices.put(itb.indexCount++, c);
    }

    // This method finds triangles of the sphere mesh which span the discontinuity at 2 * PI radians.
    // It then creates a duplicate of the vertex in each of these triangles that is out of range, and uses this
    // duplicate to form the face (replacing the original vertex).  This way, the original vertex is used only
    // in faces that do not span the discontinuity, while faces that do span the discontinuity use the
    // duplicate instead.  When it comes time for texture mapping, a different texture coordinate can be
    // mapped to the duplicate vertex than to the original, each one falling in the correct range for the face(s) it
    // comprises.

    public void fixSphereSeam(IndexedTriangleBuffer itb, float wrapThreshold)
    {
        int vertex0, vertex1, vertex2;  // indices of the three vertices of the current face
        double x0, y0, x1, y1, x2, y2;   // actual x and y point values of those vertices
        double phi0, phi1, phi2;

        Integer newVertex, wrapVertex;
        int wrapIndex = -1;     //  track index of the vertex that will be replaced by a new "wrapped" vertex
        // keep track of newly created duplicate vertices:
        Map duplicates = new HashMap();

        // for each indexed triangle, determine if phi (longitude) of any of the vertices is on the
        // opposite side of 2PI from others (the "wrap" vertex)
        int indexCount = itb.getIndexCount();
        for (int i = 0; i < indexCount; i += 3)
        {
            vertex0 = itb.indices.get(i);
            vertex1 = itb.indices.get(i + 1);
            vertex2 = itb.indices.get(i + 2);

            x0 = itb.vertices.get(3 * vertex0);
            y0 = itb.vertices.get(3 * vertex0 + 1);
            x1 = itb.vertices.get(3 * vertex1);
            y1 = itb.vertices.get(3 * vertex1 + 1);
            x2 = itb.vertices.get(3 * vertex2);
            y2 = itb.vertices.get(3 * vertex2 + 1);

            // compute phi of each of the three vertices of the face
            phi0 = Math.atan2(y0, x0);
            if (phi0 < 0.0d)
                phi0 += 2.0d * Math.PI;

            phi1 = Math.atan2(y1, x1);
            if (phi1 < 0.0d)
                phi1 += 2.0d * Math.PI;

            phi2 = Math.atan2(y2, x2);
            if (phi2 < 0.0d)
                phi2 += 2.0d * Math.PI;

            // check if face spans phi = 0 (the texture seam), and determine which is the "wrapped" vertex
            if (Math.abs(phi0 - phi1) > wrapThreshold)
            {
                if (Math.abs(phi0 - phi2) > wrapThreshold)
                    wrapIndex = i;    // vertex0 is the wrapped vertex
                else
                    wrapIndex = i + 1;   // vertex1 is the wrapped vertex
            }
            else if (Math.abs(phi1 - phi2) > wrapThreshold)
                wrapIndex = i + 2;   // vertex2 is the wrapped vertex

            if (wrapIndex >= 0)  // check if one of the vertices on this face wrapped across 2PI
            {
                wrapVertex = itb.indices.get(wrapIndex);
                //look to see if this vertex has been duplicated already
                newVertex = duplicates.get(wrapVertex);
                if (newVertex != null)
                    itb.indices.put(wrapIndex, newVertex);   // replace the old vertex with the duplicate
                else
                {
                    // create a duplicate of the wrapIndex vertex and get its index newVertex
                    newVertex = duplicateVertex(itb, wrapVertex);
                    // place the new vertex in the duplicates structure
                    duplicates.put(wrapVertex, newVertex);
                    // now replace the index at the wrapIndex with the index of the new duplicate
                    itb.indices.put(wrapIndex, newVertex);
                }
                wrapIndex = -1;     // reset the wrapVertex
            }
        }
    }

    // append copy of vertex at sourceIndex to end of vertices buffer

    private int duplicateVertex(IndexedTriangleBuffer itb, int sourceIndex)
    {
        if (itb == null)
        {
            String message = Logging.getMessage("nullValue.IndexedTriangleBufferIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (sourceIndex >= itb.vertexCount)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "sourceIndex > vertexCount");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // ensure there is room in the vertex buffer for one new vertex
        int minCapacity, oldCapacity, newCapacity;

        oldCapacity = itb.vertices.capacity();
        minCapacity = 3 * itb.getVertexCount() + 3;
        while (minCapacity > oldCapacity)
        {
            newCapacity = 2 * oldCapacity;
            itb.vertices = this.copyOf(itb.vertices, newCapacity);
            oldCapacity = newCapacity;
        }

        int destIndex = itb.getVertexCount();

        // append vertex data to vertices buffer
        itb.vertices.put(3 * destIndex, itb.vertices.get(3 * sourceIndex));
        itb.vertices.put(3 * destIndex + 1, itb.vertices.get(3 * sourceIndex + 1));
        itb.vertices.put(3 * destIndex + 2, itb.vertices.get(3 * sourceIndex + 2));

        itb.vertexCount++;
        // return index of the newly created vertex
        return itb.vertexCount - 1;
    }

    public void makeUnitSphereTextureCoordinates(IndexedTriangleBuffer itb, FloatBuffer texCoords)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 2 * itb.vertexCount;

        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + texCoords.capacity();
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeUnitSphereTextureCoordinates(itb.getVertexCount(), itb.getVertices(), texCoords, -1);
    }

    // allow for correction of seam caused by triangles that wrap across tecture bounds

    public void makeUnitSphereTextureCoordinates(IndexedTriangleBuffer itb, FloatBuffer texCoords,
        int seamVerticesIndex)
    {
        if (itb == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 2 * itb.vertexCount;

        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + texCoords.capacity();
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeUnitSphereTextureCoordinates(itb.getVertexCount(), itb.getVertices(),
            texCoords, seamVerticesIndex);
    }

    public void makeUnitSphereTextureCoordinates(int vertexCount, FloatBuffer vertices,
        FloatBuffer texCoords, int seamVerticesIndex)
    {
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.capacity() < 3 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < 2 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + texCoords.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i;
        double x, y, z;
        double theta, phi, u, v;

        // compute uv texture coordinates for each vertex and place them in the texCoords buffer.
        for (i = 0; i < vertexCount; i++)
        {
            x = vertices.get(3 * i);
            y = vertices.get(3 * i + 1);
            z = vertices.get(3 * i + 2);

            phi = Math.atan2(y, x);
            theta = Math.acos(z);

            if (phi < 0.0d)
                phi += 2.0d * Math.PI;  // shift phi to be in [0, 2*PI]

            u = phi / (2.0d * Math.PI);
            v = (Math.PI - theta) / Math.PI;

            texCoords.put(2 * i, (float) u);
            texCoords.put(2 * i + 1, (float) v);
        }

        if (seamVerticesIndex > 0)          // if the seam of the sphere was fixed
        {
            for (i = seamVerticesIndex; i < vertexCount; i++)
            {
                // wrap u (phi) texCoord for all the duplicated vertices
                u = texCoords.get(2 * i);
                if (u < 0.5)
                    texCoords.put(2 * i, (float) u + 1);
                else
                    texCoords.put(2 * i, (float) u - 1);
            }
        }
        texCoords.rewind();
    }

    // single texture version
    public void makeUnitBoxTextureCoordinates(FloatBuffer texCoords, int vertexCount)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < 2 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + texCoords.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for each of the 6 box faces and place them in the texCoords buffer.
        for (int i = 0; i < vertexCount; i += 4)
        {
            // V0 (upper left)
            texCoords.put(2 * i, 0);
            texCoords.put(2 * i + 1, 1);
            // V1 (upper right)
            texCoords.put(2 * i + 2, 1);
            texCoords.put(2 * i + 3, 1);
            // V2 (lower left)
            texCoords.put(2 * i + 4, 0);
            texCoords.put(2 * i + 5, 0);
            // V3 (lower right)
            texCoords.put(2 * i + 6, 1);
            texCoords.put(2 * i + 7, 0);
        }

        texCoords.rewind();
    }

    // multi-texture version
    public void makeUnitBoxTextureCoordinates(int index, FloatBuffer texCoords, int vertexCount)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < 2 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + texCoords.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for each of the 6 box faces and place them in the texCoords buffer.
        for (int i = 0; i < vertexCount; i += 4)
        {
            // V0 (upper left)
            texCoords.put(2 * i, 0);
            texCoords.put(2 * i + 1, 1);
            // V1 (upper right)
            texCoords.put(2 * i + 2, 1);
            texCoords.put(2 * i + 3, 1);
            // V2 (lower left)
            texCoords.put(2 * i + 4, 0);
            texCoords.put(2 * i + 5, 0);
            // V3 (lower right)
            texCoords.put(2 * i + 6, 1);
            texCoords.put(2 * i + 7, 0);
        }

        texCoords.rewind();
    }

    public void makeUnitPyramidTextureCoordinates(FloatBuffer texCoords, int vertexCount)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < 2 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + texCoords.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for each of the 4 pyramid faces and for the base, and place them
        // in the texCoords buffer.

        int i;
        for (i = 0; i < vertexCount - 4; i += 3) // create texture coords for the 4 sides of the pyramid first
        {
            // V0 (point)
            texCoords.put(2 * i, 0.5f);
            texCoords.put(2 * i + 1, 1);
            // V1 (lower left)
            texCoords.put(2 * i + 2, 0);
            texCoords.put(2 * i + 3, 0);
            // V2 (lower right)
            texCoords.put(2 * i + 4, 1);
            texCoords.put(2 * i + 5, 0);
        }

        // then create coords for the base

        // V0 (upper left)
        texCoords.put(2 * i, 0);
        texCoords.put(2 * i + 1, 1);
        // V1 (upper right)
        texCoords.put(2 * i + 2, 1);
        texCoords.put(2 * i + 3, 1);
        // V2 (lower left)
        texCoords.put(2 * i + 4, 0);
        texCoords.put(2 * i + 5, 0);
        // V3 (lower right)
        texCoords.put(2 * i + 6, 1);
        texCoords.put(2 * i + 7, 0);

        texCoords.rewind();
    }

    public void makeUnitPyramidTextureCoordinates(int index, FloatBuffer texCoords, int vertexCount)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (texCoords.capacity() < 2 * vertexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + texCoords.capacity());
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for either one of the 4 pyramid faces or for the base, and place them
        // in the texCoords buffer.

        int i = 0;
        if (index == 4)  // pyramid base
        {
            // V0 (upper left)
            texCoords.put(2 * i, 0);
            texCoords.put(2 * i + 1, 1);
            // V1 (upper right)
            texCoords.put(2 * i + 2, 1);
            texCoords.put(2 * i + 3, 1);
            // V2 (lower left)
            texCoords.put(2 * i + 4, 0);
            texCoords.put(2 * i + 5, 0);
            // V3 (lower right)
            texCoords.put(2 * i + 6, 1);
            texCoords.put(2 * i + 7, 0);
        }
        else    // pyramid side
        {
            for (i = 0; i < vertexCount; i += 3) // create texture coords for the 4 sides of the pyramid first
            {
                // V0 (point)
                texCoords.put(2 * i, 0.5f);
                texCoords.put(2 * i + 1, 1);
                // V1 (lower left)
                texCoords.put(2 * i + 2, 0);
                texCoords.put(2 * i + 3, 0);
                // V2 (lower right)
                texCoords.put(2 * i + 4, 1);
                texCoords.put(2 * i + 5, 0);
            }
        }

        texCoords.rewind();
    }

    public void makeUnitCylinderTextureCoordinates(int face, FloatBuffer texCoords, int subdivisions)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for the cylinder top, bottom and core, and place them
        // in the texCoords buffer.

        int i, index;
        float x, y, z, u, v, a, phi;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        if (face == 2)      // cylinder core
        {
            int coreTexIndex = 0;

            for (i = 0; i < slices; i++)
            {
                a = i * da;

                // cylinder core top rim
                u = 1 - a / (float) (2 * Math.PI);

                texCoords.put(coreTexIndex, u);
                texCoords.put(coreTexIndex + 1, 1);

                // cylinder core bottom rim
                texCoords.put(coreTexIndex + 2, u);
                texCoords.put(coreTexIndex + 3, 0);

                coreTexIndex += 4;
            }

            // close the texture seam
            texCoords.put(coreTexIndex, 0);
            texCoords.put(coreTexIndex + 1, 1);

            texCoords.put(coreTexIndex + 2, 0);
            texCoords.put(coreTexIndex + 3, 0);
        }
        else                // cylinder top or bottom
        {
            // center point
            texCoords.put(0, 0.5f);
            texCoords.put(1, 0.5f);

            // perimeter points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);

                u = x / 2 + 0.5f;
                v = y / 2 + 0.5f;

                if (face == 1)   // Cylinder bottom
                    u = 1 - u;

                texCoords.put(2 * (i + 1), u);
                texCoords.put(2 * (i + 1) + 1, v);
            }
        }

        texCoords.rewind();
    }

    public void makeWedgeTextureCoordinates(FloatBuffer texCoords, int subdivisions, Angle angle)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for the wedge top, bottom, core and sides, and place them
        // in the texCoords buffer.

        int i, index;
        float x, y, u, v, a;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = (float) angle.getRadians() / slices;
        int coreTexIndex = 4 * (slices + 2);

        // center points
        texCoords.put(0, 0.5f);
        texCoords.put(1, 0.5f);

        texCoords.put(2 * (slices + 2), 0.5f);
        texCoords.put(2 * (slices + 2) + 1, 0.5f);

        for (i = 0; i <= slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);

            u = x / 2 + 0.5f;
            v = y / 2 + 0.5f;

            // wedge top
            texCoords.put(2 * (i + 1), u);
            texCoords.put(2 * (i + 1) + 1, v);

            // wedge bottom
            texCoords.put(2 * (slices + i + 3), 1 - u);
            texCoords.put(2 * (slices + i + 3) + 1, v);

            // wedge core top rim
            u = 1 - a / (float) (2 * Math.PI);

            texCoords.put(coreTexIndex, u);
            texCoords.put(coreTexIndex + 1, 1);

            // wedge core bottom rim
            texCoords.put(coreTexIndex + 2, u);
            texCoords.put(coreTexIndex + 3, 0);

            coreTexIndex += 4;
        }

        // wedge sides
        for (i = 0; i < 2; i++)
        {
            index = 2 * (4 * (slices + 1 + i) + 2);

            // inner points
            texCoords.put(index, 0);
            texCoords.put(index + 1, 1);

            texCoords.put(index + 2, 0);
            texCoords.put(index + 3, 0);

            // outer points
            texCoords.put(index + 4, 1);
            texCoords.put(index + 5, 1);

            texCoords.put(index + 6, 1);
            texCoords.put(index + 7, 0);
        }

        texCoords.rewind();
    }

    public void makeUnitWedgeTextureCoordinates(int face, FloatBuffer texCoords, int subdivisions, Angle angle)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for the wedge top, bottom, core and sides, and place them
        // in the texCoords buffer.

        int i, index;
        float x, y, u, v, a;

        // face 0 = top
        // face 1 = bottom
        // face 2 = round core wall
        // face 3 = first wedge side
        // face 4 = second wedge side

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = (float) angle.getRadians() / slices;

        if (face == 0 || face == 1)   // wedge top or bottom
        {
            // center point
            texCoords.put(0, 0.5f);
            texCoords.put(1, 0.5f);

            for (i = 0; i <= slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);

                u = x / 2 + 0.5f;
                v = y / 2 + 0.5f;

                if (face == 1)   // wedge bottom
                    u = 1 - u;

                // rim point
                texCoords.put(2 * (i + 1), u);
                texCoords.put(2 * (i + 1) + 1, v);
            }
        }
        else if (face == 2)   // wedge core
        {
            int coreTexIndex = 0;

            for (i = 0; i <= slices; i++)
            {
                a = i * da;

                // cylinder core top rim
                u = 1 - a / (float) (2 * Math.PI);

                texCoords.put(coreTexIndex, u);
                texCoords.put(coreTexIndex + 1, 1);

                // cylinder core bottom rim
                texCoords.put(coreTexIndex + 2, u);
                texCoords.put(coreTexIndex + 3, 0);

                coreTexIndex += 4;
            }
        }
        else if (face == 3)     // west-facing wedge side
        {
            // inner points
            texCoords.put(0, 1);
            texCoords.put(1, 1);

            texCoords.put(2, 1);
            texCoords.put(3, 0);

            // outer points
            texCoords.put(4, 0);
            texCoords.put(5, 1);

            texCoords.put(6, 0);
            texCoords.put(7, 0);
        }
        else if (face == 4)     // adjustable wedge side
        {
            // inner points
            texCoords.put(0, 0);
            texCoords.put(1, 1);

            texCoords.put(2, 0);
            texCoords.put(3, 0);

            // outer points
            texCoords.put(4, 1);
            texCoords.put(5, 1);

            texCoords.put(6, 1);
            texCoords.put(7, 0);
        }

        texCoords.rewind();
    }

    public void makeUnitConeTextureCoordinates(FloatBuffer texCoords, int subdivisions)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for the cone bottom and core, and place them
        // in the texCoords buffer.

        int i, index;
        float x, y, z, u, v, a, phi;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;
        int coreTexIndex = 2 * (slices + 1);

        // center point
        texCoords.put(0, 0.5f);
        texCoords.put(1, 0.5f);

        for (i = 0; i < slices; i++)
        {
            a = i * da;
            x = (float) Math.sin(a);
            y = (float) Math.cos(a);

            u = x / 2 + 0.5f;
            v = y / 2 + 0.5f;

            // cone bottom
            texCoords.put(2 * (i + 1), 1 - u);
            texCoords.put(2 * (i + 1) + 1, v);

            // cone core top rim
            u = 1 - a / (float) (2 * Math.PI);

            texCoords.put(coreTexIndex, u);
            texCoords.put(coreTexIndex + 1, 1);

            // cone core bottom rim
            texCoords.put(coreTexIndex + 2, u);
            texCoords.put(coreTexIndex + 3, 0);

            coreTexIndex += 4;
        }

        // close the texture seam
        texCoords.put(coreTexIndex, 0);
        texCoords.put(coreTexIndex + 1, 1);

        texCoords.put(coreTexIndex + 2, 0);
        texCoords.put(coreTexIndex + 3, 0);

        texCoords.rewind();
    }

    public void makeUnitConeTextureCoordinates(int face, FloatBuffer texCoords, int subdivisions)
    {
        if (texCoords == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (subdivisions < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // create uv texture coordinates for the cone base and core, and place them
        // in the texCoords buffer.

        int i, index;
        float x, y, z, u, v, a, phi;

        int slices = (int) Math.pow(2, 2 + subdivisions);
        float da = 2.0f * (float) Math.PI / (float) slices;

        if (face == 1)      // cone core
        {
            int coreTexIndex = 0;

            for (i = 0; i < slices; i++)
            {
                a = i * da;

                // cone core top rim
                u = 1 - a / (float) (2 * Math.PI);

                texCoords.put(coreTexIndex, u);
                texCoords.put(coreTexIndex + 1, 1);

                // core bottom rim
                texCoords.put(coreTexIndex + 2, u);
                texCoords.put(coreTexIndex + 3, 0);

                coreTexIndex += 4;
            }

            // close the texture seam
            texCoords.put(coreTexIndex, 0);
            texCoords.put(coreTexIndex + 1, 1);

            texCoords.put(coreTexIndex + 2, 0);
            texCoords.put(coreTexIndex + 3, 0);
        }
        else if (face == 0)               // cone base
        {
            // center point
            texCoords.put(0, 0.5f);
            texCoords.put(1, 0.5f);

            // perimeter points
            for (i = 0; i < slices; i++)
            {
                a = i * da;
                x = (float) Math.sin(a);
                y = (float) Math.cos(a);

                u = x / 2 + 0.5f;
                v = y / 2 + 0.5f;

                texCoords.put(2 * (i + 1), 1 - u);
                texCoords.put(2 * (i + 1) + 1, v);
            }
        }

        texCoords.rewind();
    }

    //**************************************************************//
    //********************  Indexed Triangle Array  ****************//
    //**************************************************************//

    public int getIndexedTriangleArrayDrawMode()
    {
        return GL.GL_TRIANGLES;
    }

    public static class IndexedTriangleArray
    {
        private int indexCount;
        private int vertexCount;
        private int[] indices;
        private float[] vertices;

        public IndexedTriangleArray(int indexCount, int[] indices, int vertexCount, float[] vertices)
        {
            this.indexCount = indexCount;
            this.indices = indices;
            this.vertexCount = vertexCount;
            this.vertices = vertices;
        }

        public int getIndexCount()
        {
            return this.indexCount;
        }

        public int[] getIndices()
        {
            return this.indices;
        }

        public int getVertexCount()
        {
            return this.vertexCount;
        }

        public float[] getVertices()
        {
            return this.vertices;
        }
    }

    public void subdivideIndexedTriangleArray(IndexedTriangleArray ita)
    {
        if (ita == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int indexCount;
        int a, b, c;
        int ab, bc, ca;
        int i, j;
        HashMap edgeMap;
        Edge e;
        Integer split;

        indexCount = ita.indexCount;
        edgeMap = new HashMap();

        // Iterate over each triangle, and split the edge of each triangle. Each edge is split exactly once. The
        // index of the new vertex created by a split is stored in edgeMap.
        for (i = 0; i < indexCount; i += 3)
        {
            for (j = 0; j < 3; j++)
            {
                a = ita.indices[i + j];
                b = ita.indices[(j < 2) ? (i + j + 1) : i];
                e = new Edge(a, b);
                split = edgeMap.get(e);
                if (split == null)
                {
                    split = this.splitVertex(ita, a, b);
                    edgeMap.put(e, split);
                }
            }
        }

        // Iterate over each triangle, and create indices for four new triangles, replacing indices of the original
        // triangle.
        for (i = 0; i < indexCount; i += 3)
        {
            a = ita.indices[i];
            b = ita.indices[i + 1];
            c = ita.indices[i + 2];
            ab = edgeMap.get(new Edge(a, b));
            bc = edgeMap.get(new Edge(b, c));
            ca = edgeMap.get(new Edge(c, a));
            this.indexSplitTriangle(ita, i, a, b, c, ab, bc, ca);
        }
    }

    public IndexedTriangleArray subdivideIndexedTriangles(int indexCount, int[] indices,
        int vertexCount, float[] vertices)
    {
        int numCoords = 3 * vertexCount;

        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.length < indexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.length < numCoords)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        IndexedTriangleArray ita = new IndexedTriangleArray(indexCount, indices, vertexCount, vertices);
        this.subdivideIndexedTriangleArray(ita);

        return ita;
    }

    public void makeIndexedTriangleArrayNormals(IndexedTriangleArray ita, float[] dest)
    {
        if (ita == null)
        {
            String message = "nullValue.IndexedTriangleArray";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int numCoords = 3 * ita.vertexCount;

        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.makeIndexedTriangleArrayNormals(0, ita.indexCount, ita.indices, 0, ita.vertexCount, ita.vertices, dest);
    }

    public void makeIndexedTriangleArrayNormals(int indexPos, int indexCount, int[] indices,
        int vertexPos, int vertexCount, float[] vertices,
        float[] dest)
    {
        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.length < (indexPos + indexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.length < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + dest.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, v;
        int index;
        float nsign;
        float[] norm;
        int[] faceIndices;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        faceIndices = new int[3];

        // Compute the normal for each face, contributing that normal to each vertex of the face.
        for (i = 0; i < indexCount; i += 3)
        {
            faceIndices[0] = indices[indexPos + i];
            faceIndices[1] = indices[indexPos + i + 1];
            faceIndices[2] = indices[indexPos + i + 2];
            // Compute the normal for this face.
            this.facenorm(vertices, faceIndices[0], faceIndices[1], faceIndices[2], norm);
            // Add this face normal to the normal at each vertex.
            for (v = 0; v < 3; v++)
            {
                index = 3 * faceIndices[v];
                this.add3AndSet(dest, index, norm, 0);
            }
        }

        // Scale and normalize each vertex normal.
        for (v = 0; v < vertexCount; v++)
        {
            index = 3 * (vertexPos + v);
            this.mul3AndSet(dest, index, nsign);
            this.norm3AndSet(dest, index);
        }
    }

    public void makeIndexedTriangleStripNormals(int indexPos, int indexCount, int[] indices,
        int vertexPos, int vertexCount, float[] vertices,
        float[] dest)
    {
        if (indices == null)
        {
            String message = "nullValue.IndexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (indices.length < indexPos + indexCount)
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "indices.length=" + indices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices == null)
        {
            String message = "nullValue.VertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (vertices.length < 3 * (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "vertices.length=" + vertices.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < 3 * (vertexPos + vertexCount))
        {
            String message = Logging.getMessage("generic.ArrayInvalidLength", "dest.length=" + dest.length);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int i, v;
        int index;
        float nsign;
        float[] norm;
        int[] faceIndices;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        faceIndices = new int[3];

        // Compute the normal for each face, contributing that normal to each vertex of the face.
        for (i = 2; i < indexCount; i++)
        {
            if ((i % 2) == 0)
            {
                faceIndices[0] = indices[indexPos + i - 2];
                faceIndices[1] = indices[indexPos + i - 1];
                faceIndices[2] = indices[indexPos + i];
            }
            else
            {
                faceIndices[0] = indices[indexPos + i - 1];
                faceIndices[1] = indices[indexPos + i - 2];
                faceIndices[2] = indices[indexPos + i];
            }
            // Compute the normal for this face.
            this.facenorm(vertices, faceIndices[0], faceIndices[1], faceIndices[2], norm);
            // Add this face normal to the normal at each vertex.
            for (v = 0; v < 3; v++)
            {
                index = 3 * faceIndices[v];
                this.add3AndSet(dest, index, norm, 0);
            }
        }

        // Scale and normalize each vertex normal.
        for (v = 0; v < vertexCount; v++)
        {
            index = 3 * (vertexPos + v);
            this.mul3AndSet(dest, index, nsign);
            this.norm3AndSet(dest, index);
        }
    }

    private int splitVertex(IndexedTriangleArray ita, int a, int b)
    {
        int minCapacity, oldCapacity, newCapacity;

        oldCapacity = ita.vertices.length;
        minCapacity = 3 * (ita.vertexCount + 1);
        while (minCapacity > oldCapacity)
        {
            newCapacity = 2 * oldCapacity;
            ita.vertices = this.copyOf(ita.vertices, newCapacity);
            oldCapacity = newCapacity;
        }

        int s = ita.vertexCount;
        int is = 3 * s;
        int ia = 3 * a;
        int ib = 3 * b;
        ita.vertices[is] = (ita.vertices[ia] + ita.vertices[ib]) / 2.0f;
        ita.vertices[is + 1] = (ita.vertices[ia + 1] + ita.vertices[ib + 1]) / 2.0f;
        ita.vertices[is + 2] = (ita.vertices[ia + 2] + ita.vertices[ib + 2]) / 2.0f;
        ita.vertexCount++;

        return s;
    }

    private void indexSplitTriangle(IndexedTriangleArray ita, int original, int a, int b, int c, int ab, int bc, int ca)
    {
        int minCapacity, oldCapacity, newCapacity;

        // One of the new triangles will overwrite the original triangles, so we only need enough space to index
        // three new triangles.
        oldCapacity = ita.indices.length;
        minCapacity = ita.indexCount + 9;
        while (minCapacity > oldCapacity)
        {
            newCapacity = 2 * oldCapacity;
            ita.indices = this.copyOf(ita.indices, newCapacity);
            oldCapacity = newCapacity;
        }

        // Lower-left triangle.
        // This triangle replaces the original.
        ita.indices[original] = a;
        ita.indices[original + 1] = ab;
        ita.indices[original + 2] = ca;

        // Center triangle.
        ita.indices[ita.indexCount++] = ab;
        ita.indices[ita.indexCount++] = bc;
        ita.indices[ita.indexCount++] = ca;

        // Lower-right triangle.
        ita.indices[ita.indexCount++] = ab;
        ita.indices[ita.indexCount++] = b;
        ita.indices[ita.indexCount++] = bc;

        // Upper triangle.
        ita.indices[ita.indexCount++] = ca;
        ita.indices[ita.indexCount++] = bc;
        ita.indices[ita.indexCount++] = c;
    }

    private static class Edge
    {
        public final int a;
        public final int b;

        public Edge(int a, int b)
        {
            this.a = a;
            this.b = b;
        }

        public boolean equals(Object o)
        {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            // Compares a non directed edge between two points. Therefore we must treat edge equivalence as
            // edge(ab)=edge(ab) OR edge(ab)=edge(ba).
            Edge that = (Edge) o;
            return (this.a == that.a && this.b == that.b)
                || (this.a == that.b && this.b == that.a);
        }

        public int hashCode()
        {
            // Represents the hash for a a non directed edge between two points. Therefore we use a non-commutative
            // hash so that hash(ab)=hash(ba).
            return this.a + this.b;
        }
    }

    //**************************************************************//
    //********************  Subdivision Points  ********************//
    //**************************************************************//

    public int getSubdivisionPointsVertexCount(int subdivisions)
    {
        return (1 << subdivisions) + 1;
    }

    public void makeSubdivisionPoints(float x1, float y1, float z1, float x2, float y2, float z2,
        int subdivisions, float[] dest)
    {
        int numPoints = this.getSubdivisionPointsVertexCount(subdivisions);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "subdivisions=" + subdivisions);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < numCoords)
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int first, last;
        int index;

        first = 0;
        last = numPoints - 1;

        index = 3 * first;
        dest[index] = x1;
        dest[index + 1] = y1;
        dest[index + 2] = z1;

        index = 3 * last;
        dest[index] = x2;
        dest[index + 1] = y2;
        dest[index + 2] = z2;

        this.subdivide(x1, y1, z1, x2, y2, z2, subdivisions, dest, first, last);
    }

    private void subdivide(float x1, float y1, float z1, float x2, float y2, float z2, int subdivisions,
        float[] dest, int first, int last)
    {
        float x, y, z;
        int mid, index;

        if (subdivisions <= 0)
            return;

        x = (x1 + x2) / 2.0f;
        y = (y1 + y2) / 2.0f;
        z = (z1 + z2) / 2.0f;

        mid = (first + last) / 2;
        index = mid * 3;
        dest[index] = x;
        dest[index + 1] = y;
        dest[index + 2] = z;

        if (subdivisions > 1)
        {
            this.subdivide(x1, y1, z1, x, y, z, subdivisions - 1, dest, first, mid);
            this.subdivide(x, y, z, x2, y2, z2, subdivisions - 1, dest, mid, last);
        }
    }

    //**************************************************************//
    //********************  Bilinear Surface ********************//
    //**************************************************************//

    public int getBilinearSurfaceFillIndexCount(int uStacks, int vStacks)
    {
        return vStacks * 2 * (uStacks + 1) + 2 * (vStacks - 1);
    }

    public int getBilinearSurfaceOutlineIndexCount(int uStacks, int vStacks, int mask)
    {
        int count = 0;
        if ((mask & TOP) != 0)
            count += 2 * uStacks;
        if ((mask & BOTTOM) != 0)
            count += 2 * uStacks;
        if ((mask & LEFT) != 0)
            count += 2 * vStacks;
        if ((mask & RIGHT) != 0)
            count += 2 * vStacks;

        return count;
    }

    public int getBilinearSurfaceVertexCount(int uStacks, int vStacks)
    {
        return (uStacks + 1) * (vStacks + 1);
    }

    public int getBilinearSurfaceFillDrawMode()
    {
        return GL.GL_TRIANGLE_STRIP;
    }

    public int getBilinearSurfaceOutlineDrawMode()
    {
        return GL.GL_LINES;
    }

    public void makeBilinearSurfaceFillIndices(int vertexPos, int uStacks, int vStacks, int destPos, int[] dest)
    {
        int numIndices = this.getBilinearSurfaceFillIndexCount(uStacks, vStacks);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "uStacks=" + uStacks
                + " vStacks=" + vStacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < (numIndices + destPos))
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int ui, vi;
        int vertex, index;

        index = destPos;
        for (vi = 0; vi < vStacks; vi++)
        {
            if (vi != 0)
            {
                if (this.orientation == INSIDE)
                {
                    vertex = uStacks + vi * (uStacks + 1);
                    dest[index++] = vertexPos + vertex;
                    vertex = vi * (uStacks + 1);
                    dest[index++] = vertexPos + vertex;
                }
                else // (this.orientation == OUTSIDE)
                {
                    vertex = uStacks + (vi - 1) * (uStacks + 1);
                    dest[index++] = vertexPos + vertex;
                    vertex = vi * (uStacks + 1) + (uStacks + 1);
                    dest[index++] = vertexPos + vertex;
                }
            }
            for (ui = 0; ui <= uStacks; ui++)
            {
                vertex = ui + vi * (uStacks + 1);
                if (this.orientation == INSIDE)
                {
                    dest[index++] = vertexPos + vertex;
                    dest[index++] = vertexPos + vertex + (uStacks + 1);
                }
                else // (this.orientation == OUTSIDE)
                {
                    dest[index++] = vertexPos + vertex + (uStacks + 1);
                    dest[index++] = vertexPos + vertex;
                }
            }
        }
    }

    public void makeBilinearSurfaceOutlineIndices(int vertexPos, int uStacks, int vStacks, int mask, int destPos,
        int[] dest)
    {
        int numIndices = this.getBilinearSurfaceOutlineIndexCount(uStacks, vStacks, mask);

        if (numIndices < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "uStacks=" + uStacks
                + " vStacks=" + vStacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < (numIndices + destPos))
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int ui, vi;
        int vertex, index;

        index = destPos;
        // Bottom row.
        if ((mask & BOTTOM) != 0)
        {
            for (ui = 0; ui < uStacks; ui++)
            {
                vertex = ui;
                dest[index++] = vertexPos + vertex;
                vertex = ui + 1;
                dest[index++] = vertexPos + vertex;
            }
        }
        // Right side.
        if ((mask & RIGHT) != 0)
        {
            for (vi = 0; vi < vStacks; vi++)
            {
                vertex = uStacks + vi * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
                vertex = uStacks + (vi + 1) * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
            }
        }
        // Top side.
        if ((mask & TOP) != 0)
        {
            for (ui = uStacks; ui > 0; ui--)
            {
                vertex = ui + vStacks * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
                vertex = (ui - 1) + vStacks * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
            }
        }
        // Left side.
        if ((mask & LEFT) != 0)
        {
            for (vi = vStacks; vi > 0; vi--)
            {
                vertex = vi * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
                vertex = (vi - 1) * (uStacks + 1);
                dest[index++] = vertexPos + vertex;
            }
        }
    }

    public void makeBilinearSurfaceVertices(float[] control, int destPos, int uStacks, int vStacks, float[] dest)
    {
        int numPoints = this.getBilinearSurfaceVertexCount(uStacks, vStacks);
        int numCoords = 3 * numPoints;

        if (control == null)
        {
            String message = "nullValue.ControlPointArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (control.length < 12)
        {
            String message = "generic.ControlPointArrayInvalidLength " + control.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "uStacks=" + uStacks
                + " vStacks=" + vStacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < (numCoords + 3 * destPos))
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        float x, y, z;
        float u, v;
        float du, dv;
        float oneMinusU, oneMinusV;
        int ui, vi;
        int index;

        du = 1.0f / (float) uStacks;
        dv = 1.0f / (float) vStacks;

        for (vi = 0; vi <= vStacks; vi++)
        {
            v = vi * dv;
            oneMinusV = 1.0f - v;
            for (ui = 0; ui <= uStacks; ui++)
            {
                u = ui * du;
                oneMinusU = 1.0f - u;
                index = ui + vi * (uStacks + 1);
                index = 3 * (destPos + index);
                x = oneMinusU * oneMinusV * control[0]  // Lower left control point
                    + u * oneMinusV * control[3]  // Lower right control point
                    + u * v * control[6]  // Upper right control point
                    + oneMinusU * v * control[9]; // Upper left control point
                y = oneMinusU * oneMinusV * control[1]
                    + u * oneMinusV * control[4]
                    + u * v * control[7]
                    + oneMinusU * v * control[10];
                z = oneMinusU * oneMinusV * control[2]
                    + u * oneMinusV * control[5]
                    + u * v * control[8]
                    + oneMinusU * v * control[11];
                dest[index] = x;
                dest[index + 1] = y;
                dest[index + 2] = z;
            }
        }
    }

    public void makeBilinearSurfaceVertexNormals(int srcPos, int uStacks, int vStacks, float[] srcVerts,
        int destPos, float dest[])
    {
        int numPoints = this.getBilinearSurfaceVertexCount(uStacks, vStacks);
        int numCoords = 3 * numPoints;

        if (numPoints < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "uStacks=" + uStacks
                + " vStacks=" + vStacks);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (srcVerts == null)
        {
            String message = "nullValue.SourceVertexArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest == null)
        {
            String message = "nullValue.DestinationArrayIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dest.length < (numCoords + 3 * destPos))
        {
            String message = "generic.DestinationArrayInvalidLength " + dest.length;
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int ui, vi;
        int index;
        int vprev, vnext;
        float nsign;
        float[] norm, zero, tmp;

        nsign = (this.orientation == OUTSIDE) ? 1.0f : -1.0f;
        norm = new float[3];
        zero = new float[3];
        tmp = new float[3];

        for (vi = 0; vi <= vStacks; vi++)
        {
            for (ui = 0; ui <= uStacks; ui++)
            {
                index = ui + vi * (uStacks + 1);
                index = srcPos + index;
                vprev = index - (uStacks + 1);
                vnext = index + (uStacks + 1);

                System.arraycopy(zero, 0, norm, 0, 3);

                // Adjacent faces below.
                if (vi > 0)
                {
                    // Adjacent faces below and to the left.
                    if (ui > 0)
                    {
                        this.facenorm(srcVerts, index, index - 1, vprev - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, vprev - 1, vprev, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                    // Adjacent faces below and to the right.
                    if (ui < uStacks)
                    {
                        this.facenorm(srcVerts, index, vprev, vprev + 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, vprev + 1, index + 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                }

                // Adjacent faces above.
                if (vi < vStacks)
                {
                    // Adjacent faces above and to the left.
                    if (ui > 0)
                    {
                        this.facenorm(srcVerts, index, vnext, vnext - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, vnext - 1, index - 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                    // Adjacent faces above and to the right.
                    if (ui < uStacks)
                    {
                        this.facenorm(srcVerts, index, index + 1, vnext + 1, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                        this.facenorm(srcVerts, index, vnext + 1, vnext, tmp);
                        this.add3AndSet(norm, 0, tmp, 0);
                    }
                }

                // Normalize and place in output.
                this.mul3AndSet(norm, 0, nsign);
                this.norm3AndSet(norm, 0);
                System.arraycopy(norm, 0, dest, 3 * index, 3);
            }
        }
    }

    //**************************************************************//
    //********************  2D Shapes  *****************************//
    //**************************************************************//

    /**
     * Creates a vertex buffer for a two-dimensional ellipse centered at the specified location and with the specified
     * radii. The ellipse's center is placed at (x, y), it has a width of 2 * majorRadius, and
     * a height of 2 * minorRadius.
     * 

* If the specified slices is greater than 1 this returns a buffer with vertices evenly spaced along * the circumference of the ellipse. Otherwise this returns a buffer with one vertex. *

* The returned buffer contains pairs of xy coordinates representing the location of each vertex in the ellipse in a * counter-clockwise winding order relative to the z axis. The buffer may be rendered in OpenGL as either a triangle * fan or a line loop. * * @param x the x-coordinate of the ellipse's center. * @param y the y-coordinate of the ellipse's center. * @param majorRadius the ellipse's radius along the x axis. * @param minorRadius the ellipse's radius along the y axis. * @param slices the number of slices in the ellipse. * * @return a buffer containing the ellipse's x and y locations. * * @throws IllegalArgumentException if any of majorRadius, minorRadius, or * slices are less than zero. */ public FloatBuffer makeEllipse(float x, float y, float majorRadius, float minorRadius, int slices) { if (majorRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (minorRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (slices < 0) { String message = Logging.getMessage("generic.NumSlicesIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Return a buffer with only the first point at angle 0 if the number of slices is zero or one. if (slices <= 1) { // The buffer contains one coordinate pair. FloatBuffer buffer = Buffers.newDirectFloatBuffer(2); buffer.put(x + majorRadius); buffer.put(y); buffer.rewind(); return buffer; } float step = (float) Math.PI * 2f / (float) slices; float angle = 0; // The buffer contains one coordinate pair per slice. FloatBuffer buffer = Buffers.newDirectFloatBuffer(2 * slices); // Add each vertex on the circumference of the ellipse, starting at zero and ending one step before 360. for (int i = 0; i < slices; i++, angle += step) { buffer.put(x + (float) Math.cos(angle) * majorRadius); buffer.put(y + (float) Math.sin(angle) * minorRadius); } // Rewind and return. buffer.rewind(); return buffer; } /** * Creates a vertex buffer for a two-dimensional ellipse centered at the specified location and with the specified * radii. The ellipse's center is placed at (x, y), it has a width of 2 * majorRadius, and * a height of 2 * minorRadius. *

* If the specified slices is greater than 1 this returns a buffer with vertices evenly spaced along * the circumference of the ellipse. Otherwise this returns a buffer with one vertex. *

* If the specified leaderWidth is greater than zero and the location (leaderX, leaderY) * is outside of the rectangle that encloses the ellipse, the ellipse has a triangle attached to one side with with * its top pointing at (leaderX, leaderY). Otherwise this returns an ellipse with no leader and is * equivalent to calling {@link #makeEllipse(float, float, float, float, int)}. The leader is attached * at the center of either the top, bottom, left, or right side, depending on the leader's location relative to the * ellipse. The leader width is limited in size by the side it is attached to. For example, if the leader is * attached to the ellipse's bottom, its width is limited by the ellipse's major radius. * * @param x the x-coordinate of the ellipse's center. * @param y the y-coordinate of the ellipse's center. * @param majorRadius the ellipse's radius along the x axis. * @param minorRadius the ellipse's radius along the y axis. * @param slices the number of slices in the ellipse. * @param leaderX the x-coordinate the leader points to. * @param leaderY the y-coordinate the leader points to. * @param leaderWidth the leader triangle's width. * * @return a buffer containing the ellipse's x and y locations. * * @throws IllegalArgumentException if any of majorRadius, minorRadius, * slices, or leaderWidth are less than zero. */ public FloatBuffer makeEllipseWithLeader(float x, float y, float majorRadius, float minorRadius, int slices, float leaderX, float leaderY, float leaderWidth) { if (majorRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (minorRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (slices < 0) { String message = Logging.getMessage("generic.NumSlicesIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (leaderWidth < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Return an ellipse without a leader if the leader width is zero. if (leaderWidth == 0) return this.makeEllipse(x, y, majorRadius, minorRadius, slices); int leaderCode = this.computeLeaderLocationCode(x - majorRadius, y - minorRadius, x + majorRadius, y + minorRadius, leaderX, leaderY); // Return an ellipse without a leader if the leader point is inside the rectangle. if (leaderCode == LEADER_LOCATION_INSIDE) return this.makeEllipse(x, y, majorRadius, minorRadius, slices); // Return a buffer with only the first point at angle 0 if the number of slices is zero or one. if (slices <= 1) { // The buffer contains one coordinate pair. FloatBuffer buffer = Buffers.newDirectFloatBuffer(2); buffer.put(x + majorRadius); buffer.put(y); buffer.rewind(); return buffer; } // Determine the leader's size in radians and the starting angle according to the leader's location relative to // the ellipse. float leaderAngle; float startAngle; if ((leaderCode & LEADER_LOCATION_BOTTOM) != 0) { // Limit the leader's width by the ellipse's major radius. float maxLeaderWidth = 2f * majorRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; leaderAngle = leaderWidth / majorRadius; startAngle = 3f * (float) Math.PI / 2f; } else if ((leaderCode & LEADER_LOCATION_TOP) != 0) { // Limit the leader's width by the ellipse's major radius. float maxLeaderWidth = 2f * majorRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; leaderAngle = leaderWidth / majorRadius; startAngle = (float) Math.PI / 2f; } else if ((leaderCode & LEADER_LOCATION_LEFT) != 0) { // Limit the leader's width by the ellipse's minor radius. float maxLeaderWidth = 2f * minorRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; leaderAngle = leaderWidth / minorRadius; startAngle = (float) Math.PI; } else if ((leaderCode & LEADER_LOCATION_RIGHT) != 0) { // Limit the leader's width by the ellipse's minor radius. float maxLeaderWidth = 2f * minorRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; leaderAngle = leaderWidth / minorRadius; startAngle = 0f; } else { // Return an ellipse without a leader if the leader location code is unrecognized. This should never happen, // but we check anyway. return this.makeEllipse(x, y, majorRadius, minorRadius, slices); } float step = (float) (Math.PI * 2f - leaderAngle) / (float) slices; float angle = startAngle + leaderAngle / 2f; // The buffer contains one coordinate pair per slice, and three coordinate pairs for the leader. FloatBuffer buffer = Buffers.newDirectFloatBuffer(2 * slices + 6); // Start in the leader right corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + (float) Math.cos(startAngle + leaderAngle / 2f) * majorRadius); buffer.put(y + (float) Math.sin(startAngle + leaderAngle / 2f) * minorRadius); // Add each vertex on the circumference of the ellipse, starting at the right side of the leader, and ending at // the left side of the leader. for (int i = 0; i < slices; i++, angle += step) { buffer.put(x + (float) Math.cos(angle) * majorRadius); buffer.put(y + (float) Math.sin(angle) * minorRadius); } // Leader left corner. buffer.put(x + (float) Math.cos(startAngle - leaderAngle / 2f) * majorRadius); buffer.put(y + (float) Math.sin(startAngle - leaderAngle / 2f) * minorRadius); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } /** * Creates a vertex buffer for a two-dimensional rectangle at the specified location, and with the specified size. * The rectangle's lower left corner is placed at (x, y), and its upper right corner is placed at * (x + width, y + height). *

* The returned buffer contains pairs of xy coordinates representing the location of each vertex in the rectangle in * a counter-clockwise winding order relative to the z axis. The buffer may be rendered in OpenGL as either a * triangle fan or a line loop. * * @param x the x-coordinate of the rectangle's lower left corner. * @param y the y-coordinate of the rectangle's lower left corner. * @param width the rectangle's width. * @param height the rectangle's height. * * @return a buffer containing the rectangle's x and y locations. * * @throws IllegalArgumentException if either width or height are less than zero. */ public FloatBuffer makeRectangle(float x, float y, float width, float height) { if (width < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height < 0) { String message = Logging.getMessage("Geom.HeightIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // The buffer contains eight coordinate pairs: two pairs for each corner. FloatBuffer buffer = Buffers.newDirectFloatBuffer(8); // Lower left corner. buffer.put(x); buffer.put(y); // Lower right corner. buffer.put(x + width); buffer.put(y); // Upper right corner. buffer.put(x + width); buffer.put(y + height); // Upper left corner. buffer.put(x); buffer.put(y + height); // Rewind and return. buffer.rewind(); return buffer; } /** * Creates a vertex buffer for a two-dimensional rectangle at the specified location, with the specified size, and * with optionally rounded corners. The rectangle's lower left corner is placed at the (x, y), and its * upper right corner is placed at (x + width, y + height). *

* If the specified cornerRadius and cornerSlices are greater than 0, the rectangle's * corners have a rounded appearance. The radius specifies the size of a rounded corner, and the slices specifies * the number of segments that make a rounded corner. If either cornerRadius or * cornerSlices are 0, this returns a rectangle with sharp corners and is equivalent to calling * {@link #makeRectangle(float, float, float, float)}. The cornerRadius is limited by the * rectangle's width and height. For example, if the corner radius is 100 and the width and height are 50 and 100, * the actual corner radius used is 25 - half of the rectangle's smallest dimension. *

* The returned buffer contains pairs of xy coordinates representing the location of each vertex in the rectangle in * a counter-clockwise winding order relative to the z axis. The buffer may be rendered in OpenGL as either a * triangle fan or a line loop. * * @param x the x-coordinate of the rectangle's lower left corner. * @param y the y-coordinate of the rectangle's lower left corner. * @param width the rectangle's width. * @param height the rectangle's height. * @param cornerRadius the rectangle's rounded corner radius, or 0 to disable rounded corners. * @param cornerSlices the number of slices in each rounded corner, or 0 to disable rounded corners. * * @return a buffer containing the rectangle's x and y locations. * * @throws IllegalArgumentException if any of width, height, cornerRadius, or * cornerSlices are less than zero. */ public FloatBuffer makeRectangle(float x, float y, float width, float height, float cornerRadius, int cornerSlices) { if (width < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height < 0) { String message = Logging.getMessage("Geom.HeightIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (cornerRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (cornerSlices < 0) { String message = Logging.getMessage("generic.NumSlicesIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Limit the corner radius to half of the rectangles width or height, whichever is smaller. float maxCornerRadius = Math.min(width, height) / 2f; if (cornerRadius > maxCornerRadius) cornerRadius = maxCornerRadius; // Create a rectangle with sharp corners if either the corner radius or the number of corner slices is 0. if (cornerRadius == 0f || cornerSlices == 0) return this.makeRectangle(x, y, width, height); float piOver2 = (float) Math.PI / 2f; // The buffer contains four coordinate pairs for each corner, and two coordinate pairs per corner vertex. FloatBuffer buffer = Buffers.newDirectFloatBuffer(16 + 8 * (cornerSlices - 1)); // Lower left corner. buffer.put(x); buffer.put(y + cornerRadius); this.addRectangleRoundedCorner(x + cornerRadius, x + cornerRadius, cornerRadius, (float) Math.PI, piOver2, cornerSlices, buffer); buffer.put(x + cornerRadius); buffer.put(y); // Lower right corner. buffer.put(x + width - cornerRadius); buffer.put(y); this.addRectangleRoundedCorner(x + width - cornerRadius, y + cornerRadius, cornerRadius, -piOver2, piOver2, cornerSlices, buffer); buffer.put(x + width); buffer.put(y + cornerRadius); // Upper right corner. buffer.put(x + width); buffer.put(y + height - cornerRadius); this.addRectangleRoundedCorner(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, 0f, piOver2, cornerSlices, buffer); buffer.put(x + width - cornerRadius); buffer.put(y + height); // Upper left corner. buffer.put(x + cornerRadius); buffer.put(y + height); this.addRectangleRoundedCorner(x + cornerRadius, y + height - cornerRadius, cornerRadius, piOver2, piOver2, cornerSlices, buffer); buffer.put(x); buffer.put(y + height - cornerRadius); // Rewind and return. buffer.rewind(); return buffer; } /** * Creates a vertex buffer for a two-dimensional rectangle at the specified location, with the specified size, and * with an optional leader pointing to the specified leader location. The rectangle's lower left corner is placed at * (x, y), and its upper right corner is placed at (x + width, y + height). *

* If the specified leaderWidth is greater than zero and the location (leaderX, leaderY) * is outside of the rectangle, the rectangle has a triangle attached to one side with with its top pointing at * (leaderX, leaderY). Otherwise this returns a rectangle with no leader and is equivalent to calling * {@link #makeRectangle(float, float, float, float)}. The leader is attached at the center of either * the top, bottom, left, or right side, depending on the leader's location relative to the rectangle. The leader * width is limited in size by the side it is attached to. For example, if the leader is attached to the rectangle's * bottom, its width is limited by the rectangle's width. *

* The returned buffer contains pairs of xy coordinates representing the location of each vertex in the rectangle in * a counter-clockwise winding order relative to the z axis. The buffer may be rendered in OpenGL as either a * triangle fan or a line loop. * * @param x the x-coordinate of the rectangle's lower left corner. * @param y the y-coordinate of the rectangle's lower left corner. * @param width the rectangle's width. * @param height the rectangle's height. * @param leaderX the x-coordinate the leader points to. * @param leaderY the y-coordinate the leader points to. * @param leaderWidth the leader triangle's width. * * @return a buffer containing the rectangle's x and y locations. * * @throws IllegalArgumentException if any of width, height, or leaderWidth * are less than zero. */ @SuppressWarnings({"SuspiciousNameCombination"}) public FloatBuffer makeRectangleWithLeader(float x, float y, float width, float height, float leaderX, float leaderY, float leaderWidth) { if (width < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height < 0) { String message = Logging.getMessage("Geom.HeightIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (leaderWidth < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Return a rectangle without a leader if the leader width is zero. if (leaderWidth == 0) return this.makeRectangle(x, y, width, height); int leaderCode = this.computeLeaderLocationCode(x, y, x + width, y + height, leaderX, leaderY); // Return a rectangle without a leader if the leader point is inside the rectangle. if (leaderCode == LEADER_LOCATION_INSIDE) return this.makeRectangle(x, y, width, height); if ((leaderCode & LEADER_LOCATION_BOTTOM) != 0) { // Limit the leader's width by the rectangle's width. if (leaderWidth > width) leaderWidth = width; // The buffer contains seven xy coordinate pairs: two pairs for each corner and three pairs for the leader. FloatBuffer buffer = Buffers.newDirectFloatBuffer(14); // Start in the leader right corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width / 2f + leaderWidth / 2f); buffer.put(y); // Lower right corner. buffer.put(x + width); buffer.put(y); // Upper right corner. buffer.put(x + width); buffer.put(y + height); // Upper left corner. buffer.put(x); buffer.put(y + height); // Lower left corner. buffer.put(x); buffer.put(y); // Leader left corner. buffer.put(x + width / 2f - leaderWidth / 2f); buffer.put(y); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_TOP) != 0) { // Limit the leader's width by the rectangle's width. if (leaderWidth > width) leaderWidth = width; // The buffer contains seven xy coordinate pairs: two pairs for each corner and three pairs for the leader. FloatBuffer buffer = Buffers.newDirectFloatBuffer(14); // Start in the leader left corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width / 2f - leaderWidth / 2f); buffer.put(y + height); // Upper left corner. buffer.put(x); buffer.put(y + height); // Lower left corner. buffer.put(x); buffer.put(y); // Lower right corner. buffer.put(x + width); buffer.put(y); // Upper right corner. buffer.put(x + width); buffer.put(y + height); // Leader right corner. buffer.put(x + width / 2f + leaderWidth / 2f); buffer.put(y + height); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_LEFT) != 0) { // Limit the leader's width by the rectangle's height. if (leaderWidth > height) { //noinspection SuspiciousNameCombination leaderWidth = height; } // The buffer contains seven xy coordinate pairs: two pairs for each corner and three pairs for the leader. FloatBuffer buffer = Buffers.newDirectFloatBuffer(14); // Start in the leader bottom corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x); buffer.put(y + height / 2f - leaderWidth / 2f); // Lower left corner. buffer.put(x); buffer.put(y); // Lower right corner. buffer.put(x + width); buffer.put(y); // Upper right corner. buffer.put(x + width); buffer.put(y + height); // Upper left corner. buffer.put(x); buffer.put(y + height); // Leader top corner. buffer.put(x); buffer.put(y + height / 2f + leaderWidth / 2f); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_RIGHT) != 0) { // Limit the leader's width by the rectangle's height. if (leaderWidth > height) { //noinspection SuspiciousNameCombination leaderWidth = height; } // The buffer contains seven xy coordinate pairs: two pairs for each corner and three pairs for the leader. FloatBuffer buffer = Buffers.newDirectFloatBuffer(14); // Start in the leader top corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width); buffer.put(y + height / 2f + leaderWidth / 2f); // Upper right corner. buffer.put(x + width); buffer.put(y + height); // Upper left corner. buffer.put(x); buffer.put(y + height); // Lower left corner. buffer.put(x); buffer.put(y); // Lower right corner. buffer.put(x + width); buffer.put(y); // Leader bottom corner. buffer.put(x + width); buffer.put(y + height / 2f - leaderWidth / 2f); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else { // Return a rectangle without a leader if the leader location code is unrecognized. This should never // happen, but we check anyway. return this.makeRectangle(x, y, width, height); } } /** * Creates a vertex buffer for a two-dimensional rectangle at the specified location, with the specified size, and * with optionally rounded corners. The rectangle's lower left corner is placed at the (x, y), and its * upper right corner is placed at (x + width, y + height). *

* If the specified cornerRadius and cornerSlices are greater than 0, the rectangle's * corners have a rounded appearance. The radius specifies the size of a rounded corner, and the slices specifies * the number of segments that make a rounded corner. If either cornerRadius or * cornerSlices are 0, this returns a rectangle with sharp corners and is equivalent to calling * {@link #makeRectangleWithLeader(float, float, float, float, float, float, float)} . The * cornerRadius is limited by the rectangle's width and height. For example, if the corner radius is * 100 and the width and height are 50 and 100, the actual corner radius used is 25 - half of the rectangle's * smallest dimension. *

* If the specified leaderWidth is greater than zero and the location (leaderX, leaderY) * is outside of the rectangle, the rectangle has a triangle attached to one side with with its top pointing at * (leaderX, leaderY). Otherwise this returns a rectangle with no leader and is equivalent to calling * {@link #makeRectangle(float, float, float, float, float, int)}. The leader is attached at the center * of either the top, bottom, left, or right side, depending on the leader's location relative to the rectangle. The * leader width is limited in size by the side it is attached to. For example, if the leader is attached to the * rectangle's bottom, its width is limited by the rectangle's width minus any area used by the rounded corners. *

* The returned buffer contains pairs of xy coordinates representing the location of each vertex in the rectangle in * a counter-clockwise winding order relative to the z axis. The buffer may be rendered in OpenGL as either a * triangle fan or a line loop. * * @param x the x-coordinate of the rectangle's lower left corner. * @param y the y-coordinate of the rectangle's lower left corner. * @param width the rectangle's width. * @param height the rectangle's height. * @param cornerRadius the rectangle's rounded corner radius, or 0 to disable rounded corners. * @param cornerSlices the number of slices in each rounded corner, or 0 to disable rounded corners. * @param leaderX the x-coordinate the leader points to. * @param leaderY the y-coordinate the leader points to. * @param leaderWidth the leader triangle's width. * * @return a buffer containing the rectangle's x and y locations. * * @throws IllegalArgumentException if any of width, height, cornerRadius, * cornerSlices, or leaderWidth are less than zero. */ public FloatBuffer makeRectangleWithLeader(float x, float y, float width, float height, float cornerRadius, int cornerSlices, float leaderX, float leaderY, float leaderWidth) { if (width < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (height < 0) { String message = Logging.getMessage("Geom.HeightIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (cornerRadius < 0) { String message = Logging.getMessage("Geom.RadiusIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (cornerSlices < 0) { String message = Logging.getMessage("generic.NumSlicesIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (leaderWidth < 0) { String message = Logging.getMessage("Geom.WidthIsNegative"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Limit the corner radius to half of the rectangles width or height, whichever is smaller. float maxCornerRadius = Math.min(width, height) / 2f; if (cornerRadius > maxCornerRadius) cornerRadius = maxCornerRadius; // Create a rectangle with sharp corners if either the corner radius or the number of corner slices is 0. if (cornerRadius == 0f || cornerSlices == 0) return this.makeRectangleWithLeader(x, y, width, height, leaderX, leaderY, leaderWidth); // Return a rectangle without a leader if the leader width is zero. if (leaderWidth == 0) return this.makeRectangle(x, y, width, height, cornerRadius, cornerSlices); int leaderCode = this.computeLeaderLocationCode(x, y, x + width, y + height, leaderX, leaderY); // Return a rectangle without a leader if the leader point is inside the rectangle. if (leaderCode == LEADER_LOCATION_INSIDE) return this.makeRectangle(x, y, width, height, cornerRadius, cornerSlices); float piOver2 = (float) Math.PI / 2f; if ((leaderCode & LEADER_LOCATION_BOTTOM) != 0) { // Limit the leader width by the rectangle's width minus any width used by the rounded corners. float maxLeaderWidth = width - 2f * cornerRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; // The buffer contains two coordinate pairs for each corner, three coordinate pairs for the leader, and two // coordinate pairs per corner vertex. FloatBuffer buffer = Buffers.newDirectFloatBuffer(22 + 8 * (cornerSlices - 1)); // Start in the leader right corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width / 2f + leaderWidth / 2f); buffer.put(y); // Lower right corner. buffer.put(x + width - cornerRadius); buffer.put(y); this.addRectangleRoundedCorner(x + width - cornerRadius, y + cornerRadius, cornerRadius, -piOver2, piOver2, cornerSlices, buffer); buffer.put(x + width); buffer.put(y + cornerRadius); // Upper right corner. buffer.put(x + width); buffer.put(y + height - cornerRadius); this.addRectangleRoundedCorner(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, 0f, piOver2, cornerSlices, buffer); buffer.put(x + width - cornerRadius); buffer.put(y + height); // Upper left corner. buffer.put(x + cornerRadius); buffer.put(y + height); this.addRectangleRoundedCorner(x + cornerRadius, y + height - cornerRadius, cornerRadius, piOver2, piOver2, cornerSlices, buffer); buffer.put(x); buffer.put(y + height - cornerRadius); // Lower left corner. buffer.put(x); buffer.put(y + cornerRadius); this.addRectangleRoundedCorner(x + cornerRadius, x + cornerRadius, cornerRadius, (float) Math.PI, piOver2, cornerSlices, buffer); buffer.put(x + cornerRadius); buffer.put(y); // Leader left corner. buffer.put(x + width / 2f - leaderWidth / 2f); buffer.put(y); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_TOP) != 0) { // Limit the leader width by the rectangle's width minus any width used by the rounded corners. float maxLeaderWidth = width - 2f * cornerRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; // The buffer contains two coordinate pairs for each corner, three coordinate pairs for the leader, and two // coordinate pairs per corner vertex. FloatBuffer buffer = Buffers.newDirectFloatBuffer(22 + 8 * (cornerSlices - 1)); // Start in the leader left corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width / 2f - leaderWidth / 2f); buffer.put(y + height); // Upper left corner. buffer.put(x + cornerRadius); buffer.put(y + height); this.addRectangleRoundedCorner(x + cornerRadius, y + height - cornerRadius, cornerRadius, piOver2, piOver2, cornerSlices, buffer); buffer.put(x); buffer.put(y + height - cornerRadius); // Lower left corner. buffer.put(x); buffer.put(y + cornerRadius); this.addRectangleRoundedCorner(x + cornerRadius, x + cornerRadius, cornerRadius, (float) Math.PI, piOver2, cornerSlices, buffer); buffer.put(x + cornerRadius); buffer.put(y); // Lower right corner. buffer.put(x + width - cornerRadius); buffer.put(y); this.addRectangleRoundedCorner(x + width - cornerRadius, y + cornerRadius, cornerRadius, -piOver2, piOver2, cornerSlices, buffer); buffer.put(x + width); buffer.put(y + cornerRadius); // Upper right corner. buffer.put(x + width); buffer.put(y + height - cornerRadius); this.addRectangleRoundedCorner(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, 0f, piOver2, cornerSlices, buffer); buffer.put(x + width - cornerRadius); buffer.put(y + height); // Leader right corner. buffer.put(x + width / 2f + leaderWidth / 2f); buffer.put(y + height); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_LEFT) != 0) { // Limit the leader width by the rectangle's height minus any width used by the rounded corners. float maxLeaderWidth = height - 2f * cornerRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; // The buffer contains two coordinate pairs for each corner, three coordinate pairs for the leader, and two // coordinate pairs per corner vertex. FloatBuffer buffer = Buffers.newDirectFloatBuffer(22 + 8 * (cornerSlices - 1)); // Start in the leader bottom corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x); buffer.put(y + height / 2f - leaderWidth / 2f); // Lower left corner. buffer.put(x); buffer.put(y + cornerRadius); this.addRectangleRoundedCorner(x + cornerRadius, x + cornerRadius, cornerRadius, (float) Math.PI, piOver2, cornerSlices, buffer); buffer.put(x + cornerRadius); buffer.put(y); // Lower right corner. buffer.put(x + width - cornerRadius); buffer.put(y); this.addRectangleRoundedCorner(x + width - cornerRadius, y + cornerRadius, cornerRadius, -piOver2, piOver2, cornerSlices, buffer); buffer.put(x + width); buffer.put(y + cornerRadius); // Upper right corner. buffer.put(x + width); buffer.put(y + height - cornerRadius); this.addRectangleRoundedCorner(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, 0f, piOver2, cornerSlices, buffer); buffer.put(x + width - cornerRadius); buffer.put(y + height); // Upper left corner. buffer.put(x + cornerRadius); buffer.put(y + height); this.addRectangleRoundedCorner(x + cornerRadius, y + height - cornerRadius, cornerRadius, piOver2, piOver2, cornerSlices, buffer); buffer.put(x); buffer.put(y + height - cornerRadius); // Leader top corner. buffer.put(x); buffer.put(y + height / 2f + leaderWidth / 2f); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else if ((leaderCode & LEADER_LOCATION_RIGHT) != 0) { // Limit the leader width by the rectangle's height minus any width used by the rounded corners. float maxLeaderWidth = height - 2f * cornerRadius; if (leaderWidth > maxLeaderWidth) leaderWidth = maxLeaderWidth; // The buffer contains two coordinate pairs for each corner, three coordinate pairs for the leader, and two // coordinate pairs per corner vertex. FloatBuffer buffer = Buffers.newDirectFloatBuffer(22 + 8 * (cornerSlices - 1)); // Start in the leader top corner to ensure the vertices can be drawn as a triangle fan. buffer.put(x + width); buffer.put(y + height / 2f + leaderWidth / 2f); // Upper right corner. buffer.put(x + width); buffer.put(y + height - cornerRadius); this.addRectangleRoundedCorner(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, 0f, piOver2, cornerSlices, buffer); buffer.put(x + width - cornerRadius); buffer.put(y + height); // Upper left corner. buffer.put(x + cornerRadius); buffer.put(y + height); this.addRectangleRoundedCorner(x + cornerRadius, y + height - cornerRadius, cornerRadius, piOver2, piOver2, cornerSlices, buffer); buffer.put(x); buffer.put(y + height - cornerRadius); // Lower left corner. buffer.put(x); buffer.put(y + cornerRadius); this.addRectangleRoundedCorner(x + cornerRadius, x + cornerRadius, cornerRadius, (float) Math.PI, piOver2, cornerSlices, buffer); buffer.put(x + cornerRadius); buffer.put(y); // Lower right corner. buffer.put(x + width - cornerRadius); buffer.put(y); this.addRectangleRoundedCorner(x + width - cornerRadius, y + cornerRadius, cornerRadius, -piOver2, piOver2, cornerSlices, buffer); buffer.put(x + width); buffer.put(y + cornerRadius); // Leader bottom corner. buffer.put(x + width); buffer.put(y + height / 2f - leaderWidth / 2f); // Leader point. buffer.put(leaderX); buffer.put(leaderY); // Rewind and return. buffer.rewind(); return buffer; } else { // Return a rectangle without a leader if the leader location code is unrecognized. This should never // happen, but we check anyway. return this.makeRectangle(x, y, width, height, cornerRadius, cornerSlices); } } /** * Adds the vertices for one rounded corner of a two-dimensional rectangular to the specified buffer. * This assumes that the first and last vertices of each corner are created by the caller, so this adds only the * intermediate vertices. The number of intermediate vertices is equal to slices - 2. This does nothing * if slices is one or zero. * * @param x the x-coordinate of the corner's origin. * @param y the y-coordinate of the corner's origin. * @param radius the corner's radius. * @param start the corner's starting angle, in radians. * @param sweep the corner's angular distance, in radians. * @param slices the number of slices in the corner. * @param buffer the buffer the corner's xy coordinates are added to. */ protected void addRectangleRoundedCorner(float x, float y, float radius, float start, float sweep, int slices, FloatBuffer buffer) { if (slices == 0f) return; float step = sweep / (float) slices; float angle = start + step; for (int i = 1; i < slices; i++, angle += step) { buffer.put(x + (float) Math.cos(angle) * radius); buffer.put(y + (float) Math.sin(angle) * radius); } } /** * Returns a four bit code indicating the leader's location within the specified rectangle. The rectangle's lower * left corner is located at (x1, y1) and its upper right corner is located at (x2, y2). * The returned code includes the bit for any of LEADER_LOCATION_LEFT, * LEADER_LOCATION_RIGHT, LEADER_LOCATION_BOTTOM, and LEADER_LOCATION_TOP, * depending on whether the leader is located to the left, right, bottom, or top of the rectangle. If the leader is * inside the rectangle, this returns LEADER_LOCATION_INSIDE. * * @param x1 the rectangle's minimum x-coordinate. * @param y1 the rectangle's maximum x-coordinate. * @param x2 the rectangle's minimum y-coordinate. * @param y2 the rectangle's maximum y-coordinate. * @param leaderX the leader's x-coordinate. * @param leaderY the leader's y-coordinate. * * @return a four bit code indicating the leader's location relative to the rectangle. */ protected int computeLeaderLocationCode(float x1, float y1, float x2, float y2, float leaderX, float leaderY) { return (leaderY > y2 ? LEADER_LOCATION_TOP : 0) // bit 0: top | (leaderY < y1 ? LEADER_LOCATION_BOTTOM : 0) // bit 1: bottom | (leaderX > x2 ? LEADER_LOCATION_RIGHT : 0) // bit 2: right | (leaderX < x1 ? LEADER_LOCATION_LEFT : 0); // bit 3: left } //**************************************************************// //******************** Geometry Support ********************// //**************************************************************// public void reversePoints(int pos, int count, T[] points) { if (pos < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", "pos=" + pos); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (count < 0) { String message = Logging.getMessage("generic.ArgumentOutOfRange", "count=" + count); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (points == null) { String message = "nullValue.PointsIsNull"; Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (points.length < (pos + count)) { String message = Logging.getMessage("generic.ArrayInvalidLength", "points.length < " + (pos + count)); Logging.logger().severe(message); throw new IllegalArgumentException(message); } T tmp; int i, j, mid; for (i = 0, mid = count >> 1, j = count - 1; i < mid; i++, j--) { tmp = points[pos + i]; points[pos + i] = points[pos + j]; points[pos + j] = tmp; } } private int[] copyOf(int[] original, int newLength) { int[] copy; copy = new int[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } private float[] copyOf(float[] original, int newLength) { float[] copy; copy = new float[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } private IntBuffer copyOf(IntBuffer original, int newLength) { IntBuffer copy; copy = Buffers.newDirectIntBuffer(newLength); original.rewind(); copy.put(original); return copy; } private FloatBuffer copyOf(FloatBuffer original, int newLength) { FloatBuffer copy; copy = Buffers.newDirectFloatBuffer(newLength); original.rewind(); copy.put(original); return copy; } private void facenorm(float[] srcVerts, int vertA, int vertB, int vertC, float[] dest) { int ia, ib, ic; float[] ab, ac; ia = 3 * vertA; ib = 3 * vertB; ic = 3 * vertC; ab = new float[3]; ac = new float[3]; this.sub3(srcVerts, ib, srcVerts, ia, ab, 0); this.sub3(srcVerts, ic, srcVerts, ia, ac, 0); this.cross3(ab, ac, dest); this.norm3AndSet(dest, 0); } private void facenorm(FloatBuffer srcVerts, int vertA, int vertB, int vertC, float[] dest) { int ia, ib, ic; float[] ab, ac; ia = 3 * vertA; ib = 3 * vertB; ic = 3 * vertC; ab = new float[3]; ac = new float[3]; this.sub3(srcVerts, ib, srcVerts, ia, ab, 0); this.sub3(srcVerts, ic, srcVerts, ia, ac, 0); this.cross3(ab, ac, dest); this.norm3AndSet(dest, 0); } private void add3AndSet(float[] a, int aPos, float[] b, int bPos) { a[aPos] = a[aPos] + b[bPos]; a[aPos + 1] = a[aPos + 1] + b[bPos + 1]; a[aPos + 2] = a[aPos + 2] + b[bPos + 2]; } private void add3AndSet(FloatBuffer a, int aPos, float[] b, int bPos) { a.put(aPos, a.get(aPos) + b[bPos]); a.put(aPos + 1, a.get(aPos + 1) + b[bPos + 1]); a.put(aPos + 2, a.get(aPos + 2) + b[bPos + 2]); } private void sub3(float[] a, int aPos, float[] b, int bPos, float[] dest, int destPos) { dest[destPos] = a[aPos] - b[bPos]; dest[destPos + 1] = a[aPos + 1] - b[bPos + 1]; dest[destPos + 2] = a[aPos + 2] - b[bPos + 2]; } private void sub3(FloatBuffer a, int aPos, FloatBuffer b, int bPos, float[] dest, int destPos) { dest[destPos] = a.get(aPos) - b.get(bPos); dest[destPos + 1] = a.get(aPos + 1) - b.get(bPos + 1); dest[destPos + 2] = a.get(aPos + 2) - b.get(bPos + 2); } private void cross3(float[] a, float[] b, float[] dest) { dest[0] = a[1] * b[2] - a[2] * b[1]; dest[1] = a[2] * b[0] - a[0] * b[2]; dest[2] = a[0] * b[1] - a[1] * b[0]; } private void mul3AndSet(float[] src, int srcPos, float c) { src[srcPos] *= c; src[srcPos + 1] *= c; src[srcPos + 2] *= c; } private void mul3AndSet(FloatBuffer src, int srcPos, float c) { src.put(srcPos, src.get(srcPos) * c); src.put(srcPos + 1, src.get(srcPos + 1) * c); src.put(srcPos + 2, src.get(srcPos + 2) * c); } private void mulAndSet(FloatBuffer src, int srcPos, float b, int offset) { src.put(srcPos + offset, src.get(srcPos + offset) * b); } private void norm3AndSet(float[] src, int srcPos) { float len; len = src[srcPos] * src[srcPos] + src[srcPos + 1] * src[srcPos + 1] + src[srcPos + 2] * src[srcPos + 2]; if (len != 0.0f) { len = (float) Math.sqrt(len); src[srcPos] /= len; src[srcPos + 1] /= len; src[srcPos + 2] /= len; } } private void norm3AndSet(FloatBuffer src, int srcPos) { float len; len = src.get(srcPos) * src.get(srcPos) + src.get(srcPos + 1) * src.get(srcPos + 1) + src.get(srcPos + 2) * src.get(srcPos + 2); if (len != 0.0f) { len = (float) Math.sqrt(len); src.put(srcPos, src.get(srcPos) / len); src.put(srcPos + 1, src.get(srcPos + 1) / len); src.put(srcPos + 2, src.get(srcPos + 2) / len); } } private int nextPowerOfTwo(int n) { int i = 1; while (i < n) { i <<= 1; } return i; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy