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 org.terasology.logic.world; import com.bulletphysics.collision.shapes.BvhTriangleMeshShape; import com.bulletphysics.collision.shapes.IndexedMesh; import com.bulletphysics.collision.shapes.TriangleIndexVertexArray; import com.bulletphysics.dynamics.RigidBody; import com.bulletphysics.dynamics.RigidBodyConstructionInfo; import com.bulletphysics.linearmath.DefaultMotionState; import com.bulletphysics.linearmath.Transform; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import org.terasology.game.Terasology; import org.terasology.logic.entities.StaticEntity; import org.terasology.logic.generators.ChunkGenerator; import org.terasology.logic.manager.ConfigurationManager; import org.terasology.model.blocks.Block; import org.terasology.model.blocks.BlockManager; import org.terasology.model.structures.AABB; import org.terasology.model.structures.TeraArray; import org.terasology.model.structures.TeraSmartArray; import org.terasology.rendering.primitives.ChunkMesh; import org.terasology.rendering.primitives.ChunkTessellator; import org.terasology.utilities.FastRandom; import org.terasology.utilities.Helper; import org.terasology.utilities.MathHelper; import javax.vecmath.Matrix3f; import javax.vecmath.Matrix4f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.logging.Level; /** * Chunks are the basic components of the world. Each chunk contains a fixed amount of blocks * determined by its dimensions. They are used to manage the world efficiently and * to reduce the batch count within the render loop. * <p/> * Chunks are tessellated on creation and saved to vertex arrays. From those VBOs are generated * which are then used for the actual rendering process. * * @author Benjamin Glatzel <benjamin.glatzel@me.com> */ public class Chunk extends StaticEntity implements Comparable<Chunk>, Externalizable { public static final long serialVersionUID = 1L; /* CONSTANT VALUES */ public static final int CHUNK_DIMENSION_X = 16; public static final int CHUNK_DIMENSION_Y = 256; public static final int CHUNK_DIMENSION_Z = 16; public static final int VERTICAL_SEGMENTS = (Integer) ConfigurationManager.getInstance().getConfig() .get("Graphics.verticalChunkMeshSegments"); private static final Vector3d[] LIGHT_DIRECTIONS = { new Vector3d(1, 0, 0), new Vector3d(-1, 0, 0), new Vector3d(0, 1, 0), new Vector3d(0, -1, 0), new Vector3d(0, 0, 1), new Vector3d(0, 0, -1) }; protected FastRandom _random; /* ------ */ protected boolean _dirty, _lightDirty, _fresh; /* ------ */ protected LocalWorldProvider _parent; /* ------ */ protected final TeraArray _blocks; protected final TeraSmartArray _sunlight, _light, _states; /* ------ */ private ChunkMesh _activeMeshes[]; private ChunkMesh _newMeshes[]; /* ------ */ private final ChunkTessellator _tessellator; /* ------ */ private boolean[] _occlusionCulled = new boolean[VERTICAL_SEGMENTS]; private boolean[] _subMeshCulled = new boolean[VERTICAL_SEGMENTS]; private final int[] _queries = new int[VERTICAL_SEGMENTS]; /* ------ */ private boolean _disposed = false; /* ----- */ private AABB _aabb = null; private AABB[] _subChunkAABB = null; /* ----- */ private RigidBody _rigidBody = null; public enum LIGHT_TYPE { BLOCK, SUN } public static int getChunkIdForPosition(Vector3d position) { return MathHelper.cantorize(MathHelper.mapToPositive((int) position.x), MathHelper.mapToPositive((int) position.z)); } public Chunk() { _tessellator = new ChunkTessellator(this); _blocks = new TeraArray(CHUNK_DIMENSION_X, CHUNK_DIMENSION_Y, CHUNK_DIMENSION_Z); _sunlight = new TeraSmartArray(CHUNK_DIMENSION_X, CHUNK_DIMENSION_Y, CHUNK_DIMENSION_Z); _light = new TeraSmartArray(CHUNK_DIMENSION_X, CHUNK_DIMENSION_Y, CHUNK_DIMENSION_Z); _states = new TeraSmartArray(CHUNK_DIMENSION_X, CHUNK_DIMENSION_Y, CHUNK_DIMENSION_Z); setLightDirty(true); setDirty(true); setFresh(true); _random = new FastRandom(); } /** * Init. the chunk with a parent world and an absolute position. * * @param p The parent world * @param position The absolute position of the chunk within the world */ public Chunk(LocalWorldProvider p, Vector3d position) { this(); setPosition(position); _parent = p; _random = new FastRandom((_parent.getSeed()).hashCode() + getChunkIdForPosition(position)); } /** * Tries to load a chunk from disk. If the chunk is not present, * it is created from scratch. * * @return True if a generation has been executed */ public boolean generate() { if (isFresh()) { for (ChunkGenerator gen : _parent.getGeneratorManager().getChunkGenerators()) { gen.generate(this); } generateSunlight(); setFresh(false); return true; } return false; } /** * Updates the light of this chunk. */ public void updateLight() { if (isFresh() || !isLightDirty()) return; for (int x = 0; x < CHUNK_DIMENSION_X; x++) { for (int z = 0; z < CHUNK_DIMENSION_Z; z++) { for (int y = CHUNK_DIMENSION_Y - 1; y >= 0; y--) { byte blockValue = getBlock(x, y, z); byte lightValue = getLight(x, y, z, LIGHT_TYPE.SUN); if (!BlockManager.getInstance().getBlock(blockValue).isTranslucent()) { continue; } // Spread the sunlight in translucent blocks with a light value greater than zero. if (lightValue > 0) { spreadLight(x, y, z, lightValue, LIGHT_TYPE.SUN); } } } } setLightDirty(false); } /** * Generates the initial sunlight. */ private void generateSunlight() { for (int x = 0; x < CHUNK_DIMENSION_X; x++) { for (int z = 0; z < CHUNK_DIMENSION_Z; z++) { refreshSunlightAtLocalPos(x, z, false, false); } } } /** * Calculates the sunlight at a given column within the chunk. * * @param x Local block position on the x-axis * @param z Local block position on the z-axis * @param spreadLight Spread light if a light value is greater than the old one * @param refreshSunlight Refreshes the sunlight using the surrounding chunks when the light value is lower than before */ public void refreshSunlightAtLocalPos(int x, int z, boolean spreadLight, boolean refreshSunlight) { boolean covered = false; for (int y = CHUNK_DIMENSION_Y - 1; y >= 0; y--) { byte blockId = _blocks.get(x, y, z); Block b = BlockManager.getInstance().getBlock(blockId); // Remember if this "column" is covered if ((!b.isInvisible() && b.getBlockForm() != Block.BLOCK_FORM.BILLBOARD) && !covered) { covered = true; } byte oldValue = _sunlight.get(x, y, z); byte newValue; // If the column is not covered... if (!covered) { if (b.isInvisible() || b.getBlockForm() == Block.BLOCK_FORM.BILLBOARD) _sunlight.set(x, y, z, (byte) 15); else _sunlight.set(x, y, z, (byte) 0x0); newValue = _sunlight.get(x, y, z); // Otherwise the column is covered. Don't generate any light in the cells... } else { _sunlight.set(x, y, z, (byte) 0); // Update the sunlight at the current position (check the surrounding cells) if (refreshSunlight) { refreshLightAtLocalPos(x, y, z, LIGHT_TYPE.SUN); } newValue = _sunlight.get(x, y, z); } if (spreadLight && oldValue > newValue) unspreadLight(x, y, z, oldValue, Chunk.LIGHT_TYPE.SUN); else if (spreadLight && oldValue < newValue) { /* * Spread sunlight if the new light value is more intense * than the old value. */ spreadLight(x, y, z, newValue, LIGHT_TYPE.SUN); } } } /** * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param type The type of the light */ public void refreshLightAtLocalPos(int x, int y, int z, LIGHT_TYPE type) { int blockPosX = getBlockWorldPosX(x); int blockPosZ = getBlockWorldPosZ(z); byte bType = getBlock(x, y, z); // If a block was just placed, remove the light value at this point if (!BlockManager.getInstance().getBlock(bType).isTranslucent()) { setLight(x, y, z, (byte) 0, type); } else { // If the block was removed: Find the brightest neighbor and // set the current light value to this value - 1 byte val = getParent().getLight(blockPosX, y, blockPosZ, type); byte val1 = getParent().getLight(blockPosX + 1, y, blockPosZ, type); byte val2 = getParent().getLight(blockPosX - 1, y, blockPosZ, type); byte val3 = getParent().getLight(blockPosX, y, blockPosZ + 1, type); byte val4 = getParent().getLight(blockPosX, y, blockPosZ - 1, type); byte val5 = getParent().getLight(blockPosX, y + 1, blockPosZ, type); byte val6 = getParent().getLight(blockPosX, y - 1, blockPosZ, type); byte max = (byte) (Math.max(Math.max(Math.max(val1, val2), Math.max(val3, val4)), Math.max(val5, val6)) - 1); if (max < 0) { max = 0; } // Do nothing if the current light value is brighter byte res = (byte) Math.max(max, val); // Finally set the new light value setLight(x, y, z, res, type); } } /** * Recursive light calculation. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param lightValue The light value used to spread the light * @param type The type of the light */ public void unspreadLight(int x, int y, int z, byte lightValue, LIGHT_TYPE type) { ArrayList<Vector3d> brightSpots = new ArrayList<Vector3d>(); unspreadLight(x, y, z, lightValue, 0, type, brightSpots); for (Vector3d pos : brightSpots) { getParent().spreadLight((int) pos.x, (int) pos.y, (int) pos.z, _parent.getLight((int) pos.x, (int) pos.y, (int) pos.z, type), 0, type); } } /** * Recursive light calculation. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param lightValue The light value used to spread the light * @param depth Depth of the recursion * @param type The type of the light * @param brightSpots Log of light spots, which where brighter than the current light node */ public void unspreadLight(int x, int y, int z, byte lightValue, int depth, LIGHT_TYPE type, ArrayList<Vector3d> brightSpots) { int blockPosX = getBlockWorldPosX(x); int blockPosZ = getBlockWorldPosZ(z); // Remove the light at this point getParent().setLight(blockPosX, y, blockPosZ, (byte) 0x0, type); for (int i = 0; i < 6; i++) { byte neighborValue = getParent().getLight(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z, type); byte neighborType = getParent().getBlock(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z); if (neighborValue < lightValue && neighborValue > 0 && BlockManager.getInstance().getBlock(neighborType).isTranslucent()) { getParent().unspreadLight(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z, (byte) (lightValue - 1), depth + 1, type, brightSpots); } else if (neighborValue >= lightValue) { brightSpots.add(new Vector3d(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z)); } } } /** * Recursive light calculation. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param lightValue The light value used to spread the light * @param type The type of the light */ public void spreadLight(int x, int y, int z, byte lightValue, LIGHT_TYPE type) { spreadLight(x, y, z, lightValue, 0, type); } /** * Recursive light calculation. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param lightValue The light value used to spread the light * @param depth Depth of the recursion * @param type The type of the light */ public void spreadLight(int x, int y, int z, byte lightValue, int depth, LIGHT_TYPE type) { if (depth > lightValue || lightValue - depth < 1) { return; } int blockPosX = getBlockWorldPosX(x); int blockPosZ = getBlockWorldPosZ(z); byte newLightValue; newLightValue = (byte) (lightValue - depth); getParent().setLight(blockPosX, y, blockPosZ, newLightValue, type); for (int i = 0; i < 6; i++) { byte neighborValue = getParent().getLight(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z, type); byte neighborType = getParent().getBlock(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z); if (neighborValue < newLightValue - 1 && BlockManager.getInstance().getBlock(neighborType).isTranslucent()) { getParent().spreadLight(blockPosX + (int) LIGHT_DIRECTIONS[i].x, y + (int) LIGHT_DIRECTIONS[i].y, blockPosZ + (int) LIGHT_DIRECTIONS[i].z, lightValue, depth + 1, type); } } } /** * Returns the light intensity at a given local block position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param type The type of the light * @return The light intensity */ public byte getLight(int x, int y, int z, LIGHT_TYPE type) { byte result; if (type == LIGHT_TYPE.SUN) { result = _sunlight.get(x, y, z); } else { result = _light.get(x, y, z); } if (result >= 0) { return result; } return 15; } /** * Sets the light value at the given position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param intensity The light intensity * @param type The type of the light */ public void setLight(int x, int y, int z, byte intensity, LIGHT_TYPE type) { TeraSmartArray lSource; if (type == LIGHT_TYPE.SUN) { lSource = _sunlight; } else if (type == LIGHT_TYPE.BLOCK) { lSource = _light; } else { return; } byte oldValue = lSource.get(x, y, z); lSource.set(x, y, z, intensity); if (oldValue != intensity) { setDirty(true); // Mark the neighbors as dirty markNeighborsDirty(x, z); } } /** * Returns the block type at a given local block position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @return The block type */ public byte getBlock(int x, int y, int z) { byte result = _blocks.get(x, y, z); if (result >= 0) { return result; } return 0; } /** * Returns the state at a given local block position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @return The block type */ public byte getState(int x, int y, int z) { return _states.get(x, y, z); } public boolean canBlockSeeTheSky(int x, int y, int z) { for (int y1 = y + 1; y1 < CHUNK_DIMENSION_Y; y1++) { if (!BlockManager.getInstance().getBlock(getBlock(x, y1, z)).isTranslucent()) return false; } return true; } /** * Sets the block value at the given position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param type The block type */ public void setBlock(int x, int y, int z, byte type) { byte oldValue = _blocks.get(x, y, z); _blocks.set(x, y, z, type); if (oldValue != type) { // Update vertex arrays and light setDirty(true); // Mark the neighbors as dirty markNeighborsDirty(x, z); } } /** * Sets the state value at the given position. * * @param x Local block position on the x-axis * @param y Local block position on the y-axis * @param z Local block position on the z-axis * @param type The block type */ public void setState(int x, int y, int z, byte type) { _states.set(x, y, z, type); } /** * Calculates the distance of the chunk to the player. * * @return The distance of the chunk to the player */ public double distanceToPlayer() { Vector3d result = new Vector3d(getPosition().x * CHUNK_DIMENSION_X, 0, getPosition().z * CHUNK_DIMENSION_Z); Vector3d referencePoint = new Vector3d(_parent.getRenderingReferencePoint().x, 0, _parent.getRenderingReferencePoint().z); result.sub(referencePoint); return result.length(); } /** * Returns the neighbor chunks of this chunk. * * @return The adjacent chunks */ public Chunk[] loadOrCreateNeighbors() { Chunk[] chunks = new Chunk[8]; chunks[0] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x + 1, (int) getPosition().z); chunks[1] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x - 1, (int) getPosition().z); chunks[2] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x, (int) getPosition().z + 1); chunks[3] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x, (int) getPosition().z - 1); chunks[4] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x + 1, (int) getPosition().z + 1); chunks[5] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x - 1, (int) getPosition().z - 1); chunks[6] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x - 1, (int) getPosition().z + 1); chunks[7] = getParent().getChunkProvider().loadOrCreateChunk((int) getPosition().x + 1, (int) getPosition().z - 1); return chunks; } /** * Marks those neighbors of a chunk dirty, that are adjacent to * the given block coordinate. * * @param x Local block position on the x-axis * @param z Local block position on the z-axis */ private void markNeighborsDirty(int x, int z) { Chunk[] neighbors = loadOrCreateNeighbors(); if (x == 0 && neighbors[1] != null) { neighbors[1].setDirty(true); } if (x == CHUNK_DIMENSION_X - 1 && neighbors[0] != null) { neighbors[0].setDirty(true); } if (z == 0 && neighbors[3] != null) { neighbors[3].setDirty(true); } if (z == CHUNK_DIMENSION_Z - 1 && neighbors[2] != null) { neighbors[2].setDirty(true); } if (x == CHUNK_DIMENSION_X - 1 && z == 0 && neighbors[7] != null) { neighbors[7].setDirty(true); } if (x == 0 && z == CHUNK_DIMENSION_Z - 1 && neighbors[6] != null) { neighbors[6].setDirty(true); } if (x == 0 && z == 0 && neighbors[5] != null) { neighbors[5].setDirty(true); } if (x == CHUNK_DIMENSION_X - 1 && z == CHUNK_DIMENSION_Z - 1 && neighbors[4] != null) { neighbors[4].setDirty(true); } } @Override public String toString() { return String.format("Chunk at %s.", getPosition()); } /** * Chunks are comparable by their relative distance to the player. * * @param o The chunk to compare to * @return The comparison value */ public int compareTo(Chunk o) { if (o == null) { return 0; } double distance = distanceToPlayer(); double distance2 = o.distanceToPlayer(); if (distance == distance2) return 0; return distance2 > distance ? -1 : 1; } public AABB getAABB() { if (_aabb == null) { Vector3d dimensions = new Vector3d(CHUNK_DIMENSION_X / 2, CHUNK_DIMENSION_Y / 2, CHUNK_DIMENSION_Z / 2); Vector3d position = new Vector3d(getChunkWorldPosX() + dimensions.x - 0.5f, dimensions.y - 0.5f, getChunkWorldPosZ() + dimensions.z - 0.5f); _aabb = new AABB(position, dimensions); } return _aabb; } public AABB getSubMeshAABB(int subMesh) { if (_subChunkAABB == null) { _subChunkAABB = new AABB[VERTICAL_SEGMENTS]; int heightHalf = CHUNK_DIMENSION_Y / VERTICAL_SEGMENTS / 2; for (int i = 0; i < _subChunkAABB.length; i++) { Vector3d dimensions = new Vector3d(8, CHUNK_DIMENSION_Y / VERTICAL_SEGMENTS / 2, 8); Vector3d position = new Vector3d(getChunkWorldPosX() + dimensions.x - 0.5f, (i * heightHalf * 2) + dimensions.y - 0.5f, getChunkWorldPosZ() + dimensions.z - 0.5f); _subChunkAABB[i] = new AABB(position, dimensions); } } return _subChunkAABB[subMesh]; } public void processChunk() { /* * Generate the chunk... */ generate(); /* * ... and fetch its neighbors... */ Chunk[] neighbors = loadOrCreateNeighbors(); /* * Before starting the illumination process, make sure that the neighbor chunks * are present and fully generated. */ for (int i = 0; i < neighbors.length; i++) { if (neighbors[i] != null) { neighbors[i].generate(); } } // Finally update the light and generate the meshes updateLight(); generateMeshes(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt((int) getPosition().x); out.writeInt((int) getPosition().z); // Save flags... byte flags = 0x0; if (isLightDirty()) { flags = Helper.setFlag(flags, (short) 0); } if (isFresh()) { flags = Helper.setFlag(flags, (short) 1); } // The flags are stored in the first byte of the file... out.writeByte(flags); for (int i = 0; i < _blocks.size(); i++) out.writeByte(_blocks.getRawByte(i)); for (int i = 0; i < _sunlight.sizePacked(); i++) out.writeByte(_sunlight.getRawByte(i)); for (int i = 0; i < _light.sizePacked(); i++) out.writeByte(_light.getRawByte(i)); for (int i = 0; i < _states.sizePacked(); i++) out.writeByte(_states.getRawByte(i)); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { getPosition().x = in.readInt(); getPosition().z = in.readInt(); // The first byte contains the flags... byte flags = in.readByte(); // Parse the flags... setLightDirty(Helper.isFlagSet(flags, (short) 0)); setFresh(Helper.isFlagSet(flags, (short) 1)); for (int i = 0; i < _blocks.size(); i++) _blocks.setRawByte(i, in.readByte()); for (int i = 0; i < _sunlight.sizePacked(); i++) _sunlight.setRawByte(i, in.readByte()); for (int i = 0; i < _light.sizePacked(); i++) _light.setRawByte(i, in.readByte()); for (int i = 0; i < _states.sizePacked(); i++) _states.setRawByte(i, in.readByte()); } /** * Generates the terrain mesh (creates the internal vertex arrays). */ public void generateMeshes() { if (isFresh() || isLightDirty() || !isDirty()) return; ChunkMesh[] newMeshes = new ChunkMesh[VERTICAL_SEGMENTS]; for (int i = 0; i < VERTICAL_SEGMENTS; i++) { newMeshes[i] = _tessellator.generateMesh(CHUNK_DIMENSION_Y / VERTICAL_SEGMENTS, i * (CHUNK_DIMENSION_Y / VERTICAL_SEGMENTS)); } setNewMesh(newMeshes); setDirty(false); } /** * Draws the opaque or translucent elements of a chunk. * * @param type The type of vertices to render * @param subMesh The of the submesh to render * @return True if rendered */ public boolean render(ChunkMesh.RENDER_TYPE type, int subMesh) { if (isReadyForRendering()) { if (!isSubMeshEmpty(subMesh)) { _activeMeshes[subMesh].render(type); return true; } } return false; } public boolean generateVBOs() { if (_newMeshes != null) { for (int i = 0; i < _newMeshes.length; i++) { _newMeshes[i].generateVBOs(); } return true; } return false; } public void update() { generateVBOs(); swapActiveMesh(); } public void executeOcclusionQuery() { GL11.glColorMask(false, false, false, false); GL11.glDepthMask(false); if (_activeMeshes != null) { for (int j = 0; j < VERTICAL_SEGMENTS; j++) { if (!isSubMeshEmpty(j)) { if (_queries[j] == 0) { _queries[j] = GL15.glGenQueries(); GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, _queries[j]); getSubMeshAABB(j).renderSolid(); GL15.glEndQuery(GL15.GL_SAMPLES_PASSED); } } } } GL11.glColorMask(true, true, true, true); GL11.glDepthMask(true); } public void applyOcclusionQueries() { for (int i = 0; i < VERTICAL_SEGMENTS; i++) { if (_queries[i] != 0) { int result = GL15.glGetQueryObjectui(_queries[i], GL15.GL_QUERY_RESULT_AVAILABLE); if (result != 0) { result = GL15.glGetQueryObjectui(_queries[i], GL15.GL_QUERY_RESULT); _occlusionCulled[i] = result <= 0; GL15.glDeleteQueries(_queries[i]); _queries[i] = 0; } } } } private void setNewMesh(ChunkMesh[] newMesh) { synchronized (this) { if (_disposed) return; ChunkMesh[] oldNewMesh = _newMeshes; _newMeshes = newMesh; if (oldNewMesh != null) { for (int i = 0; i < oldNewMesh.length; i++) oldNewMesh[i].dispose(); } } } private boolean swapActiveMesh() { synchronized (this) { if (_disposed) return false; if (_newMeshes != null) { if (_newMeshes[0].isDisposed() || !_newMeshes[0].isGenerated()) return false; ChunkMesh[] newMesh = _newMeshes; _newMeshes = null; ChunkMesh[] oldActiveMesh = _activeMeshes; _activeMeshes = newMesh; _rigidBody = null; if (oldActiveMesh != null) { for (int i = 0; i < oldActiveMesh.length; i++) { oldActiveMesh[i].dispose(); } } return true; } } return false; } /** * Returns the position of the chunk within the world. * * @return The world position */ public int getChunkWorldPosX() { return (int) getPosition().x * CHUNK_DIMENSION_X; } /** * Returns the position of the chunk within the world. * * @return Thew world position */ public int getChunkWorldPosZ() { return (int) getPosition().z * CHUNK_DIMENSION_Z; } /** * Returns the position of block within the world. * * @param x The local position * @return The world position */ public int getBlockWorldPosX(int x) { return x + getChunkWorldPosX(); } /** * Returns the position of block within the world. * * @param z The local position * @return The world position */ public int getBlockWorldPosZ(int z) { return z + getChunkWorldPosZ(); } public LocalWorldProvider getParent() { return _parent; } public boolean isDirty() { return _dirty; } public boolean isFresh() { return _fresh; } public boolean isLightDirty() { return _lightDirty; } public void setFresh(boolean fresh) { _fresh = fresh; } public void setDirty(boolean dirty) { _dirty = dirty; } public void setLightDirty(boolean lightDirty) { _lightDirty = lightDirty; } public void setPosition(Vector3d position) { super.setPosition(position); } public void setParent(LocalWorldProvider parent) { _parent = parent; } public static String getChunkSavePathForPosition(Vector3d position) { String x36 = Integer.toString((int) position.x, 36); String z36 = Integer.toString((int) position.z, 36); return x36 + "/" + z36; } public static String getChunkFileNameForPosition(Vector3d position) { String x36 = Integer.toString((int) position.x, 36); String z36 = Integer.toString((int) position.z, 36); return "bc_" + x36 + "." + z36; } public String getChunkSavePath() { return Chunk.getChunkSavePathForPosition(getPosition()); } public String getChunkFileName() { return Chunk.getChunkFileNameForPosition(getPosition()); } public FastRandom getRandom() { return _random; } /** * Disposes this chunk. Can NOT be undone. */ public void dispose() { synchronized (this) { if (_disposed) return; if (_activeMeshes != null) for (int i = 0; i < _activeMeshes.length; i++) _activeMeshes[i].dispose(); if (_newMeshes != null) { for (int i = 0; i < _newMeshes.length; i++) _newMeshes[i].dispose(); } _disposed = true; } } public boolean isReadyForRendering() { return _activeMeshes != null; } public ChunkMesh getActiveSubMesh(int subMesh) { return _activeMeshes[subMesh]; } public boolean isSubMeshOcclusionCulled(int subMesh) { return _occlusionCulled[subMesh]; } public boolean isSubMeshCulled(int subMesh) { return _subMeshCulled[subMesh]; } public void setSubMeshCulled(int subMesh, boolean culled) { _subMeshCulled[subMesh] = culled; } public void resetOcclusionCulled() { _occlusionCulled = new boolean[VERTICAL_SEGMENTS]; } public void resetSubMeshCulled() { _subMeshCulled = new boolean[VERTICAL_SEGMENTS]; } public boolean isSubMeshEmpty(int subMesh) { return _activeMeshes[subMesh].isEmpty(); } public void renderAABBs(boolean solid) { if (isReadyForRendering()) { for (int i = 0; i < VERTICAL_SEGMENTS; i++) { if (!isSubMeshEmpty(i)) { if (!solid) getSubMeshAABB(i).render(2f); else getSubMeshAABB(i).renderSolid(); } } } } public int triangleCount() { int result = 0; for (int i = 0; i < VERTICAL_SEGMENTS; i++) { result += _activeMeshes[i].triangleCount(); } return result; } public void updateRigidBody() { updateRigidBody(_activeMeshes); } private void updateRigidBody(ChunkMesh[] meshes) { if (_rigidBody != null) return; if (meshes == null) return; if (meshes.length < VERTICAL_SEGMENTS) return; TriangleIndexVertexArray vertexArray = new TriangleIndexVertexArray(); int counter = 0; for (int k = 0; k < Chunk.VERTICAL_SEGMENTS; k++) { ChunkMesh mesh = meshes[k]; if (mesh != null) { IndexedMesh indexedMesh = mesh._indexedMesh; if (indexedMesh != null) { vertexArray.addIndexedMesh(indexedMesh); counter++; } mesh._indexedMesh = null; } } if (counter == VERTICAL_SEGMENTS) { try { BvhTriangleMeshShape shape = new BvhTriangleMeshShape(vertexArray, true); Matrix3f rot = new Matrix3f(); rot.setIdentity(); DefaultMotionState blockMotionState = new DefaultMotionState(new Transform(new Matrix4f(rot, new Vector3f((float) getPosition().x * Chunk.CHUNK_DIMENSION_X, (float) getPosition().y * Chunk.CHUNK_DIMENSION_Y, (float) getPosition().z * Chunk.CHUNK_DIMENSION_Z), 1.0f))); RigidBodyConstructionInfo blockConsInf = new RigidBodyConstructionInfo(0, blockMotionState, shape, new Vector3f()); _rigidBody = new RigidBody(blockConsInf); } catch (Exception e) { Terasology.getInstance().getLogger().log(Level.WARNING, "Chunk failed to create rigid body.", e); } } } public RigidBody getRigidBody() { return _rigidBody; } }