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;
 }
 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;
 }
 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 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);
     }
   }
 }
 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;
 }
  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;
  }