com.grillecube.engine.renderer.world.particles.ParticleRenderer.java Source code

Java tutorial

Introduction

Here is the source code for com.grillecube.engine.renderer.world.particles.ParticleRenderer.java

Source

/**
**   This file is part of the project https://github.com/toss-dev/VoxelEngine
**
**   License is available here: https://raw.githubusercontent.com/toss-dev/VoxelEngine/master/LICENSE.md
**
**   PEREIRA Romain
**                                       4-----7          
**                                      /|    /|
**                                     0-----3 |
**                                     | 5___|_6
**                                     |/    | /
**                                     1-----2
*/

package com.grillecube.engine.renderer.world.particles;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Comparator;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;

import com.grillecube.engine.Logger;
import com.grillecube.engine.Taskable;
import com.grillecube.engine.VoxelEngine;
import com.grillecube.engine.geometry.Cube;
import com.grillecube.engine.maths.Maths;
import com.grillecube.engine.maths.Matrix4f;
import com.grillecube.engine.maths.Vector4f;
import com.grillecube.engine.opengl.GLH;
import com.grillecube.engine.opengl.object.GLVertexArray;
import com.grillecube.engine.opengl.object.GLVertexBuffer;
import com.grillecube.engine.renderer.MainRenderer;
import com.grillecube.engine.renderer.camera.CameraProjectiveWorld;
import com.grillecube.engine.renderer.world.RendererWorld;
import com.grillecube.engine.renderer.world.ShadowCamera;
import com.grillecube.engine.world.World;

/** a simple cube rendering system for particles */
public class ParticleRenderer extends RendererWorld {
    /** program */
    private ProgramParticleBillboarded _program_billboarded_particle;
    private ProgramParticleCube _program_cube;

    // one array list is an array list of particles (one list for each sprite)
    private ArrayList<ParticleBillboarded> _billboarded_particles;
    private ArrayList<ParticleCube> _cube_particles;

    /** cube and quads vaos */
    private GLVertexArray _vao_cube;
    // the vbo which contains cube model data
    private GLVertexBuffer _vbo_cube;
    // the vbo which contains every cubes instances informations
    private GLVertexBuffer _vbo_cube_instances;
    private int _cubes_in_buffer;

    // transf matrix + color + health
    public static final int FLOATS_PER_CUBE_INSTANCE = 16 + 4 + 1;

    // TODO: increase it maybe
    private static final int MAX_CUBE_PARTICLES = 100000;

    private Comparator<ParticleBillboarded> _particle_comparator = new Comparator<ParticleBillboarded>() {
        @Override
        public int compare(ParticleBillboarded a, ParticleBillboarded b) {
            if (a.getCameraSquareDistance() < b.getCameraSquareDistance()) {
                return (1);
            } else if (a.getCameraSquareDistance() > b.getCameraSquareDistance()) {
                return (-1);
            }
            return (0);
        }

    };

    public ParticleRenderer(MainRenderer main_renderer) {
        super(main_renderer);
    }

    @Override
    public void initialize() {

        this._cubes_in_buffer = 0;

        this._program_billboarded_particle = new ProgramParticleBillboarded();
        this._program_cube = new ProgramParticleCube();

        this._billboarded_particles = new ArrayList<ParticleBillboarded>();
        this._cube_particles = new ArrayList<ParticleCube>();

        this.initializeVAO();
    }

