com.badlogic.gdx.graphics.g3d.decals.DecalBatch.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.graphics.g3d.decals.DecalBatch.java

Source

/*******************************************************************************
 * 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.g3d.decals;

import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.SortedIntList;

/** <p>
 * Renderer for {@link Decal} objects.
 * </p>
 * <p>
 * New objects are added using {@link DecalBatch#add(Decal)}, there is no limit on how many decals can be added.<br/>
 * Once all the decals have been submitted a call to {@link DecalBatch#flush()} will batch them together and send big chunks of
 * geometry to the GL.
 * </p>
 * <p>
 * The size of the batch specifies the maximum number of decals that can be batched together before they have to be submitted to
 * the graphics pipeline. The default size is {@link DecalBatch#DEFAULT_SIZE}. If it is known before hand that not as many will be
 * needed on average the batch can be downsized to save memory. If the game is basically 3d based and decals will only be needed
 * for an orthogonal HUD it makes sense to tune the size down.
 * </p>
 * <p>
 * The way the batch handles things depends on the {@link GroupStrategy}. Different strategies can be used to customize shaders,
 * states, culling etc. for more details see the {@link GroupStrategy} java doc.<br/>
 * While it shouldn't be necessary to change strategies, if you have to do so, do it before calling {@link #add(Decal)}, and if
 * you already did, call {@link #flush()} first.
 * </p> */
public class DecalBatch implements Disposable {
    private static final int DEFAULT_SIZE = 1000;
    private float[] vertices;
    private Mesh mesh;

    private final SortedIntList<Array<Decal>> groupList = new SortedIntList<Array<Decal>>();
    private GroupStrategy groupStrategy;
    private final Pool<Array<Decal>> groupPool = new Pool<Array<Decal>>(16) {
        @Override
        protected Array<Decal> newObject() {
            return new Array<Decal>(false, 100);
        }
    };
    private final Array<Array<Decal>> usedGroups = new Array<Array<Decal>>(16);

    /**
     * Creates a new DecalBatch using the given {@link GroupStrategy}. The most
     * commong strategy to use is a {@link CameraGroupStrategy}
     * @param groupStrategy
     */
    public DecalBatch(GroupStrategy groupStrategy) {
        this(DEFAULT_SIZE, groupStrategy);
    }

    public DecalBatch(int size, GroupStrategy groupStrategy) {
        initialize(size);
        setGroupStrategy(groupStrategy);
    }

    /** Sets the {@link GroupStrategy} used
     * @param groupStrategy Group strategy to use */
    public void setGroupStrategy(GroupStrategy groupStrategy) {
        this.groupStrategy = groupStrategy;
    }

    /** Initializes the batch with the given amount of decal objects the buffer is able to hold when full.
     * 
     * @param size Maximum size of decal objects to hold in memory */
    public void initialize(int size) {
        vertices = new float[size * Decal.SIZE];
        mesh = new Mesh(Mesh.VertexDataType.VertexArray, false, size * 4, size * 6,
                new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE),
                new VertexAttribute(VertexAttributes.Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
                new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2,
                        ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));

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

    /** @return maximum amount of decal objects this buffer can hold in memory */
    public int getSize() {
        return vertices.length / Decal.SIZE;
    }

    /** Add a decal to the batch, marking it for later rendering
     * 
     * @param decal Decal to add for rendering */
    public void add(Decal decal) {
        int groupIndex = groupStrategy.decideGroup(decal);
        Array<Decal> targetGroup = groupList.get(groupIndex);
        if (targetGroup == null) {
            targetGroup = groupPool.obtain();
            targetGroup.clear();
            usedGroups.add(targetGroup);
            groupList.insert(groupIndex, targetGroup);
        }
        targetGroup.add(decal);
    }

    /** Flush this batch sending all contained decals to GL. After flushing the batch is empty once again. */
    public void flush() {
        render();
        clear();
    }

    /** Renders all decals to the buffer and flushes the buffer to the GL when full/done */
    protected void render() {
        groupStrategy.beforeGroups();
        for (SortedIntList.Node<Array<Decal>> group : groupList) {
            groupStrategy.beforeGroup(group.index, group.value);
            ShaderProgram shader = groupStrategy.getGroupShader(group.index);
            render(shader, group.value);
            groupStrategy.afterGroup(group.index);
        }
        groupStrategy.afterGroups();
    }

    /** Renders a group of vertices to the buffer, flushing them to GL when done/full
     * 
     * @param decals Decals to render */
    private void render(ShaderProgram shader, Array<Decal> decals) {
        // batch vertices
        DecalMaterial lastMaterial = null;
        int idx = 0;
        for (Decal decal : decals) {
            if (lastMaterial == null || !lastMaterial.equals(decal.getMaterial())) {
                if (idx > 0) {
                    flush(shader, idx);
                    idx = 0;
                }
                decal.material.set();
                lastMaterial = decal.material;
            }
            decal.update();
            System.arraycopy(decal.vertices, 0, vertices, idx, decal.vertices.length);
            idx += decal.vertices.length;
            // if our batch is full we have to flush it
            if (idx == vertices.length) {
                flush(shader, idx);
                idx = 0;
            }
        }
        // at the end if there is stuff left in the batch we render that
        if (idx > 0) {
            flush(shader, idx);
        }
    }

    /** Flushes vertices[0,verticesPosition[ to GL verticesPosition % Decal.SIZE must equal 0
     * 
     * @param verticesPosition Amount of elements from the vertices array to flush */
    protected void flush(ShaderProgram shader, int verticesPosition) {
        mesh.setVertices(vertices, 0, verticesPosition);
        mesh.render(shader, GL20.GL_TRIANGLES, 0, verticesPosition / 4);
    }

    /** Remove all decals from batch */
    protected void clear() {
        groupList.clear();
        groupPool.freeAll(usedGroups);
        usedGroups.clear();
    }

    /** Frees up memory by dropping the buffer and underlying resources. If the batch is needed again after disposing it can be
     * {@link #initialize(int) initialized} again. */
    public void dispose() {
        clear();
        vertices = null;
        mesh.dispose();
    }
}