/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g2d; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.NumberUtils; public class PolygonSprite { PolygonRegion region; private float x, y; private float width, height; private float scaleX = 1f, scaleY = 1f; private float rotation; private float originX, originY; private float[] vertices; private boolean dirty; private Rectangle bounds = new Rectangle(); private final Color color = new Color(1f, 1f, 1f, 1f); // Note the region is copied. public PolygonSprite(PolygonRegion region) { setRegion(region); setColor(1, 1, 1, 1); setSize(region.getRegion().getRegionWidth(), region.getRegion().getRegionHeight()); setOrigin(width / 2, height / 2); } /** Creates a sprite that is a copy in every way of the specified sprite. */ public PolygonSprite(PolygonSprite sprite) { set(sprite); } public void set(PolygonSprite sprite) { if (sprite == null) throw new IllegalArgumentException("sprite cannot be null."); setRegion(sprite.region); x = sprite.x; y = sprite.y; width = sprite.width; height = sprite.height; originX = sprite.originX; originY = sprite.originY; rotation = sprite.rotation; scaleX = sprite.scaleX; scaleY = sprite.scaleY; color.set(sprite.color); dirty = sprite.dirty; } /** * Sets the position and size of the sprite when drawn, before scaling and rotation are applied. If origin, * rotation, or scale are changed, it is slightly more efficient to set the bounds after those operations. */ public void setBounds(float x, float y, float width, float height) { this.x = x; this.y = y; this.width = width; this.height = height; dirty = true; } /** * Sets the size of the sprite when drawn, before scaling and rotation are applied. If origin, rotation, or scale * are changed, it is slightly more efficient to set the size after those operations. If both position and size are * to be changed, it is better to use {@link #setBounds(float, float, float, float)}. */ public void setSize(float width, float height) { this.width = width; this.height = height; dirty = true; } /** * Sets the position where the sprite will be drawn. If origin, rotation, or scale are changed, it is slightly more * efficient to set the position after those operations. If both position and size are to be changed, it is better * to use {@link #setBounds(float, float, float, float)}. */ public void setPosition(float x, float y) { translate(x - this.x, y - this.y); } /** * Sets the x position where the sprite will be drawn. If origin, rotation, or scale are changed, it is slightly * more efficient to set the position after those operations. If both position and size are to be changed, it is * better to use {@link #setBounds(float, float, float, float)}. */ public void setX(float x) { translateX(x - this.x); } /** * Sets the y position where the sprite will be drawn. If origin, rotation, or scale are changed, it is slightly * more efficient to set the position after those operations. If both position and size are to be changed, it is * better to use {@link #setBounds(float, float, float, float)}. */ public void setY(float y) { translateY(y - this.y); } /** * Sets the x position relative to the current position where the sprite will be drawn. If origin, rotation, or * scale are changed, it is slightly more efficient to translate after those operations. */ public void translateX(float xAmount) { this.x += xAmount; if (dirty) return; final float[] vertices = this.vertices; for (int i = 0; i < vertices.length; i += Sprite.VERTEX_SIZE) { vertices[i] += xAmount; } } /** * Sets the y position relative to the current position where the sprite will be drawn. If origin, rotation, or * scale are changed, it is slightly more efficient to translate after those operations. */ public void translateY(float yAmount) { y += yAmount; if (dirty) return; final float[] vertices = this.vertices; for (int i = 0; i < vertices.length; i += Sprite.VERTEX_SIZE) { vertices[i + 1] += yAmount; } } /** * Sets the position relative to the current position where the sprite will be drawn. If origin, rotation, or scale * are changed, it is slightly more efficient to translate after those operations. */ public void translate(float xAmount, float yAmount) { x += xAmount; y += yAmount; if (dirty) return; final float[] vertices = this.vertices; for (int i = 0; i < vertices.length; i += Sprite.VERTEX_SIZE) { vertices[i] += xAmount; vertices[i + 1] += yAmount; } } public void setColor(Color tint) { float color = tint.toFloatBits(); final float[] vertices = this.vertices; for (int i = 0; i < vertices.length; i += Sprite.VERTEX_SIZE) { vertices[i + 2] = color; } } 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)); float color = NumberUtils.intToFloatColor(intBits); final float[] vertices = this.vertices; for (int i = 0; i < vertices.length; i += Sprite.VERTEX_SIZE) { vertices[i + 2] = color; } } /** Sets the origin in relation to the sprite's position for scaling and rotation. */ public void setOrigin(float originX, float originY) { this.originX = originX; this.originY = originY; dirty = true; } public void setRotation(float degrees) { this.rotation = degrees; dirty = true; } /** Sets the sprite's rotation relative to the current rotation. */ public void rotate(float degrees) { rotation += degrees; dirty = true; } public void setScale(float scaleXY) { this.scaleX = scaleXY; this.scaleY = scaleXY; dirty = true; } public void setScale(float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; dirty = true; } /** Sets the sprite's scale relative to the current scale. */ public void scale(float amount) { this.scaleX += amount; this.scaleY += amount; dirty = true; } /** Returns the packed vertices, colors, and texture coordinates for this sprite. */ public float[] getVertices() { if (dirty) { dirty = false; final float worldOriginX = x + originX; final float worldOriginY = y + originY; float sX = width / region.getRegion().getRegionWidth(); float sY = height / region.getRegion().getRegionHeight(); float fx, rx; float fy, ry; float[] localVertices = region.getLocalVertices(); final float cos = MathUtils.cosDeg(rotation); final float sin = MathUtils.sinDeg(rotation); for (int i = 0; i < localVertices.length; i += 2) { fx = localVertices[i] * sX; fy = localVertices[i + 1] * sY; fx -= originX; fy -= originY; if (scaleX != 1.0f || scaleY != 1.0) { fx *= scaleX; fy *= scaleY; } rx = cos * fx - sin * fy; ry = sin * fx + cos * fy; rx += worldOriginX; ry += worldOriginY; vertices[(i / 2) * 5] = rx; vertices[((i / 2) * 5) + 1] = ry; } } return vertices; } /** * Returns the bounding axis aligned {@link Rectangle} that bounds this sprite. The rectangles x and y coordinates * describe its bottom left corner. If you change the position or size of the sprite, you have to fetch the triangle * again for it to be recomputed. * * @return the bounding Rectangle */ public Rectangle getBoundingRectangle() { final float[] vertices = getVertices(); float minx = vertices[0]; float miny = vertices[1]; float maxx = vertices[0]; float maxy = vertices[1]; for (int i = 0; i < vertices.length; i += 5) { minx = minx > vertices[i] ? vertices[i] : minx; maxx = maxx < vertices[i] ? vertices[i] : maxx; miny = miny > vertices[i + 1] ? vertices[i + 1] : miny; maxy = maxy < vertices[i + 1] ? vertices[i + 1] : maxy; } bounds.x = minx; bounds.y = miny; bounds.width = maxx - minx; bounds.height = maxy - miny; return bounds; } public void draw(PolygonSpriteBatch spriteBatch) { spriteBatch.draw(region, getVertices(), 0, vertices.length); } public void draw(PolygonSpriteBatch spriteBatch, float alphaModulation) { Color color = getColor(); float oldAlpha = color.a; color.a *= alphaModulation; setColor(color); draw(spriteBatch); color.a = oldAlpha; setColor(color); } public float getX() { return x; } public float getY() { return y; } public float getWidth() { return width; } public float getHeight() { return height; } public float getOriginX() { return originX; } public float getOriginY() { return originY; } public float getRotation() { return rotation; } public float getScaleX() { return scaleX; } public float getScaleY() { return scaleY; } /** * Returns the color of this sprite. 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 = NumberUtils.floatToIntColor(vertices[2]); Color color = this.color; 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 setRegion(PolygonRegion region) { this.region = region; float[] localVertices = region.getLocalVertices(); float[] localTextureCoords = region.getTextureCoords(); if (vertices == null || localVertices.length != vertices.length) vertices = new float[(localVertices.length / 2) * 5]; // Pack the region info into this sprite's vertices for (int i = 0; i < localVertices.length / 2; i++) { vertices[(i * 5)] = localVertices[(i * 2)]; vertices[(i * 5) + 1] = localVertices[(i * 2) + 1]; vertices[(i * 5) + 2] = color.toFloatBits(); vertices[(i * 5) + 3] = localTextureCoords[(i * 2)]; vertices[(i * 5) + 4] = localTextureCoords[(i * 2) + 1]; } } }