/* Copyright (c) 2013, nifty-gui / Joris van der Wel * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the <organization> nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package aphelion.client.graphics.nifty; import de.lessvoid.nifty.Nifty; import de.lessvoid.nifty.Size; import de.lessvoid.nifty.effects.EffectImpl; import de.lessvoid.nifty.effects.EffectProperties; import de.lessvoid.nifty.effects.Falloff; import de.lessvoid.nifty.elements.Element; import de.lessvoid.nifty.layout.Box; import de.lessvoid.nifty.render.NiftyImage; import de.lessvoid.nifty.render.NiftyRenderEngine; import de.lessvoid.nifty.render.image.CompoundImageMode; import de.lessvoid.nifty.render.image.ImageModeFactory; import de.lessvoid.nifty.render.image.ImageModeHelper; import de.lessvoid.nifty.render.image.areaprovider.AreaProvider; import de.lessvoid.nifty.render.image.renderstrategy.RenderStrategy; import de.lessvoid.nifty.spi.render.RenderImage; import de.lessvoid.nifty.tools.Alpha; import de.lessvoid.nifty.tools.SizeValue; import de.lessvoid.nifty.spi.time.TimeProvider; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.logging.Logger; /** * * @author Joris */ public class SpriteAnimationEffect implements EffectImpl { private static final Logger log = Logger.getLogger(SpriteAnimationEffect.class.getName()); private static final Method getRenderStrategy; static { try { getRenderStrategy = ImageModeFactory.class.getDeclaredMethod("getRenderStrategy", String.class); getRenderStrategy.setAccessible(true); } catch (NoSuchMethodException ex) { throw new Error(ex); } } private NiftyImage image; // From parameters: private int spriteW; private int spriteH; private int[] frameLength; private boolean reverse; private Alpha alpha; private SizeValue inset; private SizeValue width; private SizeValue height; private boolean center; private boolean hideIfNotEnoughSpace; private boolean activeBeforeStartDelay; // this will render the effect even when using a startDelay value so that it will already render before the startDelay private int spriteCount; private int spriteCountPerLine; private int index; private long lastUpdate; private long nextChange = 0; private boolean firstUpdate; private final AreaProvider areaProvider = new AreaProvider() { @Override public Box getSourceArea(RenderImage renderImage) { int imageWidth = renderImage.getWidth(); int imageHeight = renderImage.getHeight(); int spriteX = index % spriteCountPerLine; int spriteY = index / spriteCountPerLine; int imageX = spriteX * spriteW; int imageY = spriteY * spriteH; if (((imageX + spriteW) > imageWidth) || ((imageY + spriteH) > imageHeight)) { log.warning("Sprite's area exceeds image's bounds."); } return new Box(imageX, imageY, spriteW, spriteH); } @Override public Size getNativeSize(NiftyImage image) { return new Size(spriteW, spriteH); } @Override public void setParameters(String parameters) { assert false; } }; @Override public void activate(Nifty nifty, Element element, EffectProperties parameter) { image = nifty.getRenderEngine().createImage(nifty.getCurrentScreen(), parameter.getProperty("filename"), false); try { String[] args = parameter.getProperty("sprite").split(",", 2); spriteW = Integer.parseInt(args[0]); spriteH = Integer.parseInt(args[1]); if (spriteW <= 0 || spriteH <= 0) { throw new IllegalArgumentException("Sprite width and height should be positive"); } } catch(NullPointerException | IndexOutOfBoundsException | NumberFormatException ex) { throw new IllegalArgumentException("Missing or incorrect sprite parameter. Expected [w,h] but was ["+ parameter.getProperty("sprite")+"]", ex); } spriteCountPerLine = image.getWidth() / spriteW; spriteCount = spriteCountPerLine * (image.getHeight() / spriteH); RenderStrategy renderStrategy; try { renderStrategy = (RenderStrategy) getRenderStrategy.invoke(ImageModeFactory.getSharedInstance(), ImageModeHelper.getRenderStrategyProperty(parameter)); } catch (InvocationTargetException | IllegalAccessException ex) { throw new AssertionError(ex); } image.setImageMode(new CompoundImageMode(areaProvider, renderStrategy)); try { if (parameter.containsKey("frameLength")) { String[] times = parameter.getProperty("frameLength").split(","); frameLength = new int[times.length]; if (times.length > 0) { for (int i = 0; i < times.length; ++i) { frameLength[i] = Integer.parseInt(times[i]); } } else { frameLength = new int[] { 100 }; } } else { frameLength = new int[] { 100 }; } } catch (NumberFormatException ex) { throw new IllegalArgumentException("Invalid frameLength: " + parameter.getProperty("frameLength"), ex); } nextChange = frameLength[0]; reverse = Boolean.valueOf(parameter.getProperty("reverse", "false")); alpha = new Alpha(parameter.getProperty("alpha", "#f")); inset = new SizeValue(parameter.getProperty("inset", "0px")); width = new SizeValue(parameter.getProperty("width", element.getWidth() + "px")); height = new SizeValue(parameter.getProperty("height", element.getHeight() + "px")); center = Boolean.valueOf(parameter.getProperty("center", "false")); hideIfNotEnoughSpace = Boolean.valueOf(parameter.getProperty("hideIfNotEnoughSpace", "false")); activeBeforeStartDelay = Boolean.valueOf(parameter.getProperty("activeBeforeStartDelay", "false")); } private void updateIndex(TimeProvider time) { long now = time.getMsTime(); long delta; if (firstUpdate) { delta = 0; firstUpdate = false; index = reverse ? spriteCount - 1 : 0; } else { delta = now - lastUpdate; } lastUpdate = now; nextChange -= delta; while (nextChange < 0) { index = (index + (reverse ? -1 : 1)); if (index < 0) { index += spriteCount; } index %= spriteCount; int duration = index < frameLength.length ? frameLength[index] : frameLength[frameLength.length-1]; nextChange += duration; } } @Override public void execute(Element element, float effectTime, Falloff falloff, NiftyRenderEngine r) { if (!activeBeforeStartDelay && effectTime <= 0.0) { return; } updateIndex(element.getNifty().getTimeProvider()); int insetOffset = inset.getValueAsInt(element.getWidth()); int imageX = element.getX() + insetOffset; int imageY = element.getY() + insetOffset; int imageWidth = width.getValueAsInt(element.getWidth()) - insetOffset * 2; int imageHeight = height.getValueAsInt(element.getHeight()) - insetOffset * 2; if (hideIfNotEnoughSpace) { if (imageWidth > element.getWidth() || imageHeight > element.getHeight()) { return; } } r.saveState(null); if (falloff != null) { r.setColorAlpha(alpha.mutiply(falloff.getFalloffValue()).getAlpha()); } else { if (!r.isColorAlphaChanged()) { r.setColorAlpha(alpha.getAlpha()); } } if (center) { r.renderImage(image, element.getX() + (element.getWidth() - imageWidth) / 2, element.getY() + (element.getHeight() - imageHeight) / 2, imageWidth, imageHeight); } else { r.renderImage(image, imageX, imageY, imageWidth, imageHeight); } r.restoreState(); } @Override public void deactivate() { image.dispose(); } public static void registerEffect(Nifty nifty) { nifty.registerEffect("sprite-animation", SpriteAnimationEffect.class.getName()); } }