Java tutorial
/* * 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); } }