package mekanism.client.render.ctm;
import static mekanism.client.render.ctm.Dir.BOTTOM;
import static mekanism.client.render.ctm.Dir.BOTTOM_LEFT;
import static mekanism.client.render.ctm.Dir.BOTTOM_RIGHT;
import static mekanism.client.render.ctm.Dir.LEFT;
import static mekanism.client.render.ctm.Dir.RIGHT;
import static mekanism.client.render.ctm.Dir.TOP;
import static mekanism.client.render.ctm.Dir.TOP_LEFT;
import static mekanism.client.render.ctm.Dir.TOP_RIGHT;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.EnumMap;
import java.util.List;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.IStringSerializable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import com.google.common.base.Optional;
import com.google.common.collect.Maps;
// @formatter:off
/**
* The CTM renderer will draw the block's FACE using by assembling 4 quadrants from the 5 available block
* textures. The normal Texture.png is the blocks "unconnected" texture, and is used when CTM is disabled or the block
* has nothing to connect to. This texture has all of the outside corner quadrants The texture-ctm.png contains the
* rest of the quadrants.
* <pre><blockquote>
* ┌─────────────────┐ ┌────────────────────────────────┐
* │ texture.png │ │ texture-ctm.png │
* │ ╔══════╤══════╗ │ │ ──────┼────── ║ ─────┼───── ║ │
* │ ║ │ ║ │ │ │ │ │║ │ ║ │
* │ ║ 16 │ 17 ║ │ │ │ 0 │ 1 │║ 2 │ 3 ║ │
* │ ╟──────┼──────╢ │ │ ┼──────┼──────┼╟──────┼──────╢ │
* │ ║ │ ║ │ │ │ │ │║ │ ║ │
* │ ║ 18 │ 19 ║ │ │ │ 4 │ 5 │║ 6 │ 7 ║ │
* │ ╚══════╧══════╝ │ │ ──────┼────── ║ ─────┼───── ║ │
* └─────────────────┘ │ ═══════╤═══════╝ ─────┼───── ╚ │
* │ │ │ ││ │ │ │
* │ │ 8 │ 9 ││ 10 │ 11 │ │
* │ ┼──────┼──────┼┼──────┼──────┼ │
* │ │ │ ││ │ │ │
* │ │ 12 │ 13 ││ 14 │ 15 │ │
* │ ═══════╧═══════╗ ─────┼───── ╔ │
* └────────────────────────────────┘
* </blockquote></pre>
* combining { 18, 13, 9, 16 }, we can generate a texture connected to the right!
* <pre><blockquote>
* ╔══════╤═══════
* ║ │ │
* ║ 16 │ 9 │
* ╟──────┼──────┼
* ║ │ │
* ║ 18 │ 13 │
* ╚══════╧═══════
* </blockquote></pre>
*
* combining { 18, 13, 11, 2 }, we can generate a texture, in the shape of an L (connected to the right, and up
* <pre><blockquote>
* ║ ─────┼───── ╚
* ║ │ │
* ║ 2 │ 11 │
* ╟──────┼──────┼
* ║ │ │
* ║ 18 │ 13 │
* ╚══════╧═══════
* </blockquote></pre>
*
* HAVE FUN!
* -CptRageToaster-
*/
public class CTM {
public static int REQUIRED_TEXTURES = 2;
public static int QUADS_PER_SIDE = 4;
/**
* The Uvs for the specific "magic number" value
*/
public static final ISubmap[] uvs = new ISubmap[]{
//Ctm texture
new Submap(4, 4, 0, 0), // 0
new Submap(4, 4, 4, 0), // 1
new Submap(4, 4, 8, 0), // 2
new Submap(4, 4, 12, 0), // 3
new Submap(4, 4, 0, 4), // 4
new Submap(4, 4, 4, 4), // 5
new Submap(4, 4, 8, 4), // 6
new Submap(4, 4, 12, 4), // 7
new Submap(4, 4, 0, 8), // 8
new Submap(4, 4, 4, 8), // 9
new Submap(4, 4, 8, 8), // 10
new Submap(4, 4, 12, 8), // 11
new Submap(4, 4, 0, 12), // 12
new Submap(4, 4, 4, 12), // 13
new Submap(4, 4, 8, 12), // 14
new Submap(4, 4, 12, 12), // 15
// Default texture
new Submap(8, 8, 0, 0), // 16
new Submap(8, 8, 8, 0), // 17
new Submap(8, 8, 0, 8), // 18
new Submap(8, 8, 8, 8) // 19
};
public static final ISubmap FULL_TEXTURE = new Submap(16, 16, 0, 0);
// @formatter:on
/** Some hardcoded offset values for the different corner indeces */
protected static int[] submapOffsets = { 4, 5, 1, 0 };
/** For use via the Chisel 2 config only, altering this could cause unintended behavior */
public static boolean disableObscuredFaceCheckConfig = false;
public Optional<Boolean> disableObscuredFaceCheck = Optional.absent();
protected TIntObjectMap<Dir[]> submapMap = new TIntObjectHashMap<Dir[]>();
protected EnumMap<Dir, Boolean> connectionMap = Maps.newEnumMap(Dir.class);
protected int[] submapCache;
protected CTM()
{
for (Dir dir : Dir.VALUES)
{
connectionMap.put(dir, false);
}
// Mapping the different corner indeces to their respective dirs
submapMap.put(0, new Dir[] { BOTTOM, LEFT, BOTTOM_LEFT });
submapMap.put(1, new Dir[] { BOTTOM, RIGHT, BOTTOM_RIGHT });
submapMap.put(2, new Dir[] { TOP, RIGHT, TOP_RIGHT });
submapMap.put(3, new Dir[] { TOP, LEFT, TOP_LEFT });
}
public static CTM getInstance() {
return new CTM();
}
/**
* @return The indeces of the typical 4x4 submap to use for the given face at the given location.
*
* Indeces are in counter-clockwise order starting at bottom left.
*/
public int[] createSubmapIndices(IBlockAccess world, BlockPos pos, EnumFacing side) {
submapCache = new int[] { 18, 19, 17, 16 };
if (world == null) {
return submapCache;
}
buildConnectionMap(world, pos, side);
// Map connections to submap indeces
for (int i = 0; i < 4; i++) {
fillSubmaps(i);
}
return submapCache;
}
public int[] createSubmapIndices(long data, EnumFacing side){
submapCache = new int[] { 18, 19, 17, 16 };
buildConnectionMap(data, side);
// Map connections to submap indeces
for (int i = 0; i < 4; i++) {
fillSubmaps(i);
}
return submapCache;
}
public int[] getSubmapIndices() {
return submapCache;
}
public static boolean isDefaultTexture(int id) {
return (id == 16 || id == 17 || id == 18 || id == 19);
}
/**
* Builds the connection map and stores it in this CTM instance. The {@link #connected(Dir)}, {@link #connectedAnd(Dir...)}, and {@link #connectedOr(Dir...)} methods can be used to access it.
*/
public void buildConnectionMap(IBlockAccess world, BlockPos pos, EnumFacing side) {
IBlockState state = world.getBlockState(pos);
for (Dir dir : Dir.VALUES) {
connectionMap.put(dir, dir.isConnected(this, world, pos, side, state));
}
}
public void buildConnectionMap(long data, EnumFacing side){
for (Dir dir : Dir.VALUES){
connectionMap.put(dir, false);
}
List<CTMConnections> connections = CTMConnections.decode(data);
for (CTMConnections loc : connections){
if (loc.getDirForSide(side) != null){
connectionMap.put(loc.getDirForSide(side), true);
}
}
}
private void fillSubmaps(int idx) {
Dir[] dirs = submapMap.get(idx);
if (connectedOr(dirs[0], dirs[1])) {
if (connectedAnd(dirs)) {
// If all dirs are connected, we use the fully connected face,
// the base offset value.
submapCache[idx] = submapOffsets[idx];
} else {
// This is a bit magic-y, but basically the array is ordered so
// the first dir requires an offset of 2, and the second dir
// requires an offset of 8, plus the initial offset for the
// corner.
submapCache[idx] = submapOffsets[idx] + (connected(dirs[0]) ? 2 : 0) + (connected(dirs[1]) ? 8 : 0);
}
}
}
/**
* @param dir
* The direction to check connection in.
* @return True if the cached connectionMap holds a connection in this {@link Dir direction}.
*/
public boolean connected(Dir dir) {
return connectionMap.get(dir);
}
/**
* @param dirs
* The directions to check connection in.
* @return True if the cached connectionMap holds a connection in <i><b>all</b></i> the given {@link Dir directions}.
*/
public boolean connectedAnd(Dir... dirs) {
for (Dir dir : dirs) {
if (!connected(dir)) {
return false;
}
}
return true;
}
/**
* @param dirs
* The directions to check connection in.
* @return True if the cached connectionMap holds a connection in <i><b>one of</b></i> the given {@link Dir directions}.
*/
public boolean connectedOr(Dir... dirs) {
for (Dir dir : dirs) {
if (connected(dir)) {
return true;
}
}
return false;
}
/**
* A simple check for if the given block can connect to the given direction on the given side.
*
* @param world
* @param current
* The position of your block.
* @param y
* The position of the block to check against.
* @param dir
* The {@link EnumFacing side} of the block to check for connection status. This is <i>not</i> the direction to check in.
* @return True if the given block can connect to the given location on the given side.
*/
public boolean isConnected(IBlockAccess world, BlockPos current, BlockPos connection, EnumFacing dir) {
IBlockState state = world.getBlockState(current);
return isConnected(world, current, connection, dir, state);
}
/**
* A simple check for if the given block can connect to the given direction on the given side.
*
* @param world
* @param current
* The position of your block.
* @param y
* The position of the block to check against.
* @param dir
* The {@link EnumFacing side} of the block to check for connection status. This is <i>not</i> the direction to check in.
* @param state
* The state to check against for connection.
* @return True if the given block can connect to the given location on the given side.
*/
public boolean isConnected(IBlockAccess world, BlockPos current, BlockPos connection, EnumFacing dir, IBlockState state)
{
BlockPos pos2 = connection.add(dir.getDirectionVec());
boolean disableObscured = disableObscuredFaceCheck.or(disableObscuredFaceCheckConfig);
IBlockState obscuring = disableObscured ? null : getConnectedState(world, pos2, dir);
boolean ret = canConnect(world, current, connection);
// no block obscuring this face
if(obscuring == null)
{
return ret;
}
// check that we aren't already connected outwards from this side
ret &= !obscuring.isFullCube() || !obscuring.equals(state);
return ret;
}
public static boolean canConnect(IBlockAccess world, BlockPos pos, BlockPos connection)
{
IBlockState state = world.getBlockState(pos);
IBlockState con = world.getBlockState(connection);
if(!(state.getBlock() instanceof ICTMBlock))
{
return false;
}
CTMData data = ((ICTMBlock)state.getBlock()).getCTMData(state);
// no block or a bad API user
if(con == null || data == null)
{
return false;
}
boolean ret = false;
if(con.getBlock() instanceof ICTMBlock && ((ICTMBlock)con.getBlock()).getCTMData(con) != null)
{
String state2 = ((IStringSerializable)con.getValue(((ICTMBlock)con.getBlock()).getTypeProperty())).getName();
ret = data.acceptableBlockStates.contains(state2);
}
return ret;
}
public static IBlockState getConnectedState(IBlockAccess world, BlockPos pos, EnumFacing side)
{
IBlockState state = world.getBlockState(pos);
return state;
}
}