package blusunrize.immersiveengineering.client.render; import blusunrize.immersiveengineering.api.IEProperties; import blusunrize.immersiveengineering.api.crafting.BlueprintCraftingRecipe; import blusunrize.immersiveengineering.api.crafting.IMultiblockRecipe; import blusunrize.immersiveengineering.client.ClientUtils; import blusunrize.immersiveengineering.common.IEContent; import blusunrize.immersiveengineering.common.blocks.metal.TileEntityAutoWorkbench; import blusunrize.immersiveengineering.common.blocks.metal.TileEntityMultiblockMetal.MultiblockProcess; import blusunrize.immersiveengineering.common.blocks.metal.TileEntityMultiblockMetal.MultiblockProcessInWorld; import blusunrize.immersiveengineering.common.util.ItemNBTHelper; import com.google.common.collect.HashMultimap; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.IResource; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.client.model.obj.OBJModel.OBJState; import net.minecraftforge.common.property.IExtendedBlockState; import net.minecraftforge.common.property.Properties; import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.opengl.GL11; import java.awt.*; import java.awt.image.BufferedImage; import java.util.*; import java.util.List; public class TileRenderAutoWorkbench extends TileEntitySpecialRenderer<TileEntityAutoWorkbench> { @Override public void renderTileEntityAt(TileEntityAutoWorkbench te, double x, double y, double z, float partialTicks, int destroyStage) { if(!te.formed || te.isDummy() || !te.getWorld().isBlockLoaded(te.getPos(), false)) return; //Grab model + correct eextended state final BlockRendererDispatcher blockRenderer = Minecraft.getMinecraft().getBlockRendererDispatcher(); BlockPos blockPos = te.getPos(); IBlockState state = getWorld().getBlockState(blockPos); if(state.getBlock() != IEContent.blockMetalMultiblock) return; state = state.getBlock().getActualState(state, getWorld(), blockPos); state = state.withProperty(IEProperties.DYNAMICRENDER, true); IBakedModel model = blockRenderer.getBlockModelShapes().getModelForState(state); //Initialize Tesselator and VertexBuffer Tessellator tessellator = Tessellator.getInstance(); VertexBuffer worldRenderer = tessellator.getBuffer(); //Outer GL Wrapping, initial translation GlStateManager.pushMatrix(); GlStateManager.translate(x+.5, y+.5, z+.5); if(te.mirrored) GlStateManager.scale(te.facing.getFrontOffsetX()==0?-1:1,1,te.facing.getFrontOffsetZ()==0?-1:1); //Item Displacement float[][] itemDisplays = new float[te.processQueue.size()][]; //Animations float drill = 0; float lift = 0; float press = 0; float liftPress = 0; for(int i=0; i<itemDisplays.length; i++) { MultiblockProcess<IMultiblockRecipe> process = te.processQueue.get(i); if(process==null || process.processTick<=0 || process.processTick==process.maxTicks) continue; //+partialTicks float processTimer = ((float)process.processTick)/process.maxTicks * 180; if(processTimer<=9) continue; float itemX = -1; float itemY = -.34375f; float itemZ = -.9375f; float itemAngle = 90f; if(processTimer <= 24)//slide { itemAngle = 67.5f; if(processTimer <= 19) { itemZ += .25+(19-processTimer)/10f*.5f; itemY += .25+(19-processTimer)/10f*.21875f; } else { itemZ += (24-processTimer)/5f*.25f; itemY += (24-processTimer)/5f*.25f; } } else if(processTimer <= 40) { itemX += (processTimer-24)/16f; } else if(processTimer <= 100) { itemX += 1; float drillStep = 0; if(processTimer <= 60) { lift = (processTimer-40)/20f*.3125f; drillStep = 4+(60-processTimer)*4; } else if(processTimer <= 80) { lift = .3125f; drillStep = 4; } else { lift = (100-processTimer)/20f*.3125f; drillStep = 4+(processTimer-80)*4; } if(drillStep > 0) drill = processTimer%drillStep/drillStep*360; itemY += Math.max(0, lift-.0625); } else if(processTimer <= 116) { itemX += 1; itemZ += (processTimer-100)/16f; } else if(processTimer <= 132) { itemX += 1+(processTimer-116)/16f; itemZ += 1; } else if(processTimer <= 172) { itemX += 2; itemZ += 1; if(processTimer <= 142) press = (processTimer-132)/10f; else if(processTimer <= 162) press = 1; else press = (172-processTimer)/10f; liftPress = press*.0625f; itemY += liftPress; } else if(processTimer <= 180) { itemX += 2+(processTimer-172)/16f; itemZ += 1; } itemDisplays[i] = new float[]{processTimer,itemX,itemY,itemZ,itemAngle}; } ClientUtils.bindAtlas(); GlStateManager.pushMatrix(); ItemStack blueprintStack = te.inventory[0]; if(blueprintStack!=null) renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "blueprint"); GlStateManager.translate(0, lift, 0); renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "lift"); GlStateManager.translate(0, -lift, 0); EnumFacing f = te.getFacing(); float tx = f == EnumFacing.WEST ? -.9375f : f == EnumFacing.EAST ? .9375f : 0; float tz = f == EnumFacing.NORTH ? -.9375f : f == EnumFacing.SOUTH ? .9375f : 0; GlStateManager.translate(tx, 0, tz); GlStateManager.rotate(drill, 0, 1, 0); renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "drill"); GlStateManager.rotate(-drill, 0, 1, 0); GlStateManager.translate(-tx, 0, -tz); tx = f == EnumFacing.WEST ? -.59375f : f == EnumFacing.EAST ? .59375f : 0; tz = f == EnumFacing.NORTH ? -.59375f : f == EnumFacing.SOUTH ? .59375f : 0; GlStateManager.translate(tx, -.21875, tz); GlStateManager.rotate(press * 90, -f.getFrontOffsetZ(), 0, f.getFrontOffsetX()); renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "press"); GlStateManager.rotate(-press * 90, -f.getFrontOffsetZ(), 0, f.getFrontOffsetX()); GlStateManager.translate(-tx, .21875, -tz); GlStateManager.translate(0, liftPress, 0); renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "pressLift"); GlStateManager.translate(0, -liftPress, 0); RenderHelper.enableStandardItemLighting(); GlStateManager.popMatrix(); switch(f) { case NORTH: break; case SOUTH: GlStateManager.rotate(180, 0, 1, 0); break; case WEST: GlStateManager.rotate(90, 0, 1, 0); break; case EAST: GlStateManager.rotate(-90, 0, 1, 0); break; } //DRAW ITEMS HERE for(int i=0; i<itemDisplays.length; i++) if(itemDisplays[i]!=null) { MultiblockProcess<IMultiblockRecipe> process = te.processQueue.get(i); if(process==null || !(process instanceof MultiblockProcessInWorld)) continue; float scale = .3125f; List<ItemStack> dList = ((MultiblockProcessInWorld)process).getDisplayItem(); if(!dList.isEmpty()) if(dList.size() < 2) { GlStateManager.translate(itemDisplays[i][1], itemDisplays[i][2], itemDisplays[i][3]); GlStateManager.rotate(itemDisplays[i][4], 1, 0, 0); GlStateManager.scale(scale, scale, .5f); ClientUtils.mc().getRenderItem().renderItem(dList.get(0), ItemCameraTransforms.TransformType.FIXED); GlStateManager.scale(1/scale, 1/scale, 2); GlStateManager.rotate(-itemDisplays[i][4], 1, 0, 0); GlStateManager.translate(-itemDisplays[i][1], -itemDisplays[i][2], -itemDisplays[i][3]); } else { int size = dList.size(); int lines = (int)Math.ceil(size/2f); float spacer = (lines-1)*.234375f; for(int d = 0; d < size; d++) { float oX = (size > 2?-.3125f: 0)+(lines-d/2)*.0625f+d%2*.3125f; float oZ = -spacer/2f+d/2*.234375f; float oY = 0; float localItemX = itemDisplays[i][1]+oX; float localItemY = itemDisplays[i][2]+oY; float localItemZ = itemDisplays[i][3]+oZ; float subProcess = itemDisplays[i][0]-d/2*4; float localAngle = itemDisplays[i][4]; if(subProcess <= 24)//slide { localAngle = 67.5f; if(subProcess <= 19) { localItemZ = -1+.25f+(19-subProcess)/10f*.5f; localItemY = -.34375f+.25f+(19-subProcess)/10f*.21875f; } else { localItemZ = -1+(oZ-(24-subProcess)/5f*oZ); localItemY = -.34375f+(24-subProcess)/5f*.25f; } } GlStateManager.translate(localItemX, localItemY, localItemZ); GlStateManager.rotate(localAngle, 1, 0, 0); GlStateManager.scale(scale, scale, .5f); ClientUtils.mc().getRenderItem().renderItem(dList.get(d), ItemCameraTransforms.TransformType.FIXED); GlStateManager.scale(1/scale, 1/scale, 2); GlStateManager.rotate(-localAngle, 1, 0, 0); GlStateManager.translate(-localItemX, -localItemY, -localItemZ); } } } //Blueprint double playerDistanceSq = ClientUtils.mc().thePlayer.getDistanceSq(blockPos); if(blueprintStack!=null && playerDistanceSq<1000) { BlueprintCraftingRecipe[] recipes = BlueprintCraftingRecipe.findRecipes(ItemNBTHelper.getString(blueprintStack,"blueprint")); BlueprintCraftingRecipe recipe = (te.selectedRecipe<0||te.selectedRecipe>=recipes.length)?null:recipes[te.selectedRecipe]; BlueprintLines blueprint = recipe==null?null:getBlueprintDrawable(recipe, te.getWorld()); if(blueprint!=null) { //Width depends on distance float lineWidth = playerDistanceSq < 6?3: playerDistanceSq < 25?2: playerDistanceSq < 40?1: .5f; GlStateManager.translate(-.195, .125, .97); GlStateManager.rotate(-45, 1, 0, 0); GlStateManager.disableCull(); GlStateManager.disableTexture2D(); GlStateManager.enableBlend(); float scale = .0375f/(blueprint.textureScale/16f); GlStateManager.scale(scale, -scale, scale); GlStateManager.color(1, 1, 1, 1); blueprint.draw(lineWidth); GlStateManager.scale(1/scale, -1/scale, 1/scale); GlStateManager.enableAlpha(); GlStateManager.enableTexture2D(); GlStateManager.enableCull(); } } GlStateManager.popMatrix(); } public static void renderModelPart(final BlockRendererDispatcher blockRenderer, Tessellator tessellator, VertexBuffer worldRenderer, World world, IBlockState state, IBakedModel model, BlockPos pos, String... parts) { if(state instanceof IExtendedBlockState) state = ((IExtendedBlockState)state).withProperty(Properties.AnimationProperty, new OBJState(Arrays.asList(parts), true)); RenderHelper.disableStandardItemLighting(); GlStateManager.blendFunc(770, 771); GlStateManager.enableBlend(); GlStateManager.disableCull(); if(Minecraft.isAmbientOcclusionEnabled()) GlStateManager.shadeModel(7425); else GlStateManager.shadeModel(7424); worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK); worldRenderer.setTranslation(-.5 - pos.getX(), -.5 - pos.getY(), -.5 - pos.getZ()); worldRenderer.color(255, 255, 255, 255); blockRenderer.getBlockModelRenderer().renderModel(world, model, state, pos, worldRenderer, true); worldRenderer.setTranslation(0.0D, 0.0D, 0.0D); tessellator.draw(); } public static HashMap<BlueprintCraftingRecipe, BlueprintLines> blueprintCache = new HashMap<BlueprintCraftingRecipe, BlueprintLines>(); public static BlueprintLines getBlueprintDrawable(BlueprintCraftingRecipe recipe, World world) { if(recipe==null) return null; BlueprintLines blueprint = blueprintCache.get(recipe); if(blueprint==null) { blueprint = getBlueprintDrawable(recipe.output, world); blueprintCache.put(recipe, blueprint); } return blueprint; } public static BlueprintLines getBlueprintDrawable(ItemStack stack, World world) { if(stack==null) return null; EntityPlayer player = ClientUtils.mc().thePlayer; ArrayList<BufferedImage> images = new ArrayList<>(); try { IBakedModel ibakedmodel = ClientUtils.mc().getRenderItem().getItemModelWithOverrides(stack, world, player); HashSet<String> textures = new HashSet(); Collection<BakedQuad> quads = ibakedmodel.getQuads(null, null, 0); for(BakedQuad quad : quads) if(quad != null && quad.getSprite() != null) textures.add(quad.getSprite().getIconName()); for(String s : textures) { ResourceLocation rl = new ResourceLocation(s); rl = new ResourceLocation(rl.getResourceDomain(), String.format("%s/%s%s", "textures", rl.getResourcePath(), ".png")); IResource resource = ClientUtils.mc().getResourceManager().getResource(rl); BufferedImage bufferedImage = TextureUtil.readBufferedImage(resource.getInputStream()); if(bufferedImage != null) images.add(bufferedImage); } } catch(Exception e) { } if(images.isEmpty()) return null; ArrayList<Pair<TexturePoint, TexturePoint>> lines = new ArrayList(); HashSet testSet = new HashSet(); HashMultimap<Integer, TexturePoint> area = HashMultimap.create(); int wMax = 0; for(BufferedImage bufferedImage : images) { Set<Pair<TexturePoint, TexturePoint>> temp_lines = new HashSet<>(); int w = bufferedImage.getWidth(); int h = bufferedImage.getHeight(); if(h>w) h=w; if(w>wMax) wMax = w; for(int hh=0; hh<h; hh++) for(int ww=0; ww<w; ww++) { int argb = bufferedImage.getRGB(ww, hh); float r = (argb>>16&255)/255f; float g = (argb>>8&255)/255f; float b = (argb&255)/255f; float intesity = (r+b+g) / 3f; int alpha = (argb>>24)&255; if(alpha > 0) { boolean added = false; //Check colour sets for similar colour to shade it later TexturePoint tp =new TexturePoint(ww, hh, w); if(!testSet.contains(tp)) { for(Integer key : area.keySet()) { for(Point p : area.get(key)) { int pColour = bufferedImage.getRGB(p.x, p.y); float dR = (r-(pColour>>16&255)/255f); float dG = (g-(pColour>>8&255)/255f); float dB = (b-(pColour&255)/255f); double delta = Math.sqrt(dR*dR+dG*dG+dB*dB); if(delta < .25) { area.put(key, tp); added = true; break; } } if(added) break; } if(!added) area.put(argb, tp); testSet.add(tp); } //Compare to direct neighbour for(int i=0; i<4; i++) { int xx = (i==0?-1: i==1?1: 0); int yy = (i==2?-1: i==3?1: 0); int u = ww + xx; int v = hh + yy; int neighbour = 0; float delta = 1; boolean notTransparent = false; if(u >= 0 && u < w && v >= 0 && v < h) { neighbour = bufferedImage.getRGB(u, v); notTransparent = ((neighbour >> 24) & 255) > 0; if(notTransparent) { float neighbourIntesity = ((neighbour >> 16 & 255) + (neighbour >> 8 & 255) + (neighbour & 255)) / 765f; float intesityDelta = Math.max(0, Math.min(1, Math.abs(intesity - neighbourIntesity))); float rDelta = Math.max(0, Math.min(1, Math.abs(r - (neighbour >> 16 & 255) / 255f))); float gDelta = Math.max(0, Math.min(1, Math.abs(g - (neighbour >> 8 & 255) / 255f))); float bDelta = Math.max(0, Math.min(1, Math.abs(b - (neighbour & 255) / 255f))); delta = Math.max(intesityDelta, Math.max(rDelta, Math.max(gDelta, bDelta))); delta = delta < .25 ? 0 : delta > .4 ? 1 : delta; } } if(delta > 0) { Pair<TexturePoint,TexturePoint> l = Pair.of(new TexturePoint(ww+(i==0?0:i==1?1:0), hh+(i==2?0:i==3?1:0), w), new TexturePoint(ww+(i==0?0:i==1?1:1), hh+(i==2?0:i==3?1:1), w)); temp_lines.add(l); } } } } lines.addAll(temp_lines); } ArrayList<Integer> lumiSort = new ArrayList<>(area.keySet()); Collections.sort(lumiSort, (rgb1, rgb2) -> Double.compare(getLuminance(rgb1),getLuminance(rgb2))); HashMultimap<ShadeStyle, Point> complete_areaMap = HashMultimap.create(); int lineNumber = 2; int lineStyle = 0; for(Integer i : lumiSort) { complete_areaMap.putAll(new ShadeStyle(lineNumber,lineStyle), area.get(i)); ++lineStyle; lineStyle %= 3; if(lineStyle==0) lineNumber+=1; } Set<Pair<Point, Point>> complete_lines = new HashSet<>(); for(Pair<TexturePoint,TexturePoint> line : lines) { TexturePoint p1 = line.getKey(); TexturePoint p2 = line.getValue(); complete_lines.add(Pair.of(new Point((int)(p1.x/(float)p1.scale*wMax), (int)(p1.y/(float)p1.scale*wMax)), new Point((int)(p2.x/(float)p2.scale*wMax), (int)(p2.y/(float)p2.scale*wMax)))); } return new BlueprintLines(wMax, complete_lines, complete_areaMap); } public static class BlueprintLines { final int textureScale; final Set<Pair<Point, Point>> lines; final HashMultimap<ShadeStyle, Point> areas; BlueprintLines(int textureScale, Set<Pair<Point, Point>> lines, HashMultimap<ShadeStyle, Point> areas) { this.textureScale = textureScale; this.lines = lines; this.areas = areas; } public int getTextureScale() { return textureScale; } public void draw(float lineWidth) { //Draw edges GlStateManager.glLineWidth(lineWidth); GlStateManager.glBegin(GL11.GL_LINES); for(Pair<Point, Point> line : lines) { GlStateManager.glVertex3f(line.getKey().x, line.getKey().y, 0); GlStateManager.glVertex3f(line.getValue().x, line.getValue().y, 0); } GlStateManager.glEnd(); if(lineWidth >= 1)//Draw shading if player is close enough { GlStateManager.glLineWidth(lineWidth*.66f); GL11.glPointSize(4); GlStateManager.glBegin(GL11.GL_LINES); for(ShadeStyle style : areas.keySet()) for(Point pixel : areas.get(style)) style.drawShading(pixel); GlStateManager.glEnd(); } } } private static class ShadeStyle { int stripeAmount = 1; int stripeDirection = 0; ShadeStyle(int stripeAmount, int stripeDirection) { this.stripeAmount = stripeAmount; this.stripeDirection = stripeDirection; } void drawShading(Point pixel) { float step = 1/(float)stripeAmount; float offset = step/2; if(stripeDirection>1) { int perSide = stripeAmount/2+(stripeAmount%2==1?1:0); step = 1/(float)(perSide); offset = stripeAmount%2==1?step:step/2; } for(int i=0; i<stripeAmount; i++) if(stripeDirection==0)//vertical { GlStateManager.glVertex3f(pixel.x+offset+step*i, pixel.y, 0); GlStateManager.glVertex3f(pixel.x+offset+step*i, pixel.y+1, 0); } else if(stripeDirection==1)//horizontal { GlStateManager.glVertex3f(pixel.x, pixel.y+offset+step*i, 0); GlStateManager.glVertex3f(pixel.x+1, pixel.y+offset+step*i, 0); } else if(stripeDirection==2)//diagonal { if(i==stripeAmount-1 && stripeAmount%2==1) { GlStateManager.glVertex3f(pixel.x, pixel.y + 1, 0); GlStateManager.glVertex3f(pixel.x + 1, pixel.y, 0); } else if(i%2==0) { GlStateManager.glVertex3f(pixel.x, pixel.y+offset+step*(i/2), 0); GlStateManager.glVertex3f(pixel.x+offset+step*(i/2), pixel.y, 0); } else { GlStateManager.glVertex3f(pixel.x+1-offset-step*(i/2), pixel.y+1, 0); GlStateManager.glVertex3f(pixel.x+1, pixel.y+1-offset-step*(i/2), 0); } } } } private static class TexturePoint extends Point { final int scale; public TexturePoint(int x, int y, int scale) { super(x,y); this.scale = scale; } @Override public int hashCode() { return 31*(31*x+y)+scale; } } private static double getLuminance(int rgb) { return Math.sqrt(.241*(rgb>>16&255) + .691*(rgb>>8&255) + .068*(rgb&255)); } }