Esempio n. 1
0
  public Light() {
    final VertexAttributes attributes =
        new VertexAttributes(
            new VertexAttribute(VertexAttributes.Usage.Position, 3, null),
            new VertexAttribute(VertexAttributes.Usage.ColorPacked, 4, null),
            new VertexAttribute(VertexAttributes.Usage.Normal, 3, null));

    final VertexBuffer buffer = MeshGenerator.generateCube(0.1f);
    buffer.setAttributes(attributes);
    buffer.addColors(Color.WHITE.toFloatBits());
    buffer.calculateNormals();
    mesh = new Mesh(true, buffer.getBufferSize(), 0, attributes);
    mesh.setVertices(buffer.toFloatArray());
    speed = 1;
  }
Esempio n. 2
0
 @Override
 public void reset() {
   lastPosition.x = 0;
   lastPosition.y = 0;
   position.x = 0;
   position.y = 0;
   dimensions.x = 0;
   dimensions.y = 0;
   type = GameObjectType.NONE;
   velocity.x = 0;
   velocity.y = 0;
   accelleration.x = 0;
   accelleration.y = 0;
   lastCollision = null;
   id = "";
   scale.set(1f, 1f);
   color = Color.WHITE.cpy();
   uuid = UUID.randomUUID().toString();
 }
Esempio n. 3
0
/**
 * A SpriteBatch is used to draw 2D rectangles that reference a texture (region). The class will
 * batch the drawing commands and optimize them for processing by the GPU.
 *
 * <p>To draw something with a SpriteBatch one has to first call the {@link SpriteBatch#begin()}
 * method which will setup appropriate render states. When you are done with drawing you have to
 * call {@link SpriteBatch#end()} which will actually draw the things you specified.
 *
 * <p>All drawing commands of the SpriteBatch operate in screen coordinates. The screen coordinate
 * system has an x-axis pointing to the right, an y-axis pointing upwards and the origin is in the
 * lower left corner of the screen. You can also provide your own transformation and projection
 * matrices if you so wish.
 *
 * <p>A SpriteBatch is managed. In case the OpenGL context is lost all OpenGL resources a
 * SpriteBatch uses internally get invalidated. A context is lost when a user switches to another
 * application or receives an incoming call on Android. A SpriteBatch will be automatically reloaded
 * after the OpenGL context is restored.
 *
 * <p>A SpriteBatch is a pretty heavy object so you should only ever have one in your program.
 *
 * <p>A SpriteBatch works with OpenGL ES 1.x and 2.0. In the case of a 2.0 context it will use its
 * own custom shader to draw all provided sprites. You can set your own custom shader via {@link
 * #setShader(ShaderProgram)}.
 *
 * <p>A SpriteBatch has to be disposed if it is no longer used.
 *
 * @author mzechner
 */
public class SpriteBatch implements Disposable {
  private Mesh mesh;
  private Mesh[] buffers;

  private Texture lastTexture = null;
  private float invTexWidth = 0;
  private float invTexHeight = 0;

  private int idx = 0;
  private int currBufferIdx = 0;
  private final float[] vertices;

  private final Matrix4 transformMatrix = new Matrix4();
  private final Matrix4 projectionMatrix = new Matrix4();
  private final Matrix4 combinedMatrix = new Matrix4();

  private boolean drawing = false;

  private boolean blendingDisabled = false;
  private int blendSrcFunc = GL11.GL_ONE;
  private int blendDstFunc = GL11.GL_ONE_MINUS_SRC_ALPHA;

  private final ShaderProgram shader;
  private boolean ownsShader;

  float color = Color.WHITE.toFloatBits();
  private Color tempColor = new Color(1, 1, 1, 1);

  /** number of render calls since last {@link #begin()} * */
  public int renderCalls = 0;

  /** number of rendering calls ever, will not be reset, unless it's done manually * */
  public int totalRenderCalls = 0;

  /** the maximum number of sprites rendered in one batch so far * */
  public int maxSpritesInBatch = 0;

  private ShaderProgram customShader = null;

  /**
   * Constructs a new SpriteBatch. Sets the projection matrix to an orthographic projection with
   * y-axis point upwards, x-axis point to the right and the origin being in the bottom left corner
   * of the screen. The projection will be pixel perfect with respect to the screen resolution.
   */
  public SpriteBatch() {
    this(1000);
  }

  /**
   * Constructs a SpriteBatch with the specified size and (if GL2) the default shader. See {@link
   * #SpriteBatch(int, ShaderProgram)}.
   */
  public SpriteBatch(int size) {
    this(size, null);
  }

  /**
   * Constructs a new SpriteBatch. Sets the projection matrix to an orthographic projection with
   * y-axis point upwards, x-axis point to the right and the origin being in the bottom left corner
   * of the screen. The projection will be pixel perfect with respect to the screen resolution.
   *
   * <p>The size parameter specifies the maximum size of a single batch in number of sprites
   *
   * @param size the batch size in number of sprites
   * @param defaultShader the default shader to use. This is not owned by the SpriteBatch and must
   *     be disposed separately.
   */
  public SpriteBatch(int size, ShaderProgram defaultShader) {
    this(size, 1, defaultShader);
  }

  /**
   * Constructs a SpriteBatch with the specified size and number of buffers and (if GL2) the default
   * shader. See {@link #SpriteBatch(int, int, ShaderProgram)}.
   */
  public SpriteBatch(int size, int buffers) {
    this(size, buffers, null);
  }

  /**
   * Constructs a new SpriteBatch. Sets the projection matrix to an orthographic projection with
   * y-axis point upwards, x-axis point to the right and the origin being in the bottom left corner
   * of the screen. The projection will be pixel perfect with respect to the screen resolution.
   *
   * <p>The size parameter specifies the maximum size of a single batch in number of sprites
   *
   * @param size the batch size in number of sprites
   * @param buffers the number of buffers to use. only makes sense with VBOs. This is an expert
   *     function.
   * @param defaultShader the default shader to use. This is not owned by the SpriteBatch and must
   *     be disposed separately.
   */
  public SpriteBatch(int size, int buffers, ShaderProgram defaultShader) {
    this.buffers = new Mesh[buffers];

    for (int i = 0; i < buffers; i++) {
      this.buffers[i] =
          new Mesh(
              VertexDataType.VertexArray,
              false,
              size * 4,
              size * 6,
              new VertexAttribute(Usage.Position, 2, ShaderProgram.POSITION_ATTRIBUTE),
              new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
              new VertexAttribute(
                  Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));
    }

    projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

    vertices = new float[size * Sprite.SPRITE_SIZE];

    int len = size * 6;
    short[] indices = new short[len];
    short j = 0;
    for (int i = 0; i < len; i += 6, j += 4) {
      indices[i + 0] = (short) (j + 0);
      indices[i + 1] = (short) (j + 1);
      indices[i + 2] = (short) (j + 2);
      indices[i + 3] = (short) (j + 2);
      indices[i + 4] = (short) (j + 3);
      indices[i + 5] = (short) (j + 0);
    }
    for (int i = 0; i < buffers; i++) {
      this.buffers[i].setIndices(indices);
    }
    mesh = this.buffers[0];

    if (Gdx.graphics.isGL20Available() && defaultShader == null) {
      shader = createDefaultShader();
      ownsShader = true;
    } else shader = defaultShader;
  }

  /**
   * Returns a new instance of the default shader used by SpriteBatch for GL2 when no shader is
   * specified.
   */
  public static ShaderProgram createDefaultShader() {
    String vertexShader =
        "attribute vec4 "
            + ShaderProgram.POSITION_ATTRIBUTE
            + ";\n" //
            + "attribute vec4 "
            + ShaderProgram.COLOR_ATTRIBUTE
            + ";\n" //
            + "attribute vec2 "
            + ShaderProgram.TEXCOORD_ATTRIBUTE
            + "0;\n" //
            + "uniform mat4 u_projectionViewMatrix;\n" //
            + "varying vec4 v_color;\n" //
            + "varying vec2 v_texCoords;\n" //
            + "\n" //
            + "void main()\n" //
            + "{\n" //
            + "   v_color = "
            + ShaderProgram.COLOR_ATTRIBUTE
            + ";\n" //
            + "   v_texCoords = "
            + ShaderProgram.TEXCOORD_ATTRIBUTE
            + "0;\n" //
            + "   gl_Position =  u_projectionViewMatrix * "
            + ShaderProgram.POSITION_ATTRIBUTE
            + ";\n" //
            + "}\n";
    String fragmentShader =
        "#ifdef GL_ES\n" //
            + "#define LOWP lowp\n" //
            + "precision mediump float;\n" //
            + "#else\n" //
            + "#define LOWP \n" //
            + "#endif\n" //
            + "varying LOWP vec4 v_color;\n" //
            + "varying vec2 v_texCoords;\n" //
            + "uniform sampler2D u_texture;\n" //
            + "void main()\n" //
            + "{\n" //
            + "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //
            + "}";

    ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
    if (shader.isCompiled() == false)
      throw new IllegalArgumentException("couldn't compile shader: " + shader.getLog());
    return shader;
  }

