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