PageRenderTime 67ms CodeModel.GetById 48ms app.highlight 16ms RepoModel.GetById 0ms app.codeStats 1ms

/newcode/src/com/prupe/mcpatcher/hd/CustomAnimation.java

https://bitbucket.org/prupe/mcpatcher
Java | 331 lines | 306 code | 25 blank | 0 comment | 70 complexity | 0e5fe92486cc42ba64af2e195d9e7726 MD5 | raw file
  1package com.prupe.mcpatcher.hd;
  2
  3import com.prupe.mcpatcher.Config;
  4import com.prupe.mcpatcher.MCLogger;
  5import com.prupe.mcpatcher.MCPatcherUtils;
  6import com.prupe.mcpatcher.mal.resource.*;
  7import com.prupe.mcpatcher.mal.tile.IconAPI;
  8import net.minecraft.src.ResourceLocation;
  9import org.lwjgl.opengl.GL11;
 10import org.lwjgl.util.glu.GLU;
 11
 12import java.awt.*;
 13import java.awt.image.BufferedImage;
 14import java.nio.ByteBuffer;
 15import java.util.*;
 16import java.util.List;
 17
 18public class CustomAnimation implements Comparable<CustomAnimation> {
 19    private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.CUSTOM_ANIMATIONS, "Animation");
 20
 21    private static final boolean enable = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "animations", true);
 22    private static final Set<PropertiesFile> pending = new HashSet<PropertiesFile>();
 23    private static final List<CustomAnimation> animations = new ArrayList<CustomAnimation>();
 24
 25    private final PropertiesFile properties;
 26    private final ResourceLocation dstName;
 27    private final ResourceLocation srcName;
 28    private final int mipmapLevel;
 29    private final ByteBuffer imageData;
 30    private final int x;
 31    private final int y;
 32    private final int w;
 33    private final int h;
 34
 35    private int currentFrame;
 36    private int currentDelay;
 37    private int numFrames;
 38    private int[] tileOrder;
 39    private int[] tileDelay;
 40    private final int numTiles;
 41    private boolean error;
 42
 43    static {
 44        TexturePackChangeHandler.register(new TexturePackChangeHandler(MCPatcherUtils.EXTENDED_HD, 1) {
 45            @Override
 46            public void beforeChange() {
 47                if (!pending.isEmpty()) {
 48                    logger.fine("%d animations were never registered:", pending.size());
 49                    for (PropertiesFile properties : pending) {
 50                        logger.fine("  %s", properties);
 51                    }
 52                    pending.clear();
 53                }
 54                animations.clear();
 55                MipmapHelper.reset();
 56                FancyDial.clearAll();
 57            }
 58
 59            @Override
 60            public void afterChange() {
 61                if (enable) {
 62                    for (ResourceLocation resource : ResourceList.getInstance().listResources(TexturePackAPI.MCPATCHER_SUBDIR + "anim", ".properties", false)) {
 63                        PropertiesFile properties = PropertiesFile.get(logger, resource);
 64                        if (properties != null) {
 65                            pending.add(properties);
 66                        }
 67                    }
 68                }
 69                if (IconAPI.needRegisterTileAnimations()) {
 70                    FancyDial.registerAnimations();
 71                }
 72            }
 73        });
 74    }
 75
 76    public static void updateAll() {
 77        if (!pending.isEmpty()) {
 78            try {
 79                checkPendingAnimations();
 80            } catch (Throwable e) {
 81                e.printStackTrace();
 82                logger.error("%d remaining animations cleared", pending.size());
 83                pending.clear();
 84            }
 85        }
 86        for (CustomAnimation animation : animations) {
 87            animation.update();
 88        }
 89    }
 90
 91    private static void checkPendingAnimations() {
 92        List<PropertiesFile> done = new ArrayList<PropertiesFile>();
 93        for (PropertiesFile properties : pending) {
 94            ResourceLocation textureName = properties.getResourceLocation("to", "");
 95            if (TexturePackAPI.isTextureLoaded(textureName)) {
 96                addStrip(properties);
 97                done.add(properties);
 98            }
 99        }
100        if (!done.isEmpty()) {
101            for (PropertiesFile name : done) {
102                pending.remove(name);
103            }
104            Collections.sort(animations);
105        }
106    }
107
108    private static void addStrip(PropertiesFile properties) {
109        ResourceLocation dstName = properties.getResourceLocation("to", "");
110        if (dstName == null) {
111            properties.error("missing to= property");
112            return;
113        }
114        ResourceLocation srcName = properties.getResourceLocation("from", "");
115        if (srcName == null) {
116            properties.error("missing from= property");
117            return;
118        }
119        BufferedImage srcImage = TexturePackAPI.getImage(srcName);
120        if (srcImage == null) {
121            properties.error("image %s not found in texture pack", srcName);
122            return;
123        }
124        int x = properties.getInt("x", 0);
125        int y = properties.getInt("y", 0);
126        int w = properties.getInt("w", 0);
127        int h = properties.getInt("h", 0);
128        if (dstName.toString().startsWith("minecraft:textures/atlas/")) {
129            properties.error("animations cannot have a target of %s", dstName);
130            return;
131        }
132        if (x < 0 || y < 0 || w <= 0 || h <= 0) {
133            properties.error("%s has invalid dimensions x=%d,y=%d,w=%d,h=%d", srcName, x, y, w, h);
134            return;
135        }
136        TexturePackAPI.bindTexture(dstName);
137        int dstWidth = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
138        int dstHeight = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
139        int levels = MipmapHelper.getMipmapLevelsForCurrentTexture();
140        if (x + w > dstWidth || y + h > dstHeight) {
141            properties.error("%s dimensions x=%d,y=%d,w=%d,h=%d exceed %s size %dx%d",
142                srcName, x, y, w, h, dstName, dstWidth, dstHeight
143            );
144            return;
145        }
146        int width = srcImage.getWidth();
147        int height = srcImage.getHeight();
148        if (width != w) {
149            srcImage = resizeImage(srcImage, w);
150            width = srcImage.getWidth();
151            height = srcImage.getHeight();
152        }
153        if (width != w || height < h) {
154            properties.error("%s dimensions %dx%d do not match %dx%d", srcName, width, height, w, h);
155            return;
156        }
157        ByteBuffer imageData = ByteBuffer.allocateDirect(4 * width * height);
158        int[] argb = new int[width * height];
159        byte[] rgba = new byte[4 * width * height];
160        srcImage.getRGB(0, 0, width, height, argb, 0, width);
161        ARGBtoRGBA(argb, rgba);
162        imageData.put(rgba).flip();
163        for (int mipmapLevel = 0; mipmapLevel <= levels; mipmapLevel++) {
164            add(new CustomAnimation(properties, srcName, dstName, mipmapLevel, x, y, w, h, imageData, height / h));
165            if (((x | y | w | h) & 0x1) != 0 || w <= 0 || h <= 0) {
166                break;
167            }
168            ByteBuffer newImage = ByteBuffer.allocateDirect(width * height);
169            MipmapHelper.scaleHalf(imageData.asIntBuffer(), width, height, newImage.asIntBuffer(), 0);
170            imageData = newImage;
171            width >>= 1;
172            height >>= 1;
173            x >>= 1;
174            y >>= 1;
175            w >>= 1;
176            h >>= 1;
177        }
178    }
179
180    private static void add(CustomAnimation animation) {
181        if (animation != null) {
182            animations.add(animation);
183            if (animation.mipmapLevel == 0) {
184                logger.fine("new %s", animation);
185            }
186        }
187    }
188
189    private CustomAnimation(PropertiesFile properties, ResourceLocation srcName, ResourceLocation dstName, int mipmapLevel, int x, int y, int w, int h, ByteBuffer imageData, int numFrames) {
190        this.properties = properties;
191        this.srcName = srcName;
192        this.dstName = dstName;
193        this.mipmapLevel = mipmapLevel;
194        this.x = x;
195        this.y = y;
196        this.w = w;
197        this.h = h;
198        this.imageData = imageData;
199        this.numFrames = numFrames;
200        currentFrame = -1;
201        numTiles = numFrames;
202        loadProperties(properties);
203    }
204
205    void update() {
206        if (error) {
207            return;
208        }
209        int texture = TexturePackAPI.getTextureIfLoaded(dstName);
210        if (texture < 0) {
211            return;
212        }
213        if (--currentDelay > 0) {
214            return;
215        }
216        if (++currentFrame >= numFrames) {
217            currentFrame = 0;
218        }
219        GLAPI.glBindTexture(texture);
220        update(texture, 0, 0);
221        int glError = GL11.glGetError();
222        if (glError != 0) {
223            logger.severe("%s: %s", this, GLU.gluErrorString(glError));
224            error = true;
225            return;
226        }
227        currentDelay = getDelay();
228    }
229
230    public int compareTo(CustomAnimation o) {
231        return dstName.toString().compareTo(o.dstName.toString());
232    }
233
234    @Override
235    public String toString() {
236        return String.format("CustomAnimation{%s %s %dx%d -> %s%s @ %d,%d (%d frames)}",
237            properties, srcName, w, h, dstName, (mipmapLevel > 0 ? "#" + mipmapLevel : ""), x, y, numFrames
238        );
239    }
240
241    private static void ARGBtoRGBA(int[] src, byte[] dest) {
242        for (int i = 0; i < src.length; i++) {
243            int v = src[i];
244            dest[(i * 4) + 3] = (byte) ((v >> 24) & 0xff);
245            dest[(i * 4) + 0] = (byte) ((v >> 16) & 0xff);
246            dest[(i * 4) + 1] = (byte) ((v >> 8) & 0xff);
247            dest[(i * 4) + 2] = (byte) ((v >> 0) & 0xff);
248        }
249    }
250
251    private static BufferedImage resizeImage(BufferedImage image, int width) {
252        if (width == image.getWidth()) {
253            return image;
254        }
255        int height = image.getHeight() * width / image.getWidth();
256        logger.finer("resizing to %dx%d", width, height);
257        BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
258        Graphics2D graphics2D = newImage.createGraphics();
259        graphics2D.drawImage(image, 0, 0, width, height, null);
260        return newImage;
261    }
262
263    private void loadProperties(PropertiesFile properties) {
264        loadTileOrder(properties);
265        if (tileOrder == null) {
266            tileOrder = new int[numFrames];
267            for (int i = 0; i < numFrames; i++) {
268                tileOrder[i] = i % numTiles;
269            }
270        }
271        tileDelay = new int[numFrames];
272        loadTileDelay(properties);
273        for (int i = 0; i < numFrames; i++) {
274            tileDelay[i] = Math.max(tileDelay[i], 1);
275        }
276    }
277
278    private void loadTileOrder(PropertiesFile properties) {
279        if (properties == null) {
280            return;
281        }
282        int i = 0;
283        for (; getIntValue(properties, "tile.", i) != null; i++) {
284        }
285        if (i > 0) {
286            numFrames = i;
287            tileOrder = new int[numFrames];
288            for (i = 0; i < numFrames; i++) {
289                tileOrder[i] = Math.abs(getIntValue(properties, "tile.", i)) % numTiles;
290            }
291        }
292    }
293
294    private void loadTileDelay(PropertiesFile properties) {
295        if (properties == null) {
296            return;
297        }
298        Integer defaultValue = getIntValue(properties, "duration");
299        for (int i = 0; i < numFrames; i++) {
300            Integer value = getIntValue(properties, "duration.", i);
301            if (value != null) {
302                tileDelay[i] = value;
303            } else if (defaultValue != null) {
304                tileDelay[i] = defaultValue;
305            }
306        }
307    }
308
309    private static Integer getIntValue(PropertiesFile properties, String key) {
310        try {
311            String value = properties.getString(key, "");
312            if (value != null && value.matches("^\\d+$")) {
313                return Integer.parseInt(value);
314            }
315        } catch (NumberFormatException e) {
316        }
317        return null;
318    }
319
320    private static Integer getIntValue(PropertiesFile properties, String prefix, int index) {
321        return getIntValue(properties, prefix + index);
322    }
323
324    private void update(int texture, int dx, int dy) {
325        GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, mipmapLevel, x + dx, y + dy, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) imageData.position(4 * w * h * tileOrder[currentFrame]));
326    }
327
328    private int getDelay() {
329        return tileDelay[currentFrame];
330    }
331}