connectionMap = Maps.newEnumMap(Dir.class);
+
+ 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[] getSubmapIndices(IBlockAccess world, int x, int y, int z, int side) {
+ int[] ret = new int[] { 18, 19, 17, 16 };
+
+ if (world == null) {
+ return ret;
+ }
+
+ Block block = world.getBlock(x, y, z);
+ int meta = world.getBlockMetadata(x, y, z);
+
+ buildConnectionMap(world, x, y, z, side, block, meta);
+
+ // Map connections to submap indeces
+ for (int i = 0; i < 4; i++) {
+ fillSubmaps(ret, i);
+ }
+
+ return ret;
+ }
+
+ /**
+ * 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, int x, int y, int z, int side, Block block, int meta) {
+ for (Dir dir : Dir.VALUES) {
+ connectionMap.put(dir, dir.isConnected(this, world, x, y, z, side, block, meta));
+ }
+ }
+
+ private void fillSubmaps(int[] ret, 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.
+ ret[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.
+ ret[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 all 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 one of the given {@link Dir
+ * directions}.
+ */
+ private 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 x The x coordinate of the block to check against. This is not the position of your block.
+ * @param y The y coordinate of the block to check against. This is not the position of your block.
+ * @param z The z coordinate of the block to check against. This is not the position of your block.
+ * @param side The side of the block to check for connection status. This is not the direction to check in.
+ * @param block The block to check against for connection.
+ * @param meta The metadata 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, int x, int y, int z, int side, Block block, int meta) {
+ ForgeDirection dir = ForgeDirection.VALID_DIRECTIONS[side];
+ return isConnected(world, x, y, z, dir, block, meta);
+ }
+
+ /**
+ * A simple check for if the given block can connect to the given direction on the given side.
+ *
+ * @param world
+ * @param x The x coordinate of the block to check against. This is not the position of your block.
+ * @param y The y coordinate of the block to check against. This is not the position of your block.
+ * @param z The z coordinate of the block to check against. This is not the position of your block.
+ * @param dir The {@link ForgeDirection side} of the block to check for connection status. This is not the
+ * direction to check in.
+ * @param block The block to check against for connection.
+ * @param meta The metadata 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, int x, int y, int z, ForgeDirection dir, Block block, int meta) {
+
+ if (CTMLib.chiselLoaded() && connectionBlocked(world, x, y, z, dir.ordinal())) {
+ return false;
+ }
+
+ int x2 = x + dir.offsetX;
+ int y2 = y + dir.offsetY;
+ int z2 = z + dir.offsetZ;
+
+ boolean disableObscured = disableObscuredFaceCheck.or(disableObscuredFaceCheckConfig);
+
+ Block con = getBlockOrFacade(world, x, y, z, dir.ordinal());
+ Block obscuring = disableObscured ? null : getBlockOrFacade(world, x2, y2, z2, dir.ordinal());
+
+ // no block or a bad API user
+ if (con == null) {
+ return false;
+ }
+
+ boolean ret = con.equals(block) && getBlockOrFacadeMetadata(world, x, y, z, dir.ordinal()) == meta;
+
+ // no block obscuring this face
+ if (obscuring == null) {
+ return ret;
+ }
+
+ // check that we aren't already connected outwards from this side
+ ret &= !(obscuring.equals(block) && getBlockOrFacadeMetadata(world, x2, y2, z2, dir.ordinal()) == meta);
+
+ return ret;
+ }
+
+ private boolean connectionBlocked(IBlockAccess world, int x, int y, int z, int side) {
+ Block block = world.getBlock(x, y, z);
+ if (block instanceof IConnectable) {
+ return !((IConnectable) block).canConnectCTM(world, x, y, z, side);
+ }
+ return false;
+ }
+
+ /**
+ * A utility method to access metadata from both the world and {@link IFacade} blocks in the world.
+ *
+ * @param world
+ * @param x
+ * @param y
+ * @param z
+ * @param side The side to check for. -1 for unknown.
+ * @return The metadata at this position in the world, or inside an {@link IFacade} block.
+ */
+ public int getBlockOrFacadeMetadata(IBlockAccess world, int x, int y, int z, int side) {
+ Block blk = world.getBlock(x, y, z);
+ if (CTMLib.chiselLoaded()) {
+ return getFacadeMeta(blk, world, x, y, z, side);
+ }
+ return world.getBlockMetadata(x, y, z);
+ }
+
+ private int getFacadeMeta(Block blk, IBlockAccess world, int x, int y, int z, int side) {
+ if (blk instanceof IFacade) {
+ return ((IFacade) blk).getFacadeMetadata(world, x, y, z, side);
+ }
+ return world.getBlockMetadata(x, y, z);
+ }
+
+ /**
+ * A utility method to access a block from both the world and {@link IFacade} blocks in the world.
+ *
+ * @param world
+ * @param x
+ * @param y
+ * @param z
+ * @param side The side to check for. -1 for unknown.
+ * @return The block at this position in the world, or inside an {@link IFacade} block.
+ */
+ public Block getBlockOrFacade(IBlockAccess world, int x, int y, int z, int side) {
+ Block blk = world.getBlock(x, y, z);
+ if (CTMLib.chiselLoaded()) {
+ return getFacade(blk, world, x, y, z, side);
+ }
+ return blk;
+ }
+
+ private Block getFacade(Block blk, IBlockAccess world, int x, int y, int z, int side) {
+ if (blk instanceof IFacade) {
+ blk = ((IFacade) blk).getFacade(world, x, y, z, side);
+ }
+ return blk;
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/CTMBlock.java b/src/main/java/team/chisel/ctmlib/CTMBlock.java
new file mode 100644
index 00000000..1e7d7e5c
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/CTMBlock.java
@@ -0,0 +1,71 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.IBlockAccess;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import lombok.Getter;
+
+/**
+ * Convenience implementation of ICMBlock. This does everything you should need for basic CTM.
+ *
+ * This class does not handle metadata-dependent textures.
+ */
+public class CTMBlock extends Block implements ICTMBlock {
+
+ @SideOnly(Side.CLIENT)
+ private SubmapManagerCTM manager;
+
+ @Getter
+ private final String modid;
+ @Getter
+ private final String texturePath;
+
+ private final int renderId;
+
+ public CTMBlock(String modid, String texturePath, int renderId) {
+ super(Material.rock);
+ this.modid = modid;
+ this.texturePath = texturePath;
+ this.renderId = renderId;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void registerBlockIcons(IIconRegister icon) {
+ manager = new SubmapManagerCTM(texturePath);
+ manager.registerIcons(modid, this, icon);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public IIcon getIcon(IBlockAccess world, int x, int y, int z, int side) {
+ return manager.getIcon(world, x, y, z, side);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public IIcon getIcon(int side, int meta) {
+ return manager.getIcon(side, meta);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public int getRenderType() {
+ return renderId;
+ }
+
+ @Override
+ public ISubmapManager getManager(IBlockAccess world, int x, int y, int z, int meta) {
+ return manager;
+ }
+
+ @Override
+ public ISubmapManager getManager(int meta) {
+ return manager;
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/CTMLib.java b/src/main/java/team/chisel/ctmlib/CTMLib.java
new file mode 100644
index 00000000..5f9ac563
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/CTMLib.java
@@ -0,0 +1,18 @@
+package team.chisel.ctmlib;
+
+import cpw.mods.fml.common.ModAPIManager;
+
+public class CTMLib {
+
+ public static final String VERSION = "GRADLETOKEN_VERSION";
+
+ private static boolean chiselPresent, chiselInitialized;
+
+ public static boolean chiselLoaded() {
+ if (!chiselInitialized) {
+ chiselInitialized = true;
+ chiselPresent = ModAPIManager.INSTANCE.hasAPI("ChiselAPI");
+ }
+ return chiselPresent;
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/CTMRenderer.java b/src/main/java/team/chisel/ctmlib/CTMRenderer.java
new file mode 100644
index 00000000..06097d8c
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/CTMRenderer.java
@@ -0,0 +1,99 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.crash.CrashReport;
+import net.minecraft.crash.CrashReportCategory;
+import net.minecraft.util.ReportedException;
+import net.minecraft.world.IBlockAccess;
+
+import org.lwjgl.opengl.GL11;
+
+import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
+import cpw.mods.fml.common.registry.GameRegistry;
+
+/**
+ * A convenience base class for CTM blocks. Any block using this must implement {@link ICTMBlock}.
+ */
+public class CTMRenderer implements ISimpleBlockRenderingHandler {
+
+ private int renderId;
+
+ public CTMRenderer(int renderId) {
+ this.renderId = renderId;
+ }
+
+ @Override
+ public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer) {
+ GL11.glTranslatef(-0.5F, -0.5F, -0.5F);
+ RenderBlocks rb = getContext(
+ renderer,
+ block,
+ Minecraft.getMinecraft().theWorld,
+ ((ICTMBlock>) block).getManager(metadata),
+ metadata);
+ Drawing.drawBlock(block, metadata, rb);
+ GL11.glTranslatef(0.5F, 0.5F, 0.5F);
+ rb.unlockBlockBounds();
+ }
+
+ @Override
+ public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId,
+ RenderBlocks rendererOld) {
+ try {
+ int meta = world.getBlockMetadata(x, y, z);
+ RenderBlocks rb = getContext(
+ rendererOld,
+ block,
+ world,
+ ((ICTMBlock>) block).getManager(world, x, y, z, meta),
+ meta);
+ boolean ret = rb.renderStandardBlock(block, x, y, z);
+ rb.unlockBlockBounds();
+ return ret;
+ } catch (Throwable t) {
+ CrashReport crashreport = CrashReport.makeCrashReport(t, "Rendering CTM Block");
+ CrashReportCategory crashreportcategory = crashreport.makeCategory("Block being rendered");
+ crashreportcategory.addCrashSection("Block name", GameRegistry.findUniqueIdentifierFor(block));
+ crashreportcategory.addCrashSection("Block metadata", world.getBlockMetadata(x, y, z));
+ throw new ReportedException(crashreport);
+ }
+ }
+
+ protected RenderBlocks getContext(RenderBlocks rendererOld, Block block, IBlockAccess world, ISubmapManager manager,
+ int meta) {
+ if (!rendererOld.hasOverrideBlockTexture() && manager != null) {
+ RenderBlocks rb = manager.createRenderContext(rendererOld, block, world);
+ if (rb != null && rb != rendererOld) {
+ rb.blockAccess = world;
+ if (rendererOld.lockBlockBounds) {
+ rb.overrideBlockBounds(
+ rendererOld.renderMinX,
+ rendererOld.renderMinY,
+ rendererOld.renderMinZ,
+ rendererOld.renderMaxX,
+ rendererOld.renderMaxY,
+ rendererOld.renderMaxZ);
+ }
+ if (rb instanceof RenderBlocksCTM) {
+ RenderBlocksCTM rbctm = (RenderBlocksCTM) rb;
+ rbctm.manager = rbctm.manager == null ? manager : rbctm.manager;
+ rbctm.rendererOld = rbctm.rendererOld == null ? rendererOld : rbctm.rendererOld;
+ }
+ return rb;
+ }
+ }
+ return rendererOld;
+ }
+
+ @Override
+ public boolean shouldRender3DInInventory(int renderId) {
+ return true;
+ }
+
+ @Override
+ public int getRenderId() {
+ return renderId;
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/Dir.java b/src/main/java/team/chisel/ctmlib/Dir.java
new file mode 100644
index 00000000..85e1809f
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/Dir.java
@@ -0,0 +1,104 @@
+package team.chisel.ctmlib;
+
+import static net.minecraftforge.common.util.ForgeDirection.DOWN;
+import static net.minecraftforge.common.util.ForgeDirection.EAST;
+import static net.minecraftforge.common.util.ForgeDirection.SOUTH;
+import static net.minecraftforge.common.util.ForgeDirection.UP;
+import static net.minecraftforge.common.util.ForgeDirection.WEST;
+import static net.minecraftforge.common.util.ForgeDirection.getOrientation;
+
+import net.minecraft.block.Block;
+import net.minecraft.world.IBlockAccess;
+import net.minecraftforge.common.util.ForgeDirection;
+
+/**
+ * Think of this class as a "Two dimensional ForgeDirection, with diagonals".
+ *
+ * It represents the eight different directions a face of a block can connect with CTM, and contains the logic for
+ * determining if a block is indeed connected in that direction.
+ *
+ * Note that, for example, {@link #TOP_RIGHT} does not mean connected to the {@link #TOP} and {@link #RIGHT}, but
+ * connected in the diagonal direction represented by {@link #TOP_RIGHT}. This is used for inner corner rendering.
+ */
+public enum Dir {
+
+ // @formatter:off
+ TOP(UP),
+ TOP_RIGHT(UP, EAST),
+ RIGHT(EAST),
+ BOTTOM_RIGHT(DOWN, EAST),
+ BOTTOM(DOWN),
+ BOTTOM_LEFT(DOWN, WEST),
+ LEFT(WEST),
+ TOP_LEFT(UP, WEST);
+ // @formatter:on
+
+ /**
+ * All values of this enum, used to prevent unnecessary allocation via {@link #values()}.
+ */
+ public static final Dir[] VALUES = values();
+ private static final ForgeDirection NORMAL = SOUTH;
+
+ private ForgeDirection[] dirs;
+
+ private Dir(ForgeDirection... dirs) {
+ this.dirs = dirs;
+ }
+
+ /**
+ * Finds if this block is connected for the given side in this Dir.
+ *
+ * @param inst The CTM instance to use for logic.
+ * @param world The world the block is in.
+ * @param x The x coordinate of your block.
+ * @param y The y coordinate of your block.
+ * @param z The z coordinate of your block.
+ * @param sideIdx The side index of the current face. This equivalent to {@link ForgeDirection#ordinal()}
+ * @param block The block being rendered.
+ * @param meta The metadata of the block.
+ * @return True if the block is connected in the given Dir, false otherwise.
+ */
+ public boolean isConnected(CTM inst, IBlockAccess world, int x, int y, int z, int sideIdx, Block block, int meta) {
+ ForgeDirection side = getOrientation(sideIdx);
+ ForgeDirection[] dirs = getNormalizedDirs(side);
+ for (ForgeDirection dir : dirs) {
+ x += dir.offsetX;
+ y += dir.offsetY;
+ z += dir.offsetZ;
+ }
+ return inst.isConnected(world, x, y, z, side, block, meta);
+ }
+
+ private ForgeDirection[] getNormalizedDirs(ForgeDirection normal) {
+ if (normal == NORMAL) {
+ return dirs;
+ } else if (normal == NORMAL.getOpposite()) {
+ // If this is the opposite direction of the default normal, we
+ // need to mirror the dirs
+ // A mirror version does not affect y+ and y- so we ignore those
+ ForgeDirection[] ret = new ForgeDirection[dirs.length];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = dirs[i].offsetY != 0 ? dirs[i] : dirs[i].getOpposite();
+ }
+ return ret;
+ } else {
+ ForgeDirection axis = null;
+ // Next, we need different a different rotation axis depending
+ // on if this is up/down or not
+ if (normal.offsetY == 0) {
+ // If it is not up/down, pick either the left or right-hand
+ // rotation
+ axis = normal == NORMAL.getRotation(UP) ? UP : DOWN;
+ } else {
+ // If it is up/down, pick either the up or down rotation.
+ axis = normal == UP ? NORMAL.getRotation(DOWN) : NORMAL.getRotation(UP);
+ }
+ ForgeDirection[] ret = new ForgeDirection[dirs.length];
+ // Finally apply all the rotations
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = dirs[i].getRotation(axis);
+ }
+ return ret;
+ }
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/Drawing.java b/src/main/java/team/chisel/ctmlib/Drawing.java
new file mode 100644
index 00000000..064368ec
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/Drawing.java
@@ -0,0 +1,76 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.util.IIcon;
+
+public class Drawing {
+
+ public static void drawBlock(Block block, IIcon icon, RenderBlocks renderer) {
+ Tessellator tessellator = Tessellator.instance;
+
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, -1.0F, 0.0F);
+ renderer.renderFaceYNeg(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 1.0F, 0.0F);
+ renderer.renderFaceYPos(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 0.0F, -1.0F);
+ renderer.renderFaceZNeg(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 0.0F, 1.0F);
+ renderer.renderFaceZPos(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(-1.0F, 0.0F, 0.0F);
+ renderer.renderFaceXNeg(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(1.0F, 0.0F, 0.0F);
+ renderer.renderFaceXPos(block, 0.0D, 0.0D, 0.0D, icon);
+ tessellator.draw();
+ }
+
+ public static void drawBlock(Block block, int meta, RenderBlocks renderer) {
+ Tessellator tessellator = Tessellator.instance;
+
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, -1.0F, 0.0F);
+ renderer.renderFaceYNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(0, meta));
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 1.0F, 0.0F);
+ renderer.renderFaceYPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(1, meta));
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 0.0F, -1.0F);
+ renderer.renderFaceZNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(2, meta));
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(0.0F, 0.0F, 1.0F);
+ renderer.renderFaceZPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(3, meta));
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(-1.0F, 0.0F, 0.0F);
+ renderer.renderFaceXNeg(block, 0.0D, 0.0D, 0.0D, block.getIcon(4, meta));
+ tessellator.draw();
+ tessellator.startDrawingQuads();
+ tessellator.setNormal(1.0F, 0.0F, 0.0F);
+ renderer.renderFaceXPos(block, 0.0D, 0.0D, 0.0D, block.getIcon(5, meta));
+ tessellator.draw();
+ }
+
+ public static void renderAllFaces(RenderBlocks renderer, Block block, int x, int y, int z, IIcon icon) {
+ renderer.renderFaceYNeg(block, x, y, z, icon);
+ renderer.renderFaceYPos(block, x, y, z, icon);
+ renderer.renderFaceZNeg(block, x, y, z, icon);
+ renderer.renderFaceZPos(block, x, y, z, icon);
+ renderer.renderFaceXNeg(block, x, y, z, icon);
+ renderer.renderFaceXPos(block, x, y, z, icon);
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/ICTMBlock.java b/src/main/java/team/chisel/ctmlib/ICTMBlock.java
new file mode 100644
index 00000000..2512c493
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/ICTMBlock.java
@@ -0,0 +1,19 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.world.IBlockAccess;
+
+public interface ICTMBlock {
+
+ /**
+ * Gets the {@link ISubmapManager} for this block in the world.
+ */
+ T getManager(IBlockAccess world, int x, int y, int z, int meta);
+
+ /**
+ * Gets the {@link ISubmapManager} for this block in item form.
+ *
+ * @param meta The meta (damage) of the stack.
+ */
+ T getManager(int meta);
+
+}
diff --git a/src/main/java/team/chisel/ctmlib/ISubmap.java b/src/main/java/team/chisel/ctmlib/ISubmap.java
new file mode 100644
index 00000000..a09111cb
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/ISubmap.java
@@ -0,0 +1,19 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.util.IIcon;
+
+public interface ISubmap {
+
+ /**
+ * The width, in icons, of the submap.
+ */
+ int getWidth();
+
+ /**
+ * The height, in icons, of the submap.
+ */
+ int getHeight();
+
+ IIcon getSubIcon(int x, int y);
+
+}
diff --git a/src/main/java/team/chisel/ctmlib/ISubmapManager.java b/src/main/java/team/chisel/ctmlib/ISubmapManager.java
new file mode 100644
index 00000000..6f5ce2d8
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/ISubmapManager.java
@@ -0,0 +1,48 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.IBlockAccess;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+
+public interface ISubmapManager {
+
+ /**
+ * Gets the icon for item rendering, based on side and item metadata.
+ *
+ * @param side Side of the ItemBlock.
+ * @param meta Metadata of the ItemBlock.
+ * @return The IIcon for rendering.
+ */
+ IIcon getIcon(int side, int meta);
+
+ /**
+ * Gets the icon for world rendering, based on world position and side.
+ */
+ IIcon getIcon(IBlockAccess world, int x, int y, int z, int side);
+
+ /**
+ * Hook for registering any custom icons you might need in this class. Will be called during the normal block
+ * method.
+ *
+ * @param modName The mod ID this submap was registered to.
+ * @param block The block being registered for.
+ * @param register The IIconRegister instance.
+ */
+ @SideOnly(Side.CLIENT)
+ void registerIcons(String modName, Block block, IIconRegister register);
+
+ @SideOnly(Side.CLIENT)
+ RenderBlocks createRenderContext(RenderBlocks rendererOld, Block block, IBlockAccess world);
+
+ @SideOnly(Side.CLIENT)
+ void preRenderSide(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, ForgeDirection side);
+
+ @SideOnly(Side.CLIENT)
+ void postRenderSide(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, ForgeDirection side);
+}
diff --git a/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java b/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java
new file mode 100644
index 00000000..66b824b2
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java
@@ -0,0 +1,560 @@
+package team.chisel.ctmlib;
+
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XNEG_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XNEG_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XNEG_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XNEG_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XPOS_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XPOS_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XPOS_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.XPOS_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YNEG_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YNEG_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YNEG_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YNEG_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YPOS_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YPOS_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YPOS_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.YPOS_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZNEG_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZNEG_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZNEG_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZNEG_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZPOS_LB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZPOS_LT;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZPOS_RB;
+import static team.chisel.ctmlib.RenderBlocksCTM.SubSide.ZPOS_RT;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.X;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XY;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XYZ;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XY_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XY_HALF_Z;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XZ;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XZ_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.XZ_HALF_Y;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.X_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.X_HALF_Y;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.X_HALF_YZ;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.X_HALF_Z;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Y;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.YZ;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.YZ_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.YZ_HALF_X;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Y_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Y_HALF_X;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Y_HALF_XZ;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Y_HALF_Z;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Z;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.ZERO;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Z_HALF;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Z_HALF_X;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Z_HALF_XY;
+import static team.chisel.ctmlib.RenderBlocksCTM.Vert.Z_HALF_Y;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.util.IIcon;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import lombok.Getter;
+
+public class RenderBlocksCTM extends RenderBlocks {
+
+ /**
+ * An enum for all possible 26 sub-side vertices.
+ *
+ * The naming scheme is as follows:
+ *
+ * ZERO is a special case, it is the absolute min point of the block. X, Y, Z, or any combination means that the
+ * axes listed in the name are at 1. X, Y, Z, or any combination followed by HALF means that those axes are at 0.5.
+ * X, Y, Z, or any combination after a HALF means those axes are at 1.
+ */
+ protected enum Vert {
+
+ // @formatter:off
+ ZERO(0, 0, 0),
+ XYZ(1, 1, 1),
+ X(1, 0, 0),
+ Y(0, 1, 0),
+ Z(0, 0, 1),
+ XY(1, 1, 0),
+ YZ(0, 1, 1),
+ XZ(1, 0, 1),
+ X_HALF(0.5, 0, 0),
+ Y_HALF(0, 0.5, 0),
+ Z_HALF(0, 0, 0.5),
+ XY_HALF(0.5, 0.5, 0),
+ YZ_HALF(0, 0.5, 0.5),
+ XZ_HALF(0.5, 0, 0.5),
+ X_HALF_Y(0.5, 1, 0),
+ X_HALF_Z(0.5, 0, 1),
+ Y_HALF_X(1, 0.5, 0),
+ Y_HALF_Z(0, 0.5, 1),
+ Z_HALF_X(1, 0, 0.5),
+ Z_HALF_Y(0, 1, 0.5),
+ X_HALF_YZ(0.5, 1, 1),
+ Y_HALF_XZ(1, 0.5, 1),
+ Z_HALF_XY(1, 1, 0.5),
+ XY_HALF_Z(0.5, 0.5, 1),
+ YZ_HALF_X(1, 0.5, 0.5),
+ XZ_HALF_Y(0.5, 1, 0.5);
+ // @formatter:on
+
+ private double x, y, z;
+
+ private Vert(double x, double y, double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ private static double u, v, xDiff, yDiff, zDiff, uDiff, vDiff;
+
+ void render(RenderBlocksCTM inst, ForgeDirection normal, int cacheID) {
+ if (inst.enableAO) {
+ Tessellator.instance
+ .setColorOpaque_F(inst.redCache[cacheID], inst.grnCache[cacheID], inst.bluCache[cacheID]);
+ Tessellator.instance.setBrightness(inst.lightingCache[cacheID]);
+ }
+
+ u = cacheID == 1 || cacheID == 2 ? inst.maxU : inst.minU;
+ v = cacheID < 2 ? inst.maxV : inst.minV;
+
+ uDiff = inst.maxU - inst.minU;
+ vDiff = inst.maxV - inst.minV;
+
+ if (inst.renderMinX + inst.renderMinY + inst.renderMinZ != 0
+ || inst.renderMaxX + inst.renderMaxY + inst.renderMaxZ != 3) {
+ boolean uMin = u == inst.minU;
+ boolean vMin = v == inst.minV;
+
+ xDiff = inst.renderMaxX - inst.renderMinX;
+ yDiff = inst.renderMaxY - inst.renderMinY;
+ zDiff = inst.renderMaxZ - inst.renderMinZ;
+
+ if (normal.offsetY != 0) {
+ uDiff *= uMin ? inst.renderMinX : 1 - inst.renderMaxX;
+ vDiff *= vMin ? inst.renderMinZ : 1 - inst.renderMaxZ;
+ } else if (normal.offsetX != 0) {
+ uDiff *= uMin ? inst.renderMinZ : 1 - inst.renderMaxZ;
+ vDiff *= vMin ? inst.renderMinY : 1 - inst.renderMaxY;
+ } else if (normal.offsetZ != 0) {
+ uDiff *= uMin ? inst.renderMinX : 1 - inst.renderMaxX;
+ vDiff *= vMin ? inst.renderMinY : 1 - inst.renderMaxY;
+ }
+ u = u == inst.minU ? inst.minU + uDiff : inst.maxU - uDiff;
+
+ v = v == inst.minV ? inst.minV + vDiff : inst.maxV - vDiff;
+ } else {
+ xDiff = yDiff = zDiff = 1;
+ }
+
+ Tessellator.instance.addVertexWithUV(
+ inst.renderMinX + (x * xDiff),
+ inst.renderMinY + (y * yDiff),
+ inst.renderMinZ + (z * zDiff),
+ u,
+ v);
+ }
+ }
+
+ /**
+ * Each side is divided into 4 sub-sides. LB(left bottom), RB(right bottom), LT(right top), and RT(right top).
+ *
+ * Each sub-side contains 4 {@link Vert} objects representing its position on the block.
+ */
+ protected enum SubSide {
+
+ // @formatter:off
+ XNEG_LB(ZERO, Z_HALF, YZ_HALF, Y_HALF), XNEG_RB(Z_HALF, Z, Y_HALF_Z, YZ_HALF), XNEG_RT(YZ_HALF, Y_HALF_Z, YZ, Z_HALF_Y), XNEG_LT(Y_HALF, YZ_HALF, Z_HALF_Y, Y),
+ XPOS_LB(XZ, Z_HALF_X, YZ_HALF_X, Y_HALF_XZ), XPOS_RB(Z_HALF_X, X, Y_HALF_X, YZ_HALF_X), XPOS_RT(YZ_HALF_X, Y_HALF_X, XY, Z_HALF_XY), XPOS_LT(Y_HALF_XZ, YZ_HALF_X, Z_HALF_XY, XYZ),
+
+ ZNEG_LB(X, X_HALF, XY_HALF, Y_HALF_X), ZNEG_RB(X_HALF, ZERO, Y_HALF, XY_HALF), ZNEG_RT(XY_HALF, Y_HALF, Y, X_HALF_Y), ZNEG_LT(Y_HALF_X, XY_HALF, X_HALF_Y, XY),
+ ZPOS_LB(Z, X_HALF_Z, XY_HALF_Z, Y_HALF_Z), ZPOS_RB(X_HALF_Z, XZ, Y_HALF_XZ, XY_HALF_Z), ZPOS_RT(XY_HALF_Z, Y_HALF_XZ, XYZ, X_HALF_YZ), ZPOS_LT(Y_HALF_Z, XY_HALF_Z, X_HALF_YZ, YZ),
+
+ YNEG_LB(ZERO, X_HALF, XZ_HALF, Z_HALF), YNEG_RB(X_HALF, X, Z_HALF_X, XZ_HALF), YNEG_RT(XZ_HALF, Z_HALF_X, XZ, X_HALF_Z), YNEG_LT(Z_HALF, XZ_HALF, X_HALF_Z, Z),
+ YPOS_LB(YZ, X_HALF_YZ, XZ_HALF_Y, Z_HALF_Y), YPOS_RB(X_HALF_YZ, XYZ, Z_HALF_XY, XZ_HALF_Y), YPOS_RT(XZ_HALF_Y, Z_HALF_XY, XY, X_HALF_Y), YPOS_LT(Z_HALF_Y, XZ_HALF_Y, X_HALF_Y, Y);
+ // @formatter:on
+ private Vert xmin, xmax, ymin, ymax;
+ private ForgeDirection normal;
+
+ SubSide(Vert xmin, Vert ymin, Vert ymax, Vert xmax) {
+ this.xmin = xmin;
+ this.ymin = ymin;
+ this.ymax = ymax;
+ this.xmax = xmax;
+ this.normal = calcNormal();
+ }
+
+ private ForgeDirection calcNormal() {
+ double xTot = xmin.x + xmax.x + ymin.x + ymax.x;
+ double yTot = xmin.y + xmax.y + ymin.y + ymax.y;
+ double zTot = xmin.z + xmax.z + ymin.z + ymax.z;
+ if (xTot % 4 == 0) {
+ return xTot > 0 ? ForgeDirection.EAST : ForgeDirection.WEST;
+ } else if (yTot % 4 == 0) {
+ return yTot > 0 ? ForgeDirection.UP : ForgeDirection.DOWN;
+ } else if (zTot % 4 == 0) {
+ return zTot > 0 ? ForgeDirection.SOUTH : ForgeDirection.NORTH;
+ }
+ return ForgeDirection.UNKNOWN;
+ }
+
+ void render(RenderBlocksCTM inst) {
+ xmin.render(inst, normal, 0);
+ ymin.render(inst, normal, 1);
+ ymax.render(inst, normal, 2);
+ xmax.render(inst, normal, 3);
+ }
+ }
+
+ public CTM ctm = CTM.getInstance();
+
+ // globals added to save the JVM some trouble. No need to constantly create
+ // and destroy ints if we don't have to
+ protected int blockLightBitChannel = 0;
+ protected int redBitChannel = 0;
+ protected int greenBitChannel = 0;
+ protected int blueBitChannel = 0;
+ protected int sunlightBitChannel = 0;
+
+ public RenderBlocksCTM() {
+ super();
+ renderMaxX = renderMaxY = renderMaxZ = 1;
+ }
+
+ protected Tessellator tessellator = Tessellator.instance;
+ protected double minU, maxU;
+ protected double minV, maxV;
+ protected int[] lightingCache = new int[4];
+ protected float[] redCache = new float[4];
+ protected float[] grnCache = new float[4];
+ protected float[] bluCache = new float[4];
+ public TextureSubmap submap;
+ public TextureSubmap submapSmall;
+ public RenderBlocks rendererOld;
+ public ISubmapManager manager;
+
+ protected int[][] lightmap = new int[3][3];
+ protected float[][] redmap = new float[3][3];
+ protected float[][] grnmap = new float[3][3];
+ protected float[][] blumap = new float[3][3];
+
+ protected int bx, by, bz, meta;
+
+ @Getter
+ protected boolean inWorld = false;
+
+ @Override
+ public boolean renderStandardBlock(Block block, int x, int y, int z) {
+ bx = x;
+ by = y;
+ bz = z;
+ meta = Minecraft.getMinecraft().theWorld.getBlockMetadata(x, y, z);
+
+ Tessellator.instance.setColorOpaque_F(1.0F, 1.0F, 1.0F);
+ Tessellator.instance.addTranslation(x, y, z);
+ if (rendererOld != null && rendererOld.hasOverrideBlockTexture()) {
+ setOverrideBlockTexture(rendererOld.overrideBlockTexture);
+ }
+ inWorld = true;
+ boolean res = super.renderStandardBlock(block, x, y, z);
+ inWorld = false;
+ Tessellator.instance.addTranslation(-x, -y, -z);
+
+ return res;
+ }
+
+ /* @formatter:off
+ * This method fills a 3x3 grid of light values based on the four corners, by averaging them together.
+ *
+ * 2 TL x TR
+ *
+ * 1 x x x
+ *
+ * 0 BL x BR
+ * 0 1 2
+ *
+ * Note: Variable names mean nothing... don't touch
+ *
+ * ._.
+ *
+ * shakes fist in anger
+ */
+ // @formatter:on
+ protected void fillLightmap(int bottomLeft, int bottomRight, int topRight, int topLeft) {
+ lightmap[0][0] = bottomLeft;
+ lightmap[2][0] = bottomRight;
+ lightmap[2][2] = topRight;
+ lightmap[0][2] = topLeft;
+
+ lightmap[1][0] = avg(bottomLeft, bottomRight);
+ lightmap[2][1] = avg(bottomRight, topRight);
+ lightmap[1][2] = avg(topRight, topLeft);
+ lightmap[0][1] = avg(topLeft, bottomLeft);
+
+ lightmap[1][1] = avg(bottomLeft, bottomRight, topRight, topLeft);
+ }
+
+ protected void fillColormap(float bottomLeft, float bottomRight, float topRight, float topLeft, float[][] map) {
+ map[0][0] = bottomLeft;
+ map[2][0] = bottomRight;
+ map[2][2] = topRight;
+ map[0][2] = topLeft;
+
+ map[1][0] = (bottomLeft + bottomRight) / 2.0F;
+ map[2][1] = (bottomRight + topRight) / 2.0F;
+ map[1][2] = (topRight + topLeft) / 2.0F;
+ map[0][1] = (topLeft + bottomLeft) / 2.0F;
+
+ map[1][1] = (bottomLeft + bottomRight + topRight + topLeft) / 4.0F;
+ }
+
+ protected void getLight(int x, int y) {
+ lightingCache[0] = lightmap[0 + x][0 + y];
+ lightingCache[1] = lightmap[1 + x][0 + y];
+ lightingCache[2] = lightmap[1 + x][1 + y];
+ lightingCache[3] = lightmap[0 + x][1 + y];
+
+ redCache[0] = redmap[0 + x][0 + y];
+ redCache[1] = redmap[1 + x][0 + y];
+ redCache[2] = redmap[1 + x][1 + y];
+ redCache[3] = redmap[0 + x][1 + y];
+
+ grnCache[0] = grnmap[0 + x][0 + y];
+ grnCache[1] = grnmap[1 + x][0 + y];
+ grnCache[2] = grnmap[1 + x][1 + y];
+ grnCache[3] = grnmap[0 + x][1 + y];
+
+ bluCache[0] = blumap[0 + x][0 + y];
+ bluCache[1] = blumap[1 + x][0 + y];
+ bluCache[2] = blumap[1 + x][1 + y];
+ bluCache[3] = blumap[0 + x][1 + y];
+ }
+
+ /**
+ * This works around a bug in CLC atm
+ */
+ private int avg(int... lightVals) {
+ blockLightBitChannel = 0;
+ redBitChannel = 0;
+ greenBitChannel = 0;
+ blueBitChannel = 0;
+ sunlightBitChannel = 0;
+
+ for (int light : lightVals) {
+ blockLightBitChannel += (light & 0xFF);
+ redBitChannel += (light & 0xF00);
+ greenBitChannel += (light & 0xF000);
+ blueBitChannel += (light & 0xF0000);
+ sunlightBitChannel += (light & 0xF00000);
+ }
+
+ return ((blockLightBitChannel / lightVals.length) & 0xFF) | ((redBitChannel / lightVals.length) & 0xF00)
+ | ((greenBitChannel / lightVals.length) & 0xF000)
+ | ((blueBitChannel / lightVals.length) & 0xF0000)
+ | ((sunlightBitChannel / lightVals.length) & 0xF00000);
+ }
+
+ protected void side(Block block, SubSide side, int iconIndex) {
+
+ IIcon icon;
+ TextureSubmap map;
+ if (iconIndex >= 16) {
+ iconIndex -= 16;
+ map = submapSmall;
+ } else {
+ map = submap;
+ }
+
+ if (map == null) {
+ icon = getBlockIconFromSideAndMetadata(block, side.normal.ordinal(), meta);
+ } else {
+ int x = iconIndex % map.getWidth();
+ int y = iconIndex / map.getHeight();
+ icon = map.getSubIcon(x, y);
+ }
+
+ double umax = icon.getMaxU();
+ double umin = icon.getMinU();
+ double vmax = icon.getMaxV();
+ double vmin = icon.getMinV();
+
+ minU = umin;
+ maxU = umax;
+ minV = vmin;
+ maxV = vmax;
+
+ // uCache[0] = umin;
+ // uCache[1] = umax;
+ // uCache[2] = umax;
+ // uCache[3] = umin;
+ //
+ // vCache[0] = vmax;
+ // vCache[1] = vmax;
+ // vCache[2] = vmin;
+ // vCache[3] = vmin;
+
+ side.render(this);
+ }
+
+ @Override
+ public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.WEST);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceXNeg(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 4);
+
+ fillLightmap(brightnessBottomRight, brightnessTopRight, brightnessTopLeft, brightnessBottomLeft);
+ fillColormap(colorRedBottomRight, colorRedTopRight, colorRedTopLeft, colorRedBottomLeft, redmap);
+ fillColormap(colorGreenBottomRight, colorGreenTopRight, colorGreenTopLeft, colorGreenBottomLeft, grnmap);
+ fillColormap(colorBlueBottomRight, colorBlueTopRight, colorBlueTopLeft, colorBlueBottomLeft, blumap);
+
+ getLight(0, 0);
+ side(block, XNEG_LB, tex[0]);
+ getLight(1, 0);
+ side(block, XNEG_RB, tex[1]);
+ getLight(1, 1);
+ side(block, XNEG_RT, tex[2]);
+ getLight(0, 1);
+ side(block, XNEG_LT, tex[3]);
+ }
+ post(ForgeDirection.WEST);
+ }
+
+ @Override
+ public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.EAST);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceXPos(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 5);
+
+ fillLightmap(brightnessTopLeft, brightnessBottomLeft, brightnessBottomRight, brightnessTopRight);
+ fillColormap(colorRedTopLeft, colorRedBottomLeft, colorRedBottomRight, colorRedTopRight, redmap);
+ fillColormap(colorGreenTopLeft, colorGreenBottomLeft, colorGreenBottomRight, colorGreenTopRight, grnmap);
+ fillColormap(colorBlueTopLeft, colorBlueBottomLeft, colorBlueBottomRight, colorBlueTopRight, blumap);
+
+ getLight(0, 0);
+ side(block, XPOS_LB, tex[0]);
+ getLight(1, 0);
+ side(block, XPOS_RB, tex[1]);
+ getLight(1, 1);
+ side(block, XPOS_RT, tex[2]);
+ getLight(0, 1);
+ side(block, XPOS_LT, tex[3]);
+ }
+ post(ForgeDirection.EAST);
+ }
+
+ @Override
+ public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.NORTH);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceZNeg(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 2);
+
+ fillLightmap(brightnessBottomRight, brightnessTopRight, brightnessTopLeft, brightnessBottomLeft);
+ fillColormap(colorRedBottomRight, colorRedTopRight, colorRedTopLeft, colorRedBottomLeft, redmap);
+ fillColormap(colorGreenBottomRight, colorGreenTopRight, colorGreenTopLeft, colorGreenBottomLeft, grnmap);
+ fillColormap(colorBlueBottomRight, colorBlueTopRight, colorBlueTopLeft, colorBlueBottomLeft, blumap);
+
+ getLight(0, 0);
+ side(block, ZNEG_LB, tex[0]);
+ getLight(1, 0);
+ side(block, ZNEG_RB, tex[1]);
+ getLight(1, 1);
+ side(block, ZNEG_RT, tex[2]);
+ getLight(0, 1);
+ side(block, ZNEG_LT, tex[3]);
+ }
+ post(ForgeDirection.NORTH);
+ }
+
+ @Override
+ public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.SOUTH);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceZPos(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 3);
+
+ fillLightmap(brightnessBottomLeft, brightnessBottomRight, brightnessTopRight, brightnessTopLeft);
+ fillColormap(colorRedBottomLeft, colorRedBottomRight, colorRedTopRight, colorRedTopLeft, redmap);
+ fillColormap(colorGreenBottomLeft, colorGreenBottomRight, colorGreenTopRight, colorGreenTopLeft, grnmap);
+ fillColormap(colorBlueBottomLeft, colorBlueBottomRight, colorBlueTopRight, colorBlueTopLeft, blumap);
+
+ getLight(0, 0);
+ side(block, ZPOS_LB, tex[0]);
+ getLight(1, 0);
+ side(block, ZPOS_RB, tex[1]);
+ getLight(1, 1);
+ side(block, ZPOS_RT, tex[2]);
+ getLight(0, 1);
+ side(block, ZPOS_LT, tex[3]);
+ }
+ post(ForgeDirection.SOUTH);
+ }
+
+ @Override
+ public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.DOWN);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceYNeg(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 0);
+
+ fillLightmap(brightnessBottomLeft, brightnessBottomRight, brightnessTopRight, brightnessTopLeft);
+ fillColormap(colorRedBottomLeft, colorRedBottomRight, colorRedTopRight, colorRedTopLeft, redmap);
+ fillColormap(colorGreenBottomLeft, colorGreenBottomRight, colorGreenTopRight, colorGreenTopLeft, grnmap);
+ fillColormap(colorBlueBottomLeft, colorBlueBottomRight, colorBlueTopRight, colorBlueTopLeft, blumap);
+
+ getLight(0, 0);
+ side(block, YNEG_LB, tex[0]);
+ getLight(1, 0);
+ side(block, YNEG_RB, tex[1]);
+ getLight(1, 1);
+ side(block, YNEG_RT, tex[2]);
+ getLight(0, 1);
+ side(block, YNEG_LT, tex[3]);
+ }
+ post(ForgeDirection.DOWN);
+ }
+
+ @Override
+ public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
+ pre(ForgeDirection.UP);
+ if (!inWorld || hasOverrideBlockTexture() || submap == null) {
+ super.renderFaceYPos(block, 0, 0, 0, icon);
+ } else {
+ int tex[] = ctm.getSubmapIndices(blockAccess, bx, by, bz, 1);
+
+ fillLightmap(brightnessTopRight, brightnessTopLeft, brightnessBottomLeft, brightnessBottomRight);
+ fillColormap(colorRedTopRight, colorRedTopLeft, colorRedBottomLeft, colorRedBottomRight, redmap);
+ fillColormap(colorGreenTopRight, colorGreenTopLeft, colorGreenBottomLeft, colorGreenBottomRight, grnmap);
+ fillColormap(colorBlueTopRight, colorBlueTopLeft, colorBlueBottomLeft, colorBlueBottomRight, blumap);
+
+ getLight(0, 0);
+ side(block, YPOS_LB, tex[0]);
+ getLight(1, 0);
+ side(block, YPOS_RB, tex[1]);
+ getLight(1, 1);
+ side(block, YPOS_RT, tex[2]);
+ getLight(0, 1);
+ side(block, YPOS_LT, tex[3]);
+ }
+ post(ForgeDirection.UP);
+ }
+
+ protected void pre(ForgeDirection face) {
+ manager.preRenderSide(this, blockAccess, bx, by, bz, face);
+ }
+
+ protected void post(ForgeDirection face) {
+ manager.postRenderSide(this, blockAccess, bx, by, bz, face);
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/RenderBlocksColumn.java b/src/main/java/team/chisel/ctmlib/RenderBlocksColumn.java
new file mode 100644
index 00000000..5f453a49
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/RenderBlocksColumn.java
@@ -0,0 +1,156 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.IBlockAccess;
+
+import com.cricketcraft.chisel.api.rendering.TextureType;
+
+/**
+ * Used by {@link TextureType#CTMV}
+ */
+public class RenderBlocksColumn extends RenderBlocks {
+
+ public TextureSubmap submap;
+ public IIcon iconTop;
+ private boolean inWorld;
+
+ private IIcon sides[] = new IIcon[6];
+ private CTM ctm = CTM.getInstance();
+
+ public RenderBlocksColumn() {
+ super();
+ }
+
+ boolean connected(IBlockAccess world, int x, int y, int z, Block block, int meta) {
+ Block inWorld = ctm.getBlockOrFacade(world, x, y, z, -1);
+ return inWorld != null && inWorld.equals(block) && ctm.getBlockOrFacadeMetadata(world, x, y, z, -1) == meta;
+ }
+
+ @Override
+ public boolean renderStandardBlock(Block block, int x, int y, int z) {
+ int metadata = blockAccess.getBlockMetadata(x, y, z);
+ inWorld = true;
+
+ boolean yp = connected(blockAccess, x, y + 1, z, block, metadata);
+ boolean yn = connected(blockAccess, x, y - 1, z, block, metadata);
+
+ if (yp || yn) {
+ sides[0] = iconTop;
+ sides[1] = iconTop;
+
+ if (yp && yn) sides[2] = submap.getSubIcon(0, 1);
+ else if (yp) sides[2] = submap.getSubIcon(1, 1);
+ else sides[2] = submap.getSubIcon(1, 0);
+
+ sides[3] = sides[4] = sides[5] = sides[2];
+ } else {
+ boolean xp = connected(blockAccess, x + 1, y, z, block, metadata);
+ boolean xn = connected(blockAccess, x - 1, y, z, block, metadata);
+
+ if (xp && (connected(blockAccess, x + 1, y + 1, z, block, metadata)
+ || connected(blockAccess, x + 1, y - 1, z, block, metadata))) xp = false;
+ if (xn && (connected(blockAccess, x - 1, y + 1, z, block, metadata)
+ || connected(blockAccess, x - 1, y - 1, z, block, metadata))) xn = false;
+
+ if (xp || xn) {
+ uvRotateEast = 2;
+ uvRotateWest = 1;
+ uvRotateTop = 1;
+ uvRotateBottom = 1;
+
+ sides[4] = iconTop;
+ sides[5] = iconTop;
+
+ // for some reason along the x axis the bottom texture on the ends is backwards. This flips it around.
+ if (xp && xn) {
+ sides[1] = submap.getSubIcon(0, 1);
+ sides[0] = sides[1];
+ } else if (xp) {
+ sides[1] = submap.getSubIcon(1, 1);
+ sides[0] = submap.getSubIcon(1, 0);
+ } else {
+ sides[1] = submap.getSubIcon(1, 0);
+ sides[0] = submap.getSubIcon(1, 1);
+ }
+ sides[2] = sides[3] = sides[1];
+ } else {
+ boolean zp = connected(blockAccess, x, y, z + 1, block, metadata);
+ boolean zn = connected(blockAccess, x, y, z - 1, block, metadata);
+
+ if (zp && (connected(blockAccess, x, y + 1, z + 1, block, metadata)
+ || connected(blockAccess, x, y - 1, z + 1, block, metadata))) zp = false;
+ if (zp && (connected(blockAccess, x + 1, y, z + 1, block, metadata)
+ || connected(blockAccess, x - 1, y, z + 1, block, metadata))) zp = false;
+ if (zn && (connected(blockAccess, x, y + 1, z - 1, block, metadata)
+ || connected(blockAccess, x, y - 1, z - 1, block, metadata))) zn = false;
+ if (zn && (connected(blockAccess, x + 1, y, z - 1, block, metadata)
+ || connected(blockAccess, x - 1, y, z - 1, block, metadata))) zn = false;
+
+ if (zp || zn) {
+ uvRotateSouth = 1;
+ uvRotateNorth = 2;
+
+ sides[2] = iconTop;
+ sides[3] = iconTop;
+
+ if (zp && zn) sides[0] = submap.getSubIcon(0, 1);
+ else if (zp) sides[0] = submap.getSubIcon(1, 0);
+ else sides[0] = submap.getSubIcon(1, 1);
+
+ sides[1] = sides[4] = sides[5] = sides[0];
+ } else {
+ sides[0] = sides[1] = iconTop;
+ sides[2] = sides[3] = sides[4] = sides[5] = submap.getSubIcon(0, 0);
+ }
+ }
+ }
+
+ boolean flag = super.renderStandardBlock(block, x, y, z);
+
+ uvRotateSouth = 0;
+ uvRotateEast = 0;
+ uvRotateWest = 0;
+ uvRotateNorth = 0;
+ uvRotateTop = 0;
+ uvRotateBottom = 0;
+
+ inWorld = false;
+ return flag;
+ }
+
+ @Override
+ public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceXNeg(block, x, y, z, getIcon(icon, 4));
+ }
+
+ @Override
+ public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceXPos(block, x, y, z, getIcon(icon, 5));
+ }
+
+ @Override
+ public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceZNeg(block, x, y, z, getIcon(icon, 2));
+ }
+
+ @Override
+ public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceZPos(block, x, y, z, getIcon(icon, 3));
+ }
+
+ @Override
+ public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceYNeg(block, x, y, z, getIcon(icon, 0));
+ }
+
+ @Override
+ public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) {
+ super.renderFaceYPos(block, x, y, z, getIcon(icon, 1));
+ }
+
+ private IIcon getIcon(IIcon defaultIcon, int side) {
+ return sides[side] == null || !inWorld ? defaultIcon : sides[side];
+ }
+}
diff --git a/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java b/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java
new file mode 100644
index 00000000..53cfc30f
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java
@@ -0,0 +1,71 @@
+package team.chisel.ctmlib;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.RenderBlocks;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.IBlockAccess;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import lombok.Getter;
+
+/**
+ * A convenience implementation of {@link ISubmapManager} which does the standard CTM behavior.
+ */
+public class SubmapManagerCTM implements ISubmapManager {
+
+ @SideOnly(Side.CLIENT)
+ private static RenderBlocksCTM rb;
+
+ @Getter
+ private TextureSubmap submap, submapSmall;
+ @Getter
+ private final String textureName;
+
+ /**
+ * @param textureName The path to the texture, excluding the mod ID (this is passed into
+ * {@link #registerIcons(String, Block, IIconRegister)}).
+ *
+ * The CTM texture will be this path, plus {@code "-ctm"}.
+ */
+ public SubmapManagerCTM(String textureName) {
+ this.textureName = textureName;
+ }
+
+ @Override
+ public IIcon getIcon(int side, int meta) {
+ return submapSmall.getBaseIcon();
+ }
+
+ @Override
+ public IIcon getIcon(IBlockAccess world, int x, int y, int z, int side) {
+ return getIcon(side, world.getBlockMetadata(x, y, z));
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void registerIcons(String modName, Block block, IIconRegister register) {
+ submap = new TextureSubmap(register.registerIcon(modName + ":" + textureName + "-ctm"), 4, 4);
+ submapSmall = new TextureSubmap(register.registerIcon(modName + ":" + textureName), 2, 2);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public RenderBlocks createRenderContext(RenderBlocks rendererOld, Block block, IBlockAccess world) {
+ if (rb == null) {
+ rb = new RenderBlocksCTM();
+ }
+ rb.setRenderBoundsFromBlock(block);
+ rb.submap = submap;
+ rb.submapSmall = submapSmall;
+ return rb;
+ }
+
+ @Override
+ public void preRenderSide(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, ForgeDirection side) {}
+
+ @Override
+ public void postRenderSide(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, ForgeDirection side) {}
+}
diff --git a/src/main/java/team/chisel/ctmlib/TextureSubmap.java b/src/main/java/team/chisel/ctmlib/TextureSubmap.java
new file mode 100644
index 00000000..813ae267
--- /dev/null
+++ b/src/main/java/team/chisel/ctmlib/TextureSubmap.java
@@ -0,0 +1,185 @@
+package team.chisel.ctmlib;
+
+import java.util.List;
+
+import net.minecraft.util.IIcon;
+import net.minecraftforge.client.event.TextureStitchEvent;
+import net.minecraftforge.common.MinecraftForge;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+import com.google.common.collect.Lists;
+
+import cpw.mods.fml.common.eventhandler.SubscribeEvent;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import lombok.experimental.Delegate;
+
+/**
+ * This class is used to split up a large IIcon into smaller "submapped" icons used for CTM and other texture
+ * manipulation.
+ */
+public class TextureSubmap implements IIcon, ISubmap {
+
+ private static List submaps = Lists.newArrayList();
+ private static TextureSubmap dummy = new TextureSubmap(null, 0, 0);
+ static {
+ MinecraftForge.EVENT_BUS.register(dummy);
+ }
+
+ private int width, height;
+ @Delegate
+ private IIcon baseIcon;
+
+ protected IIcon[][] icons;
+
+ /**
+ * Construct a new submap. A submap is not required to be square, but it is required to be rectangular.
+ *
+ * @param baseIcon The IIcon to submap.
+ * @param width The width of the map, in icons.
+ * @param height The height of the map, in icons.
+ */
+ public TextureSubmap(IIcon baseIcon, int width, int height) {
+ this.baseIcon = baseIcon;
+ this.width = width;
+ this.height = height;
+ this.icons = new IIcon[width][height];
+ submaps.add(this);
+ }
+
+ /**
+ * The large "base" icon used for the submap.
+ */
+ public IIcon getBaseIcon() {
+ return baseIcon;
+ }
+
+ /**
+ * Gets all the icons in this submap. This is an expensive operation as it first clones the entire 2D array.
+ *
+ * @return All icons in this submap.
+ */
+ public IIcon[][] getAllIcons() {
+ IIcon[][] ret = ArrayUtils.clone(icons);
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = ArrayUtils.clone(ret[i]);
+ }
+ return ret;
+ }
+
+ /* ==== ISubmap ==== */
+
+ /**
+ * Finds and returns the sub-icon at the given coordinates. For example, if you have a 3 by 3 submap, calling
+ * {@code getSubIcon(1, 1)} will return the center subicon.
+ */
+ @Override
+ public IIcon getSubIcon(int x, int y) {
+ x = x % icons.length;
+ return icons[x][y % icons[x].length];
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ /* ==== Internal Stitching Logic ==== */
+
+ /**
+ * For internal use only, this is used to create the virtual "subicons" used in the map.
+ */
+ @SubscribeEvent
+ public final void TexturesStitched(TextureStitchEvent.Post event) {
+ for (TextureSubmap ts : submaps) {
+ ts.texturesStitched();
+ }
+ }
+
+ public void texturesStitched() {
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ icons[x][y] = new TextureVirtual(getBaseIcon(), width, height, x, y);
+ }
+ }
+ }
+
+ private class TextureVirtual implements IIcon {
+
+ private int width, height;
+ private float umin, umax, vmin, vmax;
+ private IIcon parentIcon;
+
+ private TextureVirtual(IIcon parent, int w, int h, int x, int y) {
+ parentIcon = parent;
+
+ umin = parentIcon.getInterpolatedU(16.0 * (x) / w);
+ umax = parentIcon.getInterpolatedU(16.0 * (x + 1) / w);
+ vmin = parentIcon.getInterpolatedV(16.0 * (y) / h);
+ vmax = parentIcon.getInterpolatedV(16.0 * (y + 1) / h);
+
+ width = parentIcon.getIconWidth();
+ height = parentIcon.getIconHeight();
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getMinU() {
+ return umin;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getMaxU() {
+ return umax;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getInterpolatedU(double d0) {
+ return (float) (umin + (umax - umin) * d0 / 16.0);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getMinV() {
+ return vmin;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getMaxV() {
+ return vmax;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public float getInterpolatedV(double d0) {
+ return (float) (vmin + (vmax - vmin) * d0 / 16.0);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public String getIconName() {
+ return parentIcon.getIconName();
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public int getIconWidth() {
+ return width;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public int getIconHeight() {
+ return height;
+ }
+ }
+}