com.github.mathiewz.slick.particles.ParticleIO Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of modernized-slick Show documentation
Show all versions of modernized-slick Show documentation
The main purpose of this libraryis to modernize and maintain the slick2D library.
The newest version!
package com.github.mathiewz.slick.particles;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.github.mathiewz.slick.Color;
import com.github.mathiewz.slick.SlickException;
import com.github.mathiewz.slick.geom.Vector2f;
import com.github.mathiewz.slick.particles.ConfigurableEmitter.ColorRecord;
import com.github.mathiewz.slick.particles.ConfigurableEmitter.LinearInterpolator;
import com.github.mathiewz.slick.particles.ConfigurableEmitter.RandomValue;
import com.github.mathiewz.slick.particles.ConfigurableEmitter.SimpleValue;
import com.github.mathiewz.slick.util.Log;
import com.github.mathiewz.slick.util.ResourceLoader;
/**
* Utility methods to (de)serialize ConfigureEmitters to and from XML
*
* @author kevin
*/
public class ParticleIO {
private ParticleIO() {
// To avoid instantiation
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The reference to the XML file (file or classpath)
* @param mask
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(String ref, Color mask) throws IOException {
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref), null, null, mask);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The reference to the XML file (file or classpath)
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(String ref) throws IOException {
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref), null, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The XML file to read
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(File ref) throws IOException {
return loadConfiguredSystem(new FileInputStream(ref), null, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The stream to read the XML from
* @param mask
* The mask used to make the particle image transparent
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(InputStream ref, Color mask) throws IOException {
return loadConfiguredSystem(ref, null, null, mask);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The stream to read the XML from
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(InputStream ref) throws IOException {
return loadConfiguredSystem(ref, null, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The reference to the XML file (file or classpath)
* @return A configured particle system
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(String ref, ConfigurableEmitterFactory factory) throws IOException {
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref), factory, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The XML file to read
* @return A configured particle system
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(File ref, ConfigurableEmitterFactory factory) throws IOException {
return loadConfiguredSystem(new FileInputStream(ref), factory, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The stream to read the XML from
* @return A configured particle system
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(InputStream ref, ConfigurableEmitterFactory factory) throws IOException {
return loadConfiguredSystem(ref, factory, null, null);
}
/**
* Load a set of configured emitters into a single system
*
* @param ref
* The stream to read the XML from
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @param system
* The particle system that will be loaded into
* @param mask
* The mask used to make the image background transparent
* @return A configured particle system
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ParticleSystem loadConfiguredSystem(InputStream ref, ConfigurableEmitterFactory factory, ParticleSystem system, Color mask) throws IOException {
if (factory == null) {
factory = ConfigurableEmitter::new;
}
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(ref);
Element element = document.getDocumentElement();
if (!element.getNodeName().equals("system")) {
throw new IOException("Not a particle system file");
}
if (system == null) {
system = new ParticleSystem("com/github/mathiewz/slick/data/particle.tga", 2000, mask);
}
boolean additive = "true".equals(element.getAttribute("additive"));
if (additive) {
system.setBlendingMode(ParticleSystem.BLEND_ADDITIVE);
} else {
system.setBlendingMode(ParticleSystem.BLEND_COMBINE);
}
boolean points = "true".equals(element.getAttribute("points"));
system.setUsePoints(points);
NodeList list = element.getElementsByTagName("emitter");
for (int i = 0; i < list.getLength(); i++) {
Element em = (Element) list.item(i);
ConfigurableEmitter emitter = factory.createEmitter("new");
elementToEmitter(em, emitter);
system.addEmitter(emitter);
}
system.setRemoveCompletedEmitters(false);
return system;
} catch (IOException e) {
Log.error(e);
throw e;
} catch (Exception e) {
Log.error(e);
throw new IOException("Unable to load particle system config");
}
}
/**
* Save a particle system with only ConfigurableEmitters in to an XML file
*
* @param file
* The file to save to
* @param system
* The system to store
* @throws IOException
* Indicates a failure to save or encode the system XML.
*/
public static void saveConfiguredSystem(File file, ParticleSystem system) throws IOException {
saveConfiguredSystem(new FileOutputStream(file), system);
}
/**
* Save a particle system with only ConfigurableEmitters in to an XML file
*
* @param out
* The location to which we'll save
* @param system
* The system to store
* @throws IOException
* Indicates a failure to save or encode the system XML.
*/
public static void saveConfiguredSystem(OutputStream out, ParticleSystem system) throws IOException {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.newDocument();
Element root = document.createElement("system");
root.setAttribute("additive", String.valueOf(system.getBlendingMode() == ParticleSystem.BLEND_ADDITIVE));
root.setAttribute("points", String.valueOf(system.usePoints()));
document.appendChild(root);
for (int i = 0; i < system.getEmitterCount(); i++) {
ParticleEmitter current = system.getEmitter(i);
if (current instanceof ConfigurableEmitter) {
Element element = emitterToElement(document, (ConfigurableEmitter) current);
root.appendChild(element);
} else {
throw new SlickException("Only ConfigurableEmitter instances can be stored");
}
}
Result result = new StreamResult(new OutputStreamWriter(out, "utf-8"));
DOMSource source = new DOMSource(document);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
xformer.transform(source, result);
} catch (Exception e) {
Log.error(e);
throw new IOException("Unable to save configured particle system");
}
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The reference to the emitter XML file to load (classpath or
* file)
* @return The configured emitter
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(String ref) throws IOException {
return loadEmitter(ResourceLoader.getResourceAsStream(ref), null);
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The XML file to read
* @return The configured emitter
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(File ref) throws IOException {
return loadEmitter(new FileInputStream(ref), null);
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The stream to read the XML from
* @return The configured emitter
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(InputStream ref) throws IOException {
return loadEmitter(ref, null);
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The reference to the emitter XML file to load (classpath or
* file)
* @return The configured emitter
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(String ref, ConfigurableEmitterFactory factory) throws IOException {
return loadEmitter(ResourceLoader.getResourceAsStream(ref), factory);
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The XML file to read
* @return The configured emitter
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(File ref, ConfigurableEmitterFactory factory) throws IOException {
return loadEmitter(new FileInputStream(ref), factory);
}
/**
* Load a single emitter from an XML file
*
* @param ref
* The stream to read the XML from
* @param factory
* The factory used to create the emitter than will be poulated
* with loaded data.
* @return The configured emitter
* @throws IOException
* Indicates a failure to find, read or parse the XML file
*/
public static ConfigurableEmitter loadEmitter(InputStream ref, ConfigurableEmitterFactory factory) throws IOException {
if (factory == null) {
factory = ConfigurableEmitter::new;
}
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.parse(ref);
if (!document.getDocumentElement().getNodeName().equals("emitter")) {
throw new IOException("Not a particle emitter file");
}
ConfigurableEmitter emitter = factory.createEmitter("new");
elementToEmitter(document.getDocumentElement(), emitter);
return emitter;
} catch (IOException e) {
Log.error(e);
throw e;
} catch (Exception e) {
Log.error(e);
throw new IOException("Unable to load emitter");
}
}
/**
* Save a single emitter to the XML file
*
* @param file
* The file to save the emitter to
* @param emitter
* The emitter to store to the XML file
* @throws IOException
* Indicates a failure to write or encode the XML
*/
public static void saveEmitter(File file, ConfigurableEmitter emitter) throws IOException {
saveEmitter(new FileOutputStream(file), emitter);
}
/**
* Save a single emitter to the XML file
*
* @param out
* The location to which we should save
* @param emitter
* The emitter to store to the XML file
* @throws IOException
* Indicates a failure to write or encode the XML
*/
public static void saveEmitter(OutputStream out, ConfigurableEmitter emitter) throws IOException {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = builder.newDocument();
document.appendChild(emitterToElement(document, emitter));
Result result = new StreamResult(new OutputStreamWriter(out, "utf-8"));
DOMSource source = new DOMSource(document);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
xformer.transform(source, result);
} catch (Exception e) {
Log.error(e);
throw new IOException("Failed to save emitter");
}
}
/**
* Get the first child named as specified from the passed XML element
*
* @param element
* The element whose children are interogated
* @param name
* The name of the element to retrieve
* @return The requested element
*/
private static Element getFirstNamedElement(Element element, String name) {
NodeList list = element.getElementsByTagName(name);
if (list.getLength() == 0) {
return null;
}
return (Element) list.item(0);
}
/**
* Convert from an XML element to an configured emitter
*
* @param element
* The XML element to convert
* @param emitter
* The emitter that will be configured based on the XML
*/
private static void elementToEmitter(Element element, ConfigurableEmitter emitter) {
emitter.name = element.getAttribute("name");
emitter.setImageName(element.getAttribute("imageName"));
String renderType = element.getAttribute("renderType");
emitter.usePoints = Particle.INHERIT_POINTS;
if (renderType.equals("quads")) {
emitter.usePoints = Particle.USE_QUADS;
}
if (renderType.equals("points")) {
emitter.usePoints = Particle.USE_POINTS;
}
String useOriented = element.getAttribute("useOriented");
if (useOriented != null) {
emitter.useOriented = "true".equals(useOriented);
}
String useAdditive = element.getAttribute("useAdditive");
if (useAdditive != null) {
emitter.useAdditive = "true".equals(useAdditive);
}
parseRangeElement(getFirstNamedElement(element, "spawnInterval"), emitter.spawnInterval);
parseRangeElement(getFirstNamedElement(element, "spawnCount"), emitter.spawnCount);
parseRangeElement(getFirstNamedElement(element, "initialLife"), emitter.initialLife);
parseRangeElement(getFirstNamedElement(element, "initialSize"), emitter.initialSize);
parseRangeElement(getFirstNamedElement(element, "xOffset"), emitter.xOffset);
parseRangeElement(getFirstNamedElement(element, "yOffset"), emitter.yOffset);
parseRangeElement(getFirstNamedElement(element, "initialDistance"), emitter.initialDistance);
parseRangeElement(getFirstNamedElement(element, "speed"), emitter.speed);
parseRangeElement(getFirstNamedElement(element, "length"), emitter.length);
parseRangeElement(getFirstNamedElement(element, "emitCount"), emitter.emitCount);
parseValueElement(getFirstNamedElement(element, "spread"), emitter.spread);
parseValueElement(getFirstNamedElement(element, "angularOffset"), emitter.angularOffset);
parseValueElement(getFirstNamedElement(element, "growthFactor"), emitter.growthFactor);
parseValueElement(getFirstNamedElement(element, "gravityFactor"), emitter.gravityFactor);
parseValueElement(getFirstNamedElement(element, "windFactor"), emitter.windFactor);
parseValueElement(getFirstNamedElement(element, "startAlpha"), emitter.startAlpha);
parseValueElement(getFirstNamedElement(element, "endAlpha"), emitter.endAlpha);
parseValueElement(getFirstNamedElement(element, "alpha"), emitter.alpha);
parseValueElement(getFirstNamedElement(element, "size"), emitter.size);
parseValueElement(getFirstNamedElement(element, "velocity"), emitter.velocity);
parseValueElement(getFirstNamedElement(element, "scaleY"), emitter.scaleY);
Element color = getFirstNamedElement(element, "color");
NodeList steps = color.getElementsByTagName("step");
emitter.colors.clear();
for (int i = 0; i < steps.getLength(); i++) {
Element step = (Element) steps.item(i);
float offset = Float.parseFloat(step.getAttribute("offset"));
float r = Float.parseFloat(step.getAttribute("r"));
float g = Float.parseFloat(step.getAttribute("g"));
float b = Float.parseFloat(step.getAttribute("b"));
emitter.addColorPoint(offset, new Color(r, g, b, 1));
}
// generate new random play length
emitter.replay();
}
/**
* Convert from an emitter to a XML element description
*
* @param document
* The document the element will be part of
* @param emitter
* The emitter to convert
* @return The XML element based on the configured emitter
*/
private static Element emitterToElement(Document document, ConfigurableEmitter emitter) {
Element root = document.createElement("emitter");
root.setAttribute("name", emitter.name);
root.setAttribute("imageName", emitter.imageName == null ? "" : emitter.imageName);
root.setAttribute("useOriented", emitter.useOriented ? "true" : "false");
root.setAttribute("useAdditive", emitter.useAdditive ? "true" : "false");
if (emitter.usePoints == Particle.INHERIT_POINTS) {
root.setAttribute("renderType", "inherit");
}
if (emitter.usePoints == Particle.USE_POINTS) {
root.setAttribute("renderType", "points");
}
if (emitter.usePoints == Particle.USE_QUADS) {
root.setAttribute("renderType", "quads");
}
root.appendChild(createRangeElement(document, "spawnInterval", emitter.spawnInterval));
root.appendChild(createRangeElement(document, "spawnCount", emitter.spawnCount));
root.appendChild(createRangeElement(document, "initialLife", emitter.initialLife));
root.appendChild(createRangeElement(document, "initialSize", emitter.initialSize));
root.appendChild(createRangeElement(document, "xOffset", emitter.xOffset));
root.appendChild(createRangeElement(document, "yOffset", emitter.yOffset));
root.appendChild(createRangeElement(document, "initialDistance", emitter.initialDistance));
root.appendChild(createRangeElement(document, "speed", emitter.speed));
root.appendChild(createRangeElement(document, "length", emitter.length));
root.appendChild(createRangeElement(document, "emitCount", emitter.emitCount));
root.appendChild(createValueElement(document, "spread", emitter.spread));
root.appendChild(createValueElement(document, "angularOffset", emitter.angularOffset));
root.appendChild(createValueElement(document, "growthFactor", emitter.growthFactor));
root.appendChild(createValueElement(document, "gravityFactor", emitter.gravityFactor));
root.appendChild(createValueElement(document, "windFactor", emitter.windFactor));
root.appendChild(createValueElement(document, "startAlpha", emitter.startAlpha));
root.appendChild(createValueElement(document, "endAlpha", emitter.endAlpha));
root.appendChild(createValueElement(document, "alpha", emitter.alpha));
root.appendChild(createValueElement(document, "size", emitter.size));
root.appendChild(createValueElement(document, "velocity", emitter.velocity));
root.appendChild(createValueElement(document, "scaleY", emitter.scaleY));
Element color = document.createElement("color");
ArrayList list = emitter.colors;
for (int i = 0; i < list.size(); i++) {
ColorRecord record = list.get(i);
Element step = document.createElement("step");
step.setAttribute("offset", String.valueOf(record.pos));
step.setAttribute("r", String.valueOf(record.col.r));
step.setAttribute("g", String.valueOf(record.col.g));
step.setAttribute("b", String.valueOf(record.col.b));
color.appendChild(step);
}
root.appendChild(color);
return root;
}
/**
* Create an XML element based on a configured range
*
* @param document
* The document the element will be part of
* @param name
* The name to give the new element
* @param range
* The configured range
* @return A configured XML element on the range
*/
private static Element createRangeElement(Document document, String name, ConfigurableEmitter.Range range) {
Element element = document.createElement(name);
element.setAttribute("min", String.valueOf(range.getMin()));
element.setAttribute("max", String.valueOf(range.getMax()));
element.setAttribute("enabled", String.valueOf(range.isEnabled()));
return element;
}
/**
* Create an XML element based on a configured value
*
* @param document
* The document the element will be part of
* @param name
* The name to give the new element
* @param value
* The configured value
* @return A configure XML element based on the value
*/
private static Element createValueElement(Document document, String name, ConfigurableEmitter.Value value) {
Element element = document.createElement(name);
// void: now writes the value type
if (value instanceof SimpleValue) {
element.setAttribute("type", "simple");
element.setAttribute("value", String.valueOf(value.getValue(0)));
} else if (value instanceof RandomValue) {
element.setAttribute("type", "random");
element.setAttribute("value", String.valueOf(((RandomValue) value).getValue()));
} else if (value instanceof LinearInterpolator) {
element.setAttribute("type", "linear");
element.setAttribute("min", String.valueOf(((LinearInterpolator) value).getMin()));
element.setAttribute("max", String.valueOf(((LinearInterpolator) value).getMax()));
element.setAttribute("active", String.valueOf(((LinearInterpolator) value).isActive()));
ArrayList curve = ((LinearInterpolator) value).getCurve();
for (int i = 0; i < curve.size(); i++) {
Vector2f point = curve.get(i);
Element pointElement = document.createElement("point");
pointElement.setAttribute("x", String.valueOf(point.getX()));
pointElement.setAttribute("y", String.valueOf(point.getY()));
element.appendChild(pointElement);
}
} else {
Log.warn("unkown value type ignored: " + value.getClass());
}
return element;
}
/**
* Parse an XML element into a configured range
*
* @param element
* The XML element to parse
* @param range
* The range to configure based on the XML
*/
private static void parseRangeElement(Element element, ConfigurableEmitter.Range range) {
if (element == null) {
return;
}
range.setMin(Float.parseFloat(element.getAttribute("min")));
range.setMax(Float.parseFloat(element.getAttribute("max")));
range.setEnabled("true".equals(element.getAttribute("enabled")));
}
/**
* Parse an XML element into a configured value
*
* @param element
* The XML element to parse
* @param value
* The value to configure based on the XML
*/
private static void parseValueElement(Element element, ConfigurableEmitter.Value value) {
if (element == null) {
return;
}
String type = element.getAttribute("type");
String v = element.getAttribute("value");
if (type == null || type.length() == 0) {
// support for old style which did not write the type
if (value instanceof SimpleValue) {
((SimpleValue) value).setValue(Float.parseFloat(v));
} else if (value instanceof RandomValue) {
((RandomValue) value).setValue(Float.parseFloat(v));
} else {
Log.warn("problems reading element, skipping: " + element);
}
} else {
// type given: this is the new style
if (type.equals("simple")) {
((SimpleValue) value).setValue(Float.parseFloat(v));
} else if (type.equals("random")) {
((RandomValue) value).setValue(Float.parseFloat(v));
} else if (type.equals("linear")) {
String min = element.getAttribute("min");
String max = element.getAttribute("max");
String active = element.getAttribute("active");
NodeList points = element.getElementsByTagName("point");
ArrayList curve = new ArrayList<>();
for (int i = 0; i < points.getLength(); i++) {
Element point = (Element) points.item(i);
float x = Float.parseFloat(point.getAttribute("x"));
float y = Float.parseFloat(point.getAttribute("y"));
curve.add(new Vector2f(x, y));
}
((LinearInterpolator) value).setCurve(curve);
((LinearInterpolator) value).setMin(Integer.parseInt(min));
((LinearInterpolator) value).setMax(Integer.parseInt(max));
((LinearInterpolator) value).setActive("true".equals(active));
} else {
Log.warn("unkown type detected: " + type);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy