com.github.begla.blockmania.world.World.java Source code

Java tutorial

Introduction

Here is the source code for com.github.begla.blockmania.world.World.java

Source

/*
 * Copyright 2011 Benjamin Glatzel <benjamin.glatzel@me.com>.
 *
 * 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.github.begla.blockmania.world;

import com.github.begla.blockmania.generators.ChunkGeneratorTerrain;
import com.github.begla.blockmania.main.Blockmania;
import com.github.begla.blockmania.main.Configuration;
import com.github.begla.blockmania.rendering.ShaderManager;
import com.github.begla.blockmania.rendering.TextureManager;
import com.github.begla.blockmania.rendering.particles.BlockParticleEmitter;
import com.github.begla.blockmania.world.characters.Player;
import com.github.begla.blockmania.world.chunk.Chunk;
import com.github.begla.blockmania.world.chunk.ChunkMesh;
import com.github.begla.blockmania.world.horizon.Clouds;
import com.github.begla.blockmania.world.horizon.Skysphere;
import javolution.util.FastList;
import org.lwjgl.opengl.GL20;
import org.lwjgl.util.vector.Vector3f;

import java.util.Collections;
import java.util.logging.Level;

import static org.lwjgl.opengl.GL11.*;

/**
 * The world of Blockmania. At its most basic the world contains chunks (consisting of a fixed amount of blocks)
 * and the player.
 * <p/>
 * The world is randomly generated by using a bunch of Perlin noise generators initialized
 * with a favored seed value.
 *
 * @author Benjamin Glatzel <benjamin.glatzel@me.com>
 */
public final class World extends LocalWorldProvider {

    /* PLAYER */
    private Player _player;

    /* CHUNKS */
    private FastList<Chunk> _chunksInProximity = new FastList();

    /* PARTICLE EMITTERS */
    private final BlockParticleEmitter _blockParticleEmitter = new BlockParticleEmitter(this);

    /* HORIZON */
    private final Clouds _clouds;
    private final Skysphere _skysphere;
    protected double _daylight;

    /* WATER AND LAVA ANIMATION */
    private int _tick = 0;
    private long _lastTick;

    /* UPDATING */
    private final WorldUpdateManager _worldUpdateManager;
    private int prevChunkPosX = 0, prevChunkPosZ = 0;

    /**
     * Initializes a new world for the single player mode.
     *
     * @param title The title/description of the world
     * @param seed  The seed string used to generate the terrain
     */
    public World(String title, String seed) {
        super(title, seed);

        // Init. horizon
        _clouds = new Clouds(this);
        _skysphere = new Skysphere(this);

        _worldUpdateManager = new WorldUpdateManager();
    }

    /**
     * Renders the world.
     */
    public void render() {
        /* SKYSPHERE */
        _player.getActiveCamera().lookThroughNormalized();
        _skysphere.render();

        /* WORLD RENDERING */
        _player.getActiveCamera().lookThrough();

        _player.render();

        renderChunks();

        /* CLOUDS */
        _clouds.render();

        /* PARTICLE EFFECTS */
        _blockParticleEmitter.render();
    }

    private void updateChunksInProximity() {
        if (prevChunkPosX != calcPlayerChunkOffsetX() || prevChunkPosZ != calcPlayerChunkOffsetZ()) {
            prevChunkPosX = calcPlayerChunkOffsetX();
            prevChunkPosZ = calcPlayerChunkOffsetZ();

            FastList<Chunk> newChunksInProximity = new FastList<Chunk>();

            for (int x = -(Configuration.getSettingNumeric("V_DIST_X").intValue() / 2); x < (Configuration
                    .getSettingNumeric("V_DIST_X").intValue() / 2); x++) {
                for (int z = -(Configuration.getSettingNumeric("V_DIST_Z").intValue() / 2); z < (Configuration
                        .getSettingNumeric("V_DIST_Z").intValue() / 2); z++) {
                    Chunk c = getChunkCache().loadOrCreateChunk(calcPlayerChunkOffsetX() + x,
                            calcPlayerChunkOffsetZ() + z);
                    newChunksInProximity.add(c);
                }
            }

            Collections.sort(newChunksInProximity);
            _chunksInProximity = newChunksInProximity;
        }
    }

