package crazypants.enderio.conduit.liquid; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import net.minecraft.client.renderer.RenderBlocks; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.util.IIcon; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import com.enderio.core.client.render.BoundingBox; import com.enderio.core.client.render.RenderUtil; import com.enderio.core.common.util.ForgeDirectionOffsets; import com.enderio.core.common.vecmath.Vector2f; import com.enderio.core.common.vecmath.Vector3d; import com.enderio.core.common.vecmath.Vector3f; import com.enderio.core.common.vecmath.Vertex; import crazypants.enderio.EnderIO; import crazypants.enderio.Log; import crazypants.enderio.conduit.ConnectionMode; import crazypants.enderio.conduit.IConduit; import crazypants.enderio.conduit.IConduitBundle; import crazypants.enderio.conduit.geom.CollidableComponent; import crazypants.enderio.conduit.render.ConduitBundleRenderer; import crazypants.enderio.conduit.render.DefaultConduitRenderer; import static com.enderio.core.client.render.CubeRenderer.*; public class LiquidConduitRenderer extends DefaultConduitRenderer implements IResourceManagerReloadListener { private float downRatio; private float flatRatio; private float upRatio; private LiquidConduitRenderer() { super(); } public static LiquidConduitRenderer create() { LiquidConduitRenderer result = new LiquidConduitRenderer(); RenderUtil.registerReloadListener(result); return result; } @Override public boolean isRendererForConduit(IConduit conduit) { if (conduit instanceof LiquidConduit) { return true; } return false; } @Override public void renderEntity(ConduitBundleRenderer conduitBundleRenderer, IConduitBundle te, IConduit conduit, double x, double y, double z, float partialTick, float worldLight, RenderBlocks rb) { calculateRatios((LiquidConduit) conduit); super.renderEntity(conduitBundleRenderer, te, conduit, x, y, z, partialTick, worldLight, rb); } @Override protected void renderConduit(IIcon tex, IConduit conduit, CollidableComponent component, float brightness) { if (isNSEWUD(component.dir)) { LiquidConduit lc = (LiquidConduit) conduit; FluidStack fluid = lc.getFluidType(); if (fluid != null) { renderFluidOutline(component, fluid); } BoundingBox[] cubes = toCubes(component.bound); for (BoundingBox cube : cubes) { drawSection(cube, tex.getMinU(), tex.getMaxU(), tex.getMinV(), tex.getMaxV(), component.dir, false); } } else { drawSection(component.bound, tex.getMinU(), tex.getMaxU(), tex.getMinV(), tex.getMaxV(), component.dir, true); } if (conduit.getConnectionMode(component.dir) == ConnectionMode.DISABLED) { tex = EnderIO.blockConduitBundle.getConnectorIcon(component.data); List<Vertex> corners = component.bound.getCornersWithUvForFace(component.dir, tex.getMinU(), tex.getMaxU(), tex.getMinV(), tex.getMaxV()); for (Vertex c : corners) { addVecWithUV(c.xyz, c.uv.x, c.uv.y); } //back face for (int i = corners.size() - 1; i >= 0; i--) { Vertex c = corners.get(i); addVecWithUV(c.xyz, c.uv.x, c.uv.y); } } } public static void renderFluidOutline(CollidableComponent component, FluidStack fluid) { renderFluidOutline(component, fluid, 1, 13f / 16f); } public static void renderFluidOutline(CollidableComponent component, FluidStack fluid, double scaleFactor, float outlineWidth) { for (CachableRenderStatement elem : computeFluidOutlineToCache(component, fluid.getFluid(), scaleFactor, outlineWidth)) { elem.execute(); } } private static Map<CollidableComponent, Map<Fluid, List<CachableRenderStatement>>> cache = new WeakHashMap<CollidableComponent, Map<Fluid, List<CachableRenderStatement>>>(); public static List<CachableRenderStatement> computeFluidOutlineToCache(CollidableComponent component, Fluid fluid, double scaleFactor, float outlineWidth) { Map<Fluid, List<CachableRenderStatement>> cache0 = cache.get(component); if (cache0 == null) { cache0 = new HashMap<Fluid, List<CachableRenderStatement>>(); cache.put(component, cache0); } List<CachableRenderStatement> data = cache0.get(fluid); if (data != null) { return data; } data = new ArrayList<CachableRenderStatement>(); cache0.put(fluid, data); IIcon texture = fluid.getStillIcon(); if (texture == null) { texture = fluid.getIcon(); if (texture == null) { return data; } } BoundingBox bbb; if (scaleFactor == 1) { bbb = component.bound; } else { double xScale = Math.abs(component.dir.offsetX) == 1 ? 1 : scaleFactor; double yScale = Math.abs(component.dir.offsetY) == 1 ? 1 : scaleFactor; double zScale = Math.abs(component.dir.offsetZ) == 1 ? 1 : scaleFactor; bbb = component.bound.scale(xScale, yScale, zScale); } for (ForgeDirection face : ForgeDirection.VALID_DIRECTIONS) { if (face != component.dir && face != component.dir.getOpposite()) { data.add(new CachableRenderStatement.SetNormal(face.offsetX, face.offsetY, face.offsetZ)); Vector3d offset = ForgeDirectionOffsets.offsetScaled(face, -0.005); Vector2f uv = new Vector2f(); List<ForgeDirection> edges = RenderUtil.getEdgesForFace(face); for (ForgeDirection edge : edges) { if (edge != component.dir && edge != component.dir.getOpposite()) { float xLen = 1 - Math.abs(edge.offsetX) * outlineWidth; float yLen = 1 - Math.abs(edge.offsetY) * outlineWidth; float zLen = 1 - Math.abs(edge.offsetZ) * outlineWidth; BoundingBox bb = bbb.scale(xLen, yLen, zLen); List<Vector3f> corners = bb.getCornersForFace(face); for (Vector3f unitCorn : corners) { Vector3d corner = new Vector3d(unitCorn); corner.add(offset); corner.x += (float) (edge.offsetX * 0.5 * bbb.sizeX()) - (Math.signum(edge.offsetX) * xLen / 2f * bbb.sizeX()) * 2f; corner.y += (float) (edge.offsetY * 0.5 * bbb.sizeY()) - (Math.signum(edge.offsetY) * yLen / 2f * bbb.sizeY()) * 2f; corner.z += (float) (edge.offsetZ * 0.5 * bbb.sizeZ()) - (Math.signum(edge.offsetZ) * zLen / 2f * bbb.sizeZ()) * 2f; //polyOffset RenderUtil.getUvForCorner(uv, corner, 0, 0, 0, face, texture); data.add(new CachableRenderStatement.AddVertexWithUV(corner.x, corner.y, corner.z, uv.x, uv.y)); } } } } } return data; } private interface CachableRenderStatement { void execute(); static class SetNormal implements CachableRenderStatement { private final float x, y, z; private SetNormal(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } @Override public void execute() { Tessellator.instance.setNormal(x, y, z); } } static class AddVertexWithUV implements CachableRenderStatement { private final double x, y, z, u, v; private AddVertexWithUV(double x, double y, double z, double u, double v) { this.x = x; this.y = y; this.z = z; this.u = u; this.v = v; } @Override public void execute() { Tessellator.instance.addVertexWithUV(x, y, z, u, v); } } } @Override protected void renderTransmission(IConduit con, IIcon tex, CollidableComponent component, float brightness) { //done in the dynamic section } @Override public boolean isDynamic() { return true; } @Override public void renderDynamicEntity(ConduitBundleRenderer conduitBundleRenderer, IConduitBundle te, IConduit conduit, double x, double y, double z, float partialTick, float worldLight) { if (((LiquidConduit) conduit).getTank().getFilledRatio() <= 0) { return; } Collection<CollidableComponent> components = conduit.getCollidableComponents(); Tessellator tessellator = Tessellator.instance; calculateRatios((LiquidConduit) conduit); transmissionScaleFactor = conduit.getTransmitionGeometryScale(); IIcon tex; for (CollidableComponent component : components) { if (renderComponent(component)) { if (isNSEWUD(component.dir) && conduit.getTransmitionTextureForState(component) != null) { tessellator.setColorOpaque_F(1, 1, 1); tex = conduit.getTransmitionTextureForState(component); BoundingBox[] cubes = toCubes(component.bound); for (BoundingBox cube : cubes) { drawSection(cube, tex.getMinU(), tex.getMaxU(), tex.getMinV(), tex.getMaxV(), component.dir, true); } } } } } @Override protected void setVerticesForTransmission(BoundingBox bound, ForgeDirection id) { float yScale = getRatioForConnection(id); float xs = id.offsetX == 0 ? 0.9f : 1; float ys = id.offsetY == 0 ? Math.min(yScale, 0.9f) : yScale; float zs = id.offsetZ == 0 ? 0.9f : 1; float sizeY = bound.sizeY(); bound = bound.scale(xs, ys, zs); float transY = (bound.sizeY() - sizeY) / 2; Vector3d translation = new Vector3d(0, transY, 0); setupVertices(bound.translate(translation)); } private void calculateRatios(LiquidConduit conduit) { ConduitTank tank = conduit.getTank(); int totalAmount = tank.getFluidAmount(); int upCapacity = 0; if (conduit.containsConduitConnection(ForgeDirection.UP) || conduit.containsExternalConnection(ForgeDirection.UP)) { upCapacity = LiquidConduit.VOLUME_PER_CONNECTION; } int downCapacity = 0; if (conduit.containsConduitConnection(ForgeDirection.DOWN) || conduit.containsExternalConnection(ForgeDirection.DOWN)) { downCapacity = LiquidConduit.VOLUME_PER_CONNECTION; } int flatCapacity = tank.getCapacity() - upCapacity - downCapacity; int usedCapacity = 0; if (downCapacity > 0) { int inDown = Math.min(totalAmount, downCapacity); usedCapacity += inDown; downRatio = (float) inDown / downCapacity; } if (flatCapacity > 0 && usedCapacity < totalAmount) { int inFlat = Math.min(flatCapacity, totalAmount - usedCapacity); usedCapacity += inFlat; flatRatio = (float) inFlat / flatCapacity; } else { flatRatio = 0; } if (upCapacity > 0 && usedCapacity < totalAmount) { int inUp = Math.min(upCapacity, totalAmount - usedCapacity); upRatio = (float) inUp / upCapacity; } else { upRatio = 0; } } private float getRatioForConnection(ForgeDirection id) { if (id == ForgeDirection.UP) { return upRatio; } if (id == ForgeDirection.DOWN) { return downRatio; } return flatRatio; } @Override public void onResourceManagerReload(IResourceManager p_110549_1_) { cache.clear(); } }