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    }