/******************************************************************************* * 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.graphics.g2d.PolygonSpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.*; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ShortArray; /** * Renders polygon filled with a repeating TextureRegion with specified density * Without causing an additional flush or render call * * @author Avetis Zakharyan */ public class RepeatablePolygonSprite { private TextureRegion region; private float density; private boolean dirty = true; private Array<float[]> parts = new Array<float[]>(); private Array<float[]> vertices = new Array<float[]>(); private Array<short[]> indices = new Array<short[]>(); private int cols, rows; private float gridWidth, gridHeight; public float x = 0; public float y = 0; private Color color = Color.WHITE; private Vector2 offset = new Vector2(); /** * Sets polygon with repeating texture region, the size of repeating grid is equal to region size * @param region - region to repeat * @param vertices - cw vertices of polygon */ public void setPolygon(TextureRegion region, float[] vertices) { setPolygon(region, vertices, -1); } /** * Sets polygon with repeating texture region, the size of repeating grid is equal to region size * @param region - region to repeat * @param vertices - cw vertices of polygon * @param density - number of regions per polygon width bound */ public void setPolygon(TextureRegion region, float[] vertices, float density) { this.region = region; vertices = offset(vertices); Polygon polygon = new Polygon(vertices); Polygon tmpPoly = new Polygon(); Polygon intersectionPoly = new Polygon(); EarClippingTriangulator triangulator = new EarClippingTriangulator(); int idx; Rectangle boundRect = polygon.getBoundingRectangle(); if(density == -1) density = boundRect.getWidth()/region.getRegionWidth(); float regionAspectRatio = (float) region.getRegionHeight() / (float) region.getRegionWidth(); cols = (int) (Math.ceil(density)); gridWidth = boundRect.getWidth() / density; gridHeight = regionAspectRatio * gridWidth; rows = (int) Math.ceil(boundRect.getHeight() / gridHeight); for(int col = 0; col < cols; col++) { for(int row = 0; row < rows; row++) { float[] verts = new float[8]; idx = 0; verts[idx++] = col * gridWidth; verts[idx++] = row * gridHeight; verts[idx++] = (col) * gridWidth; verts[idx++] = (row+1) * gridHeight; verts[idx++] = (col+1) * gridWidth; verts[idx++] = (row+1) * gridHeight; verts[idx++] = (col+1) * gridWidth; verts[idx] = (row) * gridHeight; tmpPoly.setVertices(verts); Intersector.intersectPolygons(polygon, tmpPoly, intersectionPoly); verts = intersectionPoly.getVertices(); if(verts.length > 0) { parts.add(snapToGrid(verts)); ShortArray arr = triangulator.computeTriangles(verts); indices.add(arr.toArray()); } else { // adding null for key consistancy, needed to get col/row from key // the other alternative is to make parts - IntMap<FloatArray> parts.add(null); } } } buildVertices(); } /** * This is a garbage, due to Intersector returning values slightly different then the grid values * Snapping exactly to grid is important, so that during bulidVertices method, it can be figured out * if points is on the wall of it's own grid box or not, to set u/v properly. * Any other implementations are welcome */ private float[] snapToGrid(float[] vertices) { for(int i = 0; i < vertices.length; i+=2) { float numX = (vertices[i] / gridWidth) % 1; float numY = (vertices[i+1] / gridHeight) % 1; if(numX > 0.99f || numX < 0.01f) { vertices[i] = gridWidth * Math.round(vertices[i] / gridWidth); } if(numY > 0.99f || numY < 0.01f) { vertices[i+1] = gridHeight * Math.round(vertices[i+1] / gridHeight); } } return vertices; } /** * Offsets polygon to 0 coordinate for ease of calculations, later offset is put back on final render * @param vertices * @return offsetted vertices */ private float[] offset(float[] vertices) { offset.set(vertices[0], vertices[1]); for(int i = 0; i < vertices.length-1; i+=2) { if(offset.x > vertices[i]) { offset.x = vertices[i]; } if(offset.y > vertices[i+1]) { offset.y = vertices[i+1]; } } for(int i = 0; i < vertices.length; i+=2) { vertices[i] -= offset.x; vertices[i+1] -= offset.y; } return vertices; } /** * Builds final vertices with vertex attributes like coordinates, color and region u/v */ private void buildVertices() { vertices.clear(); for(int i = 0; i < parts.size; i++) { float verts[] = parts.get(i); if(verts == null) continue; float[] fullVerts = new float[5 * verts.length/2]; int idx = 0; int col = i / rows; int row = i % rows; for(int j = 0; j < verts.length; j+=2) { fullVerts[idx++] = verts[j] + offset.x + x; fullVerts[idx++] = verts[j+1] + offset.y + y; fullVerts[idx++] = color.toFloatBits(); float u = (verts[j] % gridWidth) / gridWidth; float v = (verts[j+1] % gridHeight) / gridHeight; if(verts[j] == col*gridWidth) u = 0f; if(verts[j] == (col+1)*gridWidth) u = 1f; if(verts[j+1] == row*gridHeight) v = 0f; if(verts[j+1] == (row+1)*gridHeight)v = 1f; u = region.getU() + (region.getU2() - region.getU()) * u; v = region.getV() + (region.getV2() - region.getV()) * v; fullVerts[idx++] = u; fullVerts[idx++] = v; } vertices.add(fullVerts); } dirty = false; } public void draw(PolygonSpriteBatch batch) { if(dirty) { buildVertices(); } for(int i = 0; i < vertices.size; i++) { batch.draw(region.getTexture(), vertices.get(i), 0, vertices.get(i).length, indices.get(i), 0, indices.get(i).length); } } /** * @param color - Tint color to be applied to entire polygon */ public void setColor(Color color) { this.color = color; dirty = true; } public void setPosition(float x, float y) { this.x = x; this.y = y; dirty = true; } }