  /**
   * Sets up the SpriteBatch for drawing. This will disable depth buffer writting. It enables
   * blending and texturing. If you have more texture units enabled than the first one you have to
   * disable them before calling this. Uses a screen coordinate system by default where everything
   * is given in pixels. You can specify your own projection and modelview matrices via {@link
   * #setProjectionMatrix(Matrix4)} and {@link #setTransformMatrix(Matrix4)}.
   */
  public void begin() {
    if (drawing) throw new IllegalStateException("you have to call SpriteBatch.end() first");
    renderCalls = 0;

    Gdx.gl.glDepthMask(false);
    if (Gdx.graphics.isGL20Available()) {
      if (customShader != null) customShader.begin();
      else shader.begin();
    } else {
      Gdx.gl.glEnable(GL10.GL_TEXTURE_2D);
    }
    setupMatrices();

    idx = 0;
    lastTexture = null;
    drawing = true;
  }

  /**
   * Finishes off rendering. Enables depth writes, disables blending and texturing. Must always be
   * called after a call to {@link #begin()}
   */
  public void end() {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before end.");
    if (idx > 0) renderMesh();
    lastTexture = null;
    idx = 0;
    drawing = false;

    GLCommon gl = Gdx.gl;
    gl.glDepthMask(true);
    if (isBlendingEnabled()) gl.glDisable(GL10.GL_BLEND);

    if (Gdx.graphics.isGL20Available()) {
      if (customShader != null) customShader.end();
      else shader.end();
    } else {
      gl.glDisable(GL10.GL_TEXTURE_2D);
    }
  }

  /**
   * Sets the color used to tint images when they are added to the SpriteBatch. Default is {@link
   * Color#WHITE}.
   */
  public void setColor(Color tint) {
    color = tint.toFloatBits();
  }

  /** @see #setColor(Color) */
  public void setColor(float r, float g, float b, float a) {
    /*预乘alpha,xp_20130130*/
    r *= a;
    g *= a;
    b *= a;
    /*预乘alpha,xp_20130130*/
    int intBits =
        (int) (255 * a) << 24 | (int) (255 * b) << 16 | (int) (255 * g) << 8 | (int) (255 * r);
    color = NumberUtils.intToFloatColor(intBits);
  }

  /**
   * @see #setColor(Color)
   * @see Color#toFloatBits()
   */
  public void setColor(float color) {
    this.color = color;
  }

  /**
   * @return the rendering color of this SpriteBatch. Manipulating the returned instance has no
   *     effect.
   */
  public Color getColor() {
    int intBits = NumberUtils.floatToIntColor(color);
    Color color = this.tempColor;
    color.r = (intBits & 0xff) / 255f;
    color.g = ((intBits >>> 8) & 0xff) / 255f;
    color.b = ((intBits >>> 16) & 0xff) / 255f;
    color.a = ((intBits >>> 24) & 0xff) / 255f;
    return color;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the given width and height in
   * pixels. The rectangle is offset by originX, originY relative to the origin. Scale specifies the
   * scaling factor by which the rectangle should be scaled around originX, originY. Rotation
   * specifies the angle of counter clockwise rotation of the rectangle around originX, originY. The
   * portion of the {@link Texture} given by srcX, srcY and srcWidth, srcHeight is used. These
   * coordinates and sizes are given in texels. FlipX and flipY specify whether the texture portion
   * should be fliped horizontally or vertically.
   *
   * @param texture the Texture
   * @param x the x-coordinate in screen space
   * @param y the y-coordinate in screen space
   * @param originX the x-coordinate of the scaling and rotation origin relative to the screen space
   *     coordinates
   * @param originY the y-coordinate of the scaling and rotation origin relative to the screen space
   *     coordinates
   * @param width the width in pixels
   * @param height the height in pixels
   * @param scaleX the scale of the rectangle around originX/originY in x
   * @param scaleY the scale of the rectangle around originX/originY in y
   * @param rotation the angle of counter clockwise rotation of the rectangle around originX/originY
   * @param srcX the x-coordinate in texel space
   * @param srcY the y-coordinate in texel space
   * @param srcWidth the source with in texels
   * @param srcHeight the source height in texels
   * @param flipX whether to flip the sprite horizontally
   * @param flipY whether to flip the sprite vertically
   */
  public void draw(
      Texture texture,
      float x,
      float y,
      float originX,
      float originY,
      float width,
      float height,
      float scaleX,
      float scaleY,
      float rotation,
      int srcX,
      int srcY,
      int srcWidth,
      int srcHeight,
      boolean flipX,
      boolean flipY) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) renderMesh();

    // bottom left and top right corner points relative to origin
    final float worldOriginX = x + originX;
    final float worldOriginY = y + originY;
    float fx = -originX;
    float fy = -originY;
    float fx2 = width - originX;
    float fy2 = height - originY;

    // scale
    if (scaleX != 1 || scaleY != 1) {
      fx *= scaleX;
      fy *= scaleY;
      fx2 *= scaleX;
      fy2 *= scaleY;
    }

    // construct corner points, start from top left and go counter clockwise
    final float p1x = fx;
    final float p1y = fy;
    final float p2x = fx;
    final float p2y = fy2;
    final float p3x = fx2;
    final float p3y = fy2;
    final float p4x = fx2;
    final float p4y = fy;

    float x1;
    float y1;
    float x2;
    float y2;
    float x3;
    float y3;
    float x4;
    float y4;

    // rotate
    if (rotation != 0) {
      final float cos = MathUtils.cosDeg(rotation);
      final float sin = MathUtils.sinDeg(rotation);

      x1 = cos * p1x - sin * p1y;
      y1 = sin * p1x + cos * p1y;

      x2 = cos * p2x - sin * p2y;
      y2 = sin * p2x + cos * p2y;

      x3 = cos * p3x - sin * p3y;
      y3 = sin * p3x + cos * p3y;

      x4 = x1 + (x3 - x2);
      y4 = y3 - (y2 - y1);
    } else {
      x1 = p1x;
      y1 = p1y;

      x2 = p2x;
      y2 = p2y;

      x3 = p3x;
      y3 = p3y;

      x4 = p4x;
      y4 = p4y;
    }

    x1 += worldOriginX;
    y1 += worldOriginY;
    x2 += worldOriginX;
    y2 += worldOriginY;
    x3 += worldOriginX;
    y3 += worldOriginY;
    x4 += worldOriginX;
    y4 += worldOriginY;

    float u = srcX * invTexWidth;
    float v = (srcY + srcHeight) * invTexHeight;
    float u2 = (srcX + srcWidth) * invTexWidth;
    float v2 = srcY * invTexHeight;

    if (flipX) {
      float tmp = u;
      u = u2;
      u2 = tmp;
    }

    if (flipY) {
      float tmp = v;
      v = v2;
      v2 = tmp;
    }

