/******************************************************************************* * 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.glutils; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.utils.GdxRuntimeException; /** * Renders points, lines, rectangles, filled rectangles and boxes.</p> * * This class works with OpenGL ES 1.x and 2.0. In its base configuration a 2D orthographic projection with the origin * in the lower left corner is used. Units are given in screen pixels.</p> * * To change the projection properties use the {@link #setProjectionMatrix(Matrix4)} method. Usually the * {@link Camera#combined} matrix is set via this method. If the screen orientation or resolution changes, the * projection matrix might have to be adapted as well.</p> * * Shapes are rendered in batches to increase performance. The standard use-pattern looks as follows: * * <pre> * {@code * camera.update(); * shapeRenderer.setProjectionMatrix(camera.combined); * * shapeRenderer.begin(ShapeType.Line); * shapeRenderer.color(1, 1, 0, 1); * shapeRenderer.line(x, y, x2, y2); * shapeRenderer.line(x3, y3, x4, y4); * shapeRenderer.end(); * * shapeRenderer.begin(ShapeType.Box); * shapeRenderer.color(0, 1, 0, 1); * shapeRenderer.box(x, y, z, width, height, depth); * shapeRenderer.end(); * } * </pre> * * The class has a second matrix called the transformation matrix which is used to rotate, scale and translate shapes in * a more flexible manner. This mechanism works much like matrix operations in OpenGL ES 1.x. The following example * shows how to rotate a rectangle around its center using the z-axis as the rotation axis and placing it's center at * (20, 12, 2): * * <pre> * shapeRenderer.begin(ShapeType.Rectangle); * shapeRenderer.identity(); * shapeRenderer.translate(20, 12, 2); * shapeRenderer.rotate(0, 0, 1, 90); * shapeRenderer.rect(-width / 2, -height / 2, width, height); * shapeRenderer.end(); * </pre> * * Matrix operations all use postmultiplication and work just like glTranslate, glScale and glRotate. The last * transformation specified will be the first that is applied to a shape (rotate then translate in the above example). * * The projection and transformation matrices are a state of the ShapeRenderer, just like the color and will be applied * to all shapes until they are changed. * * @author mzechner */ public class ShapeRenderer { /** * Shape types to be used with {@link #begin(ShapeType)}. * * @author mzechner */ public enum ShapeType { Point(GL10.GL_POINTS), // Line(GL10.GL_LINES), // Rectangle(GL10.GL_LINES), // FilledRectangle(GL10.GL_TRIANGLES), // Box(GL10.GL_LINES), // Circle(GL10.GL_LINES), // FilledCircle(GL10.GL_TRIANGLES), // Triangle(GL10.GL_LINES), // FilledTriangle(GL10.GL_TRIANGLES), // Cone(GL10.GL_LINES), // FilledCone(GL10.GL_TRIANGLES), // Curve(GL10.GL_LINES), // ; private final int glType; ShapeType(int glType) { this.glType = glType; } public int getGlType() { return glType; } } ImmediateModeRenderer renderer; boolean matrixDirty = false; Matrix4 projView = new Matrix4(); Matrix4 transform = new Matrix4(); Matrix4 combined = new Matrix4(); Matrix4 tmp = new Matrix4(); Color color = new Color(1, 1, 1, 1); ShapeType currType = null; public ShapeRenderer() { this(5000); } public ShapeRenderer(int maxVertices) { if (Gdx.graphics.isGL20Available()) renderer = new ImmediateModeRenderer20(maxVertices, false, true, 0); else renderer = new ImmediateModeRenderer10(maxVertices); projView.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); matrixDirty = true; } /** * Sets the {@link Color} to be used by shapes. * * @param color */ public void setColor(Color color) { this.color.set(color); } /** * Sets the {@link Color} to be used by shapes. * * @param r * @param g * @param b * @param a */ public void setColor(float r, float g, float b, float a) { this.color.set(r, g, b, a); } /** * Sets the projection matrix to be used for rendering. Usually this will be set to {@link Camera#combined}. * * @param matrix */ public void setProjectionMatrix(Matrix4 matrix) { projView.set(matrix); matrixDirty = true; } public void setTransformMatrix(Matrix4 matrix) { transform.set(matrix); matrixDirty = true; } /** Sets the transformation matrix to identity. */ public void identity() { transform.idt(); matrixDirty = true; } /** * Multiplies the current transformation matrix by a translation matrix. * * @param x * @param y * @param z */ public void translate(float x, float y, float z) { transform.translate(x, y, z); matrixDirty = true; } /** * Multiplies the current transformation matrix by a rotation matrix. * * @param angle * angle in degrees * @param axisX * @param axisY * @param axisZ */ public void rotate(float axisX, float axisY, float axisZ, float angle) { transform.rotate(axisX, axisY, axisZ, angle); matrixDirty = true; } /** * Multiplies the current transformation matrix by a scale matrix. * * @param scaleX * @param scaleY * @param scaleZ */ public void scale(float scaleX, float scaleY, float scaleZ) { transform.scale(scaleX, scaleY, scaleZ); matrixDirty = true; } /** * Starts a new batch of shapes. All shapes within the batch have to have the type specified. E.g. if * {@link ShapeType#Point} is specified, only call #point(). * * The call to this method must be paired with a call to {@link #end()}. * * In case OpenGL ES 1.x is used, the projection and modelview matrix will be modified. * * @param type * the {@link ShapeType}. */ public void begin(ShapeType type) { if (currType != null) throw new GdxRuntimeException("Call end() before beginning a new shape batch"); currType = type; if (matrixDirty) { combined.set(projView); Matrix4.mul(combined.val, transform.val); matrixDirty = false; } renderer.begin(combined, currType.getGlType()); } /** * Draws a point. The {@link ShapeType} passed to begin has to be {@link ShapeType#Point}. * * @param x * @param y * @param z */ public void point(float x, float y, float z) { if (currType != ShapeType.Point) throw new GdxRuntimeException("Must call begin(ShapeType.Point)"); checkDirty(); checkFlush(1); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); } /** * Draws a line. The {@link ShapeType} passed to begin has to be {@link ShapeType#Line}. * * @param x * @param y * @param z * @param x2 * @param y2 * @param z2 */ public void line(float x, float y, float z, float x2, float y2, float z2) { if (currType != ShapeType.Line) throw new GdxRuntimeException("Must call begin(ShapeType.Line)"); checkDirty(); checkFlush(2); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, z2); } /** * Draws a line in the x/y plane. The {@link ShapeType} passed to begin has to be {@link ShapeType#Line}. * * @param x * @param y * @param x2 * @param y2 */ public void line(float x, float y, float x2, float y2) { if (currType != ShapeType.Line) throw new GdxRuntimeException("Must call begin(ShapeType.Line)"); checkDirty(); checkFlush(2); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, 0); } /** * Calls {@link #curve(float, float, float, float, float, float, float, float, int)} by estimating the number of * segments needed for a smooth curve. */ public void curve(float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2) { float dx1 = cx1 - x1; float dy1 = cy1 - y1; float dx2 = cx2 - cx1; float dy2 = cy2 - cy1; float dx3 = x2 - cx2; float dy3 = y2 - cy2; float length = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1) + (float) Math.sqrt(dx2 * dx2 + dy2 * dy2) + (float) Math.sqrt(dx3 * dx3 + dy3 * dy3); curve(x1, y1, cx1, cy1, cx2, cy2, x2, y2, 4 * (int) Math.cbrt(length)); } /** Draws a curve in the x/y plane. The {@link ShapeType} passed to begin has to be {@link ShapeType#Curve}. */ public void curve(float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2, int segments) { if (currType != ShapeType.Curve) throw new GdxRuntimeException("Must call begin(ShapeType.Curve)"); checkDirty(); checkFlush(segments * 2 + 2); // Algorithm from: http://www.antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION float subdiv_step = 1f / segments; float subdiv_step2 = subdiv_step * subdiv_step; float subdiv_step3 = subdiv_step * subdiv_step * subdiv_step; float pre1 = 3 * subdiv_step; float pre2 = 3 * subdiv_step2; float pre4 = 6 * subdiv_step2; float pre5 = 6 * subdiv_step3; float tmp1x = x1 - cx1 * 2 + cx2; float tmp1y = y1 - cy1 * 2 + cy2; float tmp2x = (cx1 - cx2) * 3 - x1 + x2; float tmp2y = (cy1 - cy2) * 3 - y1 + y2; float fx = x1; float fy = y1; float dfx = (cx1 - x1) * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3; float dfy = (cy1 - y1) * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3; float ddfx = tmp1x * pre4 + tmp2x * pre5; float ddfy = tmp1y * pre4 + tmp2y * pre5; float dddfx = tmp2x * pre5; float dddfy = tmp2y * pre5; while (segments-- > 0) { renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(fx, fy, 0); fx += dfx; fy += dfy; dfx += ddfx; dfy += ddfy; ddfx += dddfx; ddfy += dddfy; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(fx, fy, 0); } renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(fx, fy, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, 0); } /** * Draws a rectangle in the x/y plane. The x and y coordinate specify the bottom left corner of the rectangle. The * {@link ShapeType} passed to begin has to be {@link ShapeType#Rectangle}. * * @param x * @param y * @param width * @param height */ public void rect(float x, float y, float width, float height) { if (currType != ShapeType.Rectangle) throw new GdxRuntimeException("Must call begin(ShapeType.Rectangle)"); checkDirty(); checkFlush(8); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); } /** * Draws a filled rectangle in the x/y plane. The x and y coordinate specify the bottom left corner of the * rectangle. The {@link ShapeType} passed to begin has to be {@link ShapeType#FilledRectangle}. * * @param x * @param y * @param width * @param height */ public void filledRect(float x, float y, float width, float height) { if (currType != ShapeType.FilledRectangle) throw new GdxRuntimeException("Must call begin(ShapeType.FilledRectangle)"); checkDirty(); checkFlush(8); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); } /** * Draws a filled rectangle in the x/y plane. The x and y coordinate specify the bottom left corner of the * rectangle. The {@link ShapeType} passed to begin has to be {@link ShapeType#FilledRectangle}. The 4 color * parameters specify the color for the bottom left, bottom right, top right and top left corner of the rectangle, * allowing you to create gradients. * * @param x * @param y * @param width * @param height */ public void filledRect(float x, float y, float width, float height, Color c1, Color c2, Color c3, Color c4) { if (currType != ShapeType.FilledRectangle) throw new GdxRuntimeException("Must call begin(ShapeType.FilledRectangle)"); checkDirty(); checkFlush(8); renderer.color(c1.r, c1.g, c1.b, c1.a); renderer.vertex(x, y, 0); renderer.color(c2.r, c2.g, c2.b, c2.a); renderer.vertex(x + width, y, 0); renderer.color(c3.r, c3.g, c3.b, c3.a); renderer.vertex(x + width, y + height, 0); renderer.color(c3.r, c3.g, c3.b, c3.a); renderer.vertex(x + width, y + height, 0); renderer.color(c4.r, c4.g, c4.b, c4.a); renderer.vertex(x, y + height, 0); renderer.color(c1.r, c1.g, c1.b, c1.a); renderer.vertex(x, y, 0); } /** * Draws a box. The x, y and z coordinate specify the bottom left front corner of the rectangle. The * {@link ShapeType} passed to begin has to be {@link ShapeType#Box}. * * @param x * @param y * @param width * @param height */ public void box(float x, float y, float z, float width, float height, float depth) { if (currType != ShapeType.Box) throw new GdxRuntimeException("Must call begin(ShapeType.Box)"); checkDirty(); checkFlush(16); depth = -depth; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + width, y + height, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z + depth); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y + height, z + depth); } /** Calls {@link #circle(float, float, float, int)} by estimating the number of segments needed for a smooth circle. */ public void circle(float x, float y, float radius) { circle(x, y, radius, (int) (6 * (float) Math.cbrt(radius))); } public void circle(float x, float y, float radius, int segments) { if (segments <= 0) throw new IllegalArgumentException("segments must be >= 0."); if (currType != ShapeType.Circle) throw new GdxRuntimeException("Must call begin(ShapeType.Circle)"); checkDirty(); checkFlush(segments * 2 + 2); float angle = 2 * 3.1415926f / segments; float cos = MathUtils.cos(angle); float sin = MathUtils.sin(angle); float cx = radius, cy = 0; for (int i = 0; i < segments; i++) { renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); float temp = cx; cx = cos * cx - sin * cy; cy = sin * temp + cos * cy; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); } // Ensure the last segment is identical to the first. renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); float temp = cx; cx = radius; cy = 0; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); } /** * Calls {@link #filledCircle(float, float, float, int)} by estimating the number of segments needed for a smooth * circle. */ public void filledCircle(float x, float y, float radius) { filledCircle(x, y, radius, (int) (6 * (float) Math.cbrt(radius))); } public void filledCircle(float x, float y, float radius, int segments) { if (segments <= 0) throw new IllegalArgumentException("segments must be >= 0."); if (currType != ShapeType.FilledCircle) throw new GdxRuntimeException("Must call begin(ShapeType.FilledCircle)"); checkDirty(); checkFlush(segments * 3 + 3); int inc = 360 / segments; float angle = 2 * 3.1415926f / segments; float cos = MathUtils.cos(angle); float sin = MathUtils.sin(angle); float cx = radius, cy = 0; segments--; for (int i = 0; i < segments; i++) { renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); float temp = cx; cx = cos * cx - sin * cy; cy = sin * temp + cos * cy; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); } // Ensure the last segment is identical to the first. renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); cx = radius; cy = 0; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, 0); } public void triangle(float x1, float y1, float x2, float y2, float x3, float y3) { if (currType != ShapeType.Triangle) throw new GdxRuntimeException("Must call begin(ShapeType.Triangle)"); checkDirty(); checkFlush(6); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x1, y1, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x3, y3, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x3, y3, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x1, y1, 0); } public void filledTriangle(float x1, float y1, float x2, float y2, float x3, float y3) { if (currType != ShapeType.FilledTriangle) throw new GdxRuntimeException("Must call begin(ShapeType.FilledTriangle)"); checkDirty(); checkFlush(3); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x1, y1, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x2, y2, 0); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x3, y3, 0); } public void cone(float x, float y, float z, float radius, float height) { cone(x, y, z, radius, height, (int) (6 * (float) Math.cbrt(radius))); } public void cone(float x, float y, float z, float radius, float height, int segments) { if (currType != ShapeType.Cone) throw new GdxRuntimeException("Must call begin(ShapeType.Cone)"); checkDirty(); checkFlush(segments * 4 + 2); float angle = 2 * 3.1415926f / segments; float cos = MathUtils.cos(angle); float sin = MathUtils.sin(angle); float cx = radius, cy = 0; for (int i = 0; i < segments; i++) { renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z + height); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); float temp = cx; cx = cos * cx - sin * cy; cy = sin * temp + cos * cy; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); } // Ensure the last segment is identical to the first. renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); float temp = cx; cx = radius; cy = 0; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); } /** * Calls {@link #filledCone(float, float, float, float, float, int)} by estimating the number of segments needed for * a smooth circular base. */ public void filledCone(float x, float y, float z, float radius, float height) { filledCone(x, y, z, radius, height, (int) (4 * (float) Math.sqrt(radius))); } public void filledCone(float x, float y, float z, float radius, float height, int segments) { if (segments <= 0) throw new IllegalArgumentException("segments must be >= 0."); if (currType != ShapeType.FilledCone) throw new GdxRuntimeException("Must call begin(ShapeType.FilledCone)"); checkDirty(); checkFlush(segments * 6 + 3); int inc = 360 / segments; float angle = 2 * 3.1415926f / segments; float cos = MathUtils.cos(angle); float sin = MathUtils.sin(angle); float cx = radius, cy = 0; segments--; for (int i = 0; i < segments; i++) { renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); float temp = cx; float temp2 = cy; cx = cos * cx - sin * cy; cy = sin * temp + cos * cy; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + temp, y + temp2, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z + height); } // Ensure the last segment is identical to the first. renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x, y, z); renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); cx = radius; cy = 0; renderer.color(color.r, color.g, color.b, color.a); renderer.vertex(x + cx, y + cy, z); } private void checkDirty() { if (!matrixDirty) return; ShapeType type = currType; end(); begin(type); } private void checkFlush(int newVertices) { if (renderer.getMaxVertices() - renderer.getNumVertices() >= newVertices) return; ShapeType type = currType; end(); begin(type); } /** Finishes the batch of shapes and ensures they get rendered. */ public void end() { renderer.end(); currType = null; } public void flush() { ShapeType type = currType; end(); begin(type); } /** Returns the current {@link ShapeType} used */ public ShapeType getCurrentType() { return currType; } public void dispose() { renderer.dispose(); } }