package com.jaquadro.minecraft.storagedrawers.client.renderer;
import com.jaquadro.minecraft.chameleon.Chameleon;
import com.jaquadro.minecraft.chameleon.geometry.Area2D;
import com.jaquadro.minecraft.chameleon.render.ChamRender;
import com.jaquadro.minecraft.chameleon.render.ChamRenderManager;
import com.jaquadro.minecraft.storagedrawers.StorageDrawers;
import com.jaquadro.minecraft.storagedrawers.api.render.IRenderLabel;
import com.jaquadro.minecraft.storagedrawers.api.storage.IDrawer;
import com.jaquadro.minecraft.storagedrawers.block.BlockDrawers;
import com.jaquadro.minecraft.storagedrawers.api.storage.EnumBasicDrawer;
import com.jaquadro.minecraft.storagedrawers.block.BlockStandardDrawers;
import com.jaquadro.minecraft.storagedrawers.block.dynamic.StatusModelData;
import com.jaquadro.minecraft.storagedrawers.block.tile.TileEntityDrawers;
import com.jaquadro.minecraft.storagedrawers.block.tile.TileEntityDrawersComp;
import com.jaquadro.minecraft.storagedrawers.client.model.component.DrawerSealedModel;
import com.jaquadro.minecraft.storagedrawers.item.EnumUpgradeStatus;
import com.jaquadro.minecraft.storagedrawers.storage.CountFormatter;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.block.model.ModelManager;
import net.minecraft.client.renderer.color.ItemColors;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.List;
@SideOnly(Side.CLIENT)
public class TileEntityDrawersRenderer extends TileEntitySpecialRenderer<TileEntityDrawers>
{
private boolean[] renderAsBlock = new boolean[4];
private ItemStack[] renderStacks = new ItemStack[4];
private RenderItem renderItem;
@Override
public void renderTileEntityAt (TileEntityDrawers tile, double x, double y, double z, float partialTickTime, int destroyStage) {
if (tile == null)
return;
float depth = 1;
IBlockState state = tile.getWorld().getBlockState(tile.getPos());
if (state == null)
return;
Block block = state.getBlock();
if (block instanceof BlockDrawers) {
if (state.getProperties().containsKey(BlockStandardDrawers.BLOCK)) {
EnumBasicDrawer info = state.getValue(BlockStandardDrawers.BLOCK);
depth = info.isHalfDepth() ? .5f : 1;
}
}
else
return;
GlStateManager.pushMatrix();
GlStateManager.translate(x, y, z);
renderItem = Minecraft.getMinecraft().getRenderItem();
EnumFacing side = EnumFacing.getFront(tile.getDirection());
int ambLight = getWorld().getCombinedLight(tile.getPos().offset(side), 0);
int lu = ambLight % 65536;
int lv = ambLight / 65536;
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)lu / 1.0F, (float)lv / 1.0F);
ChamRender renderer = ChamRenderManager.instance.getRenderer(Tessellator.getInstance().getBuffer());
Minecraft mc = Minecraft.getMinecraft();
boolean cache = mc.gameSettings.fancyGraphics;
mc.gameSettings.fancyGraphics = true;
renderUpgrades(renderer, tile, state);
if (!tile.isShrouded() && !tile.isSealed())
renderFastItemSet(renderer, tile, state, side, depth, partialTickTime);
mc.gameSettings.fancyGraphics = cache;
GlStateManager.enableLighting();
GlStateManager.enableLight(0);
GlStateManager.enableLight(1);
GlStateManager.enableColorMaterial();
GlStateManager.colorMaterial(1032, 5634);
GlStateManager.disableRescaleNormal();
GlStateManager.disableNormalize();
GlStateManager.disableBlend();
GlStateManager.popMatrix();
ChamRenderManager.instance.releaseRenderer(renderer);
}
private void renderFastItemSet (ChamRender renderer, TileEntityDrawers tile, IBlockState state, EnumFacing side, float depth, float partialTickTime) {
int drawerCount = tile.getDrawerCount();
for (int i = 0; i < drawerCount; i++) {
renderStacks[i] = null;
IDrawer drawer = tile.getDrawerIfEnabled(i);
if (drawer == null)
continue;
ItemStack itemStack = drawer.getStoredItemPrototype();
if (itemStack == null)
continue;
renderStacks[i] = itemStack;
renderAsBlock[i] = isItemBlockType(itemStack);
}
for (int i = 0; i < drawerCount; i++) {
if (renderStacks[i] != null && !renderAsBlock[i])
renderFastItem(renderer, renderStacks[i], tile, state, i, side, depth, partialTickTime);
}
for (int i = 0; i < drawerCount; i++) {
if (renderStacks[i] != null && renderAsBlock[i])
renderFastItem(renderer, renderStacks[i], tile, state, i, side, depth, partialTickTime);
}
if (tile.isShowingQuantity()) {
EntityPlayerSP player = Minecraft.getMinecraft().player;
BlockPos blockPos = tile.getPos().add(.5, .5, .5);
double distance = Math.sqrt(blockPos.distanceSq(player.getPosition()));
float alpha = 1;
if (distance > 4)
alpha = Math.max(1f - (float) ((distance - 4) / 6), 0.05f);
if (distance < 10) {
for (int i = 0; i < drawerCount; i++)
renderText(CountFormatter.format(getFontRenderer(), tile.getDrawer(i)), tile, state, i, side, depth, alpha);
}
}
}
private void renderText (String text, TileEntityDrawers tile, IBlockState state, int slot, EnumFacing side, float depth, float alpha) {
if (text == null || text.isEmpty())
return;
BlockDrawers block = (BlockDrawers)state.getBlock();
StatusModelData statusInfo = block.getStatusInfo(state);
float frontDepth = (float)statusInfo.getFrontDepth() * .0625f;
int textWidth = getFontRenderer().getStringWidth(text);
Area2D statusArea = statusInfo.getSlot(slot).getLabelArea();
float x = (float)(statusArea.getX() + statusArea.getWidth() / 2);
float y = 16f - (float)statusArea.getY() - (float)statusArea.getHeight();
GlStateManager.pushMatrix();
alignRendering(side);
moveRendering(.125f, x, y, 1f - depth + frontDepth - .005f);
GlStateManager.disableLighting();
GlStateManager.enablePolygonOffset();
GlStateManager.depthMask(false);
GlStateManager.enableBlend();
GlStateManager.doPolygonOffset(-1, -20);
getFontRenderer().drawString(text, -textWidth / 2, 0, (int)(255 * alpha) << 24 | 255 << 16 | 255 << 8 | 255);
GlStateManager.disableBlend();
GlStateManager.depthMask(true);
GlStateManager.disablePolygonOffset();
GlStateManager.enableLighting();
GlStateManager.popMatrix();
}
private void renderFastItem (ChamRender renderer, ItemStack itemStack, TileEntityDrawers tile, IBlockState state, int slot, EnumFacing side, float depth, float partialTickTime) {
int drawerCount = tile.getDrawerCount();
float size = (drawerCount == 1) ? .5f : .25f;
BlockDrawers block = (BlockDrawers)state.getBlock();
StatusModelData statusInfo = block.getStatusInfo(state);
float frontDepth = (float)statusInfo.getFrontDepth() * .0625f;
Area2D slotArea = statusInfo.getSlot(slot).getSlotArea();
GlStateManager.pushMatrix();
float xCenter = (float)slotArea.getX() + (float)slotArea.getWidth() / 2 - (8 * size);
float yCenter = 16 - (float)slotArea.getY() - (float)slotArea.getHeight() / 2 - (8 * size);
alignRendering(side);
moveRendering(size, xCenter, yCenter, 1f - depth + frontDepth - .005f);
List<IRenderLabel> renderHandlers = StorageDrawers.renderRegistry.getRenderHandlers();
for (int i = 0, n = renderHandlers.size(); i < n; i++) {
renderHandlers.get(i).render(tile, tile, slot, 0, partialTickTime);
}
// At the time GL_LIGHT* are configured, the coordinates are transformed by the modelview
// matrix. The transformations used in `RenderHelper.enableGUIStandardItemLighting` are
// suitable for the orthographic projection used by GUI windows, but they are just a little
// bit off when rendering a block in 3D and squishing it flat. An additional term is added
// to account for slightly different shading on the half-size "icons" in 1x2 and 2x2
// drawers due to the extreme angles caused by flattening the block (as noted below).
GlStateManager.pushMatrix();
if (drawerCount == 1) {
GlStateManager.scale(2.6f, 2.6f, 1);
GlStateManager.rotate(171.6f, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(84.9f, 1.0F, 0.0F, 0.0F);
}
else {
GlStateManager.scale(1.92f, 1.92f, 1);
GlStateManager.rotate(169.2f, 0.0F, 1.0F, 0.0F);
GlStateManager.rotate(79.0f, 1.0F, 0.0F, 0.0F);
}
RenderHelper.enableStandardItemLighting();
GlStateManager.popMatrix();
// TileEntitySkullRenderer alters both of these options on, but does not restore them.
GlStateManager.enableCull();
GlStateManager.disableRescaleNormal();
// GL_POLYGON_OFFSET is used to offset flat icons toward the viewer (-Z) in screen space,
// so they always appear on top of the drawer's front space.
GlStateManager.enablePolygonOffset();
GlStateManager.doPolygonOffset(-1, -1);
// DIRTY HACK: Fool GlStateManager into thinking GL_RESCALE_NORMAL is enabled, but disable
// it using popAttrib This prevents RenderItem from enabling it again.
//
// Normals are transformed by the inverse of the modelview and projection matrices that
// excludes the translate terms. When put through the extreme Z scale used to flatten the
// block, this makes them point away from the drawer face at a very sharp angle. These
// normals are no longer unit scale (normalized), and normalizing them via
// GL_RESCALE_NORMAL causes a loss of precision that results in the normals pointing
// directly away from the face, which is visible as the block faces having identical
// (dark) shading.
GlStateManager.pushAttrib();
GlStateManager.enableRescaleNormal();
GlStateManager.popAttrib();
renderItem.renderItemIntoGUI(itemStack, 0, 0);
GlStateManager.disableBlend(); // Clean up after RenderItem
GlStateManager.enableAlpha(); // Restore world render state after RenderItem
GlStateManager.disablePolygonOffset();
GlStateManager.popMatrix();
}
private boolean isItemBlockType (ItemStack itemStack) {
return itemStack.getItem() instanceof ItemBlock && renderItem.shouldRenderItemIn3D(itemStack);
}
private void alignRendering (EnumFacing side) {
// Rotate to face the correct direction for the drawer's orientation.
GlStateManager.translate(.5f, .5f, .5f);
GlStateManager.rotate(getRotationYForSide2D(side), 0, 1, 0);
GlStateManager.translate(-.5f, -.5f, -.5f);
}
private void moveRendering (float size, float offsetX, float offsetY, float offsetZ) {
// NOTE: RenderItem expects to be called in a context where Y increases toward the bottom of the screen
// However, for in-world rendering the opposite is true. So we translate up by 1 along Y, and then flip
// along Y. Since the item is drawn at the back of the drawer, we also translate by `1-offsetZ` to move
// it to the front.
// The 0.00001 for the Z-scale both flattens the item and negates the 32.0 Z-scale done by RenderItem.
GlStateManager.translate(0, 1, 1-offsetZ);
GlStateManager.scale(1 / 16f, -1 / 16f, 0.00001);
GlStateManager.translate(offsetX, offsetY, 0.);
GlStateManager.scale(size, size, 1);
}
private static final float[] sideRotationY2D = { 0, 0, 2, 0, 3, 1 };
private float getRotationYForSide2D (EnumFacing side) {
return sideRotationY2D[side.ordinal()] * 90;
}
private void renderUpgrades (ChamRender renderer, TileEntityDrawers tile, IBlockState state) {
Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
GlStateManager.enableAlpha();
renderIndicator(renderer, tile, state, tile.getDirection(), tile.getEffectiveStatusLevel());
renderTape(renderer, tile, state, tile.getDirection(), tile.isSealed());
}
private void renderIndicator (ChamRender renderer, TileEntityDrawers tile, IBlockState blockState, int side, int level) {
if (level <= 0 || side < 2 || side > 5)
return;
BlockDrawers block = (BlockDrawers)blockState.getBlock();
StatusModelData statusInfo = block.getStatusInfo(blockState);
if (statusInfo == null)
return;
double depth = block.isHalfDepth(blockState) ? .5 : 1;
int count = (tile instanceof TileEntityDrawersComp) ? 1 : block.getDrawerCount(blockState);
double unit = 0.0625;
double frontDepth = statusInfo.getFrontDepth() * unit;
for (int i = 0; i < count; i++) {
IDrawer drawer = tile.getDrawer(i);
if (drawer == null || tile.isShrouded())
continue;
TextureAtlasSprite iconOff = Chameleon.instance.iconRegistry.getIcon(statusInfo.getSlot(i).getOffResource(EnumUpgradeStatus.byLevel(level)));
TextureAtlasSprite iconOn = Chameleon.instance.iconRegistry.getIcon(statusInfo.getSlot(i).getOnResource(EnumUpgradeStatus.byLevel(level)));
Area2D statusArea = statusInfo.getSlot(i).getStatusArea();
Area2D activeArea = statusInfo.getSlot(i).getStatusActiveArea();
GlStateManager.enablePolygonOffset();
GlStateManager.doPolygonOffset(-1, -1);
renderer.setRenderBounds(statusArea.getX() * unit, statusArea.getY() * unit, 0,
(statusArea.getX() + statusArea.getWidth()) * unit, (statusArea.getY() + statusArea.getHeight()) * unit, depth - frontDepth);
renderer.state.setRotateTransform(ChamRender.ZPOS, side);
renderer.renderFace(ChamRender.FACE_ZPOS, null, blockState, BlockPos.ORIGIN, iconOff, 1, 1, 1);
renderer.state.clearRotateTransform();
GlStateManager.doPolygonOffset(-1, -10);
if (level == 1 && drawer.getMaxCapacity() > 0 && drawer.getRemainingCapacity() == 0) {
renderer.setRenderBounds(statusArea.getX() * unit, statusArea.getY() * unit, 0,
(statusArea.getX() + statusArea.getWidth()) * unit, (statusArea.getY() + statusArea.getHeight()) * unit, depth - frontDepth);
renderer.state.setRotateTransform(ChamRender.ZPOS, side);
renderer.renderFace(ChamRender.FACE_ZPOS, null, blockState, BlockPos.ORIGIN, iconOn, 1, 1, 1);
renderer.state.clearRotateTransform();
}
else if (level >= 2) {
int stepX = statusInfo.getSlot(i).getActiveStepsX();
int stepY = statusInfo.getSlot(i).getActiveStepsY();
double indXStart = activeArea.getX();
double indXEnd = activeArea.getX() + activeArea.getWidth();
double indXCur = (stepX == 0) ? indXEnd : getIndEnd(block, tile, i, indXStart, activeArea.getWidth(), stepX);
double indYStart = activeArea.getY();
double indYEnd = activeArea.getY() + activeArea.getHeight();
double indYCur = (stepY == 0) ? indYEnd : getIndEnd(block, tile, i, indYStart, activeArea.getHeight(), stepY);
if (indXCur > indXStart && indYCur > indYStart) {
indXCur = Math.min(indXCur, indXEnd);
indYCur = Math.min(indYCur, indYEnd);
renderer.setRenderBounds(indXStart * unit, indYStart * unit, 0,
indXCur * unit, indYCur * unit, depth - frontDepth);
renderer.state.setRotateTransform(ChamRender.ZPOS, side);
renderer.renderFace(ChamRender.FACE_ZPOS, null, blockState, BlockPos.ORIGIN, iconOn, 1, 1, 1);
renderer.state.clearRotateTransform();
}
}
GlStateManager.disablePolygonOffset();
}
}
private void renderTape (ChamRender renderer, TileEntityDrawers tile, IBlockState blockState, int side, boolean taped) {
if (!taped || side < 2 || side > 5)
return;
BlockDrawers block = (BlockDrawers)blockState.getBlock();
double depth = block.isHalfDepth(blockState) ? .5 : 1;
TextureAtlasSprite iconTape = Chameleon.instance.iconRegistry.getIcon(DrawerSealedModel.iconTapeCover);
GlStateManager.enablePolygonOffset();
GlStateManager.doPolygonOffset(-1, -1);
renderer.setRenderBounds(0, 0, 0, 1, 1, depth);
renderer.state.setRotateTransform(ChamRender.ZPOS, side);
renderer.renderPartialFace(ChamRender.FACE_ZPOS, null, blockState, BlockPos.ORIGIN, iconTape, 0, 0, 1, 1, 1, 1, 1);
renderer.state.clearRotateTransform();
GlStateManager.disablePolygonOffset();
}
private double getIndEnd (BlockDrawers block, TileEntityDrawers tile, int slot, double x, double w, int step) {
IDrawer drawer = tile.getDrawer(slot);
if (drawer == null)
return x;
int cap = drawer.getMaxCapacity();
int count = drawer.getStoredItemCount();
if (cap == 0 || count == 0)
return x;
float fillAmt = (float)(step * count / cap) / step;
return x + (w * fillAmt);
}
}