    vertices[idx++] = x1;
    vertices[idx++] = y1;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x2;
    vertices[idx++] = y2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = x3;
    vertices[idx++] = y3;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = x4;
    vertices[idx++] = y4;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the given width and height in
   * pixels. The portion of the {@link Texture} given by srcX, srcY and srcWidth, srcHeight is used.
   * These coordinates and sizes are given in texels. FlipX and flipY specify whether the texture
   * portion should be fliped horizontally or vertically.
   *
   * @param texture the Texture
   * @param x the x-coordinate in screen space
   * @param y the y-coordinate in screen space
   * @param width the width in pixels
   * @param height the height in pixels
   * @param srcX the x-coordinate in texel space
   * @param srcY the y-coordinate in texel space
   * @param srcWidth the source with in texels
   * @param srcHeight the source height in texels
   * @param flipX whether to flip the sprite horizontally
   * @param flipY whether to flip the sprite vertically
   */
  public void draw(
      Texture texture,
      float x,
      float y,
      float width,
      float height,
      int srcX,
      int srcY,
      int srcWidth,
      int srcHeight,
      boolean flipX,
      boolean flipY) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) renderMesh();

    float u = srcX * invTexWidth;
    float v = (srcY + srcHeight) * invTexHeight;
    float u2 = (srcX + srcWidth) * invTexWidth;
    float v2 = srcY * invTexHeight;
    final float fx2 = x + width;
    final float fy2 = y + height;

    if (flipX) {
      float tmp = u;
      u = u2;
      u2 = tmp;
    }

    if (flipY) {
      float tmp = v;
      v = v2;
      v2 = tmp;
    }

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the given width and height in
   * pixels. The portion of the {@link Texture} given by srcX, srcY and srcWidth, srcHeight are
   * used. These coordinates and sizes are given in texels.
   *
   * @param texture the Texture
   * @param x the x-coordinate in screen space
   * @param y the y-coordinate in screen space
   * @param srcX the x-coordinate in texel space
   * @param srcY the y-coordinate in texel space
   * @param srcWidth the source with in texels
   * @param srcHeight the source height in texels
   */
  public void draw(
      Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) renderMesh();

    final float u = srcX * invTexWidth;
    final float v = (srcY + srcHeight) * invTexHeight;
    final float u2 = (srcX + srcWidth) * invTexWidth;
    final float v2 = srcY * invTexHeight;
    final float fx2 = x + srcWidth;
    final float fy2 = y + srcHeight;

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the given width and height in
   * pixels. The portion of the {@link Texture} given by u, v and u2, v2 are used. These coordinates
   * and sizes are given in texture size percentage. The rectangle will have the given tint {@link
   * Color}.
   *
   * @param texture the Texture
   * @param x the x-coordinate in screen space
   * @param y the y-coordinate in screen space
   * @param width the width in pixels
   * @param height the height in pixels
   */
  public void draw(
      Texture texture,
      float x,
      float y,
      float width,
      float height,
      float u,
      float v,
      float u2,
      float v2) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) renderMesh();

    final float fx2 = x + width;
    final float fy2 = y + height;

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the width and height of the
   * texture.
   *
   * @param texture the Texture
   * @param x the x-coordinate in screen space
   * @param y the y-coordinate in screen space
   */
  public void draw(Texture texture, float x, float y) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) renderMesh();

    final float fx2 = x + texture.getWidth();
    final float fy2 = y + texture.getHeight();

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = 0;
    vertices[idx++] = 1;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = 0;
    vertices[idx++] = 0;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = 1;
    vertices[idx++] = 0;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = 1;
    vertices[idx++] = 1;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y and stretching the region to cover the
   * given width and height.
   */
  public void draw(Texture texture, float x, float y, float width, float height) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) //
    renderMesh();

    final float fx2 = x + width;
    final float fy2 = y + height;
    final float u = 0;
    final float v = 1;
    final float u2 = 1;
    final float v2 = 0;

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle using the given vertices. There must be 4 vertices, each made up of 5
   * elements in this order: x, y, color, u, v.
   */
  public void draw(Texture texture, float[] spriteVertices, int offset, int length) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    if (texture != lastTexture) {
      switchTexture(texture);
    }

    int remainingVertices = vertices.length - idx;
    if (remainingVertices == 0) {
      renderMesh();
      remainingVertices = vertices.length;
    }
    int vertexCount = Math.min(remainingVertices, length - offset);
    System.arraycopy(spriteVertices, offset, vertices, idx, vertexCount);
    offset += vertexCount;
    idx += vertexCount;

    while (offset < length) {
      renderMesh();
      vertexCount = Math.min(vertices.length, length - offset);
      System.arraycopy(spriteVertices, offset, vertices, 0, vertexCount);
      offset += vertexCount;
      idx += vertexCount;
    }
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y having the width and height of the region.
   */
  public void draw(TextureRegion region, float x, float y) {
    draw(region, x, y, Math.abs(region.getRegionWidth()), Math.abs(region.getRegionHeight()));
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y and stretching the region to cover the
   * given width and height.
   */
  public void draw(TextureRegion region, float x, float y, float width, float height) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    Texture texture = region.texture;
    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) //
    renderMesh();

    final float fx2 = x + width;
    final float fy2 = y + height;
    final float u = region.u;
    final float v = region.v2;
    final float u2 = region.u2;
    final float v2 = region.v;

    vertices[idx++] = x;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = fy2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = fx2;
    vertices[idx++] = y;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y and stretching the region to cover the
   * given width and height. The rectangle is offset by originX, originY relative to the origin.
   * Scale specifies the scaling factor by which the rectangle should be scaled around originX,
   * originY. Rotation specifies the angle of counter clockwise rotation of the rectangle around
   * originX, originY.
   */
  public void draw(
      TextureRegion region,
      float x,
      float y,
      float originX,
      float originY,
      float width,
      float height,
      float scaleX,
      float scaleY,
      float rotation) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    Texture texture = region.texture;
    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) //
    renderMesh();

    // bottom left and top right corner points relative to origin
    final float worldOriginX = x + originX;
    final float worldOriginY = y + originY;
    float fx = -originX;
    float fy = -originY;
    float fx2 = width - originX;
    float fy2 = height - originY;

    // scale
    if (scaleX != 1 || scaleY != 1) {
      fx *= scaleX;
      fy *= scaleY;
      fx2 *= scaleX;
      fy2 *= scaleY;
    }

    // construct corner points, start from top left and go counter clockwise
    final float p1x = fx;
    final float p1y = fy;
    final float p2x = fx;
    final float p2y = fy2;
    final float p3x = fx2;
    final float p3y = fy2;
    final float p4x = fx2;
    final float p4y = fy;

    float x1;
    float y1;
    float x2;
    float y2;
    float x3;
    float y3;
    float x4;
    float y4;

    // rotate
    if (rotation != 0) {
      final float cos = MathUtils.cosDeg(rotation);
      final float sin = MathUtils.sinDeg(rotation);

      x1 = cos * p1x - sin * p1y;
      y1 = sin * p1x + cos * p1y;

      x2 = cos * p2x - sin * p2y;
      y2 = sin * p2x + cos * p2y;

      x3 = cos * p3x - sin * p3y;
      y3 = sin * p3x + cos * p3y;

      x4 = x1 + (x3 - x2);
      y4 = y3 - (y2 - y1);
    } else {
      x1 = p1x;
      y1 = p1y;

      x2 = p2x;
      y2 = p2y;

      x3 = p3x;
      y3 = p3y;

      x4 = p4x;
      y4 = p4y;
    }

    x1 += worldOriginX;
    y1 += worldOriginY;
    x2 += worldOriginX;
    y2 += worldOriginY;
    x3 += worldOriginX;
    y3 += worldOriginY;
    x4 += worldOriginX;
    y4 += worldOriginY;

    final float u = region.u;
    final float v = region.v2;
    final float u2 = region.u2;
    final float v2 = region.v;

    vertices[idx++] = x1;
    vertices[idx++] = y1;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v;

    vertices[idx++] = x2;
    vertices[idx++] = y2;
    vertices[idx++] = color;
    vertices[idx++] = u;
    vertices[idx++] = v2;

    vertices[idx++] = x3;
    vertices[idx++] = y3;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = x4;
    vertices[idx++] = y4;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v;
  }

  /**
   * Draws a rectangle with the bottom left corner at x,y and stretching the region to cover the
   * given width and height. The rectangle is offset by originX, originY relative to the origin.
   * Scale specifies the scaling factor by which the rectangle should be scaled around originX,
   * originY. Rotation specifies the angle of counter clockwise rotation of the rectangle around
   * originX, originY.
   */
  public void draw(
      TextureRegion region,
      float x,
      float y,
      float originX,
      float originY,
      float width,
      float height,
      float scaleX,
      float scaleY,
      float rotation,
      boolean clockwise) {
    if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before draw.");

    Texture texture = region.texture;
    if (texture != lastTexture) {
      switchTexture(texture);
    } else if (idx == vertices.length) //
    renderMesh();

    // bottom left and top right corner points relative to origin
    final float worldOriginX = x + originX;
    final float worldOriginY = y + originY;
    float fx = -originX;
    float fy = -originY;
    float fx2 = width - originX;
    float fy2 = height - originY;

    // scale
    if (scaleX != 1 || scaleY != 1) {
      fx *= scaleX;
      fy *= scaleY;
      fx2 *= scaleX;
      fy2 *= scaleY;
    }

    // construct corner points, start from top left and go counter clockwise
    final float p1x = fx;
    final float p1y = fy;
    final float p2x = fx;
    final float p2y = fy2;
    final float p3x = fx2;
    final float p3y = fy2;
    final float p4x = fx2;
    final float p4y = fy;

    float x1;
    float y1;
    float x2;
    float y2;
    float x3;
    float y3;
    float x4;
    float y4;

    // rotate
    if (rotation != 0) {
      final float cos = MathUtils.cosDeg(rotation);
      final float sin = MathUtils.sinDeg(rotation);

      x1 = cos * p1x - sin * p1y;
      y1 = sin * p1x + cos * p1y;

      x2 = cos * p2x - sin * p2y;
      y2 = sin * p2x + cos * p2y;

      x3 = cos * p3x - sin * p3y;
      y3 = sin * p3x + cos * p3y;

      x4 = x1 + (x3 - x2);
      y4 = y3 - (y2 - y1);
    } else {
      x1 = p1x;
      y1 = p1y;

      x2 = p2x;
      y2 = p2y;

      x3 = p3x;
      y3 = p3y;

      x4 = p4x;
      y4 = p4y;
    }

    x1 += worldOriginX;
    y1 += worldOriginY;
    x2 += worldOriginX;
    y2 += worldOriginY;
    x3 += worldOriginX;
    y3 += worldOriginY;
    x4 += worldOriginX;
    y4 += worldOriginY;

    float u1, v1, u2, v2, u3, v3, u4, v4;
    if (clockwise) {
      u1 = region.u2;
      v1 = region.v2;
      u2 = region.u;
      v2 = region.v2;
      u3 = region.u;
      v3 = region.v;
      u4 = region.u2;
      v4 = region.v;
    } else {
      u1 = region.u;
      v1 = region.v;
      u2 = region.u2;
      v2 = region.v;
      u3 = region.u2;
      v3 = region.v2;
      u4 = region.u;
      v4 = region.v2;
    }

    vertices[idx++] = x1;
    vertices[idx++] = y1;
    vertices[idx++] = color;
    vertices[idx++] = u1;
    vertices[idx++] = v1;

    vertices[idx++] = x2;
    vertices[idx++] = y2;
    vertices[idx++] = color;
    vertices[idx++] = u2;
    vertices[idx++] = v2;

    vertices[idx++] = x3;
    vertices[idx++] = y3;
    vertices[idx++] = color;
    vertices[idx++] = u3;
    vertices[idx++] = v3;

    vertices[idx++] = x4;
    vertices[idx++] = y4;
    vertices[idx++] = color;
    vertices[idx++] = u4;
    vertices[idx++] = v4;
  }

  /** Causes any pending sprites to be rendered, without ending the SpriteBatch. */
  public void flush() {
    renderMesh();
  }

  private void renderMesh() {
    if (idx == 0) return;

    renderCalls++;
    totalRenderCalls++;
    int spritesInBatch = idx / 20;
    if (spritesInBatch > maxSpritesInBatch) maxSpritesInBatch = spritesInBatch;

    lastTexture.bind();
    mesh.setVertices(vertices, 0, idx);

    if (blendingDisabled) {
      Gdx.gl.glDisable(GL20.GL_BLEND);
    } else {
      Gdx.gl.glEnable(GL20.GL_BLEND);
      Gdx.gl.glBlendFunc(blendSrcFunc, blendDstFunc);
    }

    if (Gdx.graphics.isGL20Available()) {
      if (customShader != null) mesh.render(customShader, GL10.GL_TRIANGLES, 0, spritesInBatch * 6);
      else mesh.render(shader, GL10.GL_TRIANGLES, 0, spritesInBatch * 6);
    } else {
      mesh.render(GL10.GL_TRIANGLES, 0, spritesInBatch * 6);
    }

    idx = 0;
    currBufferIdx++;
    if (currBufferIdx == buffers.length) currBufferIdx = 0;
    mesh = buffers[currBufferIdx];
  }

  /** Disables blending for drawing sprites. Does not disable blending for text rendering */
  public void disableBlending() {
    renderMesh();
    blendingDisabled = true;
  }

  /** Enables blending for sprites */
  public void enableBlending() {
    renderMesh();
    blendingDisabled = false;
  }

  /**
   * Sets the blending function to be used when rendering sprites.
   *
   * @param srcFunc the source function, e.g. GL11.GL_SRC_ALPHA
   * @param dstFunc the destination function, e.g. GL11.GL_ONE_MINUS_SRC_ALPHA
   */
  public void setBlendFunction(int srcFunc, int dstFunc) {
    renderMesh();
    blendSrcFunc = srcFunc;
    blendDstFunc = dstFunc;
  }

  /** Disposes all resources associated with this SpriteBatch */
  public void dispose() {
    for (int i = 0; i < buffers.length; i++) buffers[i].dispose();
    if (ownsShader && shader != null) shader.dispose();
  }

  /**
   * Returns the current projection matrix. Changing this will result in undefined behaviour.
   *
   * @return the currently set projection matrix
   */
  public Matrix4 getProjectionMatrix() {
    return projectionMatrix;
  }

  /**
   * Returns the current transform matrix. Changing this will result in undefined behaviour.
   *
   * @return the currently set transform matrix
   */
  public Matrix4 getTransformMatrix() {
    return transformMatrix;
  }

  /**
   * Sets the projection matrix to be used by this SpriteBatch. If this is called inside a {@link
   * #begin()}/{@link #end()} block. the current batch is flushed to the gpu.
   *
   * @param projection the projection matrix
   */
  public void setProjectionMatrix(Matrix4 projection) {
    if (drawing) flush();
    projectionMatrix.set(projection);
    if (drawing) setupMatrices();
  }

  /**
   * Sets the transform matrix to be used by this SpriteBatch. If this is called inside a {@link
   * #begin()}/{@link #end()} block. the current batch is flushed to the gpu.
   *
   * @param transform the transform matrix
   */
  public void setTransformMatrix(Matrix4 transform) {
    if (drawing) flush();
    transformMatrix.set(transform);
    if (drawing) setupMatrices();
  }

  private void setupMatrices() {
    if (!Gdx.graphics.isGL20Available()) {
      GL10 gl = Gdx.gl10;
      gl.glMatrixMode(GL10.GL_PROJECTION);
      gl.glLoadMatrixf(projectionMatrix.val, 0);
      gl.glMatrixMode(GL10.GL_MODELVIEW);
      gl.glLoadMatrixf(transformMatrix.val, 0);
    } else {
      combinedMatrix.set(projectionMatrix).mul(transformMatrix);
      if (customShader != null) {
        customShader.setUniformMatrix("u_proj", projectionMatrix);
        customShader.setUniformMatrix("u_trans", transformMatrix);
        customShader.setUniformMatrix("u_projTrans", combinedMatrix);
        customShader.setUniformi("u_texture", 0);
      } else {
        shader.setUniformMatrix("u_projectionViewMatrix", combinedMatrix);
        shader.setUniformi("u_texture", 0);
      }
    }
  }

  private void switchTexture(Texture texture) {
    if (Gdx.graphics.isGL20Available()) {
      renderMesh();
      lastTexture = texture;
      invTexWidth = 1.0f / texture.getWidth();
      invTexHeight = 1.0f / texture.getHeight();
    } else {
      renderMesh();
      lastTexture = texture;
      invTexWidth = 1.0f / texture.getWidth();
      invTexHeight = 1.0f / texture.getHeight();
    }
  }

  /**
   * Sets the shader to be used in a GLES 2.0 environment. Vertex position attribute is called
   * "a_position", the texture coordinates attribute is called called "a_texCoords0", the color
   * attribute is called "a_color". See {@link ShaderProgram#POSITION_ATTRIBUTE}, {@link
   * ShaderProgram#COLOR_ATTRIBUTE} and {@link ShaderProgram#TEXCOORD_ATTRIBUTE} which gets "0"
   * appened to indicate the use of the first texture unit. The projection matrix is uploaded via a
   * mat4 uniform called "u_proj", the transform matrix is uploaded via a uniform called "u_trans",
   * the combined transform and projection matrx is is uploaded via a mat4 uniform called
   * "u_projTrans". The texture sampler is passed via a uniform called "u_texture".
   *
   * <p>Call this method with a null argument to use the default shader.
   *
   * @param shader the {@link ShaderProgram} or null to use the default shader.
   */
  public void setShader(ShaderProgram shader) {
    customShader = shader;
  }

  /** @return whether blending for sprites is enabled */
  public boolean isBlendingEnabled() {
    return !blendingDisabled;
  }

  public int getSrcBlendFunc() {
    return this.blendSrcFunc;
  }

  public int getDstBlendFunc() {
    return this.blendDstFunc;
  }

  public static final int X1 = 0;
  public static final int Y1 = 1;
  public static final int C1 = 2;
  public static final int U1 = 3;
  public static final int V1 = 4;
  public static final int X2 = 5;
  public static final int Y2 = 6;
  public static final int C2 = 7;
  public static final int U2 = 8;
  public static final int V2 = 9;
  public static final int X3 = 10;
  public static final int Y3 = 11;
  public static final int C3 = 12;
  public static final int U3 = 13;
  public static final int V3 = 14;
  public static final int X4 = 15;
  public static final int Y4 = 16;
  public static final int C4 = 17;
  public static final int U4 = 18;
  public static final int V4 = 19;
}
Esempio n. 4
0
public class GameObject implements Poolable {