    /**
     * Updates the daylight value according to the current world time.
     */
    private void updateDaylight() {
        double time = getTime() % 1;

        // Sunrise
        if (time < Configuration.SUN_RISE_SET_DURATION && time > 0.0f) {
            _daylight = time / Configuration.SUN_RISE_SET_DURATION;
        } else if (time >= Configuration.SUN_RISE_SET_DURATION && time <= 0.5f) {
            _daylight = 1.0f;
        }

        // Sunset
        if (time > 0.5 - Configuration.SUN_RISE_SET_DURATION && time < 0.5f) {
            _daylight = (0.5f - time) / Configuration.SUN_RISE_SET_DURATION;
        } else if (time >= 0.5 && time <= 1.0f) {
            _daylight = 0.0f;
        }
    }

    /**
     * Fetches the currently visible chunks (in sight of the player).
     *
     * @return The visible chunks
     */
    public FastList<Chunk> fetchVisibleChunks() {
        FastList<Chunk> result = new FastList<Chunk>();
        FastList<Chunk> chunksInPromity = _chunksInProximity;

        for (FastList.Node<Chunk> n = chunksInPromity.head(), end = chunksInPromity
                .tail(); (n = n.getNext()) != end;) {
            Chunk c = n.getValue();

            if (isChunkVisible(c)) {
                c.setVisible(true);
                result.add(c);
                continue;
            }

            c.setVisible(false);
        }

        return result;
    }

