Merge CTMLib

This commit is contained in:
Jason Mitchell authored and Jason Mitchell committed 2024-02-20 08:25:02 -08:00
1 parent 83cd16061f
commit e9e63d6fc5
14 files changed
+1739 -2

No files matched your search

+313
View File
@@ -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.
* <pre><blockquote>
* ┌─────────────────┐ ┌────────────────────────────────┐
* │ texture.png │ │ texture-ctm.png │
* │ ╔══════╤══════╗ │ │ ──────┼────── ║ ─────┼───── ║ │
* │ ║ │ ║ │ │ │ │ │║ │ ║ │
* │ ║ 16 │ 17 ║ │ │ │ 0 │ 1 │║ 2 │ 3 ║ │
* │ ╟──────┼──────╢ │ │ ┼──────┼──────┼╟──────┼──────╢ │
* │ ║ │ ║ │ │ │ │ │║ │ ║ │
* │ ║ 18 │ 19 ║ │ │ │ 4 │ 5 │║ 6 │ 7 ║ │
* │ ╚══════╧══════╝ │ │ ──────┼────── ║ ─────┼───── ║ │
* └─────────────────┘ │ ═══════╤═══════╝ ─────┼───── ╚ │
* │ │ │ ││ │ │ │
* │ │ 8 │ 9 ││ 10 │ 11 │ │
* │ ┼──────┼──────┼┼──────┼──────┼ │
* │ │ │ ││ │ │ │
* │ │ 12 │ 13 ││ 14 │ 15 │ │
* │ ═══════╧═══════╗ ─────┼───── ╔ │
* └────────────────────────────────┘
* </blockquote></pre>
* combining { 18, 13, 9, 16 }, we can generate a texture connected to the right!
* <pre><blockquote>
* ╔══════╤═══════
* ║ │ │
* ║ 16 │ 9 │
* ╟──────┼──────┼
* ║ │ │
* ║ 18 │ 13 │
* ╚══════╧═══════
* </blockquote></pre>
*
* combining { 18, 13, 11, 2 }, we can generate a texture, in the shape of an L (connected to the right, and up
* <pre><blockquote>
* ║ ─────┼───── ╚
* ║ │ │
* ║ 2 │ 11 │
* ╟──────┼──────┼
* ║ │ │
* ║ 18 │ 13 │
* ╚══════╧═══════
* </blockquote></pre>
*
* HAVE FUN!
* -CptRageToaster-
*/
//@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<Boolean> disableObscuredFaceCheck = Optional.absent();
protected TIntObjectMap<Dir[]> submapMap = new TIntObjectHashMap<Dir[]>();
protected EnumMap<Dir, Boolean> 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 <i><b>all</b></i> the given {@link Dir
* directions}.
*/
public boolean connectedAnd(Dir... dirs) {
for (Dir dir : dirs) {
if (!connected(dir)) {
return false;
}
}
return true;
}
/**
* @param dirs The directions to check connection in.
* @return True if the cached connectionMap holds a connection in <i><b>one of</b></i> the given {@link Dir
* directions}.
*/
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 <i>against</i>. This is not the position of your block.
* @param y The y coordinate of the block to check <i>against</i>. This is not the position of your block.
* @param z The z coordinate of the block to check <i>against</i>. This is not the position of your block.
* @param side The side of the block to check for connection status. This is <i>not</i> 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 <i>against</i>. This is not the position of your block.
* @param y The y coordinate of the block to check <i>against</i>. This is not the position of your block.
* @param z The z coordinate of the block to check <i>against</i>. This is not the position of your block.
* @param dir The {@link ForgeDirection side} of the block to check for connection status. This is <i>not</i> 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;
}
}
@@ -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.
* <p>
* This class does <i>not</i> handle metadata-dependent textures.
*/
public class CTMBlock extends Block implements ICTMBlock<ISubmapManager> {
@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;
}
}
@@ -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;
}
}
@@ -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;
}
}
+104
View File
@@ -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".
* <p>
* 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.
* <p>
* 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;
}
}
}
@@ -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);
}
}
@@ -0,0 +1,19 @@
package team.chisel.ctmlib;
import net.minecraft.world.IBlockAccess;
public interface ICTMBlock<T extends ISubmapManager> {
/**
* 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);
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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];
}
}
@@ -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)}).
* <p>
* 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) {}
}
@@ -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<TextureSubmap> 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;
}
}
}