  private Vector2 position, dimensions, accelleration, velocity, lastPosition;

  private GameObjectType type = GameObjectType.NONE;

  private CollisionResolver collision;

  private boolean staticMode = false;

  private GameObject lastCollision;

  private String id = "";

  private Color color = Color.WHITE.cpy();

  private Vector2 scale;

  private String uuid;

  public GameObject() {
    position = new Vector2();
    dimensions = new Vector2();
    lastPosition = new Vector2();
    accelleration = new Vector2();
    velocity = new Vector2();
    scale = new Vector2(1f, 1f);
    uuid = UUID.randomUUID().toString();
  }

  public void setDimensions(float width, float height) {
    this.dimensions.x = width;
    this.dimensions.y = height;
  }

  public void setType(GameObjectType type) {
    this.type = type;
  }

  public boolean isType(GameObjectType type) {
    return this.type.equals(type);
  }

  public GameObjectType getType() {
    return type;
  }

  public void move(float x, float y) {
    setPosition(this.position.x + x, this.position.y + y);
  }

  public void setPosition(float x, float y) {
    this.lastPosition.x = this.position.x;
    this.lastPosition.y = this.position.y;
    this.position.x = x;
    this.position.y = y;
  }

  public float getLeft() {
    return this.position.x;
  }

  public float getTop() {
    return this.position.y;
  }

