content: package thebetweenlands.client.render.entity;
  
  import org.lwjgl.opengl.GL11;
  
  import net.minecraft.client.renderer.BufferBuilder;
  import net.minecraft.client.renderer.GlStateManager;
  import net.minecraft.client.renderer.GlStateManager.DestFactor;
  import net.minecraft.client.renderer.GlStateManager.SourceFactor;
  import net.minecraft.client.renderer.Tessellator;
  import net.minecraft.client.renderer.culling.Frustum;
  import net.minecraft.client.renderer.entity.Render;
  import net.minecraft.client.renderer.entity.RenderManager;
  import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
  import net.minecraft.entity.Entity;
  import net.minecraft.util.ResourceLocation;
  import net.minecraft.util.math.AxisAlignedBB;
  import thebetweenlands.client.render.model.entity.ModelRopeNode;
  import thebetweenlands.client.render.shader.LightSource;
  import thebetweenlands.client.render.shader.ShaderHelper;
  import thebetweenlands.common.entity.EntityRopeNode;
  import thebetweenlands.util.LightingUtil;
  
  public class RenderRopeNode extends Render<EntityRopeNode> {
  	private Frustum frustum;
  
  	protected static final ResourceLocation TEXTURE = new ResourceLocation("thebetweenlands:textures/blocks/bulb_capped_mushroom.png");
  	
  	protected static final ModelRopeNode nodeModel = new ModelRopeNode();
  	
  	public RenderRopeNode(RenderManager renderManager) {
  		super(renderManager);
  		this.frustum = new Frustum();
  	}
  
  	@Override
  	public void doRender(EntityRopeNode ropeNode, double x, double y, double z, float yaw, float partialTicks) {
  		this.bindEntityTexture(ropeNode);
  		
  		Tessellator tessellator = Tessellator.getInstance();
  		BufferBuilder buffer = tessellator.getBuffer();
  
  		GlStateManager.pushMatrix();
  
  		GlStateManager.enableBlend();
  		GlStateManager.blendFunc(SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA);
  		GlStateManager.enableTexture2D();
  		GlStateManager.enableLighting();
  		GlStateManager.color(1.0F, 1.0F, 1.0F, 0.35F);
  		LightingUtil.INSTANCE.setLighting(255);
  
  		AxisAlignedBB boundingBox = ropeNode.getEntityBoundingBox().offset(-ropeNode.posX, -ropeNode.posY, -ropeNode.posZ);
  
  		if(ropeNode.getNextNode() == null) {
  			boundingBox = boundingBox.grow(0.025D, 0.025D, 0.025D);
  			GlStateManager.color(0.25F, 1.0F, 0.25F, 0.35F);
  		}
  		
  		if(ShaderHelper.INSTANCE.isWorldShaderActive()) {
  			double dstSq = x*x + y*y + z*z;
  			
  			if(dstSq <= 256.0D) {
  				ShaderHelper.INSTANCE.require();
  	        	double rx = ropeNode.lastTickPosX + (ropeNode.posX - ropeNode.lastTickPosX) * partialTicks;
  				double ry = ropeNode.lastTickPosY + (ropeNode.posY - ropeNode.lastTickPosY) * partialTicks;
  				double rz = ropeNode.lastTickPosZ + (ropeNode.posZ - ropeNode.lastTickPosZ) * partialTicks;
  				float brightness = 1.0F - (float)Math.sqrt(dstSq) / 16.0F;
  				ShaderHelper.INSTANCE.getWorldShader().addLight(new LightSource(rx, ry, rz, 1.25F, 1.5F * brightness, 2.0F * brightness, 4.0F * brightness));
  			}
  		}
  		
  		GlStateManager.pushMatrix();
  		GlStateManager.translate(x, y, z);
  		nodeModel.render(ropeNode, 0, 0, 0, 0, 0, 0.0625F);
  		GlStateManager.popMatrix();
  		
  		GlStateManager.color(1, 1, 1, 1);
  
  		LightingUtil.INSTANCE.revert();
  		GlStateManager.enableTexture2D();
  		GlStateManager.enableLighting();
  
  		double camPosX = this.interpolate(ropeNode.lastTickPosX, ropeNode.posX, partialTicks) - x;
  		double camPosY = this.interpolate(ropeNode.lastTickPosY, ropeNode.posY, partialTicks) - y;
  		double camPosZ = this.interpolate(ropeNode.lastTickPosZ, ropeNode.posZ, partialTicks) - z;
  
  		this.frustum.setPosition(camPosX, camPosY, camPosZ);
  
  		Entity prevNode = ropeNode.getPreviousNode();
  
  		if(prevNode != null) {
  			if(!this.renderManager.getEntityRenderObject(prevNode).shouldRender(prevNode, this.frustum, camPosX, camPosY, camPosZ)) {
  				//Previous node not rendered, render rope
  				GlStateManager.pushMatrix();
  				double renderOffsetX = this.interpolate(prevNode.lastTickPosX - ropeNode.lastTickPosX, prevNode.posX - ropeNode.posX, partialTicks);
  				double renderOffsetY = this.interpolate(prevNode.lastTickPosY - ropeNode.lastTickPosY, prevNode.posY - ropeNode.posY, partialTicks);
  				double renderOffsetZ = this.interpolate(prevNode.lastTickPosZ - ropeNode.lastTickPosZ, prevNode.posZ - ropeNode.posZ, partialTicks);
  				GlStateManager.translate(renderOffsetX, renderOffsetY, renderOffsetZ);
  				this.renderConnection(prevNode, ropeNode, tessellator, buffer, x, y, z, partialTicks);
  				GlStateManager.popMatrix();
  			}
  		}
  
  		Entity nextNode = ropeNode.getNextNode();
  
  		if(nextNode != null) {
  			this.renderConnection(ropeNode, nextNode, tessellator, buffer, x, y, z, partialTicks);
  		}
  
  		GlStateManager.popMatrix();
  	}
  
  	protected double interpolate(double prev, double now, double partialTicks) {
  		return prev + (now - prev) * partialTicks;
  	}
  
  	protected void renderConnection(Entity node1, Entity node2, Tessellator tessellator, BufferBuilder buffer, double x, double y, double z, float partialTicks) {
  		if(node2 != null) {
  			double camPosX = this.interpolate(node1.prevPosX - x, node1.posX - x, partialTicks);
  			double camPosY = this.interpolate(node1.prevPosY - y, node1.posY - y, partialTicks);
  			double camPosZ = this.interpolate(node1.prevPosZ - z, node1.posZ - z, partialTicks);
  
  			double startX = x;
  			double startY = y;
  			double startZ = z;
  			double endX = this.interpolate(node2.prevPosX - camPosX, node2.posX - camPosX, partialTicks);
  			double endY = this.interpolate(node2.prevPosY - camPosY, node2.posY - camPosY, partialTicks);
  			if(node2 instanceof EntityRopeNode == false) {
  				endY += node2.getEyeHeight() / 2.0D;
  			}
  			double endZ = this.interpolate(node2.prevPosZ - camPosZ, node2.posZ - camPosZ, partialTicks);
  
  			double diffX = (double)((float)(endX - startX));
  			double diffY = (double)((float)(endY - startY));
  			double diffZ = (double)((float)(endZ - startZ));
  
  			GlStateManager.disableTexture2D();
  			GlStateManager.disableLighting();
  			GlStateManager.disableCull();
  
  			buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR);
  			for (int i = 0; i <= 24; ++i) {
  				float r;
  				float g;
  				float b;
  
  				if (i % 2 == 0) {
  					r = 29.0f / 255.0f;
  					g = 72.0f / 255.0f;
  					b = 29.0f / 255.0f;
  				} else {
  					r = 21.0f / 255.0f;
  					g = 52.0f / 255.0f;
  					b = 21.0f / 255.0f;
  				}
  
  				float percentage = (float)i / 24.0F;
  				double yMult = endY < startY ? percentage*Math.sqrt(percentage) : percentage * percentage;
  
  				buffer.pos(x + diffX * (double)percentage + 0.0D, y + diffY * (double)(yMult + percentage) * 0.5D, z + diffZ * (double)percentage).color(r, g, b, 1).endVertex();
  				buffer.pos(x + diffX * (double)percentage + 0.025D, y + diffY * (double)(yMult + percentage) * 0.5D + 0.025D, z + diffZ * (double)percentage).color(r, g, b, 1).endVertex();
  			}
  			tessellator.draw();
  
  			buffer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_COLOR);
  			for (int i = 0; i <= 24; ++i) {
  				float r;
  				float g;
  				float b;
  
  				if (i % 2 == 0) {
  					r = 29.0f / 255.0f;
  					g = 72.0f / 255.0f;
  					b = 29.0f / 255.0f;
  				} else {
  					r = 21.0f / 255.0f;
  					g = 52.0f / 255.0f;
  					b = 21.0f / 255.0f;
  				}
  
  				float percentage = (float)i / 24.0F;
  				double yMult = endY < startY ? percentage*Math.sqrt(percentage) : percentage * percentage;
  
  				buffer.pos(x + diffX * (double)percentage + 0.0D, y + diffY * (double)(yMult + percentage) * 0.5D + 0.025D, z + diffZ * (double)percentage).color(r, g, b, 1).endVertex();
  				buffer.pos(x + diffX * (double)percentage + 0.025D, y + diffY * (double)(yMult + percentage) * 0.5D, z + diffZ * (double)percentage + 0.025D).color(r, g, b, 1).endVertex();
  			}
  			tessellator.draw();
  
  			GlStateManager.enableLighting();
  			GlStateManager.enableTexture2D();
  			GlStateManager.enableCull();
  		}
  	}
  
  	@Override
  	protected ResourceLocation getEntityTexture(EntityRopeNode entity) {
  		return TEXTURE;
  	}
  }
  