From e9e63d6fc5c07e88aa15a5d24af34bbb4da78ea9 Mon Sep 17 00:00:00 2001 From: Jason Mitchell Date: Mon, 19 Feb 2024 11:13:25 -0800 Subject: [PATCH] Merge CTMLib --- dependencies.gradle | 2 - src/main/java/team/chisel/ctmlib/CTM.java | 313 ++++++++++ .../java/team/chisel/ctmlib/CTMBlock.java | 71 +++ src/main/java/team/chisel/ctmlib/CTMLib.java | 18 + .../java/team/chisel/ctmlib/CTMRenderer.java | 99 ++++ src/main/java/team/chisel/ctmlib/Dir.java | 104 ++++ src/main/java/team/chisel/ctmlib/Drawing.java | 76 +++ .../java/team/chisel/ctmlib/ICTMBlock.java | 19 + src/main/java/team/chisel/ctmlib/ISubmap.java | 19 + .../team/chisel/ctmlib/ISubmapManager.java | 48 ++ .../team/chisel/ctmlib/RenderBlocksCTM.java | 560 ++++++++++++++++++ .../chisel/ctmlib/RenderBlocksColumn.java | 156 +++++ .../team/chisel/ctmlib/SubmapManagerCTM.java | 71 +++ .../team/chisel/ctmlib/TextureSubmap.java | 185 ++++++ 14 files changed, 1739 insertions(+), 2 deletions(-) create mode 100644 src/main/java/team/chisel/ctmlib/CTM.java create mode 100644 src/main/java/team/chisel/ctmlib/CTMBlock.java create mode 100644 src/main/java/team/chisel/ctmlib/CTMLib.java create mode 100644 src/main/java/team/chisel/ctmlib/CTMRenderer.java create mode 100644 src/main/java/team/chisel/ctmlib/Dir.java create mode 100644 src/main/java/team/chisel/ctmlib/Drawing.java create mode 100644 src/main/java/team/chisel/ctmlib/ICTMBlock.java create mode 100644 src/main/java/team/chisel/ctmlib/ISubmap.java create mode 100644 src/main/java/team/chisel/ctmlib/ISubmapManager.java create mode 100644 src/main/java/team/chisel/ctmlib/RenderBlocksCTM.java create mode 100644 src/main/java/team/chisel/ctmlib/RenderBlocksColumn.java create mode 100644 src/main/java/team/chisel/ctmlib/SubmapManagerCTM.java create mode 100644 src/main/java/team/chisel/ctmlib/TextureSubmap.java diff --git a/dependencies.gradle b/dependencies.gradle index f6b30d47..ffba4a9f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -9,8 +9,6 @@ dependencies { compileOnly("org.projectlombok:lombok:1.18.24") annotationProcessor("org.projectlombok:lombok:1.18.24") - shadowImplementation("com.github.GTNewHorizons:CTMLib:1.4.4:dev") - devOnlyNonPublishable("com.github.GTNewHorizons:waila:1.6.5:dev") { transitive = false } diff --git a/src/main/java/team/chisel/ctmlib/CTM.java b/src/main/java/team/chisel/ctmlib/CTM.java new file mode 100644 index 00000000..3db06848 --- /dev/null +++ b/src/main/java/team/chisel/ctmlib/CTM.java @@ -0,0 +1,313 @@ +package team.chisel.ctmlib; + +import static team.chisel.ctmlib.Dir.BOTTOM; +import static team.chisel.ctmlib.Dir.BOTTOM_LEFT; +import static team.chisel.ctmlib.Dir.BOTTOM_RIGHT; +import static team.chisel.ctmlib.Dir.LEFT; +import static team.chisel.ctmlib.Dir.RIGHT; +import static team.chisel.ctmlib.Dir.TOP; +import static team.chisel.ctmlib.Dir.TOP_LEFT; +import static team.chisel.ctmlib.Dir.TOP_RIGHT; + +import java.util.EnumMap; + +import net.minecraft.block.Block; +import net.minecraft.world.IBlockAccess; +import net.minecraftforge.common.util.ForgeDirection; + +import com.cricketcraft.chisel.api.IConnectable; +import com.cricketcraft.chisel.api.IFacade; +import com.google.common.base.Optional; +import com.google.common.collect.Maps; + +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; + +//@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. +*
+* ┌─────────────────┐ ┌────────────────────────────────┐ +* │ 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 │ │ +* │ ═══════╧═══════╗ ─────┼───── ╔ │ +* └────────────────────────────────┘ +*
+* combining { 18, 13, 9, 16 }, we can generate a texture connected to the right! +*
+* ╔══════╤═══════ +* ║ │ │ +* ║ 16 │ 9 │ +* ╟──────┼──────┼ +* ║ │ │ +* ║ 18 │ 13 │ +* ╚══════╧═══════ +*
+* +* combining { 18, 13, 11, 2 }, we can generate a texture, in the shape of an L (connected to the right, and up +*
+* ║ ─────┼───── ╚ +* ║ │ │ +* ║ 2 │ 11 │ +* ╟──────┼──────┼ +* ║ │ │ +* ║ 18 │ 13 │ +* ╚══════╧═══════ +*
+* +* HAVE FUN! +* -CptRageToaster- +*/ +//@formatter:on +public class CTM { + + /** 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 disableObscuredFaceCheck = Optional.absent(); + + protected TIntObjectMap submapMap = new TIntObjectHashMap(); + protected EnumMap 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; + } + } +}