  public float getRight() {
    return getLeft() + getWidth();
  }

  public float getBottom() {
    return getTop() + getHeight();
  }

  public float getWidth() {
    return this.dimensions.x;
  }

  public float getHeight() {
    return this.dimensions.y;
  }

  public void accellerate(float x, float y) {
    accelleration.x += x;
    accelleration.y += y;
  }

  public void setVelocity(float x, float y) {
    velocity.x = x;
    velocity.y = y;
  }

  public Vector2 getVelocity() {
    return velocity;
  }

  public Vector2 getAccelleration() {
    return accelleration;
  }

  public void stop() {
    accelleration.x = 0;
    accelleration.y = 0;
    velocity.x = 0;
    velocity.y = 0;
  }

  public CollisionResolver getCollisionResolver() {
    return collision;
  }

  public void setCollision(CollisionResolver resolver) {
    this.collision = resolver;
  }

  public boolean isStatic() {
    return staticMode;
  }

  public void setStatic(boolean staticMode) {
    this.staticMode = staticMode;
  }

  public Vector2 getLastPosition() {
    return lastPosition;
  }

  public GameObject getLastCollision() {
    return lastCollision;
  }

  public void setLastCollision(GameObject lastCollision) {
    this.lastCollision = lastCollision;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getId() {
    return id;
  }

  public Color getColor() {
    return color;
  }

  public void setColor(Color color) {
    setColor(color.r, color.g, color.b, color.a);
  }

  public void setColor(float r, float g, float b, float a) {
    color.set(r, g, b, a);
  }

  public Vector2 getScale() {
    return scale;
  }

  public String getUUID() {
    return uuid;
  }

  @Override
  public void reset() {
    lastPosition.x = 0;
    lastPosition.y = 0;
    position.x = 0;
    position.y = 0;
    dimensions.x = 0;
    dimensions.y = 0;
    type = GameObjectType.NONE;
    velocity.x = 0;
    velocity.y = 0;
    accelleration.x = 0;
    accelleration.y = 0;
    lastCollision = null;
    id = "";
    scale.set(1f, 1f);
    color = Color.WHITE.cpy();
    uuid = UUID.randomUUID().toString();
  }
}
 public void init(float x, float y, float scale, float speed, float colorFactor) {
   sprite.setBounds(x, y, 32 * scale, 32 * scale);
   sprite.setColor(Color.WHITE.toFloatBits() * colorFactor);
   this.speed = speed;
 }
Esempio n. 6
0
/**
 * Renders bitmap fonts. The font consists of 2 files: an image file or {@link TextureRegion}
 * containing the glyphs and a file in the AngleCode BMFont text format that describes where each
 * glyph is on the image. Currently only a single image of glyphs is supported.<br>
 * <br>
 * Text is drawn using a {@link SpriteBatch}. Text can be cached in a {@link BitmapFontCache} for
 * faster rendering of static text, which saves needing to compute the location of each glyph each
 * frame.<br>
 * <br>
 * * The texture for a BitmapFont loaded from a file is managed. {@link #dispose()} must be called
 * to free the texture when no longer needed. A BitmapFont loaded using a {@link TextureRegion} is
 * managed if the region's texture is managed. Disposing the BitmapFont disposes the region's
 * texture, which may not be desirable if the texture is still being used elsewhere.<br>
 * <br>
 * The code is based on Matthias Mann's TWL BitmapFont class. Thanks for sharing, Matthias! :)
 *
 * @author Nathan Sweet
 * @author Matthias Mann
 */
public class BitmapFont implements Disposable {
  private static final int LOG2_PAGE_SIZE = 9;
  private static final int PAGE_SIZE = 1 << LOG2_PAGE_SIZE;
  private static final int PAGES = 0x10000 / PAGE_SIZE;

  TextureRegion region;
  float lineHeight;
  float capHeight;
  float ascent;
  float descent;
  float down;
  float scaleX = 1, scaleY = 1;

  private final Glyph[][] glyphs = new Glyph[PAGES][];
  private float spaceWidth;
  private float xHeight;
  private final TextBounds textBounds = new TextBounds();
  private float color = Color.WHITE.toFloatBits();
  private Color tempColor = new Color(1, 1, 1, 1);
  private boolean flipped;

  /**
   * Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is
   * convenient to easily display text without bothering with generating a bitmap font.
   */
  public BitmapFont() {
    this(
        Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"),
        Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"),
        false);
  }

  /**
   * Creates a BitmapFont using the default 15pt Arial font included in the libgdx JAR file. This is
   * convenient to easily display text without bothering with generating a bitmap font.
   *
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the
   *     upper left corner.
   */
  public BitmapFont(boolean flip) {
    this(
        Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.fnt"),
        Gdx.files.classpath("com/badlogic/gdx/utils/arial-15.png"),
        flip);
  }

  /**
   * Creates a BitmapFont with the glyphs relative to the specified region.
   *
   * @param region The texture region containing the glyphs. The glyphs must be relative to the
   *     lower left corner (ie, the region should not be flipped).
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the
   *     upper left corner.
   */
  public BitmapFont(FileHandle fontFile, TextureRegion region, boolean flip) {
    init(fontFile, region, flip);
  }

  /**
   * Creates a BitmapFont from a BMFont file. The image file name is read from the BMFont file and
   * the image is loaded from the same directory.
   *
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the
   *     upper left corner.
   */
  public BitmapFont(FileHandle fontFile, boolean flip) {
    init(fontFile, null, flip);
  }

