/**
   * Rebuilds the cylinder based on a new set of parameters.
   *
   * @param axisSamples the number of samples along the axis.
   * @param radialSamples the number of samples around the radial.
   * @param radius the radius of the bottom of the cylinder.
   * @param radius2 the radius of the top of the cylinder.
   * @param height the cylinder's height.
   * @param closed should the cylinder have top and bottom surfaces.
   * @param inverted is the cylinder is meant to be viewed from the inside.
   */
  public void updateGeometry(
      int axisSamples,
      int radialSamples,
      float radius,
      float radius2,
      float height,
      boolean closed,
      boolean inverted) {
    this.axisSamples = axisSamples + (closed ? 2 : 0);
    this.radialSamples = radialSamples;
    this.radius = radius;
    this.radius2 = radius2;
    this.height = height;
    this.closed = closed;
    this.inverted = inverted;

    //        VertexBuffer pvb = getBuffer(Type.Position);
    //        VertexBuffer nvb = getBuffer(Type.Normal);
    //        VertexBuffer tvb = getBuffer(Type.TexCoord);

    // Vertices
    int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);

    setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));

    // Normals
    setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));

    // Texture co-ordinates
    setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));

    int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;

    setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));

    // generate geometry
    float inverseRadial = 1.0f / radialSamples;
    float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
    float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
    float halfHeight = 0.5f * height;

    // Generate points on the unit circle to be used in computing the mesh
    // points on a cylinder slice.
    float[] sin = new float[radialSamples + 1];
    float[] cos = new float[radialSamples + 1];

    for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
      float angle = FastMath.TWO_PI * inverseRadial * radialCount;
      cos[radialCount] = FastMath.cos(angle);
      sin[radialCount] = FastMath.sin(angle);
    }
    sin[radialSamples] = sin[0];
    cos[radialSamples] = cos[0];

    // calculate normals
    Vector3f[] vNormals = null;
    Vector3f vNormal = Vector3f.UNIT_Z;

    if ((height != 0.0f) && (radius != radius2)) {
      vNormals = new Vector3f[radialSamples];
      Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
      Vector3f vRadial = new Vector3f();

      for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
        vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
        Vector3f vRadius = vRadial.mult(radius);
        Vector3f vRadius2 = vRadial.mult(radius2);
        Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
        Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
        vNormals[radialCount] = vMantle.cross(vTangent).normalize();
      }
    }

    FloatBuffer nb = getFloatBuffer(Type.Normal);
    FloatBuffer pb = getFloatBuffer(Type.Position);
    FloatBuffer tb = getFloatBuffer(Type.TexCoord);

    // generate the cylinder itself
    Vector3f tempNormal = new Vector3f();
    for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
      float axisFraction;
      float axisFractionTexture;
      int topBottom = 0;
      if (!closed) {
        axisFraction = axisCount * inverseAxisLess; // in [0,1]
        axisFractionTexture = axisFraction;
      } else {
        if (axisCount == 0) {
          topBottom = -1; // bottom
          axisFraction = 0;
          axisFractionTexture = inverseAxisLessTexture;
        } else if (axisCount == axisSamples - 1) {
          topBottom = 1; // top
          axisFraction = 1;
          axisFractionTexture = 1 - inverseAxisLessTexture;
        } else {
          axisFraction = (axisCount - 1) * inverseAxisLess;
          axisFractionTexture = axisCount * inverseAxisLessTexture;
        }
      }

      // compute center of slice
      float z = -halfHeight + height * axisFraction;
      Vector3f sliceCenter = new Vector3f(0, 0, z);

      // compute slice vertices with duplication at end point
      int save = i;
      for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
        float radialFraction = radialCount * inverseRadial; // in [0,1)
        tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);

        if (vNormals != null) {
          vNormal = vNormals[radialCount];
        } else if (radius == radius2) {
          vNormal = tempNormal;
        }

        if (topBottom == 0) {
          if (!inverted) nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
          else nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
        } else {
          nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
        }

        tempNormal.multLocal((radius - radius2) * axisFraction + radius2).addLocal(sliceCenter);
        pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);

        tb.put((inverted ? 1 - radialFraction : radialFraction)).put(axisFractionTexture);
      }

      BufferUtils.copyInternalVector3(pb, save, i);
      BufferUtils.copyInternalVector3(nb, save, i);

      tb.put((inverted ? 0.0f : 1.0f)).put(axisFractionTexture);
    }

    if (closed) {
      pb.put(0).put(0).put(-halfHeight); // bottom center
      nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
      tb.put(0.5f).put(0);
      pb.put(0).put(0).put(halfHeight); // top center
      nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
      tb.put(0.5f).put(1);
    }

    IndexBuffer ib = getIndexBuffer();
    int index = 0;
    // Connectivity
    for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
      int i0 = axisStart;
      int i1 = i0 + 1;
      axisStart += radialSamples + 1;
      int i2 = axisStart;
      int i3 = i2 + 1;
      for (int i = 0; i < radialSamples; i++) {
        if (closed && axisCount == 0) {
          if (!inverted) {
            ib.put(index++, i0++);
            ib.put(index++, vertCount - 2);
            ib.put(index++, i1++);
          } else {
            ib.put(index++, i0++);
            ib.put(index++, i1++);
            ib.put(index++, vertCount - 2);
          }
        } else if (closed && axisCount == axisSamples - 2) {
          ib.put(index++, i2++);
          ib.put(index++, inverted ? vertCount - 1 : i3++);
          ib.put(index++, inverted ? i3++ : vertCount - 1);
        } else {
          ib.put(index++, i0++);
          ib.put(index++, inverted ? i2 : i1);
          ib.put(index++, inverted ? i1 : i2);
          ib.put(index++, i1++);
          ib.put(index++, inverted ? i2++ : i3++);
          ib.put(index++, inverted ? i3++ : i2++);
        }
      }
    }

    updateBound();
  }
  /**
   * Updates the geometry with the given parameters. Bounds are expressed as if the arrow was
   * pointing to the right, i.e.: x = length of the arrow (distance to the pointy bit.) y = height
   * of the arrow z = extrusion length.
   */
  public void updateGeometry(Orientation orientation, Vector3f bounds, boolean isExtruded) {
    // Create three points, according to the size and orientation.
    Vector3f p0 = new Vector3f(), p1, p2;

    switch (orientation) {
      case LEFT:
        p1 = new Vector3f(0, bounds.y, 0);
        p2 = new Vector3f(-bounds.x, bounds.y / 2, 0);
        break;
      case RIGHT:
        p1 = new Vector3f(bounds.x, bounds.y / 2, 0);
        p2 = new Vector3f(0, bounds.y, 0);
        break;
      case UP:
        p1 = new Vector3f(bounds.y, 0, 0);
        p2 = new Vector3f(bounds.y / 2, bounds.x, 0);
        break;
      case DOWN:
        p1 = new Vector3f(bounds.y / 2, -bounds.x, 0);
        p2 = new Vector3f(bounds.y, 0, 0);
        break;
      default:
        p2 = p1 = p0;
    }

    // Then, make a list of vertices and indices.
    Vector3f[] vertices, normals;
    int[] indices;
    if (!isExtruded) {
      // For a flat triangle, easy one, just take the initial point and link them.
      vertices = new Vector3f[3];
      vertices[0] = p0;
      vertices[1] = p1;
      vertices[2] = p2;

      // The normals are all along Z.
      normals = new Vector3f[3];
      normals[0] = Vector3f.UNIT_Z;
      normals[1] = Vector3f.UNIT_Z;
      normals[2] = Vector3f.UNIT_Z;

      indices = new int[] {0, 1, 2};

    } else {
      // For an extruded triangle
      vertices = new Vector3f[18];
      // First (front) triangle is regular.
      vertices[0] = p0;
      vertices[1] = p1;
      vertices[2] = p2;
      // Back triangle is easy too: retake first triangle and shift the z-coordinate.
      vertices[3] = new Vector3f(p0.x, p0.y, bounds.z);
      vertices[4] = new Vector3f(p1.x, p1.y, bounds.z);
      vertices[5] = new Vector3f(p2.x, p2.y, bounds.z);

      // Then duplicate side vertices to allow hard edges normals, resulting in 4 quads.
      vertices[6] = vertices[0];
      vertices[7] = vertices[3];
      vertices[8] = vertices[1];
      vertices[9] = vertices[4];

      vertices[10] = vertices[1];
      vertices[11] = vertices[4];
      vertices[12] = vertices[2];
      vertices[13] = vertices[5];

      vertices[14] = vertices[2];
      vertices[15] = vertices[5];
      vertices[16] = vertices[0];
      vertices[17] = vertices[3];

      // Normals:
      normals = new Vector3f[18];

      // The front side normal is still the same as for a flat.
      normals[0] = Vector3f.UNIT_Z;
      normals[1] = Vector3f.UNIT_Z;
      normals[2] = Vector3f.UNIT_Z;

      // Backside is the same, but reversed (d'uh).
      normals[3] = Vector3f.UNIT_Z.negate();
      normals[4] = normals[3];
      normals[5] = normals[3];

      // Finally, sides normals are computed from the triangle sides.
      Vector3f normal1 = new Vector3f(p1.y - p0.y, p0.x - p1.x, 0);
      Vector3f normal2 = new Vector3f(p2.y - p1.y, p1.x - p2.x, 0);
      Vector3f normal3 = new Vector3f(p0.y - p2.y, p2.x - p0.x, 0);

      normals[6] = normal1;
      normals[7] = normal1;
      normals[8] = normal1;
      normals[9] = normal1;

      normals[10] = normal2;
      normals[11] = normal2;
      normals[12] = normal2;
      normals[13] = normal2;

      normals[14] = normal3;
      normals[15] = normal3;
      normals[16] = normal3;
      normals[17] = normal3;

      // Finally, the indices.
      indices =
          new int[] {
            // Front
            0,
            1,
            2,
            // Back
            5,
            4,
            3,
            // Sides
            6,
            7,
            8,
            9,
            8,
            7,
            10,
            11,
            12,
            13,
            12,
            11,
            14,
            15,
            16,
            17,
            16,
            15
          };
    }

    // Set the mesh data
    setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
    setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
    setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indices));

    updateBound();
    // setStatic();
  }