Java tutorial
/* * BluSunrize * Copyright (c) 2017 * * This code is licensed under "Blu's License of Common Sense" * Details can be found in the license file in the root folder of this project */ 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.List; import java.util.*; public class TileRenderAutoWorkbench extends TileEntitySpecialRenderer<TileEntityAutoWorkbench> { @Override public void render(TileEntityAutoWorkbench te, double x, double y, double z, float partialTicks, int destroyStage, float alpha) { 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 BufferBuilder Tessellator tessellator = Tessellator.getInstance(); BufferBuilder 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.getXOffset() == 0 ? -1 : 1, 1, te.facing.getZOffset() == 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.get(0); if (!blueprintStack.isEmpty()) 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.getZOffset(), 0, f.getXOffset()); renderModelPart(blockRenderer, tessellator, worldRenderer, te.getWorld(), state, model, blockPos, "press"); GlStateManager.rotate(-press * 90, -f.getZOffset(), 0, f.getXOffset()); 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().player.getDistanceSq(blockPos); if (!blueprintStack.isEmpty() && 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, BufferBuilder 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.isEmpty()) return null; EntityPlayer player = ClientUtils.mc().player; 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.getNamespace(), String.format("%s/%s%s", "textures", rl.getPath(), ".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 (TexturePoint p : area.get(key)) { float mod = w / (float) p.scale; int pColour = bufferedImage.getRGB((int) (p.x * mod), (int) (p.y * mod)); 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)); } }