/*
* Copyright (c) 2003-onwards Shaven Puppy Ltd
* 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 'Shaven Puppy' 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 THE COPYRIGHT OWNER OR
* CONTRIBUTORS 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 com.shavenpuppy.jglib.resources;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import org.lwjgl.util.Color;
import org.lwjgl.util.ReadableColor;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.shavenpuppy.jglib.Resource;
import com.shavenpuppy.jglib.XMLResourceWriter;
import com.shavenpuppy.jglib.interpolators.LinearInterpolator;
import com.shavenpuppy.jglib.util.XMLUtil;
/**
* A sequence of colors. Each color blends into the next one over a number
* of frames.
*/
public class ColorSequenceResource extends Resource implements ColorSequenceWrapper {
private static final long serialVersionUID = 1L;
/** The sequence */
private ArrayList<SequenceEntry> sequence;
/** Total duration (calculated) */
private transient int duration;
/** Repeat style */
private int style;
/** Index of durations */
private transient int[] index;
/*
* Repeat styles
*/
/** The sequence simply stays on the last color at the end */
public static final int STOP = 0;
/** The sequence repeats from the beginning */
public static final int REPEAT = 1;
/** Each entry in the sequence is a SequenceEntry */
public static class SequenceEntry implements Serializable {
private static final long serialVersionUID = 1L;
private ReadableColor color;
private int duration; // The number of frames to hold this color for
private int fade; // The number of frames over which to fade into the next color
private SequenceEntry() {
}
public SequenceEntry(
ReadableColor color,
int duration,
int fade
) {
this.color = color;
this.duration = duration;
this.fade = fade;
}
}
/**
* Constructor for ColorSequence.
*/
public ColorSequenceResource(
SequenceEntry[] entries,
int style
) {
super();
sequence = new ArrayList<SequenceEntry>(entries.length);
for (int i = 0; i < entries.length; i ++) {
sequence.add(entries[i]);
}
this.style = style;
calcDuration();
}
/**
* Constructor for ColorSequence.
*/
public ColorSequenceResource() {
}
/**
* Constructor for ColorSequence.
* @param name
*/
public ColorSequenceResource(String name) {
super(name);
}
/**
* @see com.shavenpuppy.jglib.Resource#load(org.w3c.dom.Element, com.shavenpuppy.jglib.Resource.Loader)
*/
@Override
public void load(Element element, Loader loader) throws Exception {
NodeList children = element.getElementsByTagName("color");
int n = children.getLength();
sequence = new ArrayList<SequenceEntry>(n);
for (int i = 0; i < n; i ++) {
Element colorElement = (Element) children.item(i);
SequenceEntry seq = new SequenceEntry();
seq.color = new Color(Integer.parseInt(colorElement.getAttribute("r")),
Integer.parseInt(colorElement.getAttribute("g")),
Integer.parseInt(colorElement.getAttribute("b")),
Integer.parseInt(colorElement.getAttribute("a"))
);
seq.duration = Integer.parseInt(colorElement.getAttribute("d"));
seq.fade = Integer.parseInt(colorElement.getAttribute("f"));
sequence.add(seq);
}
style = decode(XMLUtil.getString(element, "style", "stop"));
calcDuration();
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.Resource#doToXML(com.shavenpuppy.jglib.XMLResourceWriter)
*/
@Override
protected void doToXML(XMLResourceWriter writer) throws IOException {
writer.writeAttribute("style", style == STOP ? "stop" : "repeat");
for (SequenceEntry se : sequence) {
writer.writeTag("color");
writer.writeAttribute("r", se.color.getRed());
writer.writeAttribute("g", se.color.getGreen());
writer.writeAttribute("b", se.color.getBlue());
writer.writeAttribute("a", se.color.getAlpha());
writer.writeAttribute("d", se.duration);
writer.writeAttribute("f", se.fade);
writer.closeTag();
}
}
/**
* Decode a style
* @param style The style name
* @return a style constant
* @throws Exception if the style is not recognised
*/
public static int decode(String styleS) throws Exception {
if (styleS.equalsIgnoreCase("repeat")) {
return REPEAT;
} else if (styleS.equalsIgnoreCase("stop")) {
return STOP;
} else {
// TODO: Add random style
throw new Exception("Illegal style '"+styleS+"'");
}
}
/**
* @return true if the color sequence is looped
*/
public boolean isLooped() {
return style == REPEAT;
}
/**
* Determine the color at a particular point in time.
* @param time The time (in frames)
* @param color A destination color, or null if a new one is to be created
* @return color, or a new color, containing the interpolated color.
*/
@Override
public Color getColor(int time, Color color) {
if (index == null) {
calcDuration();
}
if (color == null) {
color = new Color();
}
if (time < 0) {
time = 0;
}
if (style == REPEAT) {
time %= duration;
}
// Determine where we are using the index and a binary search
int idx = Arrays.binarySearch(index, time);
if (idx < 0) {
idx = -2 - idx;
}
if (idx == index.length) {
idx --;
}
SequenceEntry seq = sequence.get(idx);
time -= index[idx];
int fade = seq.duration - seq.fade;
if (time <= fade || (idx == index.length - 1 && style == STOP)) {
color.setColor(seq.color);
} else {
float fadeRatio = ((float)(time - fade)) / (float)seq.fade;
ReadableColor a = seq.color;
if (idx == index.length - 1) {
idx = 0;
} else {
idx ++;
}
ReadableColor b = (sequence.get(idx)).color;
color.set(
(int)LinearInterpolator.instance.interpolate(a.getRed(), b.getRed(), fadeRatio),
(int)LinearInterpolator.instance.interpolate(a.getGreen(), b.getGreen(), fadeRatio),
(int)LinearInterpolator.instance.interpolate(a.getBlue(), b.getBlue(), fadeRatio),
(int)LinearInterpolator.instance.interpolate(a.getAlpha(), b.getAlpha(), fadeRatio)
);
}
return color;
}
private void calcDuration() {
duration = 0;
index = new int[sequence.size()];
int count = 0;
for (SequenceEntry seq : sequence) {
index[count ++] = duration;
duration += seq.duration;
}
}
/**
* @see com.shavenpuppy.jglib.GenericResource#doDestroy()
*/
@Override
protected void doDestroy() {
index = null;
}
/**
* @return the duration of the color sequence
*/
public final int getDuration() {
assert isCreated();
return duration;
}
/* (non-Javadoc)
* @see com.shavenpuppy.jglib.resources.ColorSequenceWrapper#isFinished(int)
*/
@Override
public boolean isFinished(int tick) {
if (isLooped()) {
return false;
} else {
return tick >= duration;
}
}
}