eu.over9000.veya.rendering.Scene.java Source code

Java tutorial

Introduction

Here is the source code for eu.over9000.veya.rendering.Scene.java

Source

/*
 * Veya
 * Copyright (C) 2015 s1mpl3x
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package eu.over9000.veya.rendering;

import java.nio.FloatBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

import eu.over9000.veya.Veya;
import eu.over9000.veya.collision.AABB;
import eu.over9000.veya.collision.CollisionDetection;
import eu.over9000.veya.util.Location;
import eu.over9000.veya.util.MathUtil;
import eu.over9000.veya.util.TextureLoader;
import eu.over9000.veya.world.BlockType;
import eu.over9000.veya.world.Chunk;
import eu.over9000.veya.world.World;
import eu.over9000.veya.world.storage.ChunkRequestLevel;

public class Scene {

    public static final int MAX_CHUNK_UPDATES_PER_FRAME = 1;
    private final static int SCENE_CHUNK_VIEW_RANGE = 8;
    private final static int SCENE_CHUNK_CACHE_RANGE = SCENE_CHUNK_VIEW_RANGE + 2;

    private final Object lock = new Object();
    private boolean camPosChanged = false;

    private final World world;

    private final Light light;
    private final Shadow shadow;

    private final int texture_handle;

    private final FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
    private final Map<Chunk, ChunkVAO> displayedChunks = new ConcurrentHashMap<>();
    private final Queue<ChunkChunkVAOPair> toAdd = new ConcurrentLinkedQueue<>();

    private final Queue<ChunkChunkVAOPair> toRemove = new ConcurrentLinkedQueue<>();
    private final BlockingQueue<Chunk> toUpdateOffRender = new LinkedBlockingQueue<>();

    private final Queue<ChunkChunkVAOPair> updatedUpdateOffRender = new ConcurrentLinkedQueue<>();

    private final AtomicBoolean alive;
    public AtomicInteger chunkUpdateCounterInRender = new AtomicInteger(0);

    public AtomicInteger chunkUpdateCounterOffRender = new AtomicInteger(0);

    private final Thread displayedChunkUpdaterThread;
    private final Thread chunkVAOUpdateOffRenderThread;

    public BlockType placeBlockType = BlockType.TEST;
    private Location centerChunk = new Location(0, 0, 0);

    public Scene(final long seed) {
        this.alive = new AtomicBoolean(true);
        this.world = new World(seed, "Keaysea");
        this.texture_handle = TextureLoader.loadBlockTexture(GL13.GL_TEXTURE0);

        this.shadow = new Shadow();

        this.light = new Light(-200, 400, -40, 0.9f, 0.9f, 0.45f, 0.33f, 0.33f, 0.33f);

        final Runnable displayedChunkUpdater = () -> {
            while (Scene.this.alive.get()) {
                synchronized (Scene.this.lock) {
                    try {
                        while (!Scene.this.camPosChanged) {
                            Scene.this.lock.wait();
                        }
                    } catch (final InterruptedException e) {
                        if (!Scene.this.alive.get()) {
                            return;
                        }
                        e.printStackTrace();
                    }
                    Scene.this.camPosChanged = false;
                }

                Scene.this.updateDisplayedChunks();
                world.clearCache(centerChunk, SCENE_CHUNK_CACHE_RANGE);
            }
        };

        final Runnable offRenderChunkUpdater = () -> {
            while (Scene.this.alive.get()) {
                try {
                    final Chunk toUpdate = toUpdateOffRender.take();
                    ChunkVAO chunkVAO = new ChunkVAO(toUpdate);
                    updatedUpdateOffRender.add(new ChunkChunkVAOPair(toUpdate, chunkVAO));
                    chunkUpdateCounterOffRender.incrementAndGet();
                } catch (InterruptedException e) {
                    if (!Scene.this.alive.get()) {
                        return;
                    }
                    e.printStackTrace();
                }

            }
        };

        this.displayedChunkUpdaterThread = new Thread(displayedChunkUpdater, "DisplayedChunkUpdater");
        this.displayedChunkUpdaterThread.start();

        this.chunkVAOUpdateOffRenderThread = new Thread(offRenderChunkUpdater, "OffRenderChunkUpdater");
        this.chunkVAOUpdateOffRenderThread.start();

    }

    public void init() {
        GL20.glUniform1i(Veya.program_normal.getUniformLocation("textureData"), 0);
        GL20.glUniform1i(Veya.program_normal.getUniformLocation("shadowMap"), 1);

        this.light.init();
        this.shadow.init();
        this.updateModelMatrix();
    }

    public Light getLight() {
        return this.light;
    }

    private boolean checkChunkInViewRange(final Chunk chunk) {

        final int min_x = centerChunk.x - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_x = centerChunk.x + Scene.SCENE_CHUNK_VIEW_RANGE;
        final int min_y = centerChunk.y - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_y = centerChunk.y + Scene.SCENE_CHUNK_VIEW_RANGE;
        final int min_z = centerChunk.z - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_z = centerChunk.z + Scene.SCENE_CHUNK_VIEW_RANGE;

        return MathUtil.isBetween(chunk.getChunkX(), min_x, max_x)
                && MathUtil.isBetween(chunk.getChunkY(), min_y, max_y)
                && MathUtil.isBetween(chunk.getChunkZ(), min_z, max_z);
    }

    private void updateDisplayedChunks() {
        final int min_x = centerChunk.x - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_x = centerChunk.x + Scene.SCENE_CHUNK_VIEW_RANGE;
        final int min_y = centerChunk.y - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_y = centerChunk.y + Scene.SCENE_CHUNK_VIEW_RANGE;
        final int min_z = centerChunk.z - Scene.SCENE_CHUNK_VIEW_RANGE;
        final int max_z = centerChunk.z + Scene.SCENE_CHUNK_VIEW_RANGE;

        // remove chunks outside display area
        for (final Entry<Chunk, ChunkVAO> entry : this.displayedChunks.entrySet()) {
            if (!checkChunkInViewRange(entry.getKey())) {

                final ChunkChunkVAOPair candidate = new ChunkChunkVAOPair(entry.getKey(), entry.getValue());
                if (!toRemove.contains(candidate)) {
                    this.toRemove.add(candidate);
                }
            }
        }

        final List<Location> locations = new ArrayList<>();
        // load chunks in display area
        for (int x = min_x; x <= max_x; x++) {
            for (int y = min_y; y <= max_y; y++) {
                for (int z = min_z; z <= max_z; z++) {
                    locations.add(new Location(x, y, z, centerChunk));
                }
            }
        }

        Collections.sort(locations);

        for (final Location chunkLocation : locations) {
            final Chunk chunk = this.world.getChunkAt(chunkLocation.x, chunkLocation.y, chunkLocation.z,
                    ChunkRequestLevel.NEIGHBORS_LOADED, false);

            if (chunk == null) {
                continue;
            }

            final boolean isDisplayed = this.displayedChunks.containsKey(chunk);

            if (!isDisplayed) {
                chunk.getAndResetChangedFlag();
                final ChunkChunkVAOPair candidate = new ChunkChunkVAOPair(chunk, new ChunkVAO(chunk));
                if (!toAdd.contains(candidate)) {
                    this.toAdd.add(candidate);
                }
            }
        }
    }

    public void render() {
        this.checkCameraPosition();

        ChunkChunkVAOPair addEntry;
        while ((addEntry = this.toAdd.poll()) != null) {
            addEntry.getChunkVAO().create();
            this.displayedChunks.put(addEntry.getChunk(), addEntry.getChunkVAO());
        }

        ChunkChunkVAOPair removeEntry;
        while ((removeEntry = this.toRemove.poll()) != null) {
            this.displayedChunks.remove(removeEntry.getChunk());
            removeEntry.getChunkVAO().dispose();
        }

        ChunkChunkVAOPair updatedEntry;
        while ((updatedEntry = this.updatedUpdateOffRender.poll()) != null) {
            updatedEntry.getChunkVAO().create();
            final ChunkVAO oldVAO = this.displayedChunks.put(updatedEntry.getChunk(), updatedEntry.getChunkVAO());
            if (oldVAO != null) {
                oldVAO.dispose();
            }
        }

        int updates = 0;
        for (final Chunk chunk : displayedChunks.keySet()) {
            if (world.hasChunkChanged(chunk)) {
                if (chunk.getLocation().nextTo(centerChunk)) {

                    final ChunkVAO newVAO = new ChunkVAO(chunk);
                    final ChunkVAO oldVAO = this.displayedChunks.put(chunk, newVAO);
                    oldVAO.dispose();
                    newVAO.create();
                    chunkUpdateCounterInRender.incrementAndGet();
                    updates++;
                    if (updates > MAX_CHUNK_UPDATES_PER_FRAME) {
                        break;
                    }
                } else {
                    toUpdateOffRender.add(chunk);
                }
            }
        }

        shadow.preRender();
        Util.checkGLError();
        internalRender(false);
        Util.checkGLError();
        shadow.postRender();
        Util.checkGLError();

        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        GL11.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.texture_handle);
        internalRender(true);

        if (Veya.debugShadow) {
            Veya.program_debug.use(true);
            GL20.glUniform1f(Veya.program_debug.getUniformLocation("near_plane"), Shadow.SHADOW_NEAR);
            GL20.glUniform1f(Veya.program_debug.getUniformLocation("far_plane"), Shadow.SHADOW_FAR);
            GL13.glActiveTexture(GL13.GL_TEXTURE0);
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, shadow.getDepthMap());

            shadow.renderQuad();

        }

        Veya.program_normal.use(true);
    }

    private void internalRender(final boolean transparent) {
        for (final Entry<Chunk, ChunkVAO> entry : this.displayedChunks.entrySet()) {
            if (entry.getValue() != null) {

                entry.getValue().render(true);
            }
        }
        //if (transparent) {
        for (final Entry<Chunk, ChunkVAO> entry : this.displayedChunks.entrySet()) {
            if (entry.getValue() != null) {

                entry.getValue().render(false);
            }
        }
        //}
    }

    private void checkCameraPosition() {
        final int center_x = World.worldToChunkCoordinate((int) Veya.camera.getPosition().getX());
        final int center_y = World.worldToChunkCoordinate((int) Veya.camera.getPosition().getY());
        final int center_z = World.worldToChunkCoordinate((int) Veya.camera.getPosition().getZ());

        if (center_x != centerChunk.x || center_y != centerChunk.y || center_z != centerChunk.z) {

            centerChunk = new Location(center_x, center_y, center_z);

            System.out.println("Camera changed chunk: " + center_x + "," + center_y + "," + center_z);

            synchronized (this.lock) {
                this.camPosChanged = true;
                this.lock.notifyAll();
            }
        }

    }

    private void updateModelMatrix() {
        final Matrix4f modelMatrix = new Matrix4f();
        modelMatrix.store(this.matrixBuffer);
        this.matrixBuffer.flip();
        GL20.glUniformMatrix4(Veya.program_normal.getUniformLocation("modelMatrix"), false, this.matrixBuffer);

        Veya.program_shadow.use(true);
        modelMatrix.store(this.matrixBuffer);
        this.matrixBuffer.flip();
        GL20.glUniformMatrix4(Veya.program_shadow.getUniformLocation("modelMatrix"), false, this.matrixBuffer);

        Veya.program_normal.use(true);
    }

    public void dispose() {
        this.alive.set(false);
        this.displayedChunkUpdaterThread.interrupt();
        this.chunkVAOUpdateOffRenderThread.interrupt();
        for (final Entry<Chunk, ChunkVAO> entry : this.displayedChunks.entrySet()) {
            if (entry.getValue() != null) {
                entry.getValue().dispose();
            }
        }
        this.world.onExit();
        this.displayedChunks.clear();
    }

    public World getWorld() {
        return world;
    }

    private LookAtResult getLookAtBlock() {
        final Vector3f position = Veya.camera.getPosition();
        final Vector3f viewDirection = Veya.camera.getViewDirection();

        final List<Location> candidates = Location.getLocationsAround((int) position.x, (int) position.y,
                (int) position.z, 3);
        Collections.sort(candidates);

        for (final Location candidate : candidates) {
            final BlockType type = world.getBlockAt(candidate.x, candidate.y, candidate.z);

            if (type == null || type == BlockType.BEDROCK) {
                continue;
            }

            final int[] intersectionResult = CollisionDetection.checkCollision(position, viewDirection, candidate.x,
                    candidate.y, candidate.z);

            if (intersectionResult != null) {
                return new LookAtResult(candidate, intersectionResult);
            }
        }

        return null;
    }

    public void performLeftClick() {

        final LookAtResult lookAt = getLookAtBlock();

        if (lookAt != null) {
            world.clearBlockAt(lookAt.location.x, lookAt.location.y, lookAt.location.z, ChunkRequestLevel.CACHE);
        }

    }

    public void performRightClick() {
        final LookAtResult lookAt = getLookAtBlock();

        if (lookAt != null) {
            final Location placeLocation = CollisionDetection.getNeighborBlockFromIntersectionResult(
                    lookAt.location.x, lookAt.location.y, lookAt.location.z, lookAt.lookAtResult);

            final AABB blockAABB = new AABB(placeLocation);
            final AABB cameraAABB = Veya.camera.getAABB();

            if (!CollisionDetection.checkCollision(cameraAABB, blockAABB)) {
                world.setBlockAt(placeLocation.x, placeLocation.y, placeLocation.z, placeBlockType,
                        ChunkRequestLevel.CACHE, true);
            }

        }
    }

    public void performMiddleClick() {
        final LookAtResult lookAt = getLookAtBlock();

        if (lookAt != null) {
            placeBlockType = world.getBlockAt(lookAt.location.x, lookAt.location.y, lookAt.location.z);
        }
    }

    public void onNewChunk(final Chunk chunk) {
        if (checkChunkInViewRange(chunk)) {
            toAdd.add(new ChunkChunkVAOPair(chunk, new ChunkVAO(chunk)));
        }
    }

    public int getChunkCount() {
        return this.displayedChunks.size();
    }

    public void filterAir(final List<Location> locations) {
        for (final Iterator<Location> iterator = locations.iterator(); iterator.hasNext();) {
            final Location location = iterator.next();
            if (world.getBlockAt(location) == null || Veya.ignoreBlocks.contains(world.getBlockAt(location))) {
                iterator.remove();
            }
        }
    }

    public void setBlock(final BlockType type) {
        this.placeBlockType = type;
    }

    private class LookAtResult {
        public final int[] lookAtResult;
        public final Location location;

        public LookAtResult(final Location location, final int[] lookAtResult) {
            this.location = location;
            this.lookAtResult = lookAtResult;
        }
    }
}