    /** create a new cube object */
    private void initializeVAO() {
        this._vao_cube = GLH.glhGenVAO();
        this._vbo_cube = GLH.glhGenVBO();

        this._vao_cube.bind();

        this._vbo_cube.bind(GL15.GL_ARRAY_BUFFER);
        this._vbo_cube.bufferData(GL15.GL_ARRAY_BUFFER, Cube.makeWithTrianglesAndFaces(1), GL15.GL_STATIC_DRAW);
        this._vao_cube.setAttribute(this._vbo_cube, 0, 4, GL11.GL_FLOAT, false, 4 * 4, 0);

        this._vbo_cube_instances = GLH.glhGenVBO();
        this._vbo_cube_instances.bind(GL15.GL_ARRAY_BUFFER);
        this._vbo_cube_instances.bufferSize(GL15.GL_ARRAY_BUFFER, 0, GL15.GL_STREAM_DRAW);
        this._vao_cube.setAttributeInstanced(1, 4, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 0 * 4);
        this._vao_cube.setAttributeInstanced(2, 4, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 4 * 4);
        this._vao_cube.setAttributeInstanced(3, 4, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 8 * 4);
        this._vao_cube.setAttributeInstanced(4, 4, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 12 * 4);
        this._vao_cube.setAttributeInstanced(5, 4, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 16 * 4);
        this._vao_cube.setAttributeInstanced(6, 1, GL11.GL_FLOAT, false, FLOATS_PER_CUBE_INSTANCE * 4, 20 * 4);

        this._vao_cube.enableAttribute(0);
        this._vao_cube.enableAttribute(1);
        this._vao_cube.enableAttribute(2);
        this._vao_cube.enableAttribute(3);
        this._vao_cube.enableAttribute(4);
        this._vao_cube.enableAttribute(5);
        this._vao_cube.enableAttribute(6);
    }

    @Override
    public void deinitialize() {

        GLH.glhDeleteObject(this._program_billboarded_particle);
        this._program_billboarded_particle = null;

        GLH.glhDeleteObject(this._program_cube);
        this._program_cube = null;

        GLH.glhDeleteObject(this._vao_cube);
        this._vao_cube = null;

        GLH.glhDeleteObject(this._vbo_cube);
        this._vbo_cube = null;

        GLH.glhDeleteObject(this._vbo_cube_instances);
        this._vbo_cube_instances = null;

        this._billboarded_particles = null;
        this._cube_particles = null;
    }

    @Override
    public void onWorldSet(World world) {
        this.removeAllParticles();
    }

    @Override
    public void onWorldUnset(World world) {
        this.removeAllParticles();
    }

    @Override
    public void getTasks(VoxelEngine engine, ArrayList<VoxelEngine.Callable<Taskable>> tasks, World world,
            CameraProjectiveWorld camera) { // TODO move make this support
        // concurrency
        // tasks.add(new Callable<Taskable>() {
        // @Override
        // public Taskable call() throws Exception {
        // // TODO: split update in multiple tasks
        // updateParticles(world, camera);
        // return (ParticleRenderer.this);
        // }
        // });
    }

    @Override
    public void preRender() {
        this.updateParticles(this.getWorld(), this.getCamera());
        this.updateVBO(this.getCamera());
    }

    private void clearParticles() {
        this._billboarded_particles.clear();
        this._cube_particles.clear();
    }

    private void updateParticles(World world, CameraProjectiveWorld camera) {

        int i;

        // update billboarded particles
        i = 0;
        while (i < this._billboarded_particles.size()) {
            ParticleBillboarded particle = this._billboarded_particles.get(i);

            if (particle == null || particle.isDead()) {
                this._billboarded_particles.remove(i);
                continue;
            }

            particle.update(world, camera);
            ++i;
        }

        // update cube particles
        i = 0;
        while (i < this._cube_particles.size()) {
            ParticleCube particle = this._cube_particles.get(i);

            if (particle == null || particle.isDead()) {
                this._cube_particles.remove(i);
                continue;
            }

            particle.update(world, camera);
            ++i;
        }

    }

