001package cpw.mods.fml.client; 002 003import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; 004import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D; 005 006import java.awt.Dimension; 007import java.awt.image.BufferedImage; 008import java.io.IOException; 009import java.io.InputStream; 010import java.nio.ByteBuffer; 011import java.util.ArrayList; 012import java.util.HashMap; 013import java.util.HashSet; 014import java.util.IdentityHashMap; 015import java.util.List; 016import java.util.ListIterator; 017import java.util.Map; 018import java.util.logging.Level; 019 020import javax.imageio.ImageIO; 021 022import net.minecraft.client.Minecraft; 023import net.minecraft.client.renderer.RenderEngine; 024import net.minecraft.client.renderer.texturefx.TextureFX; 025import net.minecraft.client.texturepacks.ITexturePack; 026import net.minecraft.src.ModTextureStatic; 027 028import org.lwjgl.opengl.GL11; 029 030import com.google.common.collect.ArrayListMultimap; 031import com.google.common.collect.Maps; 032import com.google.common.collect.Multimap; 033 034import cpw.mods.fml.common.FMLCommonHandler; 035import cpw.mods.fml.common.FMLLog; 036import cpw.mods.fml.common.ModContainer; 037 038public class TextureFXManager 039{ 040 private static final TextureFXManager INSTANCE = new TextureFXManager(); 041 042 private class TextureProperties 043 { 044 private int textureId; 045 private Dimension dim; 046 } 047 048 private Map<Integer,TextureProperties> textureProperties = Maps.newHashMap(); 049 private Multimap<String, OverrideInfo> overrideInfo = ArrayListMultimap.create(); 050 private HashSet<OverrideInfo> animationSet = new HashSet<OverrideInfo>(); 051 052 private List<TextureFX> addedTextureFX = new ArrayList<TextureFX>(); 053 054 private Minecraft client; 055 056 void setClient(Minecraft client) 057 { 058 this.client = client; 059 } 060 061 public boolean onUpdateTextureEffect(TextureFX effect) 062 { 063 ITextureFX ifx = (effect instanceof ITextureFX ? ((ITextureFX)effect) : null); 064 065 if (ifx != null && ifx.getErrored()) 066 { 067 return false; 068 } 069 070 String name = effect.getClass().getSimpleName(); 071 client.mcProfiler.startSection(name); 072 try 073 { 074 if (!FMLClientHandler.instance().hasOptifine()) 075 { 076 effect.onTick(); 077 } 078 } 079 catch (Exception e) 080 { 081 FMLLog.log("fml.TextureManager", Level.WARNING, "Texture FX %s has failed to animate. Likely caused by a texture pack change that they did not respond correctly to", name); 082 if (ifx != null) 083 { 084 ifx.setErrored(true); 085 } 086 client.mcProfiler.endSection(); 087 return false; 088 } 089 client.mcProfiler.endSection(); 090 091 if (ifx != null) 092 { 093 Dimension dim = getTextureDimensions(effect); 094 int target = ((dim.width >> 4) * (dim.height >> 4)) << 2; 095 if (effect.imageData.length != target) 096 { 097 FMLLog.log("fml.TextureManager", Level.WARNING, "Detected a texture FX sizing discrepancy in %s (%d, %d)", name, effect.imageData.length, target); 098 ifx.setErrored(true); 099 return false; 100 } 101 } 102 return true; 103 } 104 105 //Quick and dirty image scaling, no smoothing or fanciness, meant for speed as it will be called every tick. 106 public void scaleTextureFXData(byte[] data, ByteBuffer buf, int target, int length) 107 { 108 int sWidth = (int)Math.sqrt(data.length / 4); 109 int factor = target / sWidth; 110 byte[] tmp = new byte[4]; 111 112 buf.clear(); 113 114 if (factor > 1) 115 { 116 for (int y = 0; y < sWidth; y++) 117 { 118 int sRowOff = sWidth * y; 119 int tRowOff = target * y * factor; 120 for (int x = 0; x < sWidth; x++) 121 { 122 int sPos = (x + sRowOff) * 4; 123 tmp[0] = data[sPos + 0]; 124 tmp[1] = data[sPos + 1]; 125 tmp[2] = data[sPos + 2]; 126 tmp[3] = data[sPos + 3]; 127 128 int tPosTop = (x * factor) + tRowOff; 129 for (int y2 = 0; y2 < factor; y2++) 130 { 131 buf.position((tPosTop + (y2 * target)) * 4); 132 for (int x2 = 0; x2 < factor; x2++) 133 { 134 buf.put(tmp); 135 } 136 } 137 } 138 } 139 } 140 141 buf.position(0).limit(length); 142 } 143 144 public void onPreRegisterEffect(TextureFX effect) 145 { 146 Dimension dim = getTextureDimensions(effect); 147 if (effect instanceof ITextureFX) 148 { 149 ((ITextureFX)effect).onTextureDimensionsUpdate(dim.width, dim.height); 150 } 151 } 152 153 154 public int getEffectTexture(TextureFX effect) 155 { 156 Integer id = effectTextures.get(effect); 157 if (id != null) 158 { 159 return id; 160 } 161 162 int old = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); 163 effect.bindImage(client.renderEngine); 164 id = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); 165 GL11.glBindTexture(GL_TEXTURE_2D, old); 166 effectTextures.put(effect, id); 167 effect.textureId = id; 168 return id; 169 } 170 171 public void onTexturePackChange(RenderEngine engine, ITexturePack texturepack, List<TextureFX> effects) 172 { 173 pruneOldTextureFX(texturepack, effects); 174 175 for (TextureFX tex : effects) 176 { 177 if (tex instanceof ITextureFX) 178 { 179 ((ITextureFX)tex).onTexturePackChanged(engine, texturepack, getTextureDimensions(tex)); 180 } 181 } 182 183 loadTextures(texturepack); 184 } 185 186 private HashMap<Integer, Dimension> textureDims = new HashMap<Integer, Dimension>(); 187 private IdentityHashMap<TextureFX, Integer> effectTextures = new IdentityHashMap<TextureFX, Integer>(); 188 private ITexturePack earlyTexturePack; 189 public void setTextureDimensions(int id, int width, int height, List<TextureFX> effects) 190 { 191 Dimension dim = new Dimension(width, height); 192 textureDims.put(id, dim); 193 194 for (TextureFX tex : effects) 195 { 196 if (getEffectTexture(tex) == id && tex instanceof ITextureFX) 197 { 198 ((ITextureFX)tex).onTextureDimensionsUpdate(width, height); 199 } 200 } 201 } 202 203 public Dimension getTextureDimensions(TextureFX effect) 204 { 205 return getTextureDimensions(getEffectTexture(effect)); 206 } 207 208 public Dimension getTextureDimensions(int id) 209 { 210 return textureDims.get(id); 211 } 212 213 public void addAnimation(TextureFX anim) 214 { 215 OverrideInfo info=new OverrideInfo(); 216 info.index=anim.iconIndex; 217 info.imageIndex=anim.tileImage; 218 info.textureFX=anim; 219 if (animationSet.contains(info)) { 220 animationSet.remove(info); 221 } 222 animationSet.add(info); 223 } 224 225 226 public void loadTextures(ITexturePack texturePack) 227 { 228 registerTextureOverrides(client.renderEngine); 229 } 230 231 232 public void registerTextureOverrides(RenderEngine renderer) { 233 for (OverrideInfo animationOverride : animationSet) { 234 renderer.registerTextureFX(animationOverride.textureFX); 235 addedTextureFX.add(animationOverride.textureFX); 236 FMLLog.log("fml.TextureManager", Level.FINE, "Registered texture override %d (%d) on %s (%d)", animationOverride.index, animationOverride.textureFX.iconIndex, animationOverride.textureFX.getClass().getSimpleName(), animationOverride.textureFX.tileImage); 237 } 238 239 for (String fileToOverride : overrideInfo.keySet()) { 240 for (OverrideInfo override : overrideInfo.get(fileToOverride)) { 241 try 242 { 243 BufferedImage image=loadImageFromTexturePack(renderer, override.override); 244 ModTextureStatic mts=new ModTextureStatic(override.index, 1, override.texture, image); 245 renderer.registerTextureFX(mts); 246 addedTextureFX.add(mts); 247 FMLLog.log("fml.TextureManager", Level.FINE, "Registered texture override %d (%d) on %s (%d)", override.index, mts.iconIndex, override.texture, mts.tileImage); 248 } 249 catch (IOException e) 250 { 251 FMLLog.log("fml.TextureManager", Level.WARNING, e, "Exception occurred registering texture override for %s", fileToOverride); 252 } 253 } 254 } 255 } 256 257 protected void registerAnimatedTexturesFor(ModContainer mod) 258 { 259 } 260 261 public void onEarlyTexturePackLoad(ITexturePack fallback) 262 { 263 if (client==null) { 264 // We're far too early- let's wait 265 this.earlyTexturePack = fallback; 266 } else { 267 loadTextures(fallback); 268 } 269 } 270 271 272 public void pruneOldTextureFX(ITexturePack var1, List<TextureFX> effects) 273 { 274 ListIterator<TextureFX> li = addedTextureFX.listIterator(); 275 while (li.hasNext()) 276 { 277 TextureFX tex = li.next(); 278 if (tex instanceof FMLTextureFX) 279 { 280 if (((FMLTextureFX)tex).unregister(client.renderEngine, effects)) 281 { 282 li.remove(); 283 } 284 } 285 else 286 { 287 effects.remove(tex); 288 li.remove(); 289 } 290 } 291 } 292 public void addNewTextureOverride(String textureToOverride, String overridingTexturePath, int location) { 293 OverrideInfo info = new OverrideInfo(); 294 info.index = location; 295 info.override = overridingTexturePath; 296 info.texture = textureToOverride; 297 overrideInfo.put(textureToOverride, info); 298 FMLLog.log("fml.TextureManager", Level.FINE, "Overriding %s @ %d with %s. %d slots remaining",textureToOverride, location, overridingTexturePath, SpriteHelper.freeSlotCount(textureToOverride)); 299 } 300 301 public BufferedImage loadImageFromTexturePack(RenderEngine renderEngine, String path) throws IOException 302 { 303 InputStream image=client.texturePackList.getSelectedTexturePack().getResourceAsStream(path); 304 if (image==null) { 305 throw new RuntimeException(String.format("The requested image path %s is not found",path)); 306 } 307 BufferedImage result=ImageIO.read(image); 308 if (result==null) 309 { 310 throw new RuntimeException(String.format("The requested image path %s appears to be corrupted",path)); 311 } 312 return result; 313 } 314 315 public static TextureFXManager instance() 316 { 317 return INSTANCE; 318 } 319 320}