  /**
   * Creates a BitmapFont from a BMFont file, using the specified image for glyphs. Any image
   * specified in the BMFont file is ignored.
   *
   * @param flip If true, the glyphs will be flipped for use with a perspective where 0,0 is the
   *     upper left corner.
   */
  public BitmapFont(FileHandle fontFile, FileHandle imageFile, boolean flip) {
    region = new TextureRegion(new Texture(imageFile, false));
    init(fontFile, region, flip);
  }

  private void init(FileHandle fontFile, TextureRegion region, boolean flip) {
    flipped = flip;
    BufferedReader reader = new BufferedReader(new InputStreamReader(fontFile.read()), 512);
    try {
      reader.readLine(); // info

      String line = reader.readLine();
      if (line == null) throw new GdxRuntimeException("Invalid font file: " + fontFile);
      String[] common = line.split(" ", 4);
      if (common.length < 4) throw new GdxRuntimeException("Invalid font file: " + fontFile);

      if (!common[1].startsWith("lineHeight="))
        throw new GdxRuntimeException("Invalid font file: " + fontFile);
      lineHeight = Integer.parseInt(common[1].substring(11));

      if (!common[2].startsWith("base="))
        throw new GdxRuntimeException("Invalid font file: " + fontFile);
      int baseLine = Integer.parseInt(common[2].substring(5));

      if (region != null) reader.readLine(); // page
      else {
        line = reader.readLine();
        if (line == null) throw new GdxRuntimeException("Invalid font file: " + fontFile);
        String[] page = line.split(" ", 4);
        if (!page[2].startsWith("file="))
          throw new GdxRuntimeException("Invalid font file: " + fontFile);
        String imgFilename = null;
        if (page[2].endsWith("\"")) {
          imgFilename = page[2].substring(6, page[2].length() - 1);
        } else {
          imgFilename = page[2].substring(5, page[2].length());
        }
        FileHandle imageFile = fontFile.parent().child(imgFilename);
        region = new TextureRegion(new Texture(imageFile, false));
      }

      this.region = region;
      float invTexWidth = 1.0f / region.getTexture().getWidth();
      float invTexHeight = 1.0f / region.getTexture().getHeight();
      float u = region.u;
      float v = region.v;

      descent = 0;
      //			descent = g != null? baseLine + g.yoffset:0;

      while (true) {
        line = reader.readLine();
        if (line == null) break;
        if (line.startsWith("kernings ")) break;
        if (!line.startsWith("char ")) continue;

        Glyph glyph = new Glyph();

        StringTokenizer tokens = new StringTokenizer(line, " =");
        tokens.nextToken();
        tokens.nextToken();
        int ch = Integer.parseInt(tokens.nextToken());
        if (ch <= Character.MAX_VALUE) {
          Glyph[] page = glyphs[ch / PAGE_SIZE];
          if (page == null) glyphs[ch / PAGE_SIZE] = page = new Glyph[PAGE_SIZE];
          page[ch & PAGE_SIZE - 1] = glyph;
        } else continue;
        tokens.nextToken();
        int srcX = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        int srcY = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        glyph.width = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        glyph.height = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        glyph.xoffset = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        if (flip) glyph.yoffset = Integer.parseInt(tokens.nextToken());
        else glyph.yoffset = -(glyph.height + Integer.parseInt(tokens.nextToken()));
        tokens.nextToken();
        glyph.xadvance = Integer.parseInt(tokens.nextToken());

        glyph.u = u + srcX * invTexWidth;
        glyph.u2 = u + (srcX + glyph.width) * invTexWidth;
        if (flip) {
          glyph.v = v + srcY * invTexHeight;
          glyph.v2 = v + (srcY + glyph.height) * invTexHeight;
        } else {
          glyph.v2 = v + srcY * invTexHeight;
          glyph.v = v + (srcY + glyph.height) * invTexHeight;
        }

        descent = Math.min(baseLine + glyph.yoffset, descent);
      }

      while (true) {
        line = reader.readLine();
        if (line == null) break;
        if (!line.startsWith("kerning ")) break;

        StringTokenizer tokens = new StringTokenizer(line, " =");
        tokens.nextToken();
        tokens.nextToken();
        int first = Integer.parseInt(tokens.nextToken());
        tokens.nextToken();
        int second = Integer.parseInt(tokens.nextToken());
        if (first < 0 || first > Character.MAX_VALUE || second < 0 || second > Character.MAX_VALUE)
          continue;
        Glyph glyph = getGlyph((char) first);
        tokens.nextToken();
        int amount = Integer.parseInt(tokens.nextToken());
        glyph.setKerning(second, amount);
      }

      Glyph g = getGlyph(' ');
      if (g == null) {
        g = new Glyph();
        g.xadvance = getGlyph('l').xadvance;
        Glyph[] page = glyphs[' ' / PAGE_SIZE];
        if (page == null) glyphs[' ' / PAGE_SIZE] = page = new Glyph[PAGE_SIZE];
        page[' ' & PAGE_SIZE - 1] = g;
      }
      spaceWidth = g != null ? g.xadvance + g.width : 1;

      g = getGlyph('x');
      xHeight = g != null ? g.height : 1;

      g = getGlyph('M');
      capHeight = g != null ? g.height : 1;

      ascent = baseLine - capHeight;
      down = -lineHeight;
      if (flip) {
        ascent = -ascent;
        down = -down;
      }
    } catch (Exception ex) {
      throw new GdxRuntimeException("Error loading font file: " + fontFile, ex);
    } finally {
      try {
        reader.close();
      } catch (IOException ignored) {
      }
    }
  }

  Glyph getGlyph(char ch) {
    Glyph[] page = glyphs[ch / PAGE_SIZE];
    if (page != null) return page[ch & PAGE_SIZE - 1];
    return null;
  }

  /**
   * Draws a string at the specified position.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline).
   *     Note the same TextBounds instance is used for all methods that return TextBounds.
   */
  public TextBounds draw(SpriteBatch spriteBatch, CharSequence str, float x, float y) {
    return draw(spriteBatch, str, x, y, 0, str.length());
  }

  /**
   * Draws a substring at the specified position.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @param start The first character of the string to draw.
   * @param end The last character of the string to draw (exclusive).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline).
   *     Note the same TextBounds instance is used for all methods that return TextBounds.
   */
  public TextBounds draw(
      SpriteBatch spriteBatch, CharSequence str, float x, float y, int start, int end) {
    float batchColor = spriteBatch.color;
    spriteBatch.setColor(color);
    final Texture texture = region.getTexture();
    y += ascent;
    float startX = x;
    Glyph lastGlyph = null;
    if (scaleX == 1 && scaleY == 1) {
      while (start < end) {
        lastGlyph = getGlyph(str.charAt(start++));
        if (lastGlyph != null) {
          spriteBatch.draw(
              texture, //
              x + lastGlyph.xoffset,
              y + lastGlyph.yoffset, //
              lastGlyph.width,
              lastGlyph.height, //
              lastGlyph.u,
              lastGlyph.v,
              lastGlyph.u2,
              lastGlyph.v2);
          x += lastGlyph.xadvance;
          break;
        }
      }
      while (start < end) {
        char ch = str.charAt(start++);
        Glyph g = getGlyph(ch);
        if (g == null) continue;
        x += lastGlyph.getKerning(ch);
        lastGlyph = g;
        spriteBatch.draw(
            texture, //
            x + lastGlyph.xoffset,
            y + lastGlyph.yoffset, //
            lastGlyph.width,
            lastGlyph.height, //
            lastGlyph.u,
            lastGlyph.v,
            lastGlyph.u2,
            lastGlyph.v2);
        x += g.xadvance;
      }
    } else {
      float scaleX = this.scaleX, scaleY = this.scaleY;
      while (start < end) {
        lastGlyph = getGlyph(str.charAt(start++));
        if (lastGlyph != null) {
          spriteBatch.draw(
              texture, //
              x + lastGlyph.xoffset * scaleX, //
              y + lastGlyph.yoffset * scaleY, //
              lastGlyph.width * scaleX, //
              lastGlyph.height * scaleY, //
              lastGlyph.u,
              lastGlyph.v,
              lastGlyph.u2,
              lastGlyph.v2);
          x += lastGlyph.xadvance * scaleX;
          break;
        }
      }
      while (start < end) {
        char ch = str.charAt(start++);
        Glyph g = getGlyph(ch);
        if (g == null) continue;
        x += lastGlyph.getKerning(ch) * scaleX;
        lastGlyph = g;
        spriteBatch.draw(
            texture, //
            x + lastGlyph.xoffset * scaleX, //
            y + lastGlyph.yoffset * scaleY, //
            lastGlyph.width * scaleX, //
            lastGlyph.height * scaleY, //
            lastGlyph.u,
            lastGlyph.v,
            lastGlyph.u2,
            lastGlyph.v2);
        x += g.xadvance * scaleX;
      }
    }
    spriteBatch.setColor(batchColor);
    textBounds.width = x - startX;
    textBounds.height = capHeight;
    return textBounds;
  }

