public static void setup(TextureAtlasSprite icon) { if (!fboSupported) { return; } String name = IconAPI.getIconName(icon).replaceFirst("^minecraft:items/", ""); if (icon instanceof TextureClock && name.equals("compass")) { // 1.5 bug name = "clock"; } if ("compass".equals(name)) { if (!enableCompass) { return; } } else if ("clock".equals(name)) { if (!enableClock) { return; } } else { logger.warning("ignoring custom animation for %s not compass or clock", name); return; } ResourceLocation resource = TexturePackAPI.newMCPatcherResourceLocation( "/misc/" + name + ".properties", "dial/" + name + ".properties"); if (TexturePackAPI.hasResource(resource)) { logger.fine("found custom %s (%s)", name, resource); setupInfo.put(icon, resource); active = true; } }
static { logger.config("fbo: supported=%s", fboSupported); logger.config("GL13: supported=%s, enabled=%s", gl13Supported, useGL13); int bits = GL11.GL_VIEWPORT_BIT | GL11.GL_SCISSOR_BIT | GL11.GL_DEPTH_BITS | GL11.GL_LIGHTING_BIT; if (useGL13) { bits |= GL13.GL_MULTISAMPLE_BIT; } glAttributes = bits; GL11.glNewList(drawList, GL11.GL_COMPILE); drawBox(); GL11.glEndList(); }
private void writeCustomImage() { try { BufferedImage image = new BufferedImage(width, outputFrames * height, BufferedImage.TYPE_INT_ARGB); IntBuffer intBuffer = scratchBuffer.asIntBuffer(); int[] argb = new int[width * height]; File path = MCPatcherUtils.getGamePath("custom_" + name + ".png"); logger.info("generating %d %s frames", outputFrames, name); for (int i = 0; i < outputFrames; i++) { renderToItems(i * (360.0 / outputFrames)); itemsFBO.read(scratchBuffer); intBuffer.position(0); for (int j = 0; j < argb.length; j++) { switch (MipmapHelper.TEX_FORMAT) { case GL12.GL_BGRA: int bgra = intBuffer.get(j); argb[j] = (bgra << 24) | ((bgra & 0xff00) << 8) | ((bgra & 0xff0000) >> 8) | (bgra >>> 24); break; default: if (i == 0 && j == 0) { logger.warning( "unhandled texture format %d, color channels may be incorrect", MipmapHelper.TEX_FORMAT); } // fall through case GL11.GL_RGBA: argb[j] = Integer.rotateRight(intBuffer.get(j), 8); break; } } image.setRGB(0, i * height, width, height, argb, 0, width); } ImageIO.write(image, "png", path); logger.info("wrote %dx%d %s", image.getWidth(), image.getHeight(), path.getPath()); } catch (Throwable e) { e.printStackTrace(); } }
static void clearAll() { logger.finer("FancyDial.clearAll"); if (initialized) { active = false; setupInfo.clear(); } for (FancyDial instance : instances.values()) { if (instance != null) { instance.finish(); } } instances.clear(); initialized = true; }
void registerAnimation(List<TextureAtlasSprite> animations) { if (animations.contains(icon)) { return; } animations.add(icon); if (icon.animationFrames == null) { icon.animationFrames = new ArrayList<int[]>(); } if (icon.animationFrames.isEmpty()) { int[] dummyRGB = new int[width * height]; Arrays.fill(dummyRGB, 0xffff00ff); icon.animationFrames.add(dummyRGB); } logger.fine("registered %s animation", name); }
public static boolean update(TextureAtlasSprite icon, boolean itemFrameRenderer) { if (!initialized) { logger.finer("deferring %s update until initialization finishes", IconAPI.getIconName(icon)); return false; } if (!active) { return false; } int oldFB = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT); if (oldFB != 0 && warnCount < 10) { logger.finer( "rendering %s while non-default framebuffer %d is active", IconAPI.getIconName(icon), oldFB); warnCount++; } int oldTexture = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); try { FancyDial instance = getInstance(icon); return instance != null && instance.render(itemFrameRenderer); } finally { EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFB); GLAPI.glBindTexture(oldTexture); } }
private FancyDial(TextureAtlasSprite icon, PropertiesFile properties) { this.icon = icon; this.properties = properties; name = IconAPI.getIconName(icon); x0 = IconAPI.getIconX0(icon); y0 = IconAPI.getIconY0(icon); width = IconAPI.getIconWidth(icon); height = IconAPI.getIconHeight(icon); scratchBuffer = ByteBuffer.allocateDirect(4 * width * height); int itemsTexture = TexturePackAPI.getTextureIfLoaded(TexturePackAPI.ITEMS_PNG); if (itemsTexture < 0) { logger.severe("could not get items texture"); return; } itemsFBO = new FBO(itemsTexture, x0, y0, width, height); if (useScratchTexture) { logger.fine("rendering %s to %dx%d scratch texture", name, width, height); for (int i = 0; i < scratchFBO.length; i++) { scratchFBO[i] = new FBO(width, height); } } else { logger.fine("rendering %s directly to atlas", name); } boolean debug = false; for (int i = 0; ; i++) { Layer layer = newLayer(properties, "." + i); if (layer == null) { if (i > 0) { break; } continue; } layers.add(layer); debug |= layer.debug; logger.fine(" new %s", layer); } keyboard = new InputHandler(name, debug); if (layers.size() < 2) { logger.error("custom %s needs at least two layers defined", name); return; } outputFrames = properties.getInt("outputFrames", 0); int glError = GL11.glGetError(); if (glError != 0) { logger.severe("%s during %s setup", GLU.gluErrorString(glError), name); return; } ok = true; }
public class CITUtils18 { private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.CUSTOM_ITEM_TEXTURES, "CIT"); private static ItemStack currentItem; private static CTMUtils18 ctm; private static ItemOverride itemOverride; private static boolean renderingEnchantment; public static void preRender(ItemStack itemStack) { if (renderingEnchantment) { // rendering custom enchantment -- keep current state } else if (itemStack == null) { clear(); } else if (itemStack.getItem() instanceof ItemBlock) { clear(); ctm = CTMUtils18.getInstance(); ctm.preRenderHeld( null, ((ItemBlock) itemStack.getItem()).getBlock(), itemStack.getItemDamage()); } else { ctm = null; currentItem = itemStack; itemOverride = CITUtils.findItemOverride(itemStack); } } public static void postRender() { if (ctm != null) { ctm.clear(); } clear(); } public static ModelFace getModelFace(ModelFace origFace) { if (renderingEnchantment) { return FaceInfo.getFaceInfo(origFace).getNonAtlasFace(); } else if (ctm != null) { int face = FaceInfo.getFaceInfo(origFace).getEffectiveFace(); ctm.setDirection(face < 0 ? null : Direction.values()[face]); return ctm.getModelFace(origFace); } else if (itemOverride == null) { return origFace; } else { FaceInfo faceInfo = FaceInfo.getFaceInfo(origFace); TextureAtlasSprite newIcon = (TextureAtlasSprite) itemOverride.getReplacementIcon(faceInfo.getSprite()); return faceInfo.getAltFace(newIcon); } } public static boolean renderEnchantments3D(RenderItemCustom renderItem, IModel model) { if (currentItem != null) { EnchantmentList enchantments = CITUtils.findEnchantments(currentItem); if (!enchantments.isEmpty()) { renderingEnchantment = true; Enchantment.beginOuter3D(); for (int i = 0; i < enchantments.size(); i++) { Enchantment enchantment = enchantments.getEnchantment(i); float intensity = enchantments.getIntensity(i); if (intensity > 0.0f && enchantment.bindTexture(null)) { enchantment.begin(intensity); renderItem.renderItem1(model, -1, null); enchantment.end(); } } Enchantment.endOuter3D(); TexturePackAPI.bindTexture(TexturePackAPI.ITEMS_PNG); renderingEnchantment = false; } } return !CITUtils.useGlint; } public static ResourceLocation getArmorTexture( ResourceLocation origTexture, ItemStack itemStack, int slot) { ArmorOverride override = CITUtils.findArmorOverride(itemStack); if (override == null) { return origTexture; } else { return override.getReplacementTexture(origTexture); } } public static boolean renderArmorEnchantments( EntityLivingBase entity, ModelBase model, ItemStack itemStack, int slot, float f1, float f2, float f3, float f4, float f5, float f6) { EnchantmentList enchantments = CITUtils.findEnchantments(itemStack); if (!enchantments.isEmpty()) { Enchantment.beginOuter3D(); for (int i = 0; i < enchantments.size(); i++) { Enchantment enchantment = enchantments.getEnchantment(i); float intensity = enchantments.getIntensity(i); if (intensity > 0.0f && enchantment.bindTexture(null)) { enchantment.begin(intensity); model.render(entity, f1, f2, f3, f4, f5, f6); enchantment.end(); } } Enchantment.endOuter3D(); } return !CITUtils.useGlint; } static void clear() { currentItem = null; ctm = null; itemOverride = null; renderingEnchantment = false; } }
public class ColorizeBlock18 { private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.CUSTOM_COLORS); private static final ResourceLocation COLOR_PROPERTIES = TexturePackAPI.newMCPatcherResourceLocation("color.properties"); private static Block grassBlock; private static Block mycelBlock; private final CTMUtils18 ctm; private boolean useCM; private IColorMap colorMap; private boolean isSmooth; private final float[][] vertexColors = new float[4][3]; public final float[] vertexColor = new float[3]; private static final int[][][] FACE_VERTICES = new int[][][] { // bottom face (y=0) { {0, 0, 1}, // top left {0, 0, 0}, // bottom left {1, 0, 0}, // bottom right {1, 0, 1}, // top right }, // top face (y=1) { {0, 1, 0}, {0, 1, 1}, {1, 1, 1}, {1, 1, 0}, }, // north face (z=0) { {1, 1, 0}, {1, 0, 0}, {0, 0, 0}, {0, 1, 0}, }, // south face (z=1) { {0, 1, 1}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}, }, // west face (x=0) { {0, 1, 0}, {0, 0, 0}, {0, 0, 1}, {0, 1, 1}, }, // east face (x=1) { {1, 1, 1}, {1, 0, 1}, {1, 0, 0}, {1, 1, 0}, } }; private static final int[][][] FACE_VERTICES_WATER = new int[][][] { // bottom face (y=0) { {0, 0, 1}, // top left {0, 0, 0}, // bottom left {1, 0, 0}, // bottom right {1, 0, 1}, // top right }, // top face (y=1) { {0, 1, 0}, {0, 1, 1}, {1, 1, 1}, {1, 1, 0}, }, // north face (z=0) { {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0}, }, // south face (z=1) { {1, 1, 1}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}, }, // west face (x=0) { {0, 1, 1}, {0, 1, 0}, {0, 0, 0}, {0, 0, 1}, }, // east face (x=1) { {1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0}, } }; static { TexturePackChangeHandler.register( new TexturePackChangeHandler(MCPatcherUtils.CUSTOM_COLORS, 2) { @Override public void beforeChange() { reset(); } @Override public void afterChange() { PropertiesFile properties = PropertiesFile.getNonNull(logger, COLOR_PROPERTIES); ColorizeBlock.reloadAll(properties); } }); } public static void reset() { try { grassBlock = BlockAPI.getFixedBlock("minecraft:grass"); mycelBlock = BlockAPI.getFixedBlock("minecraft:mycelium"); ColorizeBlock.reset(); } catch (Throwable e) { e.printStackTrace(); } } public ColorizeBlock18(CTMUtils18 ctm) { this.ctm = ctm; } public void preRender( IBlockAccess blockAccess, IModel model, IBlockState blockState, Position position, Block block, boolean useAO) { colorMap = null; useCM = RenderPassAPI.instance.useColorMultiplierThisPass(block); if (useCM) { List<BlockStateMatcher> maps = ColorizeBlock.findColorMaps(block); if (maps != null) { for (BlockStateMatcher matcher : maps) { if (matcher.matchBlockState(blockState)) { colorMap = ColorizeBlock.getThreadLocal(matcher); break; } } } } isSmooth = false; } public void preRenderHeld(IModel model, IBlockState blockState, Block block) { colorMap = null; isSmooth = false; List<BlockStateMatcher> maps = ColorizeBlock.findColorMaps(block); if (maps != null) { for (BlockStateMatcher matcher : maps) { if (matcher.matchBlockState(blockState)) { colorMap = ColorizeBlock.getThreadLocal(matcher); break; } } } } public void clear() { colorMap = null; isSmooth = false; } public void setDirection(Direction direction) { setDirection(direction, FACE_VERTICES); } public void setDirectionWater(Direction direction) { setDirection(direction, FACE_VERTICES_WATER); } private void setDirection(Direction direction, int[][][] faceVertices) { if (ColorizeBlock.enableSmoothBiomes && direction != null && colorMap != null && ctm.isInWorld()) { isSmooth = true; int[][] offsets = faceVertices[direction.ordinal()]; computeVertexColor(offsets[0], vertexColors[0]); computeVertexColor(offsets[1], vertexColors[1]); computeVertexColor(offsets[2], vertexColors[2]); computeVertexColor(offsets[3], vertexColors[3]); } else { isSmooth = false; } } private void computeVertexColor(int[] offsets, float[] color) { int i = ctm.getI() + offsets[0]; int j = ctm.getJ() + offsets[1]; int k = ctm.getK() + offsets[2]; if (ColorizeBlock.enableTestColorSmoothing) { int rgb = 0; if (i % 2 == 0) { rgb |= 0xff0000; } if (j % 2 == 0) { rgb |= 0x00ff00; } if (k % 2 == 0) { rgb |= 0x0000ff; } ColorUtils.intToFloat3(rgb, color); } else { float[] tmp = colorMap.getColorMultiplierF(ctm.getBlockAccess(), i, j, k); color[0] = tmp[0]; color[1] = tmp[1]; color[2] = tmp[2]; } } public boolean useColormap(ModelFace face) { return useCM && (face.useColormap() || (colorMap != null && ctm.getBlock() != grassBlock && ctm.getBlock() != mycelBlock)); } public int colorMultiplier(int color) { if (colorMap == null) { return color; } else if (ctm.isInWorld()) { return colorMap.getColorMultiplier(ctm.getBlockAccess(), ctm.getI(), ctm.getJ(), ctm.getK()); } else { return colorMap.getColorMultiplier(); } } public float getVertexColor(float color, int vertex, int channel) { if (isSmooth) { return vertexColors[vertex][channel]; } else { return color; } } public void applyVertexColor(Tessellator tessellator, float base, int vertex) { if (isSmooth) { float[] rgb = vertexColors[vertex]; TessellatorAPI.setColorOpaque_F(tessellator, base * rgb[0], base * rgb[1], base * rgb[2]); } } public float applyVertexColor(float base, int vertex, float r, float g, float b) { if (isSmooth) { float[] rgb = vertexColors[vertex]; vertexColor[0] = base * rgb[0]; vertexColor[1] = base * rgb[1]; vertexColor[2] = base * rgb[2]; } else { vertexColor[0] = r; vertexColor[1] = g; vertexColor[2] = b; } return vertexColor[0]; } public float getVertexColor(int index) { return vertexColor[index]; } public int getParticleColor( IBlockAccess blockAccess, IBlockState blockState, Position position, int defaultColor) { return getColorMultiplier(blockAccess, blockState, position, defaultColor); } // public static methods requested by MamiyaOtaru for VoxelMap public static int getColorMultiplier( IBlockAccess blockAccess, IBlockState blockState, Position position, int defaultColor) { List<BlockStateMatcher> maps = ColorizeBlock.findColorMaps(blockState.getBlock()); if (maps != null) { for (BlockStateMatcher matcher : maps) { if (matcher.matchBlockState(blockState)) { IColorMap colorMap = ColorizeBlock.getThreadLocal(matcher); return colorMap.getColorMultiplier( blockAccess, position.getI(), position.getJ(), position.getK()); } } } return defaultColor; } public static int getColorMultiplier( IBlockAccess blockAccess, IBlockState blockState, Position position) { return getColorMultiplier(blockAccess, blockState, position, 0xffffff); } }
private boolean render(boolean itemFrameRenderer) { if (!ok) { return false; } if (!itemFrameRenderer) { boolean changed = true; if (!keyboard.isEnabled()) { changed = false; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD2)) { scaleYDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD8)) { scaleYDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD4)) { scaleXDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD6)) { scaleXDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_DOWN)) { offsetYDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_UP)) { offsetYDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_LEFT)) { offsetXDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_RIGHT)) { offsetXDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_MULTIPLY)) { scaleXDelta = scaleYDelta = offsetXDelta = offsetYDelta = 0.0f; } else { changed = false; } if (changed) { logger.info(""); logger.info("scaleX %+f", scaleXDelta); logger.info("scaleY %+f", scaleYDelta); logger.info("offsetX %+f", offsetXDelta); logger.info("offsetY %+f", offsetYDelta); lastAngle = ANGLE_UNSET; } if (outputFrames > 0) { writeCustomImage(); outputFrames = 0; } } double angle = getAngle(icon); if (!useScratchTexture) { // render directly to items.png if (angle != lastAngle) { renderToItems(angle); lastAngle = angle; } } else if (itemFrameRenderer) { // look in itemFrames cache first ByteBuffer buffer = itemFrames.get(angle); if (buffer == null) { logger.fine("rendering %s at angle %f for item frame", name, angle); buffer = ByteBuffer.allocateDirect(width * height * 4); renderToItems(angle); itemsFBO.read(buffer); itemFrames.put(angle, buffer); } else { itemsFBO.write(buffer); } lastItemFrameRenderer = true; } else if (lastAngle == ANGLE_UNSET) { // first time rendering - render all N copies for (FBO fbo : scratchFBO) { renderToFB(angle, fbo); } scratchFBO[0].read(scratchBuffer); itemsFBO.write(scratchBuffer); lastAngle = angle; scratchIndex = 0; } else if (lastItemFrameRenderer || angle != lastAngle) { // render to buffer i + 1 // update items.png from buffer i int nextIndex = (scratchIndex + 1) % NUM_SCRATCH_TEXTURES; if (angle != lastAngle) { renderToFB(angle, scratchFBO[nextIndex]); scratchFBO[scratchIndex].read(scratchBuffer); } itemsFBO.write(scratchBuffer); lastAngle = angle; scratchIndex = nextIndex; lastItemFrameRenderer = false; } int glError = GL11.glGetError(); if (glError != 0) { logger.severe("%s during %s update", GLU.gluErrorString(glError), name); ok = false; } return ok; }
public class FancyDial { private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.CUSTOM_ANIMATIONS, "Animation"); private static final double ANGLE_UNSET = Double.MAX_VALUE; private static final int NUM_SCRATCH_TEXTURES = 3; private static final boolean fboSupported = GLContext.getCapabilities().GL_EXT_framebuffer_object; private static final boolean gl13Supported = GLContext.getCapabilities().OpenGL13; private static final boolean enableCompass = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "fancyCompass", true); private static final boolean enableClock = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "fancyClock", true); private static final boolean useGL13 = gl13Supported && Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "useGL13", true); private static final boolean useScratchTexture = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "useScratchTexture", true); private static final int glAttributes; private static boolean initialized; private static boolean active; private static final int drawList = GL11.glGenLists(1); private static final Map<TextureAtlasSprite, ResourceLocation> setupInfo = new IdentityHashMap<TextureAtlasSprite, ResourceLocation>(); private static final Map<TextureAtlasSprite, FancyDial> instances = new IdentityHashMap<TextureAtlasSprite, FancyDial>(); private static int warnCount; private final TextureAtlasSprite icon; private final PropertiesFile properties; private final String name; private final int x0; private final int y0; private final int width; private final int height; private final ByteBuffer scratchBuffer; private final FBO[] scratchFBO = new FBO[NUM_SCRATCH_TEXTURES]; private FBO itemsFBO; private int scratchIndex; private Map<Double, ByteBuffer> itemFrames = new TreeMap<Double, ByteBuffer>(); private int outputFrames; private boolean ok; private double lastAngle = ANGLE_UNSET; private boolean lastItemFrameRenderer; private final List<Layer> layers = new ArrayList<Layer>(); private InputHandler keyboard; private static final float STEP = 0.01f; private float scaleXDelta; private float scaleYDelta; private float offsetXDelta; private float offsetYDelta; static { logger.config("fbo: supported=%s", fboSupported); logger.config("GL13: supported=%s, enabled=%s", gl13Supported, useGL13); int bits = GL11.GL_VIEWPORT_BIT | GL11.GL_SCISSOR_BIT | GL11.GL_DEPTH_BITS | GL11.GL_LIGHTING_BIT; if (useGL13) { bits |= GL13.GL_MULTISAMPLE_BIT; } glAttributes = bits; GL11.glNewList(drawList, GL11.GL_COMPILE); drawBox(); GL11.glEndList(); } public static void setup(TextureAtlasSprite icon) { if (!fboSupported) { return; } String name = IconAPI.getIconName(icon).replaceFirst("^minecraft:items/", ""); if (icon instanceof TextureClock && name.equals("compass")) { // 1.5 bug name = "clock"; } if ("compass".equals(name)) { if (!enableCompass) { return; } } else if ("clock".equals(name)) { if (!enableClock) { return; } } else { logger.warning("ignoring custom animation for %s not compass or clock", name); return; } ResourceLocation resource = TexturePackAPI.newMCPatcherResourceLocation( "/misc/" + name + ".properties", "dial/" + name + ".properties"); if (TexturePackAPI.hasResource(resource)) { logger.fine("found custom %s (%s)", name, resource); setupInfo.put(icon, resource); active = true; } } public static boolean update(TextureAtlasSprite icon, boolean itemFrameRenderer) { if (!initialized) { logger.finer("deferring %s update until initialization finishes", IconAPI.getIconName(icon)); return false; } if (!active) { return false; } int oldFB = GL11.glGetInteger(EXTFramebufferObject.GL_FRAMEBUFFER_BINDING_EXT); if (oldFB != 0 && warnCount < 10) { logger.finer( "rendering %s while non-default framebuffer %d is active", IconAPI.getIconName(icon), oldFB); warnCount++; } int oldTexture = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); try { FancyDial instance = getInstance(icon); return instance != null && instance.render(itemFrameRenderer); } finally { EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, oldFB); GLAPI.glBindTexture(oldTexture); } } static void clearAll() { logger.finer("FancyDial.clearAll"); if (initialized) { active = false; setupInfo.clear(); } for (FancyDial instance : instances.values()) { if (instance != null) { instance.finish(); } } instances.clear(); initialized = true; } static void registerAnimations() { TextureObject texture = TexturePackAPI.getTextureObject(TexturePackAPI.ITEMS_PNG); if (texture instanceof TextureAtlas) { List<TextureAtlasSprite> animations = ((TextureAtlas) texture).animations; for (FancyDial instance : instances.values()) { instance.registerAnimation(animations); } } } void registerAnimation(List<TextureAtlasSprite> animations) { if (animations.contains(icon)) { return; } animations.add(icon); if (icon.animationFrames == null) { icon.animationFrames = new ArrayList<int[]>(); } if (icon.animationFrames.isEmpty()) { int[] dummyRGB = new int[width * height]; Arrays.fill(dummyRGB, 0xffff00ff); icon.animationFrames.add(dummyRGB); } logger.fine("registered %s animation", name); } private static FancyDial getInstance(TextureAtlasSprite icon) { if (instances.containsKey(icon)) { return instances.get(icon); } ResourceLocation resource = setupInfo.remove(icon); instances.put(icon, null); if (resource == null) { return null; } PropertiesFile properties = PropertiesFile.get(logger, resource); if (properties == null) { return null; } try { FancyDial instance = new FancyDial(icon, properties); if (instance.ok) { instances.put(icon, instance); return instance; } instance.finish(); } catch (Throwable e) { e.printStackTrace(); } return null; } private FancyDial(TextureAtlasSprite icon, PropertiesFile properties) { this.icon = icon; this.properties = properties; name = IconAPI.getIconName(icon); x0 = IconAPI.getIconX0(icon); y0 = IconAPI.getIconY0(icon); width = IconAPI.getIconWidth(icon); height = IconAPI.getIconHeight(icon); scratchBuffer = ByteBuffer.allocateDirect(4 * width * height); int itemsTexture = TexturePackAPI.getTextureIfLoaded(TexturePackAPI.ITEMS_PNG); if (itemsTexture < 0) { logger.severe("could not get items texture"); return; } itemsFBO = new FBO(itemsTexture, x0, y0, width, height); if (useScratchTexture) { logger.fine("rendering %s to %dx%d scratch texture", name, width, height); for (int i = 0; i < scratchFBO.length; i++) { scratchFBO[i] = new FBO(width, height); } } else { logger.fine("rendering %s directly to atlas", name); } boolean debug = false; for (int i = 0; ; i++) { Layer layer = newLayer(properties, "." + i); if (layer == null) { if (i > 0) { break; } continue; } layers.add(layer); debug |= layer.debug; logger.fine(" new %s", layer); } keyboard = new InputHandler(name, debug); if (layers.size() < 2) { logger.error("custom %s needs at least two layers defined", name); return; } outputFrames = properties.getInt("outputFrames", 0); int glError = GL11.glGetError(); if (glError != 0) { logger.severe("%s during %s setup", GLU.gluErrorString(glError), name); return; } ok = true; } private boolean render(boolean itemFrameRenderer) { if (!ok) { return false; } if (!itemFrameRenderer) { boolean changed = true; if (!keyboard.isEnabled()) { changed = false; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD2)) { scaleYDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD8)) { scaleYDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD4)) { scaleXDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_NUMPAD6)) { scaleXDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_DOWN)) { offsetYDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_UP)) { offsetYDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_LEFT)) { offsetXDelta -= STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_RIGHT)) { offsetXDelta += STEP; } else if (keyboard.isKeyPressed(Keyboard.KEY_MULTIPLY)) { scaleXDelta = scaleYDelta = offsetXDelta = offsetYDelta = 0.0f; } else { changed = false; } if (changed) { logger.info(""); logger.info("scaleX %+f", scaleXDelta); logger.info("scaleY %+f", scaleYDelta); logger.info("offsetX %+f", offsetXDelta); logger.info("offsetY %+f", offsetYDelta); lastAngle = ANGLE_UNSET; } if (outputFrames > 0) { writeCustomImage(); outputFrames = 0; } } double angle = getAngle(icon); if (!useScratchTexture) { // render directly to items.png if (angle != lastAngle) { renderToItems(angle); lastAngle = angle; } } else if (itemFrameRenderer) { // look in itemFrames cache first ByteBuffer buffer = itemFrames.get(angle); if (buffer == null) { logger.fine("rendering %s at angle %f for item frame", name, angle); buffer = ByteBuffer.allocateDirect(width * height * 4); renderToItems(angle); itemsFBO.read(buffer); itemFrames.put(angle, buffer); } else { itemsFBO.write(buffer); } lastItemFrameRenderer = true; } else if (lastAngle == ANGLE_UNSET) { // first time rendering - render all N copies for (FBO fbo : scratchFBO) { renderToFB(angle, fbo); } scratchFBO[0].read(scratchBuffer); itemsFBO.write(scratchBuffer); lastAngle = angle; scratchIndex = 0; } else if (lastItemFrameRenderer || angle != lastAngle) { // render to buffer i + 1 // update items.png from buffer i int nextIndex = (scratchIndex + 1) % NUM_SCRATCH_TEXTURES; if (angle != lastAngle) { renderToFB(angle, scratchFBO[nextIndex]); scratchFBO[scratchIndex].read(scratchBuffer); } itemsFBO.write(scratchBuffer); lastAngle = angle; scratchIndex = nextIndex; lastItemFrameRenderer = false; } int glError = GL11.glGetError(); if (glError != 0) { logger.severe("%s during %s update", GLU.gluErrorString(glError), name); ok = false; } return ok; } private void writeCustomImage() { try { BufferedImage image = new BufferedImage(width, outputFrames * height, BufferedImage.TYPE_INT_ARGB); IntBuffer intBuffer = scratchBuffer.asIntBuffer(); int[] argb = new int[width * height]; File path = MCPatcherUtils.getGamePath("custom_" + name + ".png"); logger.info("generating %d %s frames", outputFrames, name); for (int i = 0; i < outputFrames; i++) { renderToItems(i * (360.0 / outputFrames)); itemsFBO.read(scratchBuffer); intBuffer.position(0); for (int j = 0; j < argb.length; j++) { switch (MipmapHelper.TEX_FORMAT) { case GL12.GL_BGRA: int bgra = intBuffer.get(j); argb[j] = (bgra << 24) | ((bgra & 0xff00) << 8) | ((bgra & 0xff0000) >> 8) | (bgra >>> 24); break; default: if (i == 0 && j == 0) { logger.warning( "unhandled texture format %d, color channels may be incorrect", MipmapHelper.TEX_FORMAT); } // fall through case GL11.GL_RGBA: argb[j] = Integer.rotateRight(intBuffer.get(j), 8); break; } } image.setRGB(0, i * height, width, height, argb, 0, width); } ImageIO.write(image, "png", path); logger.info("wrote %dx%d %s", image.getWidth(), image.getHeight(), path.getPath()); } catch (Throwable e) { e.printStackTrace(); } } private void renderToItems(double angle) { renderToFB(angle, itemsFBO); } private void renderToFB(double angle, FBO fbo) { if (fbo != null) { fbo.bind(); renderImpl(angle); fbo.unbind(); } } private void renderImpl(double angle) { for (Layer layer : layers) { layer.blendMethod.applyBlending(); GL11.glPushMatrix(); TexturePackAPI.bindTexture(layer.textureName); float offsetX = layer.offsetX; float offsetY = layer.offsetY; float scaleX = layer.scaleX; float scaleY = layer.scaleY; if (layer.debug) { offsetX += offsetXDelta; offsetY += offsetYDelta; scaleX += scaleXDelta; scaleY += scaleYDelta; } GL11.glTranslatef(offsetX, offsetY, 0.0f); GL11.glScalef(scaleX, scaleY, 1.0f); float layerAngle = (float) (angle * layer.rotationMultiplier + layer.rotationOffset); GL11.glRotatef(layerAngle, 0.0f, 0.0f, 1.0f); GL11.glCallList(drawList); GL11.glPopMatrix(); } } private static void drawBox() { GL11.glBegin(GL11.GL_QUADS); GL11.glTexCoord2f(0.0f, 0.0f); GL11.glVertex3f(-1.0f, -1.0f, 0.0f); GL11.glTexCoord2f(1.0f, 0.0f); GL11.glVertex3f(1.0f, -1.0f, 0.0f); GL11.glTexCoord2f(1.0f, 1.0f); GL11.glVertex3f(1.0f, 1.0f, 0.0f); GL11.glTexCoord2f(0.0f, 1.0f); GL11.glVertex3f(-1.0f, 1.0f, 0.0f); GL11.glEnd(); } private void finish() { for (int i = 0; i < scratchFBO.length; i++) { if (scratchFBO[i] != null) { scratchFBO[i].delete(); scratchFBO[i] = null; } } if (itemsFBO != null) { itemsFBO.delete(); itemsFBO = null; } itemFrames.clear(); layers.clear(); ok = false; } @Override public String toString() { return String.format("FancyDial{%s, %dx%d @ %d,%d}", name, width, height, x0, y0); } @Override protected void finalize() throws Throwable { finish(); super.finalize(); } private static double getAngle(Icon icon) { if (icon instanceof TextureCompass) { return ((TextureCompass) icon).currentAngle * 180.0 / Math.PI; } else if (icon instanceof TextureClock) { return ((TextureClock) icon).currentAngle * 360.0; } else { return 0.0; } } Layer newLayer(PropertiesFile properties, String suffix) { ResourceLocation textureResource = properties.getResourceLocation("source" + suffix, ""); if (textureResource == null) { return null; } if (!TexturePackAPI.hasResource(textureResource)) { properties.error("could not read %s", textureResource); return null; } float scaleX = properties.getFloat("scaleX" + suffix, 1.0f); float scaleY = properties.getFloat("scaleY" + suffix, 1.0f); float offsetX = properties.getFloat("offsetX" + suffix, 0.0f); float offsetY = properties.getFloat("offsetY" + suffix, 0.0f); float angleMultiplier = properties.getFloat("rotationSpeed" + suffix, 0.0f); float angleOffset = properties.getFloat("rotationOffset" + suffix, 0.0f); String blend = properties.getString("blend" + suffix, "alpha"); BlendMethod blendMethod = BlendMethod.parse(blend); if (blendMethod == null) { properties.error("unknown blend method %s", blend); return null; } boolean debug = properties.getBoolean("debug" + suffix, false); return new Layer( textureResource, scaleX, scaleY, offsetX, offsetY, angleMultiplier, angleOffset, blendMethod, debug); } private class Layer { final ResourceLocation textureName; final float scaleX; final float scaleY; final float offsetX; final float offsetY; final float rotationMultiplier; final float rotationOffset; final BlendMethod blendMethod; final boolean debug; Layer( ResourceLocation textureName, float scaleX, float scaleY, float offsetX, float offsetY, float rotationMultiplier, float rotationOffset, BlendMethod blendMethod, boolean debug) { this.textureName = textureName; this.scaleX = scaleX; this.scaleY = scaleY; this.offsetX = offsetX; this.offsetY = offsetY; this.rotationMultiplier = rotationMultiplier; this.rotationOffset = rotationOffset; this.blendMethod = blendMethod; this.debug = debug; } @Override public String toString() { return String.format( "Layer{%s %f %f %+f %+f x%f}", textureName, scaleX, scaleY, offsetX, offsetY, rotationMultiplier); } } private static class FBO { private final int texture; private final boolean ownTexture; private final int x0; private final int y0; private final int width; private final int height; private final int frameBuffer; private boolean lightmapEnabled; private boolean deleted; FBO(int width, int height) { this(blankTexture(width, height), true, 0, 0, width, height); } FBO(int texture, int x0, int y0, int width, int height) { this(texture, false, x0, y0, width, height); } private FBO(int texture, boolean ownTexture, int x0, int y0, int width, int height) { this.texture = texture; this.ownTexture = ownTexture; this.x0 = x0; this.y0 = y0; this.width = width; this.height = height; frameBuffer = EXTFramebufferObject.glGenFramebuffersEXT(); if (frameBuffer < 0) { throw new RuntimeException("could not get framebuffer object"); } GLAPI.glBindTexture(texture); EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, frameBuffer); EXTFramebufferObject.glFramebufferTexture2DEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, texture, 0); } void bind() { EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, frameBuffer); GL11.glPushAttrib(glAttributes); GL11.glViewport(x0, y0, width, height); GL11.glEnable(GL11.GL_SCISSOR_TEST); GL11.glScissor(x0, y0, width, height); lightmapEnabled = false; if (gl13Supported) { GL13.glActiveTexture(GL13.GL_TEXTURE1); lightmapEnabled = GL11.glIsEnabled(GL11.GL_TEXTURE_2D); if (lightmapEnabled) { GL11.glDisable(GL11.GL_TEXTURE_2D); } GL13.glActiveTexture(GL13.GL_TEXTURE0); } GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glDisable(GL11.GL_DEPTH_TEST); GLAPI.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); GL11.glDisable(GL11.GL_LIGHTING); GL11.glEnable(GL11.GL_ALPHA_TEST); GLAPI.glAlphaFunc(GL11.GL_GREATER, 0.01f); if (useGL13) { GL11.glDisable(GL13.GL_MULTISAMPLE); } GLAPI.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPushMatrix(); GL11.glLoadIdentity(); } void unbind() { GL11.glPopAttrib(); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPopMatrix(); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPopMatrix(); if (lightmapEnabled) { GL13.glActiveTexture(GL13.GL_TEXTURE1); GL11.glEnable(GL11.GL_TEXTURE_2D); GL13.glActiveTexture(GL13.GL_TEXTURE0); } GL11.glEnable(GL11.GL_BLEND); GLAPI.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0); } void read(ByteBuffer buffer) { EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, frameBuffer); buffer.position(0); GL11.glReadPixels( x0, y0, width, height, MipmapHelper.TEX_FORMAT, MipmapHelper.TEX_DATA_TYPE, buffer); } void write(ByteBuffer buffer) { GLAPI.glBindTexture(texture); buffer.position(0); GL11.glTexSubImage2D( GL11.GL_TEXTURE_2D, 0, x0, y0, width, height, MipmapHelper.TEX_FORMAT, MipmapHelper.TEX_DATA_TYPE, buffer); } void delete() { if (!deleted) { deleted = true; if (ownTexture) { GL11.glDeleteTextures(texture); } EXTFramebufferObject.glDeleteFramebuffersEXT(frameBuffer); } } @Override protected void finalize() throws Throwable { delete(); super.finalize(); } private static int blankTexture(int width, int height) { int texture = GL11.glGenTextures(); MipmapHelper.setupTexture(texture, width, height, "scratch"); return texture; } } }