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;
018
019import javax.imageio.ImageIO;
020
021import net.minecraft.client.Minecraft;
022import net.minecraft.client.renderer.RenderEngine;
023import net.minecraft.client.renderer.texturefx.TextureFX;
024import net.minecraft.client.texturepacks.ITexturePack;
025import net.minecraft.src.ModTextureStatic;
026
027import org.lwjgl.opengl.GL11;
028
029import com.google.common.collect.ArrayListMultimap;
030import com.google.common.collect.Maps;
031import com.google.common.collect.Multimap;
032
033import cpw.mods.fml.common.FMLCommonHandler;
034import cpw.mods.fml.common.FMLLog;
035import cpw.mods.fml.common.ModContainer;
036
037public 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}