  /**
   * Draws a string, which may contain newlines (\n), at the specified position.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline of
   *     the last line). Note the same TextBounds instance is used for all methods that return
   *     TextBounds.
   */
  public TextBounds drawMultiLine(SpriteBatch spriteBatch, CharSequence str, float x, float y) {
    return drawMultiLine(spriteBatch, str, x, y, 0, HAlignment.LEFT);
  }

  /**
   * Draws a string, which may contain newlines (\n), at the specified position and alignment. Each
   * line is aligned horizontally within a rectangle of the specified width.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline of
   *     the last line). Note the same TextBounds instance is used for all methods that return
   *     TextBounds.
   */
  public TextBounds drawMultiLine(
      SpriteBatch spriteBatch,
      CharSequence str,
      float x,
      float y,
      float alignmentWidth,
      HAlignment alignment) {
    float batchColor = spriteBatch.color;
    float down = this.down;
    int start = 0;
    int numLines = 0;
    int length = str.length();
    float maxWidth = 0;
    while (start < length) {
      int lineEnd = indexOf(str, '\n', start);
      float xOffset = 0;
      if (alignment != HAlignment.LEFT) {
        float lineWidth = getBounds(str, start, lineEnd).width;
        xOffset = alignmentWidth - lineWidth;
        if (alignment == HAlignment.CENTER) xOffset = xOffset / 2;
      }
      float lineWidth = draw(spriteBatch, str, x + xOffset, y, start, lineEnd).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      start = lineEnd + 1;
      y += down;
      numLines++;
    }
    spriteBatch.setColor(batchColor);

    textBounds.width = maxWidth;
    textBounds.height = capHeight + (numLines - 1) * lineHeight;
    return textBounds;
  }

  /**
   * Draws a string, which may contain newlines (\n), with the specified position. Each line is
   * automatically wrapped to keep it within a rectangle of the specified width.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline of
   *     the last line). Note the same TextBounds instance is used for all methods that return
   *     TextBounds.
   */
  public TextBounds drawWrapped(
      SpriteBatch spriteBatch, CharSequence str, float x, float y, float wrapWidth) {
    return drawWrapped(spriteBatch, str, x, y, wrapWidth, HAlignment.LEFT);
  }

  /**
   * Draws a string, which may contain newlines (\n), with the specified position. Each line is
   * automatically wrapped to keep it within a rectangle of the specified width, and aligned
   * horizontally within that rectangle.
   *
   * @param x The x position for the left most character.
   * @param y The y position for the top of most capital letters in the font (the {@link
   *     #getCapHeight() cap height}).
   * @return The bounds of the rendered string (the height is the distance from y to the baseline of
   *     the last line). Note the same TextBounds instance is used for all methods that return
   *     TextBounds.
   */
  public TextBounds drawWrapped(
      SpriteBatch spriteBatch,
      CharSequence str,
      float x,
      float y,
      float wrapWidth,
      HAlignment alignment) {
    float batchColor = spriteBatch.color;
    float down = this.down;
    int start = 0;
    int numLines = 0;
    int length = str.length();
    float maxWidth = 0;
    while (start < length) {
      int lineEnd = start + computeVisibleGlyphs(str, start, indexOf(str, '\n', start), wrapWidth);
      int nextLineStart;
      if (lineEnd < length) {
        int originalLineEnd = lineEnd;
        while (lineEnd > start) {
          char ch = str.charAt(lineEnd);
          if (ch == ' ' || ch == '\n') break;
          lineEnd--;
        }
        if (lineEnd == start) {
          lineEnd = originalLineEnd;
          if (lineEnd == start) lineEnd++;
          nextLineStart = lineEnd;
        } else nextLineStart = lineEnd + 1; // Eat space or newline.
      } else {
        if (lineEnd == start) lineEnd++;
        nextLineStart = length;
      }
      float xOffset = 0;
      if (alignment != HAlignment.LEFT) {
        float lineWidth = getBounds(str, start, lineEnd).width;
        xOffset = wrapWidth - lineWidth;
        if (alignment == HAlignment.CENTER) xOffset /= 2;
      }
      float lineWidth = draw(spriteBatch, str, x + xOffset, y, start, lineEnd).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      start = nextLineStart;
      y += down;
      numLines++;
    }
    spriteBatch.setColor(batchColor);
    textBounds.width = maxWidth;
    textBounds.height = capHeight + (numLines - 1) * lineHeight;
    return textBounds;
  }

  /**
   * Returns the size of the specified string. The height is the distance from the top of most
   * capital letters in the font (the {@link #getCapHeight() cap height}) to the baseline. Note the
   * same TextBounds instance is used for all methods that return TextBounds.
   */
  public TextBounds getBounds(CharSequence str) {
    return getBounds(str, 0, str.length());
  }

  /**
   * Returns the size of the specified substring. The height is the distance from the top of most
   * capital letters in the font (the {@link #getCapHeight() cap height}) to the baseline. Note the
   * same TextBounds instance is used for all methods that return TextBounds.
   *
   * @param start The first character of the string.
   * @param end The last character of the string (exclusive).
   */
  public TextBounds getBounds(CharSequence str, int start, int end) {
    int width = 0;
    Glyph lastGlyph = null;
    while (start < end) {
      lastGlyph = getGlyph(str.charAt(start++));
      if (lastGlyph != null) {
        width = lastGlyph.xadvance;
        break;
      }
    }
    while (start < end) {
      char ch = str.charAt(start++);
      Glyph g = getGlyph(ch);
      if (g != null) {
        width += lastGlyph.getKerning(ch);
        lastGlyph = g;
        width += g.xadvance;
      }
    }
    textBounds.width = width * scaleX;
    textBounds.height = capHeight;
    return textBounds;
  }

  /**
   * Returns the size of the specified string, which may contain newlines. The height is the
   * distance from the top of most capital letters in the font (the {@link #getCapHeight() cap
   * height}) to the baseline of the last line of text. Note the same TextBounds instance is used
   * for all methods that return TextBounds.
   */
  public TextBounds getMultiLineBounds(CharSequence str) {
    int start = 0;
    float maxWidth = 0;
    int numLines = 0;
    int length = str.length();
    while (start < length) {
      int lineEnd = indexOf(str, '\n', start);
      float lineWidth = getBounds(str, start, lineEnd).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      start = lineEnd + 1;
      numLines++;
    }
    textBounds.width = maxWidth;
    textBounds.height = capHeight + (numLines - 1) * lineHeight;
    return textBounds;
  }

  /**
   * Returns the size of the specified string, which may contain newlines and is wrapped to keep it
   * within a rectangle of the specified width. The height is the distance from the top of most
   * capital letters in the font (the {@link #getCapHeight() cap height}) to the baseline of the
   * last line of text. Note the same TextBounds instance is used for all methods that return
   * TextBounds.
   */
  public TextBounds getWrappedBounds(CharSequence str, float wrapWidth) {
    int start = 0;
    int numLines = 0;
    int length = str.length();
    float maxWidth = 0;
    while (start < length) {
      int lineEnd = start + computeVisibleGlyphs(str, start, indexOf(str, '\n', start), wrapWidth);
      int nextLineStart;
      if (lineEnd < length) {
        int originalLineEnd = lineEnd;
        while (lineEnd > start) {
          char ch = str.charAt(lineEnd);
          if (ch == ' ' || ch == '\n') break;
          lineEnd--;
        }
        if (lineEnd == start) {
          lineEnd = originalLineEnd;
          if (lineEnd == start) lineEnd++;
          nextLineStart = lineEnd;
        } else nextLineStart = lineEnd + 1; // Eat space or newline.
      } else {
        if (lineEnd == start) lineEnd++;
        nextLineStart = length;
      }
      float lineWidth = getBounds(str, start, lineEnd).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      start = nextLineStart;
      numLines++;
    }
    textBounds.width = maxWidth;
    textBounds.height = capHeight + (numLines - 1) * lineHeight;
    return textBounds;
  }