    /**
     * Renders the chunks.
     */
    private void renderChunks() {
        ShaderManager.getInstance().enableShader("chunk");

        int daylight = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "daylight");
        int swimming = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "swimming");
        int animationOffset = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"),
                "animationOffset");
        int animated = GL20.glGetUniformLocation(ShaderManager.getInstance().getShader("chunk"), "animated");

        GL20.glUniform1f(daylight, (float) getDaylight());
        GL20.glUniform1i(swimming, _player.isHeadUnderWater() ? 1 : 0);

        FastList<Chunk> visibleChunks = fetchVisibleChunks();

        glEnable(GL_TEXTURE_2D);

        GL20.glUniform1i(animated, 0);
        TextureManager.getInstance().bindTexture("terrain");

        // OPAQUE ELEMENTS
        for (FastList.Node<Chunk> n = visibleChunks.head(), end = visibleChunks.tail(); (n = n.getNext()) != end;) {
            Chunk c = n.getValue();

            c.render(ChunkMesh.RENDER_TYPE.OPAQUE);

            if (Configuration.getSettingBoolean("CHUNK_OUTLINES")) {
                c.getAABB().render();
            }
        }

        // ANIMATED LAVA
        GL20.glUniform1i(animated, 1);
        TextureManager.getInstance().bindTexture("custom_lava_still");

        GL20.glUniform1f(animationOffset, ((float) (_tick % 16)) * (1.0f / 16f));

        for (FastList.Node<Chunk> n = visibleChunks.head(), end = visibleChunks.tail(); (n = n.getNext()) != end;) {
            Chunk c = n.getValue();
            c.render(ChunkMesh.RENDER_TYPE.LAVA);
        }

        GL20.glUniform1i(animated, 0);
        TextureManager.getInstance().bindTexture("terrain");

        // BILLBOARDS AND TRANSLUCENT ELEMENTS
        for (FastList.Node<Chunk> n = visibleChunks.head(), end = visibleChunks.tail(); (n = n.getNext()) != end;) {
            Chunk c = n.getValue();
            c.render(ChunkMesh.RENDER_TYPE.BILLBOARD_AND_TRANSLUCENT);
        }

        GL20.glUniform1i(animated, 1);
        GL20.glUniform1f(animationOffset, ((float) (_tick / 2 % 12)) * (1.0f / 16f));
        TextureManager.getInstance().bindTexture("custom_water_still");

        for (int i = 0; i < 2; i++) {
            // ANIMATED WATER
            for (FastList.Node<Chunk> n = visibleChunks.head(), end = visibleChunks
                    .tail(); (n = n.getNext()) != end;) {
                Chunk c = n.getValue();

                if (i == 0) {
                    glColorMask(false, false, false, false);
                } else {
                    glColorMask(true, true, true, true);
                }

                c.render(ChunkMesh.RENDER_TYPE.WATER);
            }
        }

        ShaderManager.getInstance().enableShader(null);

        glDisable(GL_TEXTURE_2D);
    }

    public void update() {
        updateDaylight();
        updateTick();

        _skysphere.update();

        // Update the player
        _player.update();
        // Update the clouds
        _clouds.update();

        // Update the list of relevant chunks
        updateChunksInProximity();
        // And fetch those chunks that are in sight of the player
        FastList<Chunk> visibleChunks = fetchVisibleChunks();

        // Update chunks
        for (FastList.Node<Chunk> n = visibleChunks.head(), end = visibleChunks.tail(); (n = n.getNext()) != end;) {
            // Queue dirty chunks for updating
            if (n.getValue().isDirty() || n.getValue().isLightDirty()) {
                _worldUpdateManager.queueChunkUpdate(n.getValue());
            }

            n.getValue().update();
        }

        // Update the particle emitters
        _blockParticleEmitter.update();

        // Generate new VBOs if available
        _worldUpdateManager.updateVBOs();

        _chunkCache.freeCacheSpace();
    }

    /**
     * Updates the tick value used for animating the textures.
     */
    private void updateTick() {
        if (Blockmania.getInstance().getTime() - _lastTick >= 100) {
            _tick++;
            _lastTick = Blockmania.getInstance().getTime();
        }
    }

    /**
     * Chunk position of the player.
     *
     * @return The player offset on the x-axis
     */
    private int calcPlayerChunkOffsetX() {
        return (int) (_player.getPosition().x / Configuration.CHUNK_DIMENSIONS.x);
    }

    /**
     * Chunk position of the player.
     *
     * @return The player offset on the z-axis
     */
    private int calcPlayerChunkOffsetZ() {
        return (int) (_player.getPosition().z / Configuration.CHUNK_DIMENSIONS.z);
    }

    /**
     * Sets a new player and spawns him at the spawning point.
     *
     * @param p The player
     */
    public void setPlayer(Player p) {
        _player = p;

        _player.setSpawningPoint(nextRandomSpawningPoint());
        _player.reset();
        _player.respawn();
    }

    /**
     * Writes all chunks to disk and disposes them.
     */
    public void dispose() {
        Blockmania.getInstance().getLogger().log(Level.INFO, "Disposing world {0} and saving all chunks.",
                getTitle());

        saveMetaData();
        getChunkCache().saveAndDisposeAllChunks();
    }

    /**
     * Displays some information about the world formatted as a string.
     *
     * @return String with world information
     */
    @Override
    public String toString() {
        return String.format(
                "world (biome: %s, time: %f, sun: %f, vbo-updates: %d, cache: %d, cu-duration: %fs, seed: \"%s\", title: \"%s\")",
                getActiveBiome(), getTime(), _skysphere.getSunPosAngle(), _worldUpdateManager.getVboUpdatesSize(),
                _chunkCache.size(), _worldUpdateManager.getAverageUpdateDuration() / 1000d, _seed, _title);
    }

    public Player getPlayer() {
        return _player;
    }

    @Override
    public Vector3f getOrigin() {
        return _player.getPosition();
    }

    public boolean isChunkVisible(Chunk c) {
        return _player.getActiveCamera().getViewFrustum().intersects(c.getAABB());
    }

    public double getDaylight() {
        return _daylight;
    }

    public FastList<Chunk> getChunksInProximity() {
        return _chunksInProximity;
    }

    public BlockParticleEmitter getBlockParticleEmitter() {
        return _blockParticleEmitter;
    }

    /**
     * Returns the active biome at the player's position.
     */
    public ChunkGeneratorTerrain.BIOME_TYPE getActiveBiome() {
        return getActiveBiome((int) _player.getPosition().x, (int) _player.getPosition().z);
    }

    /**
     * Returns the humidity at the player's position.
     *
     * @return
     */
    public double getActiveHumidity() {

        return getHumidityAt((int) _player.getPosition().x, (int) _player.getPosition().z);
    }

    /**
     * Returns the temeperature at the player's position.
     *
     * @return
     */
    public double getActiveTemperature() {
        return getTemperatureAt((int) _player.getPosition().x, (int) _player.getPosition().z);
    }
}