/*
* 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 static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glIsEnabled;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import javax.vecmath.Vector2f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4f;
import org.lwjgl.opengl.GL11;
import org.spout.api.material.BlockMaterial;
import org.terasology.logic.manager.ShaderManager;
import org.terasology.math.Side;
import org.terasology.model.blocks.BlockFamily;
import org.terasology.model.shapes.BlockMeshPart;
import org.terasology.model.structures.AABB;
import org.terasology.model.structures.BlockPosition;
import org.terasology.rendering.interfaces.IGameObject;
import org.terasology.rendering.primitives.Mesh;
import org.terasology.rendering.primitives.Tessellator;
import org.terasology.rendering.shader.ShaderProgram;
/**
* Stores all information for a specific block type.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
* @author Rasmus 'Cervator' Praestholm <cervator@gmail.com>
*/
public class TeraBlock {
public static final int ATLAS_SIZE_IN_PX = 256;
public static final int TEXTURE_SIZE_IN_PX = 16;
public static final int ATLAS_ELEMENTS_PER_ROW_AND_COLUMN = ATLAS_SIZE_IN_PX / TEXTURE_SIZE_IN_PX;
public static final float TEXTURE_OFFSET = 0.0625f;
public static final float TEXTURE_OFFSET_WIDTH = 0.0624f;
private static final EnumMap<Side, Float> DIRECTION_LIT_LEVEL = new EnumMap<Side, Float>(Side.class);
/**
* Possible forms of blocks.
*/
public static enum BLOCK_FORM {
DEFAULT, LOWERED_BLOCK, BILLBOARD
}
/**
* Different color sources for blocks.
*/
public static enum COLOR_SOURCE {
DEFAULT, COLOR_LUT, FOLIAGE_LUT
}
/**
* Init. the LUTs.
*/
static {
DIRECTION_LIT_LEVEL.put(Side.TOP, 0.9f);
DIRECTION_LIT_LEVEL.put(Side.BOTTOM, 0.9f);
DIRECTION_LIT_LEVEL.put(Side.FRONT, 1.0f);
DIRECTION_LIT_LEVEL.put(Side.BACK, 1.0f);
DIRECTION_LIT_LEVEL.put(Side.LEFT, 0.75f);
DIRECTION_LIT_LEVEL.put(Side.RIGHT, 0.75f);
}
private final BlockMaterial handle;
/* PROPERTIES */
private BlockFamily _family = null;
private boolean _translucent;
private boolean _invisible;
private boolean _penetrable;
private boolean _castsShadows;
private boolean _renderBoundingBox;
private boolean _allowBlockAttachment;
private boolean _bypassSelectionRay;
private boolean _liquid;
private boolean _waving;
private boolean _transparent;
private boolean _usable;
// Inventory settings
private boolean _straightToInventory;
private boolean _stackable = true;
private boolean _entityTemporary = false;
private String _entityPrefab = "";
private int _lootAmount;
private BLOCK_FORM _blockForm;
private COLOR_SOURCE _colorSource;
private byte _luminance;
private byte _hardness;
private float _mass;
/* RENDERING */
private Mesh _mesh;
private BlockMeshPart _centerMesh;
private EnumMap<Side, BlockMeshPart> _sideMesh = new EnumMap<Side, BlockMeshPart>(Side.class);
private EnumSet<Side> _fullSide = EnumSet.noneOf(Side.class);
private EnumSet<Side> _affectedByLut = EnumSet.noneOf(Side.class);
// For liquid handling
private EnumMap<Side, BlockMeshPart> _loweredSideMesh = new EnumMap<Side, BlockMeshPart>(Side.class);
private EnumMap<Side, Vector4f> _colorOffset = new EnumMap<Side, Vector4f>(Side.class);
private EnumMap<Side, Vector2f> _textureAtlasPos = new EnumMap<Side, Vector2f>(Side.class);
/* COLLISION */
private List<AABB> _colliders = new ArrayList<AABB>();
public AABB _bounds = new AABB(new Vector3d(), new Vector3d());
/**
* Init. a new block with default properties in place.
* @param block
*/
public TeraBlock(BlockMaterial mat) {
handle = mat;
for (Side side : Side.values()) {
withTextureAtlasPos(side, new Vector2f(0.0f, 0.0f));
withColorOffset(side, new Vector4f(1.0f, 1.0f, 1.0f, 1.0f));
withFullSide(side, false);
withAffectedByLut(side, true);
}
// Load the default settings
withPenetrable(false);
withAllowBlockAttachment(true);
withBypassSelectionRay(false);
withBlockForm(BLOCK_FORM.DEFAULT);
withColorSource(COLOR_SOURCE.DEFAULT);
withCastsShadows(true);
withTranslucent(false);
withInvisible(false);
withRenderBoundingBox(true);
withHardness((byte) 3);
withLuminance((byte) 0);
withLiquid(false);
withMass(64000f);
withLootAmount(2);
}
/**
* Calculates the color offset for a given block type and a specific
* side of the block.
*
* @param side The block side
* @param temperature The temperature
* @param humidity The humidity
* @return The color offset
*/
public Vector4f calcColorOffsetFor(Side side) {
Vector4f color = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
Vector4f colorOffset = _colorOffset.get(side);
color.x *= colorOffset.x;
color.y *= colorOffset.y;
color.z *= colorOffset.z;
color.w *= colorOffset.w;
return color;
}
/**
* Calculates the texture atlas offset for a given block type and a specific
* side.
*
* @param side The side of the block
* @return The texture offset
*/
public Vector2f calcTextureOffsetFor(Side side) {
Vector2f pos = getTextureAtlasPos(side);
return new Vector2f(pos.x * TEXTURE_OFFSET, pos.y * TEXTURE_OFFSET);
}
public void renderWithLightValue(float light) {
if (isInvisible())
return;
ShaderProgram shader = ShaderManager.getInstance().getShaderProgram("block");
shader.enable();
shader.setFloat("light", light);
if (_mesh == null) {
Tessellator tessellator = new Tessellator();
tessellator.setColor(new Vector4f(1, 1, 1, 1));
if (_centerMesh != null) {
tessellator.addMeshPart(_centerMesh);
}
for (Side dir : Side.values()) {
BlockMeshPart part = _sideMesh.get(dir);
if (part != null) {
float lightLevel = DIRECTION_LIT_LEVEL.get(dir);
tessellator.setColor(new Vector4f(lightLevel, lightLevel, lightLevel, lightLevel));
tessellator.addMeshPart(part);
}
}
_mesh = tessellator.generateMesh();
}
if (getBlockForm() != BLOCK_FORM.BILLBOARD || !glIsEnabled(GL11.GL_CULL_FACE)) {
_mesh.render();
} else {
glDisable(GL11.GL_CULL_FACE);
_mesh.render();
glEnable(GL11.GL_CULL_FACE);
}
}
public void render() {
renderWithLightValue(1.0f);
}
public TeraBlock withBlockFamily(BlockFamily family) {
_family = family;
return this;
}
public TeraBlock withTranslucent(boolean translucent) {
_translucent = translucent;
return this;
}
public TeraBlock withTransparent(boolean transparent) {
_transparent = transparent;
return this;
}
public TeraBlock withWaving(boolean waving) {
_waving = waving;
return this;
}
public TeraBlock withMass(float mass) {
_mass = mass;
return this;
}
public TeraBlock withInvisible(boolean invisible) {
_invisible = invisible;
return this;
}
public TeraBlock withPenetrable(boolean penetrable) {
_penetrable = penetrable;
return this;
}
public TeraBlock withCastsShadows(boolean castsShadows) {
_castsShadows = castsShadows;
return this;
}
public TeraBlock withRenderBoundingBox(boolean renderBoundingBox) {
_renderBoundingBox = renderBoundingBox;
return this;
}
public TeraBlock withAllowBlockAttachment(boolean allowBlockAttachment) {
_allowBlockAttachment = allowBlockAttachment;
return this;
}
public TeraBlock withBypassSelectionRay(boolean bypassSelectionRay) {
_bypassSelectionRay = bypassSelectionRay;
return this;
}
public TeraBlock withLiquid(boolean liquid) {
_liquid = liquid;
return this;
}
public TeraBlock withBlockForm(BLOCK_FORM blockForm) {
_blockForm = blockForm;
return this;
}
public TeraBlock withColorSource(COLOR_SOURCE colorSource) {
_colorSource = colorSource;
return this;
}
public TeraBlock withLootAmount(int lootAmount) {
_lootAmount = lootAmount;
return this;
}
public TeraBlock withLuminance(byte luminance) {
_luminance = luminance;
return this;
}
public TeraBlock withHardness(byte hardness) {
_hardness = hardness;
return this;
}
public TeraBlock withColorOffset(Side side, Vector4f colorOffset) {
_colorOffset.put(side, colorOffset);
return this;
}
public TeraBlock withTextureAtlasPos(Side side, Vector2f atlasPos) {
_textureAtlasPos.put(side, atlasPos);
return this;
}
public TeraBlock withColorOffset(Vector4f colorOffset) {
for (Side side : Side.values()) {
withColorOffset(side, colorOffset);
}
return this;
}
public TeraBlock withCenterMesh(BlockMeshPart meshPart) {
_centerMesh = meshPart;
return this;
}
public TeraBlock withSideMesh(Side side, BlockMeshPart meshPart) {
_sideMesh.put(side, meshPart);
return this;
}
public TeraBlock withLoweredSideMesh(Side side, BlockMeshPart meshPart) {
_loweredSideMesh.put(side, meshPart);
return this;
}
public TeraBlock withFullSide(Side side, boolean full) {
if (full) {
_fullSide.add(side);
} else {
_fullSide.remove(side);
}
return this;
}
public TeraBlock withAffectedByLut(Side side, boolean full) {
if (full) {
_affectedByLut.add(side);
} else {
_affectedByLut.remove(side);
}
return this;
}
public TeraBlock withStraightToInventory(boolean straightToInventory) {
_straightToInventory = straightToInventory;
return this;
}
public TeraBlock withStackable(boolean stackable) {
_stackable = stackable;
return this;
}
public TeraBlock withEntityTemporary(boolean entityTemporary) {
_entityTemporary = entityTemporary;
return this;
}
public TeraBlock withEntityPrefab(String entityPrefab) {
_entityPrefab = entityPrefab;
return this;
}
public TeraBlock withUsable(boolean usable) {
_usable = usable;
return this;
}
public void setColliders(List<AABB> colliders) {
_colliders = new ArrayList<AABB>(colliders);
_bounds = new AABB(colliders);
}
public COLOR_SOURCE getColorSource() {
return _colorSource;
}
public byte getHardness() {
return _hardness;
}
public Vector4f getColorOffset(Side side) {
return _colorOffset.get(side);
}
public Vector2f getTextureAtlasPos(Side side) {
return _textureAtlasPos.get(side);
}
public BLOCK_FORM getBlockForm() {
return _blockForm;
}
public String getTitle() {
return handle.getName();
}
public short getId() {
return handle.getId();
}
public int getLootAmount() {
return _lootAmount;
}
public BlockFamily getBlockFamily() {
return _family;
}
public boolean isBlockingSide(Side side) {
return _fullSide.contains(side);
}
public BlockMeshPart getSideMesh(Side side) {
return _sideMesh.get(side);
}
public BlockMeshPart getCenterMesh() {
return _centerMesh;
}
public BlockMeshPart getLoweredSideMesh(Side side) {
return _loweredSideMesh.get(side);
}
public boolean isInvisible() {
return _invisible;
}
public boolean isPenetrable() {
return _penetrable;
}
public boolean isCastsShadows() {
return _castsShadows;
}
public boolean isRenderBoundingBox() {
return _renderBoundingBox;
}
public byte getLuminance() {
return _luminance;
}
public float getMass() {
return _mass;
}
public boolean isDestructible() {
return getHardness() > 0;
}
public boolean isAllowBlockAttachment() {
return _allowBlockAttachment;
}
public boolean isLiquid() {
return _liquid;
}
public boolean isWaving() {
return _waving;
}
public boolean isSelectionRayThrough() {
return _bypassSelectionRay;
}
public boolean isTransparent() {
return _transparent;
}
public boolean isTranslucent() {
return _translucent;
}
public boolean isStraightToInventory() {
return _straightToInventory;
}
public boolean isStackable() {
return _stackable;
}
public boolean isEntityTemporary() {
return _entityTemporary || _entityPrefab.isEmpty();
}
public boolean isUsable() {
return _usable;
}
public String getEntityPrefab() {
return _entityPrefab;
}
/**
* @param x x offset
* @param y y offset
* @param z z offset
* @return An iterator over the colliders for this block, offset by the given values
*/
public Iterable<AABB> getColliders(int x, int y, int z) {
return new OffsetAABBIterator(_colliders, x, y, z);
}
public AABB getBounds(BlockPosition pos) {
return new AABB(new Vector3d(_bounds.getPosition().x + pos.x, _bounds.getPosition().y + pos.y, _bounds.getPosition().z + pos.z), _bounds.getDimensions());
}
private class OffsetAABBIterator implements Iterable<AABB>, Iterator<AABB> {
Iterator<AABB> _source;
int _x;
int _y;
int _z;
public OffsetAABBIterator(Iterable<AABB> source, int x, int y, int z) {
_source = source.iterator();
_x = x;
_y = y;
_z = z;
}
@Override
public Iterator<AABB> iterator() {
return this;
}
@Override
public boolean hasNext() {
return _source.hasNext();
}
@Override
public AABB next() {
AABB base = _source.next();
return new AABB(new Vector3d(base.getPosition().x + _x, base.getPosition().y + _y, base.getPosition().z + _z),
base.getDimensions());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns the AABB for a block at the given position.
*
* @param pos The position
* @return The AABB
*/
public static AABB AABBForBlockAt(Vector3d pos) {
return new AABB(pos, new Vector3d(0.5f, 0.5f, 0.5f));
}
@Override
public String toString() {
return "TB:" + handle.toString();
}
}