  /**
   * Computes the glyph advances for the given character sequence and stores them in the provided
   * {@link FloatArray}. The FloatArray is cleared. This will add an additional element at the end.
   *
   * @param str the character sequence
   * @param glyphAdvances the glyph advances output array.
   * @param glyphPositions the glyph positions output array.
   */
  public void computeGlyphAdvancesAndPositions(
      CharSequence str, FloatArray glyphAdvances, FloatArray glyphPositions) {
    glyphAdvances.clear();
    glyphPositions.clear();
    int index = 0;
    int end = str.length();
    int width = 0;
    Glyph lastGlyph = null;
    if (scaleX == 1) {
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch);
          lastGlyph = g;
          glyphAdvances.add(g.xadvance);
          glyphPositions.add(width);
          width += g.xadvance;
        }
      }
      glyphAdvances.add(0);
      glyphPositions.add(width);
    } else {
      float scaleX = this.scaleX;
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch) * scaleX;
          lastGlyph = g;
          glyphAdvances.add(g.xadvance * scaleX);
          glyphPositions.add(width);
          width += g.xadvance;
        }
      }
      glyphAdvances.add(0);
      glyphPositions.add(width);
    }
  }

  /**
   * Returns the number of glyphs from the substring that can be rendered in the specified width.
   *
   * @param start The first character of the string.
   * @param end The last character of the string (exclusive).
   */
  public int computeVisibleGlyphs(CharSequence str, int start, int end, float availableWidth) {
    int index = start;
    int width = 0;
    Glyph lastGlyph = null;
    if (scaleX == 1) {
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch);
          lastGlyph = g;
          if (width + g.width + g.xoffset > availableWidth) break;
          width += g.xadvance;
        }
      }
    } else {
      float scaleX = this.scaleX;
      for (; index < end; index++) {
        char ch = str.charAt(index);
        Glyph g = getGlyph(ch);
        if (g != null) {
          if (lastGlyph != null) width += lastGlyph.getKerning(ch) * scaleX;
          lastGlyph = g;
          if (width + (g.width + g.xoffset) * scaleX > availableWidth) break;
          width += g.xadvance * scaleX;
        }
      }
    }
    return index - start;
  }

  public void setColor(Color tint) {
    this.color = tint.toFloatBits();
  }

  public void setColor(float r, float g, float b, float a) {
    int intBits =
        (int) (255 * a) << 24 | (int) (255 * b) << 16 | (int) (255 * g) << 8 | (int) (255 * r);
    color = Float.intBitsToFloat((intBits & 0xfeffffff));
  }

  /**
   * Returns the color of this font. Changing the returned color will have no affect, {@link
   * #setColor(Color)} or {@link #setColor(float, float, float, float)} must be used.
   */
  public Color getColor() {
    int intBits = Float.floatToRawIntBits(color);
    Color color = this.tempColor;
    color.r = (intBits & 0xff) / 255f;
    color.g = ((intBits >>> 8) & 0xff) / 255f;
    color.b = ((intBits >>> 16) & 0xff) / 255f;
    color.a = ((intBits >>> 24) & 0xff) / 255f;
    return color;
  }

  public void setScale(float scaleX, float scaleY) {
    spaceWidth = spaceWidth / this.scaleX * scaleX;
    xHeight = xHeight / this.scaleY * scaleY;
    capHeight = capHeight / this.scaleY * scaleY;
    ascent = ascent / this.scaleY * scaleY;
    descent = descent / this.scaleY * scaleY;
    down = down / this.scaleY * scaleY;
    this.scaleX = scaleX;
    this.scaleY = scaleY;
  }

  /**
   * Scales the font by the specified amount in both directions.<br>
   * <br>
   * Note that smoother scaling can be achieved if the texture backing the BitmapFont is using
   * {@link TextureFilter#Linear}. The default is Nearest, so use a BitmapFont constructor that
   * takes a {@link TextureRegion}.
   */
  public void setScale(float scaleXY) {
    setScale(scaleXY, scaleXY);
  }

  /** Sets the font's scale relative to the current scale. */
  public void scale(float amount) {
    setScale(scaleX + amount, scaleY + amount);
  }

  public float getScaleX() {
    return scaleX;
  }

  public float getScaleY() {
    return scaleY;
  }

  public TextureRegion getRegion() {
    return region;
  }

  /** Returns the line height, which is the distance from one line of text to the next. */
  public float getLineHeight() {
    return lineHeight;
  }

  /** Returns the width of the space character. */
  public float getSpaceWidth() {
    return spaceWidth;
  }

  /**
   * Returns the x-height, which is the distance from the top of most lowercase characters to the
   * basline.
   */
  public float getXHeight() {
    return xHeight;
  }

  /**
   * Returns the cap height, which is the distance from the top of most uppercase characters to the
   * basline. Since the drawing position is the cap height of the first line, the cap height can be
   * used to get the location of the baseline.
   */
  public float getCapHeight() {
    return capHeight;
  }

  /**
   * Returns the ascent, which is the distance from the cap height to the top of the tallest glyph.
   */
  public float getAscent() {
    return ascent;
  }

  /**
   * Returns the descent, which is the distance from the baseline to the bottom of the glyph 'g'.
   * This number is negative.
   */
  public float getDescent() {
    return descent;
  }

  /** Returns true if this BitmapFont has been flipped for use with a y-down coordinate system. */
  public boolean isFlipped() {
    return flipped;
  }

  /** Disposes the texture used by this BitmapFont's region. */
  public void dispose() {
    region.getTexture().dispose();
  }

  /**
   * Makes the specified glyphs fixed width. This can be useful to make the numbers in a font fixed
   * width. Eg, when horizontally centering a score or loading percentage text, it will not jump
   * around as different numbers are shown.
   */
  public void setFixedWidthGlyphs(CharSequence glyphs) {
    int maxAdvance = 0;
    for (int index = 0, end = glyphs.length(); index < end; index++) {
      Glyph g = getGlyph(glyphs.charAt(index));
      if (g != null && g.xadvance > maxAdvance) maxAdvance = g.xadvance;
    }
    for (int index = 0, end = glyphs.length(); index < end; index++) {
      Glyph g = getGlyph(glyphs.charAt(index));
      if (g == null) continue;
      g.xoffset += (maxAdvance - g.xadvance) / 2;
      g.xadvance = maxAdvance;
      g.kerning = null;
    }
  }

  static class Glyph {
    int width, height;
    float u, v, u2, v2;
    int xoffset, yoffset;
    int xadvance;
    byte[][] kerning;

    int getKerning(char ch) {
      if (kerning != null) {
        byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
        if (page != null) return page[ch & PAGE_SIZE - 1];
      }
      return 0;
    }

    void setKerning(int ch, int value) {
      if (kerning == null) kerning = new byte[PAGES][];
      byte[] page = kerning[ch >>> LOG2_PAGE_SIZE];
      if (page == null) kerning[ch >>> LOG2_PAGE_SIZE] = page = new byte[PAGE_SIZE];
      page[ch & PAGE_SIZE - 1] = (byte) value;
    }
  }

  static int indexOf(CharSequence text, char ch, int start) {
    final int n = text.length();
    for (; start < n; start++) if (text.charAt(start) == ch) return start;
    return n;
  }

  public static class TextBounds {
    public float width;
    public float height;

    public TextBounds() {}

    public TextBounds(TextBounds bounds) {
      set(bounds);
    }

    public void set(TextBounds bounds) {
      width = bounds.width;
      height = bounds.height;
    }
  }

  public static enum HAlignment {
    LEFT,
    CENTER,
    RIGHT
  }

  /**
   * @param character
   * @return whether the given character is contained in this font.
   */
  public boolean containsCharacter(char character) {
    return getGlyph(character) != null;
  }
}