    /** update the cube instances vbo data */
    private void updateVBO(CameraProjectiveWorld camera) {

        if (this._cube_particles.size() == 0) {
            return;
        }

        // get the number of cube particle alive
        int cube_count = Maths.min(this._cube_particles.size(), ParticleRenderer.MAX_CUBE_PARTICLES);

        if (cube_count == ParticleRenderer.MAX_CUBE_PARTICLES) {
            Logger.get().log(Logger.Level.WARNING, "Max number of cube particle reached! "
                    + this._cube_particles.size() + "/" + ParticleRenderer.MAX_CUBE_PARTICLES);
        }

        // create a buffer to hold them all
        FloatBuffer floats = BufferUtils.createFloatBuffer(cube_count * ParticleRenderer.FLOATS_PER_CUBE_INSTANCE);
        this._cubes_in_buffer = 0;
        for (int i = 0; i < cube_count; i++) {
            ParticleCube particle = this._cube_particles.get(i);

            // if not in frustum, do not render it
            if (!camera.isBoxInFrustum(particle.getPosition(), particle.getScale())) {
                continue;
            }

            Matrix4f mat = particle.getTransfMatrix();
            Vector4f color = particle.getColor();
            float health = particle.getHealthRatio();

            floats.put(mat.m00);
            floats.put(mat.m01);
            floats.put(mat.m02);
            floats.put(mat.m03);

            floats.put(mat.m10);
            floats.put(mat.m11);
            floats.put(mat.m12);
            floats.put(mat.m13);

            floats.put(mat.m20);
            floats.put(mat.m21);
            floats.put(mat.m22);
            floats.put(mat.m23);

            floats.put(mat.m30);
            floats.put(mat.m31);
            floats.put(mat.m32);
            floats.put(mat.m33);

            floats.put(color.x);
            floats.put(color.y);
            floats.put(color.z);
            floats.put(color.w);

            floats.put(health);

            ++this._cubes_in_buffer;
        }

        floats.flip();
        this._vbo_cube_instances.bind(GL15.GL_ARRAY_BUFFER);
        int buffersize = this._cubes_in_buffer * FLOATS_PER_CUBE_INSTANCE * 4;
        this._vbo_cube_instances.bufferDataUpdate(GL15.GL_ARRAY_BUFFER, floats, buffersize);
    }

    @Override
    public void render() {
        this.render(this.getCamera());
    }

    public void render(CameraProjectiveWorld camera) {

        World world = super.getWorld();

        this.renderCubeParticles(world, camera);
        this.renderBillboardedParticles(world, camera);
    }

    public void renderReflection(CameraProjectiveWorld camera, Vector4f clipplane) {
        this.render(camera); // TODO optimize this, clip particles
    }

    public void renderRefraction(CameraProjectiveWorld camera, Vector4f clipplane) {
        this.render(camera); // TODO optimize this, clip particles
    }

    /** render every quad particles */
    private void renderBillboardedParticles(World world, CameraProjectiveWorld camera) {
        if (this._billboarded_particles.size() == 0) {
            return;
        }
        GL13.glActiveTexture(GL13.GL_TEXTURE0 + 0); // Texture unit 0

        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        this._program_billboarded_particle.useStart();
        this.getParent().getDefaultVAO().bind();

        this._program_billboarded_particle.loadGlobalUniforms(camera);
        this._billboarded_particles.sort(this._particle_comparator);

        int i = 0;
        while (i < this._billboarded_particles.size()) {

            ParticleBillboarded particle = this._billboarded_particles.get(i);
            float radius = Maths.max(particle.getScale().x, particle.getScale().y);
            if (particle != null && particle.getCameraSquareDistance() < camera.getSquaredRenderDistance()
                    && camera.isSphereInFrustum(particle.getPosition(), radius)) {
                this._program_billboarded_particle.loadInstanceUniforms(particle);
                this.getParent().getDefaultVAO().draw(GL11.GL_POINTS, 0, 1);
            }
            ++i;
        }
    }

    /** render every cube particles */
    private void renderCubeParticles(World world, CameraProjectiveWorld camera) {
        if (this._cube_particles.size() == 0) {
            return;
        }

        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        this._program_cube.useStart();
        this._vao_cube.bind();
        this._program_cube.loadGlobalUniforms(camera);

        this._vao_cube.drawInstanced(GL11.GL_TRIANGLES, 0, 36, this._cubes_in_buffer);

        GL11.glDisable(GL11.GL_DEPTH_TEST);
        GL11.glDisable(GL11.GL_BLEND);
    }

    @Override
    public void renderShadow(ShadowCamera shadow_camera) {
    } // unused, particles doesnt cast shadow

    public void removeAllParticles() {
        this.clearParticles();
    }

    /** add a particule to the update functions */
    public void spawnParticle(ParticleBillboarded particle) {
        this._billboarded_particles.add(particle);
    }

    /** add a particule to the update functions */
    public void spawnParticle(ParticleCube particle) {
        this._cube_particles.add(particle);
    }
}