/*
* Copyright 2012 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.teraspout;
import java.util.concurrent.locks.ReentrantLock;
import javax.vecmath.Vector3d;
import org.spout.api.geo.cuboid.Chunk;
import org.spout.api.material.BlockMaterial;
import org.spout.engine.world.SpoutChunk;
import org.terasology.logic.manager.Config;
import org.terasology.math.TeraMath;
import org.terasology.math.Vector3i;
import org.terasology.model.structures.AABB;
import org.terasology.model.structures.TeraSmartArray;
import org.terasology.rendering.primitives.ChunkMesh;
import com.bulletphysics.dynamics.RigidBody;
import com.google.common.base.Objects;
/**
* 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 TeraChunk {
public enum State {
ADJACENCY_GENERATION_PENDING,
INTERNAL_LIGHT_GENERATION_PENDING,
LIGHT_PROPAGATION_PENDING,
FULL_LIGHT_CONNECTIVITY_PENDING,
COMPLETE
}
/* PUBLIC CONSTANT VALUES */
public static final int INNER_CHUNK_POS_FILTER_X = TeraMath.ceilPowerOfTwo(Chunk.BLOCKS.SIZE) - 1;
public static final int INNER_CHUNK_POS_FILTER_Z = TeraMath.ceilPowerOfTwo(Chunk.BLOCKS.SIZE) - 1;
public static final int POWER_X = TeraMath.sizeOfPower(Chunk.BLOCKS.SIZE);
public static final int POWER_Z = TeraMath.sizeOfPower(Chunk.BLOCKS.SIZE);
public static final int VERTICAL_SEGMENTS = Config.getInstance().getVerticalChunkMeshSegments();
public static final byte MAX_LIGHT = 0x0f;
public static final Vector3i CHUNK_POWER = new Vector3i(POWER_X, 0, POWER_Z);
public static final Vector3i CHUNK_SIZE = new Vector3i(Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE);
public static final Vector3i INNER_CHUNK_POS_FILTER = new Vector3i(INNER_CHUNK_POS_FILTER_X, 0, INNER_CHUNK_POS_FILTER_Z);
protected TeraSmartArray sunlight, light, states;
private State chunkState = State.ADJACENCY_GENERATION_PENDING;
private boolean dirty;
private boolean animated;
private AABB aabb;
// Rendering
private ChunkMesh[] mesh;
private ChunkMesh[] pendingMesh;
private AABB[] subMeshAABB = null;
// Physics
private RigidBody rigidBody = null;
private ReentrantLock lock = new ReentrantLock();
private boolean disposed = false;
private final SpoutChunk handle;
public TeraChunk(SpoutChunk handle) {
this.handle = handle;
sunlight = new TeraSmartArray(Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE);
light = new TeraSmartArray(Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE);
states = new TeraSmartArray(Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE, Chunk.BLOCKS.SIZE);
setDirty(true);
}
public void lock() {
lock.lock();
}
public void unlock() {
lock.unlock();
}
public boolean isLocked() {
return lock.isLocked();
}
public Vector3i getPos() {
return new Vector3i(handle.getBlockX(), handle.getBlockY(), handle.getBlockZ());
}
public boolean isInBounds(int x, int y, int z) {
return handle.containsBlock(x, y, z);
}
public State getChunkState() {
return chunkState;
}
public void setChunkState(State chunkState) {
this.chunkState = chunkState;
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
lock();
try {
this.dirty = dirty;
} finally {
unlock();
}
}
public short getBlockId(int x, int y, int z) {
return handle.getBlockMaterial(x, y, z).getId();
}
public TeraBlock getBlock(Vector3i pos) {
return getBlock(pos.x, pos.y, pos.z);
}
public TeraBlock getBlock(int x, int y, int z) {
BlockMaterial mat = handle.getBlock(x, y, z, null).getMaterial();
return TeraSpout.getInstance().getBlock(mat);
}
public byte getSunlight(Vector3i pos) {
return sunlight.get(pos.x, pos.y, pos.z);
}
public byte getSunlight(int x, int y, int z) {
return sunlight.get(x, y, z);
}
public boolean setSunlight(Vector3i pos, byte amount) {
return setSunlight(pos.x, pos.y, pos.z, amount);
}
public boolean setSunlight(int x, int y, int z, byte amount) {
byte oldValue = sunlight.set(x, y, z, amount);
return oldValue != amount;
}
public byte getLight(Vector3i pos) {
return light.get(pos.x, pos.y, pos.z);
}
public byte getLight(int x, int y, int z) {
return light.get(x, y, z);
}
public boolean setLight(Vector3i pos, byte amount) {
return setLight(pos.x, pos.y, pos.z, amount);
}
public boolean setLight(int x, int y, int z, byte amount) {
byte oldValue = light.set(x, y, z, amount);
return (oldValue != amount);
}
public boolean setState(Vector3i pos, byte state, byte oldState) {
return setState(pos.x, pos.y, pos.z, state, oldState);
}
public boolean setState(int x, int y, int z, byte state, byte oldState) {
byte prev = states.set(x, y, z, state, oldState);
return prev == oldState;
}
public byte getState(Vector3i pos) {
return states.get(pos.x, pos.y, pos.z);
}
public byte getState(int x, int y, int z) {
return states.get(x, y, z);
}
public Vector3i getChunkWorldPos() {
return new Vector3i(getChunkWorldPosX(), getChunkWorldPosY(), getChunkWorldPosZ());
}
public int getChunkWorldPosX() {
return handle.getBase().getBlockX();
}
public int getChunkWorldPosY() {
return handle.getBase().getBlockY();
}
public int getChunkWorldPosZ() {
return handle.getBase().getBlockZ();
}
public Vector3i getBlockWorldPos(Vector3i blockPos) {
return getBlockWorldPos(blockPos.x, blockPos.y, blockPos.z);
}
public Vector3i getBlockWorldPos(int x, int y, int z) {
return new Vector3i(getBlockWorldPosX(x), getBlockWorldPosY(y), getBlockWorldPosZ(z));
}
public int getBlockWorldPosX(int x) {
return x + getChunkWorldPosX();
}
public int getBlockWorldPosY(int y) {
return y + getChunkWorldPosY();
}
public int getBlockWorldPosZ(int z) {
return z + getChunkWorldPosZ();
}
public AABB getAABB() {
if (aabb == null) {
Vector3d dimensions = new Vector3d(Chunk.BLOCKS.HALF_SIZE, Chunk.BLOCKS.HALF_SIZE, Chunk.BLOCKS.HALF_SIZE);
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;
}
@Override
public String toString() {
return "Chunk " + handle.getBase().toString();
}
@Override
public int hashCode() {
return Objects.hashCode(handle.getBase());
}
public void setMesh(ChunkMesh[] mesh) {
this.mesh = mesh;
if (rigidBody != null) {
rigidBody.destroy();
rigidBody = null;
}
}
public void setPendingMesh(ChunkMesh[] mesh) {
this.pendingMesh = mesh;
}
public void setAnimated(boolean animated) {
this.animated = animated;
}
public boolean getAnimated() {
return animated;
}
public ChunkMesh[] getMesh() {
return mesh;
}
public ChunkMesh[] getPendingMesh() {
return pendingMesh;
}
public AABB getSubMeshAABB(int subMesh) {
if (subMeshAABB == null) {
subMeshAABB = new AABB[VERTICAL_SEGMENTS];
int heightHalf = Chunk.BLOCKS.SIZE / VERTICAL_SEGMENTS / 2;
for (int i = 0; i < subMeshAABB.length; i++) {
Vector3d dimensions = new Vector3d(8, heightHalf, 8);
Vector3d position = new Vector3d(getChunkWorldPosX() + dimensions.x - 0.5f, (i * heightHalf * 2) + dimensions.y - 0.5f, getChunkWorldPosZ() + dimensions.z - 0.5f);
subMeshAABB[i] = new AABB(position, dimensions);
}
}
return subMeshAABB[subMesh];
}
public RigidBody getRigidBody() {
return rigidBody;
}
public void setRigidBody(RigidBody rigidBody) {
this.rigidBody = rigidBody;
}
public void dispose() {
disposed = true;
if (rigidBody != null) {
rigidBody.destroy();
rigidBody = null;
}
if (mesh != null) {
for (ChunkMesh chunkMesh : mesh) {
chunkMesh.dispose();
}
mesh = null;
}
}
public boolean isDisposed() {
return disposed;
}
}